@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.
- 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 +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/start.d.ts +2 -1
- package/dist/cli/start.js +6 -3
- package/dist/cli/start.js.map +1 -1
- 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 +4 -7
- package/dist/core/daemon.js +143 -259
- 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/registry.js +1 -1
- package/dist/core/registry.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 -375
- 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 -115
- 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 -1308
- 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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 therajushahi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -115,6 +115,45 @@ The AI assistant sees the injected message as if you typed it, maintaining full
|
|
|
115
115
|
- Node.js 18+
|
|
116
116
|
- An AgentMesh HQ account with API key
|
|
117
117
|
|
|
118
|
+
## Versioning
|
|
119
|
+
|
|
120
|
+
This package uses [Semantic Versioning](https://semver.org). Releases are fully automated — **do not publish manually**.
|
|
121
|
+
|
|
122
|
+
### How to release
|
|
123
|
+
|
|
124
|
+
1. Update `version` in `packages/agent/package.json` on the `dev` branch (via PR).
|
|
125
|
+
2. Push a tag matching `agent-v<version>` from the commit where the version was bumped:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# After your version-bump PR is merged to dev and pulled locally:
|
|
129
|
+
git tag agent-v0.2.1 <merge-commit-sha>
|
|
130
|
+
git push origin agent-v0.2.1
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. The `publish-agent.yml` GitHub Actions workflow triggers automatically and:
|
|
134
|
+
- Builds and tests the package
|
|
135
|
+
- Verifies `package.json` version matches the tag
|
|
136
|
+
- Publishes to npm as `@agentmeshhq/agent`
|
|
137
|
+
- Creates a GitHub Release
|
|
138
|
+
- Updates the Homebrew formula in `therajushahi/homebrew-agentmesh`
|
|
139
|
+
|
|
140
|
+
### Rules
|
|
141
|
+
|
|
142
|
+
- **Never run `npm publish` manually.** It bypasses the version check and can publish a mismatched version (e.g. a `package.json` that says `0.1.2` getting published as `0.2.0`).
|
|
143
|
+
- **Never skip a version number.** The Homebrew formula uses the version string; if the npm registry already has a higher version, `brew upgrade` will not install the new formula.
|
|
144
|
+
- **The tag drives everything.** The tag name (`agent-v0.2.1`) is the source of truth — the workflow validates that `package.json` matches before publishing.
|
|
145
|
+
- **All work targets `dev`.** `main` is for releases only, managed by the Product Owner. PRs must target `dev`.
|
|
146
|
+
|
|
147
|
+
### Version history incident
|
|
148
|
+
|
|
149
|
+
In Feb 2026, `0.2.0` was published manually with `npm publish` from a commit whose `package.json` said `0.1.2`. This caused:
|
|
150
|
+
- npm registry to have `0.2.0` as the latest version
|
|
151
|
+
- The Homebrew formula to point at `0.2.0`
|
|
152
|
+
- All subsequent CI-published versions (`0.1.3`, `0.1.4`) to be *lower* than `0.2.0`, so `brew upgrade` ignored them
|
|
153
|
+
- Workers already on `0.2.0` unable to receive fixes
|
|
154
|
+
|
|
155
|
+
The fix was to bump past `0.2.0` → `0.2.1` using the correct automated pipeline.
|
|
156
|
+
|
|
118
157
|
## License
|
|
119
158
|
|
|
120
159
|
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for orphaned process prevention (P0 fix for the daemon process leak).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - killProcessTree: SIGTERM → SIGKILL escalation
|
|
6
|
+
* - getTmuxPanePid: reads PID from tmux pane
|
|
7
|
+
* - getProcessTree: recursive child enumeration
|
|
8
|
+
* - captureAgentChildPids: captures all PIDs for a given agent
|
|
9
|
+
* - destroySession: kills child PIDs before killing tmux session
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for orphaned process prevention (P0 fix for the daemon process leak).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - killProcessTree: SIGTERM → SIGKILL escalation
|
|
6
|
+
* - getTmuxPanePid: reads PID from tmux pane
|
|
7
|
+
* - getProcessTree: recursive child enumeration
|
|
8
|
+
* - captureAgentChildPids: captures all PIDs for a given agent
|
|
9
|
+
* - destroySession: kills child PIDs before killing tmux session
|
|
10
|
+
*/
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { captureAgentChildPids, getProcessTree, getTmuxPanePid } from "../core/daemon/state.js";
|
|
14
|
+
import { destroySession, killProcessTree } from "../core/tmux.js";
|
|
15
|
+
// ─── Mocks ────────────────────────────────────────────────────────────────────
|
|
16
|
+
vi.mock("node:child_process", () => ({
|
|
17
|
+
execSync: vi.fn(),
|
|
18
|
+
execFileSync: vi.fn(),
|
|
19
|
+
spawnSync: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
vi.mock("../core/tmux-runtime.js", () => ({
|
|
22
|
+
buildInteractiveCommand: vi.fn().mockReturnValue("opencode"),
|
|
23
|
+
detectRunnerCommandKind: vi.fn().mockReturnValue("opencode"),
|
|
24
|
+
prepareOpenCodeRuntime: vi.fn().mockReturnValue("/tmp/test-runtime"),
|
|
25
|
+
}));
|
|
26
|
+
// Helper to mock process.kill without type gymnastics
|
|
27
|
+
function mockProcessKill(impl) {
|
|
28
|
+
return vi.spyOn(process, "kill").mockImplementation(impl);
|
|
29
|
+
}
|
|
30
|
+
const mockExecSync = vi.mocked(execSync);
|
|
31
|
+
// ─── killProcessTree ──────────────────────────────────────────────────────────
|
|
32
|
+
describe("killProcessTree", () => {
|
|
33
|
+
let killSpy;
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
killSpy = mockProcessKill(() => true);
|
|
36
|
+
vi.useFakeTimers();
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
killSpy.mockRestore();
|
|
40
|
+
vi.useRealTimers();
|
|
41
|
+
});
|
|
42
|
+
it("returns true immediately if pids list is empty", () => {
|
|
43
|
+
const result = killProcessTree([]);
|
|
44
|
+
expect(result).toBe(true);
|
|
45
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
it("sends SIGTERM to all provided PIDs", () => {
|
|
48
|
+
// Track whether SIGTERM has been sent to each PID
|
|
49
|
+
const terminatedPids = new Set();
|
|
50
|
+
killSpy.mockImplementation((_pid, sig) => {
|
|
51
|
+
if (sig === "SIGTERM") {
|
|
52
|
+
terminatedPids.add(_pid);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (sig === 0) {
|
|
56
|
+
// Alive until SIGTERM has been sent
|
|
57
|
+
if (terminatedPids.has(_pid))
|
|
58
|
+
throw new Error("no such process");
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
});
|
|
63
|
+
mockExecSync.mockReturnValue("");
|
|
64
|
+
killProcessTree([1234, 5678]);
|
|
65
|
+
expect(killSpy).toHaveBeenCalledWith(1234, "SIGTERM");
|
|
66
|
+
expect(killSpy).toHaveBeenCalledWith(5678, "SIGTERM");
|
|
67
|
+
});
|
|
68
|
+
it("escalates to SIGKILL after timeout when processes remain alive", () => {
|
|
69
|
+
// Process stays alive through SIGTERM, only dies after SIGKILL check
|
|
70
|
+
let killedWithSigkill = false;
|
|
71
|
+
killSpy.mockImplementation((_pid, sig) => {
|
|
72
|
+
if (sig === "SIGKILL") {
|
|
73
|
+
killedWithSigkill = true;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (sig === 0) {
|
|
77
|
+
// Stay alive until SIGKILL has been sent
|
|
78
|
+
if (killedWithSigkill)
|
|
79
|
+
throw new Error("no such process");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
85
|
+
if (cmd === "sleep 0.1")
|
|
86
|
+
vi.advanceTimersByTime(100);
|
|
87
|
+
return "";
|
|
88
|
+
});
|
|
89
|
+
killProcessTree([1234], 200);
|
|
90
|
+
expect(killSpy).toHaveBeenCalledWith(1234, "SIGTERM");
|
|
91
|
+
expect(killSpy).toHaveBeenCalledWith(1234, "SIGKILL");
|
|
92
|
+
});
|
|
93
|
+
it("does not SIGKILL when processes exit gracefully before timeout", () => {
|
|
94
|
+
let sigkillCalled = false;
|
|
95
|
+
let terminatedPid = null;
|
|
96
|
+
killSpy.mockImplementation((_pid, sig) => {
|
|
97
|
+
if (sig === "SIGKILL") {
|
|
98
|
+
sigkillCalled = true;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (sig === "SIGTERM") {
|
|
102
|
+
terminatedPid = _pid;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (sig === 0) {
|
|
106
|
+
// Dead after SIGTERM
|
|
107
|
+
if (terminatedPid === _pid)
|
|
108
|
+
throw new Error("no such process");
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
mockExecSync.mockReturnValue("");
|
|
114
|
+
killProcessTree([1234]);
|
|
115
|
+
expect(sigkillCalled).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// ─── getTmuxPanePid ────────────────────────────────────────────────────────────
|
|
119
|
+
describe("getTmuxPanePid", () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
vi.clearAllMocks();
|
|
122
|
+
});
|
|
123
|
+
it("returns parsed PID from tmux list-panes output", () => {
|
|
124
|
+
mockExecSync.mockReturnValue("42000\n");
|
|
125
|
+
const pid = getTmuxPanePid("agentmesh-test-agent");
|
|
126
|
+
expect(pid).toBe(42000);
|
|
127
|
+
});
|
|
128
|
+
it("returns null when tmux command fails", () => {
|
|
129
|
+
mockExecSync.mockImplementation(() => {
|
|
130
|
+
throw new Error("no server running");
|
|
131
|
+
});
|
|
132
|
+
const pid = getTmuxPanePid("agentmesh-nonexistent");
|
|
133
|
+
expect(pid).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it("returns null when output is not a valid number", () => {
|
|
136
|
+
mockExecSync.mockReturnValue("not-a-pid\n");
|
|
137
|
+
const pid = getTmuxPanePid("agentmesh-test-agent");
|
|
138
|
+
expect(pid).toBeNull();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// ─── getProcessTree ────────────────────────────────────────────────────────────
|
|
142
|
+
describe("getProcessTree", () => {
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
vi.clearAllMocks();
|
|
145
|
+
});
|
|
146
|
+
it("returns direct children of a process", () => {
|
|
147
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
148
|
+
const c = cmd;
|
|
149
|
+
if (c.includes("pgrep -P 1000"))
|
|
150
|
+
return "1001\n1002\n";
|
|
151
|
+
return "";
|
|
152
|
+
});
|
|
153
|
+
const tree = getProcessTree(1000);
|
|
154
|
+
expect(tree).toContain(1001);
|
|
155
|
+
expect(tree).toContain(1002);
|
|
156
|
+
});
|
|
157
|
+
it("recursively includes grandchildren", () => {
|
|
158
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
159
|
+
const c = cmd;
|
|
160
|
+
if (c.includes("pgrep -P 1000"))
|
|
161
|
+
return "1001\n";
|
|
162
|
+
if (c.includes("pgrep -P 1001"))
|
|
163
|
+
return "1002\n";
|
|
164
|
+
return "";
|
|
165
|
+
});
|
|
166
|
+
const tree = getProcessTree(1000);
|
|
167
|
+
expect(tree).toContain(1001);
|
|
168
|
+
expect(tree).toContain(1002);
|
|
169
|
+
});
|
|
170
|
+
it("returns empty array when process has no children", () => {
|
|
171
|
+
mockExecSync.mockReturnValue("");
|
|
172
|
+
const tree = getProcessTree(9999);
|
|
173
|
+
expect(tree).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
it("returns empty array when pgrep fails", () => {
|
|
176
|
+
mockExecSync.mockImplementation(() => {
|
|
177
|
+
throw new Error("pgrep: no matching processes");
|
|
178
|
+
});
|
|
179
|
+
const tree = getProcessTree(9999);
|
|
180
|
+
expect(tree).toEqual([]);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// ─── captureAgentChildPids ─────────────────────────────────────────────────────
|
|
184
|
+
describe("captureAgentChildPids", () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
vi.clearAllMocks();
|
|
187
|
+
});
|
|
188
|
+
it("returns empty array when tmux session has no pane PID", () => {
|
|
189
|
+
mockExecSync.mockImplementation(() => {
|
|
190
|
+
throw new Error("no server");
|
|
191
|
+
});
|
|
192
|
+
const pids = captureAgentChildPids("test-agent");
|
|
193
|
+
expect(pids).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
it("returns pane PID and all descendants", () => {
|
|
196
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
197
|
+
const c = cmd;
|
|
198
|
+
if (c.includes("list-panes"))
|
|
199
|
+
return "5000\n";
|
|
200
|
+
if (c.includes("pgrep -P 5000"))
|
|
201
|
+
return "5001\n5002\n";
|
|
202
|
+
return "";
|
|
203
|
+
});
|
|
204
|
+
const pids = captureAgentChildPids("test-agent");
|
|
205
|
+
expect(pids).toContain(5000);
|
|
206
|
+
expect(pids).toContain(5001);
|
|
207
|
+
expect(pids).toContain(5002);
|
|
208
|
+
});
|
|
209
|
+
it("deduplicates PIDs", () => {
|
|
210
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
211
|
+
const c = cmd;
|
|
212
|
+
if (c.includes("list-panes"))
|
|
213
|
+
return "5000\n";
|
|
214
|
+
if (c.includes("pgrep -P 5000"))
|
|
215
|
+
return "5001\n5001\n"; // duplicate
|
|
216
|
+
return "";
|
|
217
|
+
});
|
|
218
|
+
const pids = captureAgentChildPids("test-agent");
|
|
219
|
+
const uniquePids = [...new Set(pids)];
|
|
220
|
+
expect(pids.length).toBe(uniquePids.length);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
// ─── destroySession with childPids ────────────────────────────────────────────
|
|
224
|
+
describe("destroySession with childPids", () => {
|
|
225
|
+
let killSpy;
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
killSpy = mockProcessKill(() => true);
|
|
228
|
+
vi.clearAllMocks();
|
|
229
|
+
});
|
|
230
|
+
afterEach(() => {
|
|
231
|
+
killSpy.mockRestore();
|
|
232
|
+
});
|
|
233
|
+
it("kills child PIDs before killing the tmux session", () => {
|
|
234
|
+
const callOrder = [];
|
|
235
|
+
const terminatedPids = new Set();
|
|
236
|
+
killSpy.mockImplementation((_pid, sig) => {
|
|
237
|
+
if (sig === "SIGTERM") {
|
|
238
|
+
callOrder.push(`kill-${_pid}`);
|
|
239
|
+
terminatedPids.add(_pid);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (sig === 0) {
|
|
243
|
+
if (terminatedPids.has(_pid))
|
|
244
|
+
throw new Error("dead");
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
250
|
+
const c = cmd;
|
|
251
|
+
if (c.includes("has-session"))
|
|
252
|
+
return "";
|
|
253
|
+
if (c.includes("kill-session"))
|
|
254
|
+
callOrder.push("tmux-kill");
|
|
255
|
+
return "";
|
|
256
|
+
});
|
|
257
|
+
destroySession("test-agent", [1234, 5678]);
|
|
258
|
+
const childKillIndex = callOrder.findIndex((c) => c.startsWith("kill-"));
|
|
259
|
+
const tmuxKillIndex = callOrder.indexOf("tmux-kill");
|
|
260
|
+
expect(childKillIndex).toBeGreaterThanOrEqual(0);
|
|
261
|
+
expect(tmuxKillIndex).toBeGreaterThan(childKillIndex);
|
|
262
|
+
});
|
|
263
|
+
it("still destroys tmux session when no childPids provided", () => {
|
|
264
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
265
|
+
const c = cmd;
|
|
266
|
+
if (c.includes("has-session"))
|
|
267
|
+
return "";
|
|
268
|
+
return "";
|
|
269
|
+
});
|
|
270
|
+
destroySession("test-agent");
|
|
271
|
+
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("kill-session"));
|
|
272
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
it("still destroys tmux session when childPids is empty array", () => {
|
|
275
|
+
mockExecSync.mockImplementation((cmd) => {
|
|
276
|
+
const c = cmd;
|
|
277
|
+
if (c.includes("has-session"))
|
|
278
|
+
return "";
|
|
279
|
+
return "";
|
|
280
|
+
});
|
|
281
|
+
destroySession("test-agent", []);
|
|
282
|
+
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("kill-session"));
|
|
283
|
+
expect(killSpy).not.toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
//# sourceMappingURL=orphan-process.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-process.test.js","sourceRoot":"","sources":["../../src/__tests__/orphan-process.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,11 @@ describe("Runner Module", () => {
|
|
|
12
12
|
expect(detectRunner("claude --some-flag")).toBe("claude");
|
|
13
13
|
expect(detectRunner("CLAUDE")).toBe("claude");
|
|
14
14
|
});
|
|
15
|
+
it("should detect codex runner", () => {
|
|
16
|
+
expect(detectRunner("codex")).toBe("codex");
|
|
17
|
+
expect(detectRunner("codex --some-flag")).toBe("codex");
|
|
18
|
+
expect(detectRunner("CODEX")).toBe("codex");
|
|
19
|
+
});
|
|
15
20
|
it("should detect custom runner for unknown commands", () => {
|
|
16
21
|
expect(detectRunner("aider")).toBe("custom");
|
|
17
22
|
expect(detectRunner("cursor")).toBe("custom");
|
|
@@ -75,11 +80,22 @@ describe("Runner Module", () => {
|
|
|
75
80
|
expect(config.model).toBe("gpt-4");
|
|
76
81
|
expect(Object.keys(config.env)).toHaveLength(0);
|
|
77
82
|
});
|
|
83
|
+
it("should build config for codex with CODEX_MODEL env", () => {
|
|
84
|
+
const config = buildRunnerConfig({
|
|
85
|
+
cliModel: "openai/gpt-5-codex",
|
|
86
|
+
defaultModel: "anthropic/claude-sonnet-4-5",
|
|
87
|
+
command: "codex",
|
|
88
|
+
});
|
|
89
|
+
expect(config.type).toBe("codex");
|
|
90
|
+
expect(config.model).toBe("openai/gpt-5-codex");
|
|
91
|
+
expect(config.env.CODEX_MODEL).toBe("openai/gpt-5-codex");
|
|
92
|
+
});
|
|
78
93
|
});
|
|
79
94
|
describe("getRunnerDisplayName", () => {
|
|
80
95
|
it("should return correct display names", () => {
|
|
81
96
|
expect(getRunnerDisplayName("opencode")).toBe("OpenCode");
|
|
82
97
|
expect(getRunnerDisplayName("claude")).toBe("Claude CLI");
|
|
98
|
+
expect(getRunnerDisplayName("codex")).toBe("Codex CLI");
|
|
83
99
|
expect(getRunnerDisplayName("custom")).toBe("Custom");
|
|
84
100
|
});
|
|
85
101
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../src/__tests__/runner.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,EACpB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAClD,MAAM,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9D,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC1D,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,MAAM,GAAG,YAAY,CAAC;gBAC1B,QAAQ,EAAE,WAAW;gBACrB,UAAU,EAAE,aAAa;gBACzB,YAAY,EAAE,eAAe;gBAC7B,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,YAAY,CAAC;gBAC1B,UAAU,EAAE,aAAa;gBACzB,YAAY,EAAE,eAAe;gBAC7B,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,MAAM,GAAG,YAAY,CAAC;gBAC1B,YAAY,EAAE,eAAe;gBAC7B,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAClE,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,QAAQ,EAAE,iBAAiB;gBAC3B,YAAY,EAAE,iBAAiB;gBAC/B,OAAO,EAAE,UAAU;aACpB,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAC9D,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,QAAQ,EAAE,iBAAiB;gBAC3B,YAAY,EAAE,iBAAiB;gBAC/B,OAAO,EAAE,QAAQ;aAClB,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;YAChE,MAAM,MAAM,GAAG,iBAAiB,CAAC;gBAC/B,QAAQ,EAAE,OAAO;gBACjB,YAAY,EAAE,iBAAiB;gBAC/B,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC1D,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1D,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../src/__tests__/runner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
6
|
import * as tmux from "../core/tmux.js";
|
|
4
|
-
import { checkAgentProgress, cleanupOrphanContainers, detectPermissionPrompt, findOrphanContainers, getLastActivityTime, isProcessRunning, sendNudge, } from "../core/watchdog.js";
|
|
7
|
+
import { checkAgentProgress, cleanupOrphanContainers, clearWaitingForHuman, detectPermissionPrompt, findOrphanContainers, getLastActivityTime, isProcessRunning, isWaitingForHuman, sendNudge, setWaitingForHuman, } from "../core/watchdog.js";
|
|
5
8
|
// Mock child_process
|
|
6
9
|
vi.mock("node:child_process", () => ({
|
|
7
10
|
execSync: vi.fn(),
|
|
@@ -11,6 +14,19 @@ vi.mock("node:child_process", () => ({
|
|
|
11
14
|
vi.mock("../core/tmux.js", () => ({
|
|
12
15
|
captureSessionOutput: vi.fn(),
|
|
13
16
|
}));
|
|
17
|
+
// Mock node:fs for waiting signal file tests
|
|
18
|
+
vi.mock("node:fs", () => ({
|
|
19
|
+
default: {
|
|
20
|
+
mkdirSync: vi.fn(),
|
|
21
|
+
writeFileSync: vi.fn(),
|
|
22
|
+
unlinkSync: vi.fn(),
|
|
23
|
+
readFileSync: vi.fn(),
|
|
24
|
+
},
|
|
25
|
+
mkdirSync: vi.fn(),
|
|
26
|
+
writeFileSync: vi.fn(),
|
|
27
|
+
unlinkSync: vi.fn(),
|
|
28
|
+
readFileSync: vi.fn(),
|
|
29
|
+
}));
|
|
14
30
|
describe("Watchdog Module", () => {
|
|
15
31
|
beforeEach(() => {
|
|
16
32
|
vi.resetAllMocks();
|
|
@@ -48,20 +64,36 @@ describe("Watchdog Module", () => {
|
|
|
48
64
|
const result = detectPermissionPrompt("test-agent");
|
|
49
65
|
expect(result).toBeNull();
|
|
50
66
|
});
|
|
67
|
+
it("should not treat standalone 'Reject' text as a permission prompt", () => {
|
|
68
|
+
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Unit test failed\nReject this PR until CI is green");
|
|
69
|
+
const result = detectPermissionPrompt("test-agent");
|
|
70
|
+
expect(result).toBeNull();
|
|
71
|
+
});
|
|
51
72
|
});
|
|
52
73
|
describe("getLastActivityTime", () => {
|
|
53
74
|
it("should parse timestamp from local log file", () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
const originalHome = process.env.HOME;
|
|
76
|
+
try {
|
|
77
|
+
process.env.HOME = "/tmp/agent-home";
|
|
78
|
+
vi.mocked(spawnSync).mockReturnValue({
|
|
79
|
+
status: 0,
|
|
80
|
+
stdout: "INFO 2026-02-26T00:14:42 +0ms service=opencode message=test",
|
|
81
|
+
stderr: "",
|
|
82
|
+
pid: 123,
|
|
83
|
+
signal: null,
|
|
84
|
+
output: [],
|
|
85
|
+
});
|
|
86
|
+
const result = getLastActivityTime("test-agent");
|
|
87
|
+
expect(result).toBeInstanceOf(Date);
|
|
88
|
+
expect(result?.toISOString()).toContain("2026-02-26");
|
|
89
|
+
expect(spawnSync).toHaveBeenCalledWith("sh", [
|
|
90
|
+
"-c",
|
|
91
|
+
"ls -t /tmp/agent-home/.agentmesh/opencode-data/test-agent/opencode/log/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null",
|
|
92
|
+
], { encoding: "utf-8", timeout: 5000 });
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
process.env.HOME = originalHome;
|
|
96
|
+
}
|
|
65
97
|
});
|
|
66
98
|
it("should parse timestamp from container log file", () => {
|
|
67
99
|
vi.mocked(spawnSync).mockReturnValue({
|
|
@@ -286,5 +318,99 @@ describe("Watchdog Module", () => {
|
|
|
286
318
|
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('\\"quotes\\"'), expect.any(Object));
|
|
287
319
|
});
|
|
288
320
|
});
|
|
321
|
+
describe("waiting-for-human signal", () => {
|
|
322
|
+
const agentName = "test-agent";
|
|
323
|
+
const waitingFilePath = path.join(os.homedir(), ".agentmesh", "agents", `${agentName}.waiting`);
|
|
324
|
+
beforeEach(() => {
|
|
325
|
+
vi.resetAllMocks();
|
|
326
|
+
});
|
|
327
|
+
describe("setWaitingForHuman", () => {
|
|
328
|
+
it("should create the agents directory and write the signal file", () => {
|
|
329
|
+
setWaitingForHuman(agentName, "Waiting for approval");
|
|
330
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(os.homedir(), ".agentmesh", "agents"), {
|
|
331
|
+
recursive: true,
|
|
332
|
+
});
|
|
333
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(waitingFilePath, expect.stringContaining("Waiting for approval"), { mode: 0o600 });
|
|
334
|
+
});
|
|
335
|
+
it("should use default reason when none provided", () => {
|
|
336
|
+
setWaitingForHuman(agentName);
|
|
337
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(waitingFilePath, expect.stringContaining("Waiting for human input"), { mode: 0o600 });
|
|
338
|
+
});
|
|
339
|
+
it("should write valid JSON with reason and since fields", () => {
|
|
340
|
+
setWaitingForHuman(agentName, "Test reason");
|
|
341
|
+
const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0];
|
|
342
|
+
const written = JSON.parse(writeCall[1]);
|
|
343
|
+
expect(written).toHaveProperty("reason", "Test reason");
|
|
344
|
+
expect(written).toHaveProperty("since");
|
|
345
|
+
expect(() => new Date(written.since)).not.toThrow();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
describe("clearWaitingForHuman", () => {
|
|
349
|
+
it("should delete the signal file", () => {
|
|
350
|
+
clearWaitingForHuman(agentName);
|
|
351
|
+
expect(fs.unlinkSync).toHaveBeenCalledWith(waitingFilePath);
|
|
352
|
+
});
|
|
353
|
+
it("should not throw when the signal file does not exist", () => {
|
|
354
|
+
vi.mocked(fs.unlinkSync).mockImplementation(() => {
|
|
355
|
+
throw new Error("ENOENT: no such file");
|
|
356
|
+
});
|
|
357
|
+
expect(() => clearWaitingForHuman(agentName)).not.toThrow();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
describe("isWaitingForHuman", () => {
|
|
361
|
+
it("should return waiting=true with reason and since when signal file exists", () => {
|
|
362
|
+
const payload = { reason: "Awaiting handoff reply", since: "2026-02-28T10:00:00.000Z" };
|
|
363
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(payload));
|
|
364
|
+
const result = isWaitingForHuman(agentName);
|
|
365
|
+
expect(result.waiting).toBe(true);
|
|
366
|
+
expect(result.reason).toBe("Awaiting handoff reply");
|
|
367
|
+
expect(result.since).toBe("2026-02-28T10:00:00.000Z");
|
|
368
|
+
});
|
|
369
|
+
it("should return waiting=false when signal file does not exist", () => {
|
|
370
|
+
vi.mocked(fs.readFileSync).mockImplementation(() => {
|
|
371
|
+
throw new Error("ENOENT: no such file");
|
|
372
|
+
});
|
|
373
|
+
const result = isWaitingForHuman(agentName);
|
|
374
|
+
expect(result.waiting).toBe(false);
|
|
375
|
+
});
|
|
376
|
+
it("should return waiting=false on malformed JSON", () => {
|
|
377
|
+
vi.mocked(fs.readFileSync).mockReturnValue("not-json");
|
|
378
|
+
const result = isWaitingForHuman(agentName);
|
|
379
|
+
expect(result.waiting).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
describe("checkAgentProgress with waiting_for_human", () => {
|
|
383
|
+
it("should return waiting_for_human when signal file exists, before any other check", () => {
|
|
384
|
+
const payload = {
|
|
385
|
+
reason: "Waiting for Terraform decision",
|
|
386
|
+
since: "2026-02-28T10:00:00.000Z",
|
|
387
|
+
};
|
|
388
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(payload));
|
|
389
|
+
// Even with a permission prompt visible in tmux, waiting_for_human takes priority
|
|
390
|
+
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Permission required\nAllow once");
|
|
391
|
+
const result = checkAgentProgress(agentName);
|
|
392
|
+
expect(result.status).toBe("waiting_for_human");
|
|
393
|
+
expect(result.details).toBe("Waiting for Terraform decision");
|
|
394
|
+
});
|
|
395
|
+
it("should fall through to normal checks when no signal file", () => {
|
|
396
|
+
vi.mocked(fs.readFileSync).mockImplementation(() => {
|
|
397
|
+
throw new Error("ENOENT");
|
|
398
|
+
});
|
|
399
|
+
vi.mocked(tmux.captureSessionOutput).mockReturnValue("Normal output");
|
|
400
|
+
// Mock recent activity so it returns active
|
|
401
|
+
const recentTime = new Date(Date.now() - 30 * 1000);
|
|
402
|
+
vi.mocked(spawnSync).mockReturnValue({
|
|
403
|
+
status: 0,
|
|
404
|
+
stdout: `INFO ${recentTime.toISOString().slice(0, 19)} +0ms service=opencode`,
|
|
405
|
+
stderr: "",
|
|
406
|
+
pid: 123,
|
|
407
|
+
signal: null,
|
|
408
|
+
output: [],
|
|
409
|
+
});
|
|
410
|
+
const result = checkAgentProgress(agentName);
|
|
411
|
+
expect(result.status).toBe("active");
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|
|
289
415
|
});
|
|
290
416
|
//# sourceMappingURL=watchdog.test.js.map
|