gnhf 0.1.10 → 0.1.11
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/README.md +17 -1
- package/dist/cli.mjs +226 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,6 @@ You wake up to a branch full of clean work and a log of everything that happened
|
|
|
47
47
|
- **Dead simple** — one command starts an autonomous loop that runs until you Ctrl+C or a configured runtime cap is reached
|
|
48
48
|
- **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries and exponential backoff
|
|
49
49
|
- **Agent-agnostic** — works with Claude Code, Codex, Rovo Dev, or OpenCode out of the box
|
|
50
|
-
- **Terminal-safe rendering** — the live UI keeps wide Unicode text such as emoji and CJK glyphs aligned instead of clipping or shifting the frame
|
|
51
50
|
|
|
52
51
|
## Quick Start
|
|
53
52
|
|
|
@@ -161,6 +160,11 @@ Config lives at `~/.gnhf/config.yml`:
|
|
|
161
160
|
# Agent to use by default (claude, codex, rovodev, or opencode)
|
|
162
161
|
agent: claude
|
|
163
162
|
|
|
163
|
+
# Custom paths to agent binaries (optional)
|
|
164
|
+
# agentPathOverride:
|
|
165
|
+
# claude: /path/to/custom-claude
|
|
166
|
+
# codex: /path/to/custom-codex
|
|
167
|
+
|
|
164
168
|
# Abort after this many consecutive failures
|
|
165
169
|
maxConsecutiveFailures: 3
|
|
166
170
|
|
|
@@ -172,6 +176,18 @@ If the file does not exist yet, `gnhf` creates it on first run using the resolve
|
|
|
172
176
|
|
|
173
177
|
CLI flags override config file values. `--prevent-sleep` accepts `on`/`off` as well as `true`/`false`; the config file always uses a boolean.
|
|
174
178
|
The iteration and token caps are runtime-only flags and are not persisted in `config.yml`.
|
|
179
|
+
|
|
180
|
+
### Custom Agent Paths
|
|
181
|
+
|
|
182
|
+
Use `agentPathOverride` to point any agent at a custom binary — useful for wrappers like Claude Code Switch or custom Codex builds that accept the same flags and arguments as the original:
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
agentPathOverride:
|
|
186
|
+
claude: ~/bin/claude-code-switch
|
|
187
|
+
codex: /usr/local/bin/my-codex-wrapper
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Paths may be absolute, bare executable names already on your `PATH`, `~`-prefixed, or relative to the config directory (`~/.gnhf/`). The override replaces only the binary name; all standard arguments are preserved, so the replacement must be CLI-compatible with the original agent. On Windows, `.cmd` and `.bat` wrappers are supported, including bare names resolved from `PATH`. For `rovodev`, the override must point to an `acli`-compatible binary since gnhf invokes it as `<bin> rovodev serve ...`.
|
|
175
191
|
When sleep prevention is enabled, `gnhf` uses the native mechanism for your OS: `caffeinate` on macOS, `systemd-inhibit` on Linux, and a small PowerShell helper backed by `SetThreadExecutionState` on Windows.
|
|
176
192
|
|
|
177
193
|
## Debug Logs
|
package/dist/cli.mjs
CHANGED
|
@@ -11,8 +11,15 @@ import { createServer } from "node:net";
|
|
|
11
11
|
import { EventEmitter } from "node:events";
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
//#region src/core/config.ts
|
|
14
|
+
const AGENT_NAMES = [
|
|
15
|
+
"claude",
|
|
16
|
+
"codex",
|
|
17
|
+
"rovodev",
|
|
18
|
+
"opencode"
|
|
19
|
+
];
|
|
14
20
|
const DEFAULT_CONFIG = {
|
|
15
21
|
agent: "claude",
|
|
22
|
+
agentPathOverride: {},
|
|
16
23
|
maxConsecutiveFailures: 3,
|
|
17
24
|
preventSleep: true
|
|
18
25
|
};
|
|
@@ -25,7 +32,35 @@ function normalizePreventSleep(value) {
|
|
|
25
32
|
if (value === "on") return true;
|
|
26
33
|
if (value === "off") return false;
|
|
27
34
|
}
|
|
28
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a user-supplied path against the config directory (~/.gnhf).
|
|
37
|
+
* Expands leading `~` or `~/` to the home directory, then resolves relative
|
|
38
|
+
* paths against `baseDir` so that entries like `./bin/codex` work predictably
|
|
39
|
+
* regardless of the repo's cwd. Bare executable names and absolute paths pass
|
|
40
|
+
* through unchanged.
|
|
41
|
+
*/
|
|
42
|
+
function resolveConfigPath(raw, baseDir) {
|
|
43
|
+
if (raw !== "~" && !raw.startsWith("~/") && !raw.startsWith("~\\") && !raw.includes("/") && !raw.includes("\\")) return raw;
|
|
44
|
+
const home = homedir();
|
|
45
|
+
let expanded = raw;
|
|
46
|
+
if (expanded === "~") expanded = home;
|
|
47
|
+
else if (expanded.startsWith("~/") || expanded.startsWith("~\\")) expanded = join(home, expanded.slice(2));
|
|
48
|
+
return resolve(baseDir, expanded);
|
|
49
|
+
}
|
|
50
|
+
function normalizeAgentPathOverride(value, configDir) {
|
|
51
|
+
if (value === void 0 || value === null) return void 0;
|
|
52
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new InvalidConfigError(`Invalid config value for agentPathOverride: expected an object mapping agent names to paths`);
|
|
53
|
+
const validNames = new Set(AGENT_NAMES);
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const [key, val] of Object.entries(value)) {
|
|
56
|
+
if (!validNames.has(key)) throw new InvalidConfigError(`Invalid agent name in agentPathOverride: "${key}". Use "claude", "codex", "rovodev", or "opencode".`);
|
|
57
|
+
if (typeof val !== "string") throw new InvalidConfigError(`Invalid path for agentPathOverride.${key}: expected a string`);
|
|
58
|
+
if (val.trim() === "") throw new InvalidConfigError(`Invalid path for agentPathOverride.${key}: expected a non-empty string`);
|
|
59
|
+
result[key] = resolveConfigPath(val, configDir);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function normalizeConfig(config, configDir) {
|
|
29
64
|
const normalized = { ...config };
|
|
30
65
|
const hasPreventSleep = Object.prototype.hasOwnProperty.call(config, "preventSleep");
|
|
31
66
|
const preventSleep = normalizePreventSleep(config.preventSleep);
|
|
@@ -33,22 +68,47 @@ function normalizeConfig(config) {
|
|
|
33
68
|
if (hasPreventSleep && config.preventSleep !== void 0) throw new InvalidConfigError(`Invalid config value for preventSleep: ${String(config.preventSleep)}`);
|
|
34
69
|
delete normalized.preventSleep;
|
|
35
70
|
} else normalized.preventSleep = preventSleep;
|
|
71
|
+
if (Object.prototype.hasOwnProperty.call(config, "agentPathOverride")) {
|
|
72
|
+
const resolveDir = configDir ?? join(homedir(), ".gnhf");
|
|
73
|
+
const agentPathOverride = normalizeAgentPathOverride(config.agentPathOverride, resolveDir);
|
|
74
|
+
if (agentPathOverride === void 0) delete normalized.agentPathOverride;
|
|
75
|
+
else normalized.agentPathOverride = agentPathOverride;
|
|
76
|
+
} else delete normalized.agentPathOverride;
|
|
36
77
|
return normalized;
|
|
37
78
|
}
|
|
38
79
|
function isMissingConfigError(error) {
|
|
39
80
|
if (!(error instanceof Error)) return false;
|
|
40
81
|
return "code" in error ? error.code === "ENOENT" : error.message.includes("ENOENT");
|
|
41
82
|
}
|
|
83
|
+
function serializeAgentPathOverride(agentPathOverride) {
|
|
84
|
+
const serializedOverrides = Object.fromEntries(AGENT_NAMES.flatMap((name) => {
|
|
85
|
+
const value = agentPathOverride[name];
|
|
86
|
+
return value === void 0 ? [] : [[name, value]];
|
|
87
|
+
}));
|
|
88
|
+
if (Object.keys(serializedOverrides).length === 0) return "";
|
|
89
|
+
return yaml.dump({ agentPathOverride: serializedOverrides }, {
|
|
90
|
+
lineWidth: -1,
|
|
91
|
+
noRefs: true,
|
|
92
|
+
sortKeys: false
|
|
93
|
+
}).trimEnd();
|
|
94
|
+
}
|
|
42
95
|
function serializeConfig(config) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
96
|
+
const agentPathOverrideSection = serializeAgentPathOverride(config.agentPathOverride);
|
|
97
|
+
const lines = [
|
|
98
|
+
"# Agent to use by default",
|
|
99
|
+
`agent: ${config.agent}`,
|
|
100
|
+
"",
|
|
101
|
+
"# Custom paths to agent binaries (optional)",
|
|
102
|
+
"# Paths may be absolute, bare executable names on PATH,",
|
|
103
|
+
"# ~-prefixed, or relative to this config directory.",
|
|
104
|
+
"# Note: rovodev overrides must point to an acli-compatible binary.",
|
|
105
|
+
"# agentPathOverride:",
|
|
106
|
+
"# claude: /path/to/custom-claude",
|
|
107
|
+
"# codex: /path/to/custom-codex"
|
|
108
|
+
];
|
|
109
|
+
if (agentPathOverrideSection) lines.push(...agentPathOverrideSection.split("\n"));
|
|
110
|
+
lines.push("", "# Abort after this many consecutive failures", `maxConsecutiveFailures: ${config.maxConsecutiveFailures}`, "", "# Prevent the machine from sleeping during a run", `preventSleep: ${config.preventSleep}`, "");
|
|
111
|
+
return lines.join("\n");
|
|
52
112
|
}
|
|
53
113
|
function loadConfig(overrides) {
|
|
54
114
|
const configDir = join(homedir(), ".gnhf");
|
|
@@ -57,7 +117,7 @@ function loadConfig(overrides) {
|
|
|
57
117
|
let shouldBootstrapConfig = false;
|
|
58
118
|
try {
|
|
59
119
|
const raw = readFileSync(configPath, "utf-8");
|
|
60
|
-
fileConfig = normalizeConfig(yaml.load(raw) ?? {});
|
|
120
|
+
fileConfig = normalizeConfig(yaml.load(raw) ?? {}, configDir);
|
|
61
121
|
} catch (error) {
|
|
62
122
|
if (error instanceof InvalidConfigError) throw error;
|
|
63
123
|
if (isMissingConfigError(error)) shouldBootstrapConfig = true;
|
|
@@ -745,10 +805,12 @@ function parseJSONLStream(stream, logStream, callback) {
|
|
|
745
805
|
* Wire an AbortSignal to kill a child process.
|
|
746
806
|
* Returns true if the signal was already aborted (caller should return early).
|
|
747
807
|
*/
|
|
748
|
-
function setupAbortHandler(signal, child, reject) {
|
|
808
|
+
function setupAbortHandler(signal, child, reject, abortChild = () => {
|
|
809
|
+
child.kill("SIGTERM");
|
|
810
|
+
}) {
|
|
749
811
|
if (!signal) return false;
|
|
750
812
|
const onAbort = () => {
|
|
751
|
-
|
|
813
|
+
abortChild();
|
|
752
814
|
reject(/* @__PURE__ */ new Error("Agent was aborted"));
|
|
753
815
|
};
|
|
754
816
|
if (signal.aborted) {
|
|
@@ -761,13 +823,52 @@ function setupAbortHandler(signal, child, reject) {
|
|
|
761
823
|
}
|
|
762
824
|
//#endregion
|
|
763
825
|
//#region src/core/agents/claude.ts
|
|
826
|
+
function shouldUseWindowsShell$2(bin, platform) {
|
|
827
|
+
if (platform !== "win32") return false;
|
|
828
|
+
if (/\.(cmd|bat)$/i.test(bin)) return true;
|
|
829
|
+
if (/[\\/]/.test(bin)) return false;
|
|
830
|
+
try {
|
|
831
|
+
const firstMatch = execFileSync("where", [bin], {
|
|
832
|
+
encoding: "utf8",
|
|
833
|
+
stdio: [
|
|
834
|
+
"ignore",
|
|
835
|
+
"pipe",
|
|
836
|
+
"ignore"
|
|
837
|
+
]
|
|
838
|
+
}).split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
839
|
+
return firstMatch ? /\.(cmd|bat)$/i.test(firstMatch) : false;
|
|
840
|
+
} catch {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function terminateClaudeProcess(child, platform) {
|
|
845
|
+
if (platform === "win32" && child.pid) {
|
|
846
|
+
try {
|
|
847
|
+
execFileSync("taskkill", [
|
|
848
|
+
"/T",
|
|
849
|
+
"/F",
|
|
850
|
+
"/PID",
|
|
851
|
+
String(child.pid)
|
|
852
|
+
], { stdio: "ignore" });
|
|
853
|
+
} catch {}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
child.kill("SIGTERM");
|
|
857
|
+
}
|
|
764
858
|
var ClaudeAgent = class {
|
|
765
859
|
name = "claude";
|
|
860
|
+
bin;
|
|
861
|
+
platform;
|
|
862
|
+
constructor(binOrDeps = {}) {
|
|
863
|
+
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
864
|
+
this.bin = deps.bin ?? "claude";
|
|
865
|
+
this.platform = deps.platform ?? process.platform;
|
|
866
|
+
}
|
|
766
867
|
run(prompt, cwd, options) {
|
|
767
868
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
768
869
|
return new Promise((resolve, reject) => {
|
|
769
870
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
770
|
-
const child = spawn(
|
|
871
|
+
const child = spawn(this.bin, [
|
|
771
872
|
"-p",
|
|
772
873
|
prompt,
|
|
773
874
|
"--verbose",
|
|
@@ -778,6 +879,7 @@ var ClaudeAgent = class {
|
|
|
778
879
|
"--dangerously-skip-permissions"
|
|
779
880
|
], {
|
|
780
881
|
cwd,
|
|
882
|
+
shell: shouldUseWindowsShell$2(this.bin, this.platform),
|
|
781
883
|
stdio: [
|
|
782
884
|
"ignore",
|
|
783
885
|
"pipe",
|
|
@@ -785,7 +887,7 @@ var ClaudeAgent = class {
|
|
|
785
887
|
],
|
|
786
888
|
env: process.env
|
|
787
889
|
});
|
|
788
|
-
if (setupAbortHandler(signal, child, reject)) return;
|
|
890
|
+
if (setupAbortHandler(signal, child, reject, () => terminateClaudeProcess(child, this.platform))) return;
|
|
789
891
|
let resultEvent = null;
|
|
790
892
|
const cumulative = {
|
|
791
893
|
inputTokens: 0,
|
|
@@ -841,17 +943,54 @@ var ClaudeAgent = class {
|
|
|
841
943
|
};
|
|
842
944
|
//#endregion
|
|
843
945
|
//#region src/core/agents/codex.ts
|
|
946
|
+
function shouldUseWindowsShell$1(bin, platform) {
|
|
947
|
+
if (platform !== "win32") return false;
|
|
948
|
+
if (/\.(cmd|bat)$/i.test(bin)) return true;
|
|
949
|
+
if (/[\\/]/.test(bin)) return false;
|
|
950
|
+
try {
|
|
951
|
+
const firstMatch = execFileSync("where", [bin], {
|
|
952
|
+
encoding: "utf8",
|
|
953
|
+
stdio: [
|
|
954
|
+
"ignore",
|
|
955
|
+
"pipe",
|
|
956
|
+
"ignore"
|
|
957
|
+
]
|
|
958
|
+
}).split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
959
|
+
return firstMatch ? /\.(cmd|bat)$/i.test(firstMatch) : false;
|
|
960
|
+
} catch {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function terminateCodexProcess(child, platform) {
|
|
965
|
+
if (platform === "win32" && child.pid) {
|
|
966
|
+
try {
|
|
967
|
+
execFileSync("taskkill", [
|
|
968
|
+
"/T",
|
|
969
|
+
"/F",
|
|
970
|
+
"/PID",
|
|
971
|
+
String(child.pid)
|
|
972
|
+
], { stdio: "ignore" });
|
|
973
|
+
} catch {}
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
child.kill("SIGTERM");
|
|
977
|
+
}
|
|
844
978
|
var CodexAgent = class {
|
|
845
979
|
name = "codex";
|
|
980
|
+
bin;
|
|
981
|
+
platform;
|
|
846
982
|
schemaPath;
|
|
847
|
-
constructor(schemaPath) {
|
|
983
|
+
constructor(schemaPath, binOrDeps = {}) {
|
|
984
|
+
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
985
|
+
this.bin = deps.bin ?? "codex";
|
|
986
|
+
this.platform = deps.platform ?? process.platform;
|
|
848
987
|
this.schemaPath = schemaPath;
|
|
849
988
|
}
|
|
850
989
|
run(prompt, cwd, options) {
|
|
851
990
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
852
991
|
return new Promise((resolve, reject) => {
|
|
853
992
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
854
|
-
const child = spawn(
|
|
993
|
+
const child = spawn(this.bin, [
|
|
855
994
|
"exec",
|
|
856
995
|
prompt,
|
|
857
996
|
"--json",
|
|
@@ -862,6 +1001,7 @@ var CodexAgent = class {
|
|
|
862
1001
|
"never"
|
|
863
1002
|
], {
|
|
864
1003
|
cwd,
|
|
1004
|
+
shell: shouldUseWindowsShell$1(this.bin, this.platform),
|
|
865
1005
|
stdio: [
|
|
866
1006
|
"ignore",
|
|
867
1007
|
"pipe",
|
|
@@ -869,7 +1009,7 @@ var CodexAgent = class {
|
|
|
869
1009
|
],
|
|
870
1010
|
env: process.env
|
|
871
1011
|
});
|
|
872
|
-
if (setupAbortHandler(signal, child, reject)) return;
|
|
1012
|
+
if (setupAbortHandler(signal, child, reject, () => terminateCodexProcess(child, this.platform))) return;
|
|
873
1013
|
let lastAgentMessage = null;
|
|
874
1014
|
const cumulative = {
|
|
875
1015
|
inputTokens: 0,
|
|
@@ -1018,6 +1158,7 @@ function withTimeoutSignal$1(signal, timeoutMs) {
|
|
|
1018
1158
|
}
|
|
1019
1159
|
var OpenCodeAgent = class {
|
|
1020
1160
|
name = "opencode";
|
|
1161
|
+
bin;
|
|
1021
1162
|
fetchFn;
|
|
1022
1163
|
getPortFn;
|
|
1023
1164
|
killProcessFn;
|
|
@@ -1026,6 +1167,7 @@ var OpenCodeAgent = class {
|
|
|
1026
1167
|
server = null;
|
|
1027
1168
|
closingPromise = null;
|
|
1028
1169
|
constructor(deps = {}) {
|
|
1170
|
+
this.bin = deps.bin ?? "opencode";
|
|
1029
1171
|
this.fetchFn = deps.fetch ?? fetch;
|
|
1030
1172
|
this.getPortFn = deps.getPort ?? getAvailablePort$1;
|
|
1031
1173
|
this.killProcessFn = deps.killProcess ?? process.kill.bind(process);
|
|
@@ -1077,7 +1219,7 @@ var OpenCodeAgent = class {
|
|
|
1077
1219
|
const port = await this.getPortFn();
|
|
1078
1220
|
const isWindows = this.platform === "win32";
|
|
1079
1221
|
const detached = !isWindows;
|
|
1080
|
-
const child = this.spawnFn(
|
|
1222
|
+
const child = this.spawnFn(this.bin, [
|
|
1081
1223
|
"serve",
|
|
1082
1224
|
"--hostname",
|
|
1083
1225
|
"127.0.0.1",
|
|
@@ -1463,6 +1605,38 @@ function createAbortError() {
|
|
|
1463
1605
|
function isAbortError(error) {
|
|
1464
1606
|
return error instanceof Error && error.name === "AbortError";
|
|
1465
1607
|
}
|
|
1608
|
+
function shouldUseWindowsShell(bin, platform) {
|
|
1609
|
+
if (platform !== "win32") return false;
|
|
1610
|
+
if (/\.(cmd|bat)$/i.test(bin)) return true;
|
|
1611
|
+
if (/[\\/]/.test(bin)) return false;
|
|
1612
|
+
try {
|
|
1613
|
+
const firstMatch = execFileSync("where", [bin], {
|
|
1614
|
+
encoding: "utf8",
|
|
1615
|
+
stdio: [
|
|
1616
|
+
"ignore",
|
|
1617
|
+
"pipe",
|
|
1618
|
+
"ignore"
|
|
1619
|
+
]
|
|
1620
|
+
}).split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
1621
|
+
return firstMatch ? /\.(cmd|bat)$/i.test(firstMatch) : false;
|
|
1622
|
+
} catch {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function terminateRovoDevProcess(child, platform) {
|
|
1627
|
+
if (platform === "win32" && child.pid) {
|
|
1628
|
+
try {
|
|
1629
|
+
execFileSync("taskkill", [
|
|
1630
|
+
"/T",
|
|
1631
|
+
"/F",
|
|
1632
|
+
"/PID",
|
|
1633
|
+
String(child.pid)
|
|
1634
|
+
], { stdio: "ignore" });
|
|
1635
|
+
} catch {}
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
child.kill("SIGTERM");
|
|
1639
|
+
}
|
|
1466
1640
|
function getAvailablePort() {
|
|
1467
1641
|
return new Promise((resolve, reject) => {
|
|
1468
1642
|
const server = createServer();
|
|
@@ -1509,18 +1683,22 @@ async function delay(ms, signal) {
|
|
|
1509
1683
|
}
|
|
1510
1684
|
var RovoDevAgent = class {
|
|
1511
1685
|
name = "rovodev";
|
|
1686
|
+
bin;
|
|
1512
1687
|
schemaPath;
|
|
1513
1688
|
fetchFn;
|
|
1514
1689
|
getPortFn;
|
|
1515
1690
|
killProcessFn;
|
|
1691
|
+
platform;
|
|
1516
1692
|
spawnFn;
|
|
1517
1693
|
server = null;
|
|
1518
1694
|
closingPromise = null;
|
|
1519
1695
|
constructor(schemaPath, deps = {}) {
|
|
1696
|
+
this.bin = deps.bin ?? "acli";
|
|
1520
1697
|
this.schemaPath = schemaPath;
|
|
1521
1698
|
this.fetchFn = deps.fetch ?? fetch;
|
|
1522
1699
|
this.getPortFn = deps.getPort ?? getAvailablePort;
|
|
1523
1700
|
this.killProcessFn = deps.killProcess ?? process.kill.bind(process);
|
|
1701
|
+
this.platform = deps.platform ?? process.platform;
|
|
1524
1702
|
this.spawnFn = deps.spawn ?? spawn;
|
|
1525
1703
|
}
|
|
1526
1704
|
async run(prompt, cwd, options) {
|
|
@@ -1564,8 +1742,8 @@ var RovoDevAgent = class {
|
|
|
1564
1742
|
}
|
|
1565
1743
|
if (this.server && !this.server.closed) await this.shutdownServer();
|
|
1566
1744
|
const port = await this.getPortFn();
|
|
1567
|
-
const detached =
|
|
1568
|
-
const child = this.spawnFn(
|
|
1745
|
+
const detached = this.platform !== "win32";
|
|
1746
|
+
const child = this.spawnFn(this.bin, [
|
|
1569
1747
|
"rovodev",
|
|
1570
1748
|
"serve",
|
|
1571
1749
|
"--disable-session-token",
|
|
@@ -1573,6 +1751,7 @@ var RovoDevAgent = class {
|
|
|
1573
1751
|
], {
|
|
1574
1752
|
cwd,
|
|
1575
1753
|
detached,
|
|
1754
|
+
shell: shouldUseWindowsShell(this.bin, this.platform),
|
|
1576
1755
|
stdio: [
|
|
1577
1756
|
"ignore",
|
|
1578
1757
|
"pipe",
|
|
@@ -1837,11 +2016,29 @@ var RovoDevAgent = class {
|
|
|
1837
2016
|
cwd: server.cwd,
|
|
1838
2017
|
port: server.port
|
|
1839
2018
|
});
|
|
1840
|
-
this.closingPromise =
|
|
2019
|
+
this.closingPromise = this.platform === "win32" ? new Promise((resolve) => {
|
|
2020
|
+
const handleClose = () => {
|
|
2021
|
+
server.child.off("close", handleClose);
|
|
2022
|
+
resolve();
|
|
2023
|
+
};
|
|
2024
|
+
server.child.on("close", handleClose);
|
|
2025
|
+
try {
|
|
2026
|
+
terminateRovoDevProcess(server.child, this.platform);
|
|
2027
|
+
} catch {
|
|
2028
|
+
server.child.off("close", handleClose);
|
|
2029
|
+
resolve();
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
setTimeout(() => {
|
|
2033
|
+
server.child.off("close", handleClose);
|
|
2034
|
+
resolve();
|
|
2035
|
+
}, 100).unref?.();
|
|
2036
|
+
}) : shutdownChildProcess(server.child, {
|
|
1841
2037
|
detached: server.detached,
|
|
1842
2038
|
killProcess: this.killProcessFn,
|
|
1843
2039
|
timeoutMs: 3e3
|
|
1844
|
-
})
|
|
2040
|
+
});
|
|
2041
|
+
this.closingPromise = this.closingPromise.finally(() => {
|
|
1845
2042
|
if (this.server === server) this.server = null;
|
|
1846
2043
|
this.closingPromise = null;
|
|
1847
2044
|
});
|
|
@@ -1875,12 +2072,12 @@ function withTimeoutSignal(signal, timeoutMs) {
|
|
|
1875
2072
|
}
|
|
1876
2073
|
//#endregion
|
|
1877
2074
|
//#region src/core/agents/factory.ts
|
|
1878
|
-
function createAgent(name, runInfo) {
|
|
2075
|
+
function createAgent(name, runInfo, pathOverride) {
|
|
1879
2076
|
switch (name) {
|
|
1880
|
-
case "claude": return new ClaudeAgent();
|
|
1881
|
-
case "codex": return new CodexAgent(runInfo.schemaPath);
|
|
1882
|
-
case "opencode": return new OpenCodeAgent();
|
|
1883
|
-
case "rovodev": return new RovoDevAgent(runInfo.schemaPath);
|
|
2077
|
+
case "claude": return new ClaudeAgent(pathOverride);
|
|
2078
|
+
case "codex": return new CodexAgent(runInfo.schemaPath, pathOverride);
|
|
2079
|
+
case "opencode": return new OpenCodeAgent({ bin: pathOverride });
|
|
2080
|
+
case "rovodev": return new RovoDevAgent(runInfo.schemaPath, { bin: pathOverride });
|
|
1884
2081
|
}
|
|
1885
2082
|
}
|
|
1886
2083
|
//#endregion
|
|
@@ -3081,7 +3278,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
3081
3278
|
}
|
|
3082
3279
|
}
|
|
3083
3280
|
appendDebugLog("run:start", { args: process$1.argv.slice(2) });
|
|
3084
|
-
const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo), runInfo, prompt, cwd, startIteration, {
|
|
3281
|
+
const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent]), runInfo, prompt, cwd, startIteration, {
|
|
3085
3282
|
maxIterations: options.maxIterations,
|
|
3086
3283
|
maxTokens: options.maxTokens
|
|
3087
3284
|
});
|