@agentmeshhq/agent 0.2.0 → 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.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/__tests__/orphan-process.test.d.ts +11 -0
- package/dist/__tests__/orphan-process.test.js +286 -0
- package/dist/__tests__/orphan-process.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +16 -0
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/watchdog.test.js +138 -12
- package/dist/__tests__/watchdog.test.js.map +1 -1
- package/dist/cli/index.js +0 -0
- package/dist/cli/status.js +11 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/stop.js +7 -2
- package/dist/cli/stop.js.map +1 -1
- package/dist/config/schema.d.ts +4 -2
- package/dist/core/daemon/assignment-message.d.ts +12 -0
- package/dist/core/daemon/assignment-message.js +36 -0
- package/dist/core/daemon/assignment-message.js.map +1 -0
- package/dist/core/daemon/bootstrap.d.ts +35 -0
- package/dist/core/daemon/bootstrap.js +52 -0
- package/dist/core/daemon/bootstrap.js.map +1 -0
- package/dist/core/daemon/crash-log.d.ts +16 -0
- package/dist/core/daemon/crash-log.js +24 -0
- package/dist/core/daemon/crash-log.js.map +1 -0
- package/dist/core/daemon/health-policy.d.ts +21 -0
- package/dist/core/daemon/health-policy.js +32 -0
- package/dist/core/daemon/health-policy.js.map +1 -0
- package/dist/core/daemon/sandbox-config.d.ts +9 -0
- package/dist/core/daemon/sandbox-config.js +17 -0
- package/dist/core/daemon/sandbox-config.js.map +1 -0
- package/dist/core/daemon/state.d.ts +33 -0
- package/dist/core/daemon/state.js +77 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/daemon/tmux-session.d.ts +17 -0
- package/dist/core/daemon/tmux-session.js +34 -0
- package/dist/core/daemon/tmux-session.js.map +1 -0
- package/dist/core/daemon/workspace.d.ts +10 -0
- package/dist/core/daemon/workspace.js +51 -0
- package/dist/core/daemon/workspace.js.map +1 -0
- package/dist/core/daemon.d.ts +0 -6
- package/dist/core/daemon.js +123 -244
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/injector.js +6 -0
- package/dist/core/injector.js.map +1 -1
- package/dist/core/runner/build.d.ts +9 -0
- package/dist/core/runner/build.js +53 -0
- package/dist/core/runner/build.js.map +1 -0
- package/dist/core/runner/detect.d.ts +5 -0
- package/dist/core/runner/detect.js +14 -0
- package/dist/core/runner/detect.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +5 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/model.d.ts +5 -0
- package/dist/core/runner/model.js +7 -0
- package/dist/core/runner/model.js.map +1 -0
- package/dist/core/runner/opencode-models.d.ts +15 -0
- package/dist/core/runner/opencode-models.js +70 -0
- package/dist/core/runner/opencode-models.js.map +1 -0
- package/dist/core/runner/types.d.ts +19 -0
- package/dist/core/runner/types.js +8 -0
- package/dist/core/runner/types.js.map +1 -0
- package/dist/core/runner.d.ts +5 -47
- package/dist/core/runner.js +5 -167
- package/dist/core/runner.js.map +1 -1
- package/dist/core/tmux-runtime.d.ts +13 -0
- package/dist/core/tmux-runtime.js +72 -0
- package/dist/core/tmux-runtime.js.map +1 -0
- package/dist/core/tmux.d.ts +7 -1
- package/dist/core/tmux.js +75 -45
- package/dist/core/tmux.js.map +1 -1
- package/dist/core/watchdog.d.ts +18 -1
- package/dist/core/watchdog.js +78 -29
- package/dist/core/watchdog.js.map +1 -1
- package/package.json +30 -11
- package/dist/cli/inbox.d.ts +0 -5
- package/dist/cli/inbox.js +0 -123
- package/dist/cli/inbox.js.map +0 -1
- package/dist/cli/issue.d.ts +0 -42
- package/dist/cli/issue.js +0 -297
- package/dist/cli/issue.js.map +0 -1
- package/dist/cli/ready.d.ts +0 -5
- package/dist/cli/ready.js +0 -131
- package/dist/cli/ready.js.map +0 -1
- package/dist/cli/sync.d.ts +0 -8
- package/dist/cli/sync.js +0 -154
- package/dist/cli/sync.js.map +0 -1
- package/dist/core/issue-cache.d.ts +0 -44
- package/dist/core/issue-cache.js +0 -75
- package/dist/core/issue-cache.js.map +0 -1
- package/src/__tests__/context.test.ts +0 -464
- package/src/__tests__/injector.test.ts +0 -29
- package/src/__tests__/jwt.test.ts +0 -112
- package/src/__tests__/loader.test.ts +0 -239
- package/src/__tests__/runner.test.ts +0 -104
- package/src/__tests__/sandbox.test.ts +0 -435
- package/src/__tests__/watchdog.test.ts +0 -368
- package/src/cli/attach.ts +0 -22
- package/src/cli/build.ts +0 -145
- package/src/cli/config.ts +0 -148
- package/src/cli/context.ts +0 -231
- package/src/cli/deploy.ts +0 -155
- package/src/cli/index.ts +0 -376
- package/src/cli/init.ts +0 -75
- package/src/cli/list.ts +0 -70
- package/src/cli/local.ts +0 -183
- package/src/cli/logs.ts +0 -64
- package/src/cli/migrate.ts +0 -212
- package/src/cli/nudge.ts +0 -81
- package/src/cli/restart.ts +0 -59
- package/src/cli/slack.ts +0 -70
- package/src/cli/start.ts +0 -118
- package/src/cli/status.ts +0 -91
- package/src/cli/stop.ts +0 -48
- package/src/cli/test.ts +0 -143
- package/src/cli/token.ts +0 -188
- package/src/cli/whoami.ts +0 -142
- package/src/config/loader.ts +0 -121
- package/src/config/schema.ts +0 -68
- package/src/context/handoff.ts +0 -122
- package/src/context/index.ts +0 -8
- package/src/context/schema.ts +0 -111
- package/src/context/storage.ts +0 -197
- package/src/core/daemon.ts +0 -1317
- package/src/core/heartbeat.ts +0 -129
- package/src/core/injector.ts +0 -292
- package/src/core/registry.ts +0 -159
- package/src/core/runner.ts +0 -225
- package/src/core/sandbox.ts +0 -547
- package/src/core/session-id.ts +0 -111
- package/src/core/tmux.ts +0 -405
- package/src/core/watchdog.ts +0 -238
- package/src/core/websocket.ts +0 -94
- package/src/index.ts +0 -10
- package/src/utils/jwt.ts +0 -87
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -12
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import * as tmux from "../core/tmux.js";
|
|
4
|
-
import {
|
|
5
|
-
checkAgentProgress,
|
|
6
|
-
cleanupOrphanContainers,
|
|
7
|
-
detectPermissionPrompt,
|
|
8
|
-
findOrphanContainers,
|
|
9
|
-
getLastActivityTime,
|
|
10
|
-
isProcessRunning,
|
|
11
|
-
sendNudge,
|
|
12
|
-
type WatchdogResult,
|
|
13
|
-
} from "../core/watchdog.js";
|
|
14
|
-
|
|
15
|
-
// Mock child_process
|
|
16
|
-
vi.mock("node:child_process", () => ({
|
|
17
|
-
execSync: vi.fn(),
|
|
18
|
-
spawnSync: vi.fn(),
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
// Mock tmux module
|
|
22
|
-
vi.mock("../core/tmux.js", () => ({
|
|
23
|
-
captureSessionOutput: vi.fn(),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
describe("Watchdog Module", () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
vi.resetAllMocks();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe("detectPermissionPrompt", () => {
|
|
32
|
-
it("should return null when no permission prompt detected", () => {
|
|
33
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue(
|
|
34
|
-
"Normal output without any prompts\nJust some code being written",
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const result = detectPermissionPrompt("test-agent");
|
|
38
|
-
expect(result).toBeNull();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should detect 'Permission required' prompt", () => {
|
|
42
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue(
|
|
43
|
-
"Some output\nPermission required\nAllow once | Allow always | Reject",
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const result = detectPermissionPrompt("test-agent");
|
|
47
|
-
expect(result).toBe("Permission prompt detected");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should detect external directory access prompt", () => {
|
|
51
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue(
|
|
52
|
-
"Access external directory /tmp/some-path\nAllow once | Allow always | Reject",
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const result = detectPermissionPrompt("test-agent");
|
|
56
|
-
expect(result).toBe("External directory: /tmp/some-path");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should detect triangle permission prompt", () => {
|
|
60
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue(
|
|
61
|
-
"Some output\n△ Permission required\nWaiting for approval",
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const result = detectPermissionPrompt("test-agent");
|
|
65
|
-
expect(result).toBe("Permission prompt detected");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("should return null when capture fails", () => {
|
|
69
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue(null);
|
|
70
|
-
|
|
71
|
-
const result = detectPermissionPrompt("test-agent");
|
|
72
|
-
expect(result).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should return null when capture throws", () => {
|
|
76
|
-
vi.mocked(tmux.captureSessionOutput).mockImplementation(() => {
|
|
77
|
-
throw new Error("Session not found");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const result = detectPermissionPrompt("test-agent");
|
|
81
|
-
expect(result).toBeNull();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("getLastActivityTime", () => {
|
|
86
|
-
it("should parse timestamp from local log file", () => {
|
|
87
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
88
|
-
status: 0,
|
|
89
|
-
stdout: "INFO 2026-02-26T00:14:42 +0ms service=opencode message=test",
|
|
90
|
-
stderr: "",
|
|
91
|
-
pid: 123,
|
|
92
|
-
signal: null,
|
|
93
|
-
output: [],
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const result = getLastActivityTime("test-agent");
|
|
97
|
-
expect(result).toBeInstanceOf(Date);
|
|
98
|
-
expect(result?.toISOString()).toContain("2026-02-26");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("should parse timestamp from container log file", () => {
|
|
102
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
103
|
-
status: 0,
|
|
104
|
-
stdout: "INFO 2026-02-26T01:30:00 +0ms service=opencode",
|
|
105
|
-
stderr: "",
|
|
106
|
-
pid: 123,
|
|
107
|
-
signal: null,
|
|
108
|
-
output: [],
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const result = getLastActivityTime("test-agent", "container-123");
|
|
112
|
-
expect(result).toBeInstanceOf(Date);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("should return null when command fails", () => {
|
|
116
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
117
|
-
status: 1,
|
|
118
|
-
stdout: "",
|
|
119
|
-
stderr: "No such file",
|
|
120
|
-
pid: 123,
|
|
121
|
-
signal: null,
|
|
122
|
-
output: [],
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = getLastActivityTime("test-agent");
|
|
126
|
-
expect(result).toBeNull();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("should return null when no timestamp found", () => {
|
|
130
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
131
|
-
status: 0,
|
|
132
|
-
stdout: "Some output without timestamp",
|
|
133
|
-
stderr: "",
|
|
134
|
-
pid: 123,
|
|
135
|
-
signal: null,
|
|
136
|
-
output: [],
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const result = getLastActivityTime("test-agent");
|
|
140
|
-
expect(result).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should return null on empty output", () => {
|
|
144
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
145
|
-
status: 0,
|
|
146
|
-
stdout: "",
|
|
147
|
-
stderr: "",
|
|
148
|
-
pid: 123,
|
|
149
|
-
signal: null,
|
|
150
|
-
output: [],
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const result = getLastActivityTime("test-agent");
|
|
154
|
-
expect(result).toBeNull();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe("checkAgentProgress", () => {
|
|
159
|
-
it("should return permission_blocked when permission prompt detected", () => {
|
|
160
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Permission required\nAllow once");
|
|
161
|
-
|
|
162
|
-
const result = checkAgentProgress("test-agent");
|
|
163
|
-
expect(result.status).toBe("permission_blocked");
|
|
164
|
-
expect(result.blockedOn).toBe("Permission prompt detected");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("should return active when recent activity", () => {
|
|
168
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Normal output");
|
|
169
|
-
|
|
170
|
-
// Mock recent timestamp (1 minute ago)
|
|
171
|
-
const recentTime = new Date(Date.now() - 60 * 1000);
|
|
172
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
173
|
-
status: 0,
|
|
174
|
-
stdout: `INFO ${recentTime.toISOString().slice(0, 19)} +0ms service=opencode`,
|
|
175
|
-
stderr: "",
|
|
176
|
-
pid: 123,
|
|
177
|
-
signal: null,
|
|
178
|
-
output: [],
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const result = checkAgentProgress("test-agent");
|
|
182
|
-
expect(result.status).toBe("active");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("should return idle when no activity for 3 minutes", () => {
|
|
186
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Normal output");
|
|
187
|
-
|
|
188
|
-
// Mock timestamp 3 minutes ago
|
|
189
|
-
const idleTime = new Date(Date.now() - 3 * 60 * 1000);
|
|
190
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
191
|
-
status: 0,
|
|
192
|
-
stdout: `INFO ${idleTime.toISOString().slice(0, 19)} +0ms service=opencode`,
|
|
193
|
-
stderr: "",
|
|
194
|
-
pid: 123,
|
|
195
|
-
signal: null,
|
|
196
|
-
output: [],
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const result = checkAgentProgress("test-agent");
|
|
200
|
-
expect(result.status).toBe("idle");
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("should return stuck when no activity for 6 minutes", () => {
|
|
204
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Normal output");
|
|
205
|
-
|
|
206
|
-
// Mock timestamp 6 minutes ago
|
|
207
|
-
const stuckTime = new Date(Date.now() - 6 * 60 * 1000);
|
|
208
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
209
|
-
status: 0,
|
|
210
|
-
stdout: `INFO ${stuckTime.toISOString().slice(0, 19)} +0ms service=opencode`,
|
|
211
|
-
stderr: "",
|
|
212
|
-
pid: 123,
|
|
213
|
-
signal: null,
|
|
214
|
-
output: [],
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const result = checkAgentProgress("test-agent");
|
|
218
|
-
expect(result.status).toBe("stuck");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("should return active when activity time cannot be determined", () => {
|
|
222
|
-
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Normal output");
|
|
223
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
224
|
-
status: 1,
|
|
225
|
-
stdout: "",
|
|
226
|
-
stderr: "",
|
|
227
|
-
pid: 123,
|
|
228
|
-
signal: null,
|
|
229
|
-
output: [],
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const result = checkAgentProgress("test-agent");
|
|
233
|
-
expect(result.status).toBe("active");
|
|
234
|
-
expect(result.details).toContain("Unable to determine");
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe("isProcessRunning", () => {
|
|
239
|
-
it("should return true for running process", () => {
|
|
240
|
-
// Current process is always running
|
|
241
|
-
const result = isProcessRunning(process.pid);
|
|
242
|
-
expect(result).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("should return false for non-existent process", () => {
|
|
246
|
-
// Use a very high PID that's unlikely to exist
|
|
247
|
-
const result = isProcessRunning(999999999);
|
|
248
|
-
expect(result).toBe(false);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe("findOrphanContainers", () => {
|
|
253
|
-
it("should return list of container IDs", () => {
|
|
254
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
255
|
-
status: 0,
|
|
256
|
-
stdout: "abc123\ndef456\nghi789",
|
|
257
|
-
stderr: "",
|
|
258
|
-
pid: 123,
|
|
259
|
-
signal: null,
|
|
260
|
-
output: [],
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const result = findOrphanContainers("test-agent");
|
|
264
|
-
expect(result).toEqual(["abc123", "def456", "ghi789"]);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("should return empty array when no containers found", () => {
|
|
268
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
269
|
-
status: 0,
|
|
270
|
-
stdout: "",
|
|
271
|
-
stderr: "",
|
|
272
|
-
pid: 123,
|
|
273
|
-
signal: null,
|
|
274
|
-
output: [],
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const result = findOrphanContainers("test-agent");
|
|
278
|
-
expect(result).toEqual([]);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("should return empty array on command failure", () => {
|
|
282
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
283
|
-
status: 1,
|
|
284
|
-
stdout: "",
|
|
285
|
-
stderr: "docker not found",
|
|
286
|
-
pid: 123,
|
|
287
|
-
signal: null,
|
|
288
|
-
output: [],
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const result = findOrphanContainers("test-agent");
|
|
292
|
-
expect(result).toEqual([]);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe("cleanupOrphanContainers", () => {
|
|
297
|
-
it("should remove found containers and return count", () => {
|
|
298
|
-
vi.mocked(spawnSync)
|
|
299
|
-
.mockReturnValueOnce({
|
|
300
|
-
// findOrphanContainers call
|
|
301
|
-
status: 0,
|
|
302
|
-
stdout: "abc123\ndef456",
|
|
303
|
-
stderr: "",
|
|
304
|
-
pid: 123,
|
|
305
|
-
signal: null,
|
|
306
|
-
output: [],
|
|
307
|
-
})
|
|
308
|
-
.mockReturnValue({
|
|
309
|
-
// docker rm calls
|
|
310
|
-
status: 0,
|
|
311
|
-
stdout: "",
|
|
312
|
-
stderr: "",
|
|
313
|
-
pid: 123,
|
|
314
|
-
signal: null,
|
|
315
|
-
output: [],
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const result = cleanupOrphanContainers("test-agent");
|
|
319
|
-
expect(result).toBe(2);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("should return 0 when no orphan containers", () => {
|
|
323
|
-
vi.mocked(spawnSync).mockReturnValue({
|
|
324
|
-
status: 0,
|
|
325
|
-
stdout: "",
|
|
326
|
-
stderr: "",
|
|
327
|
-
pid: 123,
|
|
328
|
-
signal: null,
|
|
329
|
-
output: [],
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const result = cleanupOrphanContainers("test-agent");
|
|
333
|
-
expect(result).toBe(0);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe("sendNudge", () => {
|
|
338
|
-
it("should send nudge message to tmux session", () => {
|
|
339
|
-
vi.mocked(execSync).mockReturnValue(Buffer.from(""));
|
|
340
|
-
|
|
341
|
-
const result = sendNudge("test-agent", "Please continue working");
|
|
342
|
-
expect(result).toBe(true);
|
|
343
|
-
expect(execSync).toHaveBeenCalledWith(
|
|
344
|
-
expect.stringContaining("tmux send-keys"),
|
|
345
|
-
expect.any(Object),
|
|
346
|
-
);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("should return false when tmux command fails", () => {
|
|
350
|
-
vi.mocked(execSync).mockImplementation(() => {
|
|
351
|
-
throw new Error("tmux session not found");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const result = sendNudge("test-agent", "Continue");
|
|
355
|
-
expect(result).toBe(false);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it("should escape quotes in message", () => {
|
|
359
|
-
vi.mocked(execSync).mockReturnValue(Buffer.from(""));
|
|
360
|
-
|
|
361
|
-
sendNudge("test-agent", 'Message with "quotes"');
|
|
362
|
-
expect(execSync).toHaveBeenCalledWith(
|
|
363
|
-
expect.stringContaining('\\"quotes\\"'),
|
|
364
|
-
expect.any(Object),
|
|
365
|
-
);
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
});
|
package/src/cli/attach.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import pc from "picocolors";
|
|
2
|
-
import { attachSession, getSessionName, sessionExists } from "../core/tmux.js";
|
|
3
|
-
|
|
4
|
-
export function attach(name: string): void {
|
|
5
|
-
if (!name) {
|
|
6
|
-
console.log(pc.red("Agent name is required."));
|
|
7
|
-
process.exit(1);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const sessionName = getSessionName(name);
|
|
11
|
-
|
|
12
|
-
if (!sessionExists(sessionName)) {
|
|
13
|
-
console.log(pc.red(`Agent "${name}" is not running.`));
|
|
14
|
-
console.log(`Start it with: ${pc.cyan(`agentmesh start --name ${name}`)}`);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
console.log(`Attaching to ${sessionName}...`);
|
|
19
|
-
console.log(pc.dim("Detach with: Ctrl+B, D\n"));
|
|
20
|
-
|
|
21
|
-
attachSession(name);
|
|
22
|
-
}
|
package/src/cli/build.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import pc from "picocolors";
|
|
4
|
-
|
|
5
|
-
function findProjectRoot(): string {
|
|
6
|
-
let dir = process.cwd();
|
|
7
|
-
for (let i = 0; i < 10; i++) {
|
|
8
|
-
const packageJson = path.join(dir, "package.json");
|
|
9
|
-
const pnpmWorkspace = path.join(dir, "pnpm-workspace.yaml");
|
|
10
|
-
try {
|
|
11
|
-
execSync(`test -f "${packageJson}" && test -f "${pnpmWorkspace}"`, { stdio: "ignore" });
|
|
12
|
-
return dir;
|
|
13
|
-
} catch {
|
|
14
|
-
dir = path.dirname(dir);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
throw new Error(
|
|
18
|
-
"Could not find AgentMesh project root. Make sure you're in the agentmesh repository.",
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface BuildOptions {
|
|
23
|
-
docker?: boolean;
|
|
24
|
-
package?: string;
|
|
25
|
-
clean?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function build(options: BuildOptions = {}): Promise<void> {
|
|
29
|
-
const projectRoot = findProjectRoot();
|
|
30
|
-
|
|
31
|
-
if (options.docker) {
|
|
32
|
-
await buildDocker(projectRoot, options);
|
|
33
|
-
} else {
|
|
34
|
-
await buildPackages(projectRoot, options);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function buildPackages(projectRoot: string, options: BuildOptions): Promise<void> {
|
|
39
|
-
console.log(pc.cyan("Building AgentMesh packages..."));
|
|
40
|
-
console.log();
|
|
41
|
-
|
|
42
|
-
const args = ["pnpm"];
|
|
43
|
-
|
|
44
|
-
if (options.package) {
|
|
45
|
-
args.push("--filter", options.package);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (options.clean) {
|
|
49
|
-
console.log(pc.dim("Cleaning build artifacts..."));
|
|
50
|
-
const cleanResult = spawnSync("pnpm", ["-r", "exec", "rm", "-rf", "dist"], {
|
|
51
|
-
cwd: projectRoot,
|
|
52
|
-
stdio: "inherit",
|
|
53
|
-
});
|
|
54
|
-
if (cleanResult.status !== 0) {
|
|
55
|
-
console.warn(pc.yellow("Warning: Clean may have partially failed"));
|
|
56
|
-
}
|
|
57
|
-
console.log();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
args.push("build");
|
|
61
|
-
|
|
62
|
-
const result = spawnSync(args[0], args.slice(1), {
|
|
63
|
-
cwd: projectRoot,
|
|
64
|
-
stdio: "inherit",
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (result.status !== 0) {
|
|
68
|
-
console.error(pc.red("Build failed"));
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log();
|
|
73
|
-
console.log(pc.green("Build completed successfully!"));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function buildDocker(projectRoot: string, options: BuildOptions): Promise<void> {
|
|
77
|
-
console.log(pc.cyan("Building Docker images..."));
|
|
78
|
-
console.log();
|
|
79
|
-
|
|
80
|
-
// Check if docker is available
|
|
81
|
-
try {
|
|
82
|
-
execSync("docker --version", { stdio: "ignore" });
|
|
83
|
-
} catch {
|
|
84
|
-
console.error(pc.red("Docker is not installed or not running"));
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Use docker buildx bake for multi-image builds
|
|
89
|
-
const dockerDir = path.join(projectRoot, "docker");
|
|
90
|
-
const dockerfilePath = path.join(dockerDir, "Dockerfile");
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
execSync(`test -f "${dockerfilePath}"`, { stdio: "ignore" });
|
|
94
|
-
} catch {
|
|
95
|
-
console.error(pc.red("Dockerfile not found at docker/Dockerfile"));
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Build using docker compose to leverage the existing setup
|
|
100
|
-
const composePath = path.join(dockerDir, "docker-compose.local.yml");
|
|
101
|
-
|
|
102
|
-
const args = ["compose", "-f", composePath, "build"];
|
|
103
|
-
if (options.package) {
|
|
104
|
-
// Map package names to service names
|
|
105
|
-
const serviceMap: Record<string, string> = {
|
|
106
|
-
"@agentmesh/hub": "hub-api",
|
|
107
|
-
"@agentmesh/admin": "admin",
|
|
108
|
-
hub: "hub-api",
|
|
109
|
-
admin: "admin",
|
|
110
|
-
};
|
|
111
|
-
const service = serviceMap[options.package] || options.package;
|
|
112
|
-
args.push(service);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const result = spawnSync("docker", args, {
|
|
116
|
-
cwd: projectRoot,
|
|
117
|
-
stdio: "inherit",
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (result.status !== 0) {
|
|
121
|
-
console.error(pc.red("Docker build failed"));
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log();
|
|
126
|
-
console.log(pc.green("Docker images built successfully!"));
|
|
127
|
-
|
|
128
|
-
// List built images
|
|
129
|
-
console.log();
|
|
130
|
-
console.log(pc.bold("Built images:"));
|
|
131
|
-
const listResult = spawnSync(
|
|
132
|
-
"docker",
|
|
133
|
-
[
|
|
134
|
-
"images",
|
|
135
|
-
"--filter",
|
|
136
|
-
"reference=*agentmesh*",
|
|
137
|
-
"--format",
|
|
138
|
-
"{{.Repository}}:{{.Tag}}\t{{.Size}}",
|
|
139
|
-
],
|
|
140
|
-
{ encoding: "utf-8" },
|
|
141
|
-
);
|
|
142
|
-
if (listResult.stdout) {
|
|
143
|
-
console.log(pc.dim(listResult.stdout));
|
|
144
|
-
}
|
|
145
|
-
}
|
package/src/cli/config.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import pc from "picocolors";
|
|
2
|
-
import { loadConfig, saveConfig } from "../config/loader.js";
|
|
3
|
-
import { CONFIG_PATH, type Config } from "../config/schema.js";
|
|
4
|
-
|
|
5
|
-
export async function configCmd(action: string, key?: string, value?: string): Promise<void> {
|
|
6
|
-
const config = loadConfig();
|
|
7
|
-
|
|
8
|
-
if (!config) {
|
|
9
|
-
console.log(pc.red("No config found. Run 'agentmesh init' first."));
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
switch (action) {
|
|
14
|
-
case "show":
|
|
15
|
-
case "view":
|
|
16
|
-
showConfig(config);
|
|
17
|
-
break;
|
|
18
|
-
|
|
19
|
-
case "get":
|
|
20
|
-
if (!key) {
|
|
21
|
-
console.log(pc.red("Key required. Usage: agentmesh config get <key>"));
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
getConfigValue(config, key);
|
|
25
|
-
break;
|
|
26
|
-
|
|
27
|
-
case "set":
|
|
28
|
-
if (!key || value === undefined) {
|
|
29
|
-
console.log(pc.red("Key and value required. Usage: agentmesh config set <key> <value>"));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
setConfigValue(config, key, value);
|
|
33
|
-
break;
|
|
34
|
-
|
|
35
|
-
case "path":
|
|
36
|
-
console.log(CONFIG_PATH);
|
|
37
|
-
break;
|
|
38
|
-
|
|
39
|
-
case "edit":
|
|
40
|
-
await editConfig();
|
|
41
|
-
break;
|
|
42
|
-
|
|
43
|
-
default:
|
|
44
|
-
// Default to show
|
|
45
|
-
showConfig(config);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function showConfig(config: Config): void {
|
|
50
|
-
console.log(pc.bold("AgentMesh Configuration\n"));
|
|
51
|
-
console.log(pc.dim(`Path: ${CONFIG_PATH}\n`));
|
|
52
|
-
|
|
53
|
-
// Show main settings
|
|
54
|
-
console.log(`hubUrl: ${pc.cyan(config.hubUrl)}`);
|
|
55
|
-
console.log(`workspace: ${pc.cyan(config.workspace)}`);
|
|
56
|
-
console.log(`apiKey: ${pc.dim(maskApiKey(config.apiKey))}`);
|
|
57
|
-
|
|
58
|
-
// Show defaults
|
|
59
|
-
console.log();
|
|
60
|
-
console.log(pc.dim("Defaults:"));
|
|
61
|
-
console.log(` command: ${config.defaults.command}`);
|
|
62
|
-
console.log(` model: ${config.defaults.model}`);
|
|
63
|
-
|
|
64
|
-
// Show agents
|
|
65
|
-
if (config.agents.length > 0) {
|
|
66
|
-
console.log();
|
|
67
|
-
console.log(pc.dim("Agent Configs:"));
|
|
68
|
-
for (const agent of config.agents) {
|
|
69
|
-
console.log(` ${agent.name}:`);
|
|
70
|
-
if (agent.command) console.log(` command: ${agent.command}`);
|
|
71
|
-
if (agent.model) console.log(` model: ${agent.model}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getConfigValue(config: Config, key: string): void {
|
|
77
|
-
const parts = key.split(".");
|
|
78
|
-
let value: unknown = config;
|
|
79
|
-
|
|
80
|
-
for (const part of parts) {
|
|
81
|
-
if (value && typeof value === "object" && part in value) {
|
|
82
|
-
value = (value as Record<string, unknown>)[part];
|
|
83
|
-
} else {
|
|
84
|
-
console.log(pc.red(`Key "${key}" not found.`));
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (typeof value === "object") {
|
|
90
|
-
console.log(JSON.stringify(value, null, 2));
|
|
91
|
-
} else {
|
|
92
|
-
console.log(String(value));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function setConfigValue(config: Config, key: string, value: string): void {
|
|
97
|
-
const parts = key.split(".");
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
-
let target: any = config;
|
|
100
|
-
|
|
101
|
-
// Navigate to parent
|
|
102
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
103
|
-
const part = parts[i];
|
|
104
|
-
if (!(part in target)) {
|
|
105
|
-
target[part] = {};
|
|
106
|
-
}
|
|
107
|
-
target = target[part];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const finalKey = parts[parts.length - 1];
|
|
111
|
-
const oldValue = target[finalKey];
|
|
112
|
-
|
|
113
|
-
// Try to parse as JSON, otherwise use string
|
|
114
|
-
try {
|
|
115
|
-
target[finalKey] = JSON.parse(value);
|
|
116
|
-
} catch {
|
|
117
|
-
target[finalKey] = value;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
saveConfig(config);
|
|
121
|
-
|
|
122
|
-
console.log(pc.green(`Set ${key}:`));
|
|
123
|
-
console.log(` Old: ${pc.dim(String(oldValue))}`);
|
|
124
|
-
console.log(` New: ${pc.cyan(String(target[finalKey]))}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function editConfig(): Promise<void> {
|
|
128
|
-
const { spawn } = await import("node:child_process");
|
|
129
|
-
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
130
|
-
|
|
131
|
-
console.log(pc.dim(`Opening ${CONFIG_PATH} with ${editor}...`));
|
|
132
|
-
|
|
133
|
-
const child = spawn(editor, [CONFIG_PATH], {
|
|
134
|
-
stdio: "inherit",
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
child.on("exit", (code) => {
|
|
138
|
-
if (code === 0) {
|
|
139
|
-
console.log(pc.green("Config saved."));
|
|
140
|
-
}
|
|
141
|
-
process.exit(code ?? 0);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function maskApiKey(key: string): string {
|
|
146
|
-
if (!key || key.length < 12) return "***";
|
|
147
|
-
return `${key.slice(0, 8)}...${key.slice(-4)}`;
|
|
148
|
-
}
|