gnhf 0.1.14 → 0.1.16
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 +15 -0
- package/dist/cli.mjs +190 -84
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -165,6 +165,15 @@ agent: claude
|
|
|
165
165
|
# claude: /path/to/custom-claude
|
|
166
166
|
# codex: /path/to/custom-codex
|
|
167
167
|
|
|
168
|
+
# Per-agent CLI arg overrides (optional)
|
|
169
|
+
# agentArgsOverride:
|
|
170
|
+
# codex:
|
|
171
|
+
# - -m
|
|
172
|
+
# - gpt-5.4
|
|
173
|
+
# - -c
|
|
174
|
+
# - model_reasoning_effort="high"
|
|
175
|
+
# - --full-auto
|
|
176
|
+
|
|
168
177
|
# Abort after this many consecutive failures
|
|
169
178
|
maxConsecutiveFailures: 3
|
|
170
179
|
|
|
@@ -177,6 +186,12 @@ If the file does not exist yet, `gnhf` creates it on first run using the resolve
|
|
|
177
186
|
CLI flags override config file values. `--prevent-sleep` accepts `on`/`off` as well as `true`/`false`; the config file always uses a boolean.
|
|
178
187
|
The iteration and token caps are runtime-only flags and are not persisted in `config.yml`.
|
|
179
188
|
|
|
189
|
+
`agentArgsOverride.<name>` lets you pass through extra CLI flags for any supported agent.
|
|
190
|
+
|
|
191
|
+
- Use it for agent-specific options like models, profiles, or reasoning settings without adding a dedicated `gnhf` config field for each one.
|
|
192
|
+
- For `codex` and `claude`, `gnhf` adds its usual non-interactive permission default only when you do not provide your own permission or execution-mode flag. If you set one explicitly, `gnhf` treats that as user-managed and does not add its default on top.
|
|
193
|
+
- Flags that `gnhf` manages itself for a given agent, such as output-shaping or local-server startup flags, are rejected during config loading so you get a clear error instead of duplicate-argument ambiguity.
|
|
194
|
+
|
|
180
195
|
### Custom Agent Paths
|
|
181
196
|
|
|
182
197
|
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:
|
package/dist/cli.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const AGENT_NAMES = [
|
|
|
20
20
|
const DEFAULT_CONFIG = {
|
|
21
21
|
agent: "claude",
|
|
22
22
|
agentPathOverride: {},
|
|
23
|
+
agentArgsOverride: {},
|
|
23
24
|
maxConsecutiveFailures: 3,
|
|
24
25
|
preventSleep: true
|
|
25
26
|
};
|
|
@@ -32,6 +33,14 @@ function normalizePreventSleep(value) {
|
|
|
32
33
|
if (value === "on") return true;
|
|
33
34
|
if (value === "off") return false;
|
|
34
35
|
}
|
|
36
|
+
function isReservedAgentArg(agent, arg) {
|
|
37
|
+
switch (agent) {
|
|
38
|
+
case "claude": return arg === "-p" || arg === "--print" || arg === "--verbose" || arg === "--output-format" || arg.startsWith("--output-format=") || arg === "--json-schema" || arg.startsWith("--json-schema=");
|
|
39
|
+
case "codex": return arg === "exec" || arg === "--json" || arg === "--output-schema" || arg.startsWith("--output-schema=") || arg === "--color" || arg.startsWith("--color=");
|
|
40
|
+
case "opencode": return arg === "serve" || arg === "--hostname" || arg.startsWith("--hostname=") || arg === "--port" || arg.startsWith("--port=") || arg === "--print-logs";
|
|
41
|
+
case "rovodev": return arg === "rovodev" || arg === "serve" || arg === "--disable-session-token";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
35
44
|
/**
|
|
36
45
|
* Resolve a user-supplied path against the config directory (~/.gnhf).
|
|
37
46
|
* Expands leading `~` or `~/` to the home directory, then resolves relative
|
|
@@ -60,6 +69,29 @@ function normalizeAgentPathOverride(value, configDir) {
|
|
|
60
69
|
}
|
|
61
70
|
return result;
|
|
62
71
|
}
|
|
72
|
+
function normalizeAgentExtraArgs(value, label, agent) {
|
|
73
|
+
if (value === void 0 || value === null) return void 0;
|
|
74
|
+
if (!Array.isArray(value)) throw new InvalidConfigError(`Invalid config value for ${label}: expected an array of strings`);
|
|
75
|
+
return value.map((entry, index) => {
|
|
76
|
+
if (typeof entry !== "string") throw new InvalidConfigError(`Invalid config value for ${label}[${index}]: expected a string`);
|
|
77
|
+
const trimmed = entry.trim();
|
|
78
|
+
if (trimmed === "") throw new InvalidConfigError(`Invalid config value for ${label}[${index}]: expected a non-empty string`);
|
|
79
|
+
if (isReservedAgentArg(agent, trimmed)) throw new InvalidConfigError(`Invalid config value for ${label}[${index}]: "${trimmed}" is managed by gnhf and cannot be overridden`);
|
|
80
|
+
return trimmed;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function normalizeAgentArgsOverride(value) {
|
|
84
|
+
if (value === void 0 || value === null) return void 0;
|
|
85
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new InvalidConfigError(`Invalid config value for agentArgsOverride: expected an object`);
|
|
86
|
+
const validNames = new Set(AGENT_NAMES);
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const [key, rawConfig] of Object.entries(value)) {
|
|
89
|
+
if (!validNames.has(key)) throw new InvalidConfigError(`Invalid agent name in agentArgsOverride: "${key}". Use "claude", "codex", "rovodev", or "opencode".`);
|
|
90
|
+
const args = normalizeAgentExtraArgs(rawConfig, `agentArgsOverride.${key}`, key);
|
|
91
|
+
if (args !== void 0) result[key] = args;
|
|
92
|
+
}
|
|
93
|
+
return Object.keys(result).length === 0 ? void 0 : result;
|
|
94
|
+
}
|
|
63
95
|
function normalizeConfig(config, configDir) {
|
|
64
96
|
const normalized = { ...config };
|
|
65
97
|
const hasPreventSleep = Object.prototype.hasOwnProperty.call(config, "preventSleep");
|
|
@@ -74,6 +106,11 @@ function normalizeConfig(config, configDir) {
|
|
|
74
106
|
if (agentPathOverride === void 0) delete normalized.agentPathOverride;
|
|
75
107
|
else normalized.agentPathOverride = agentPathOverride;
|
|
76
108
|
} else delete normalized.agentPathOverride;
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(config, "agentArgsOverride")) {
|
|
110
|
+
const agentArgsOverride = normalizeAgentArgsOverride(config.agentArgsOverride);
|
|
111
|
+
if (agentArgsOverride === void 0) delete normalized.agentArgsOverride;
|
|
112
|
+
else normalized.agentArgsOverride = agentArgsOverride;
|
|
113
|
+
} else delete normalized.agentArgsOverride;
|
|
77
114
|
return normalized;
|
|
78
115
|
}
|
|
79
116
|
function isMissingConfigError(error) {
|
|
@@ -92,8 +129,17 @@ function serializeAgentPathOverride(agentPathOverride) {
|
|
|
92
129
|
sortKeys: false
|
|
93
130
|
}).trimEnd();
|
|
94
131
|
}
|
|
132
|
+
function serializeAgentArgsOverride(agentArgsOverride) {
|
|
133
|
+
if (Object.keys(agentArgsOverride).length === 0) return "";
|
|
134
|
+
return yaml.dump({ agentArgsOverride }, {
|
|
135
|
+
lineWidth: -1,
|
|
136
|
+
noRefs: true,
|
|
137
|
+
sortKeys: false
|
|
138
|
+
}).trimEnd();
|
|
139
|
+
}
|
|
95
140
|
function serializeConfig(config) {
|
|
96
141
|
const agentPathOverrideSection = serializeAgentPathOverride(config.agentPathOverride);
|
|
142
|
+
const agentArgsOverrideSection = serializeAgentArgsOverride(config.agentArgsOverride);
|
|
97
143
|
const lines = [
|
|
98
144
|
"# Agent to use by default",
|
|
99
145
|
`agent: ${config.agent}`,
|
|
@@ -104,9 +150,19 @@ function serializeConfig(config) {
|
|
|
104
150
|
"# Note: rovodev overrides must point to an acli-compatible binary.",
|
|
105
151
|
"# agentPathOverride:",
|
|
106
152
|
"# claude: /path/to/custom-claude",
|
|
107
|
-
"# codex: /path/to/custom-codex"
|
|
153
|
+
"# codex: /path/to/custom-codex",
|
|
154
|
+
"",
|
|
155
|
+
"# Per-agent CLI arg overrides (optional)",
|
|
156
|
+
"# agentArgsOverride:",
|
|
157
|
+
"# codex:",
|
|
158
|
+
"# - -m",
|
|
159
|
+
"# - gpt-5.4",
|
|
160
|
+
"# - -c",
|
|
161
|
+
"# - model_reasoning_effort=\"high\"",
|
|
162
|
+
"# - --full-auto"
|
|
108
163
|
];
|
|
109
164
|
if (agentPathOverrideSection) lines.push(...agentPathOverrideSection.split("\n"));
|
|
165
|
+
if (agentArgsOverrideSection) lines.push(...agentArgsOverrideSection.split("\n"));
|
|
110
166
|
lines.push("", "# Abort after this many consecutive failures", `maxConsecutiveFailures: ${config.maxConsecutiveFailures}`, "", "# Prevent the machine from sleeping during a run", `preventSleep: ${config.preventSleep}`, "");
|
|
111
167
|
return lines.join("\n");
|
|
112
168
|
}
|
|
@@ -433,6 +489,17 @@ function getLastIterationNumber(runInfo) {
|
|
|
433
489
|
}
|
|
434
490
|
return max;
|
|
435
491
|
}
|
|
492
|
+
function toStringArray(value) {
|
|
493
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
494
|
+
if (typeof value === "string") {
|
|
495
|
+
try {
|
|
496
|
+
const parsed = JSON.parse(value);
|
|
497
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
498
|
+
} catch {}
|
|
499
|
+
return [value];
|
|
500
|
+
}
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
436
503
|
function formatListSection(title, items) {
|
|
437
504
|
if (items.length === 0) return "";
|
|
438
505
|
return `**${title}:**\n${items.map((item) => `- ${item}`).join("\n")}\n`;
|
|
@@ -953,29 +1020,37 @@ function terminateClaudeProcess(child, platform) {
|
|
|
953
1020
|
}
|
|
954
1021
|
child.kill("SIGTERM");
|
|
955
1022
|
}
|
|
1023
|
+
function buildClaudeArgs(prompt, extraArgs) {
|
|
1024
|
+
const userArgs = extraArgs ?? [];
|
|
1025
|
+
const userSpecifiedPermissionMode = userArgs.some((arg) => arg === "--dangerously-skip-permissions" || arg === "--permission-mode" || arg.startsWith("--permission-mode=") || arg === "--permission-prompt-tool" || arg.startsWith("--permission-prompt-tool="));
|
|
1026
|
+
return [
|
|
1027
|
+
...userArgs,
|
|
1028
|
+
"-p",
|
|
1029
|
+
prompt,
|
|
1030
|
+
"--verbose",
|
|
1031
|
+
"--output-format",
|
|
1032
|
+
"stream-json",
|
|
1033
|
+
"--json-schema",
|
|
1034
|
+
JSON.stringify(AGENT_OUTPUT_SCHEMA),
|
|
1035
|
+
...userSpecifiedPermissionMode ? [] : ["--dangerously-skip-permissions"]
|
|
1036
|
+
];
|
|
1037
|
+
}
|
|
956
1038
|
var ClaudeAgent = class {
|
|
957
1039
|
name = "claude";
|
|
958
1040
|
bin;
|
|
1041
|
+
extraArgs;
|
|
959
1042
|
platform;
|
|
960
1043
|
constructor(binOrDeps = {}) {
|
|
961
1044
|
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
962
1045
|
this.bin = deps.bin ?? "claude";
|
|
1046
|
+
this.extraArgs = deps.extraArgs;
|
|
963
1047
|
this.platform = deps.platform ?? process.platform;
|
|
964
1048
|
}
|
|
965
1049
|
run(prompt, cwd, options) {
|
|
966
1050
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
967
1051
|
return new Promise((resolve, reject) => {
|
|
968
1052
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
969
|
-
const child = spawn(this.bin,
|
|
970
|
-
"-p",
|
|
971
|
-
prompt,
|
|
972
|
-
"--verbose",
|
|
973
|
-
"--output-format",
|
|
974
|
-
"stream-json",
|
|
975
|
-
"--json-schema",
|
|
976
|
-
JSON.stringify(AGENT_OUTPUT_SCHEMA),
|
|
977
|
-
"--dangerously-skip-permissions"
|
|
978
|
-
], {
|
|
1053
|
+
const child = spawn(this.bin, buildClaudeArgs(prompt, this.extraArgs), {
|
|
979
1054
|
cwd,
|
|
980
1055
|
shell: shouldUseWindowsShell$2(this.bin, this.platform),
|
|
981
1056
|
stdio: [
|
|
@@ -1073,14 +1148,31 @@ function terminateCodexProcess(child, platform) {
|
|
|
1073
1148
|
}
|
|
1074
1149
|
child.kill("SIGTERM");
|
|
1075
1150
|
}
|
|
1151
|
+
function buildCodexArgs(prompt, schemaPath, extraArgs) {
|
|
1152
|
+
const userArgs = extraArgs ?? [];
|
|
1153
|
+
const userSpecifiedExecutionMode = userArgs.some((arg) => arg === "--full-auto" || arg === "--dangerously-bypass-approvals-and-sandbox" || arg === "--sandbox" || arg.startsWith("--sandbox=") || arg === "-s" || arg === "--ask-for-approval" || arg.startsWith("--ask-for-approval=") || arg === "-a");
|
|
1154
|
+
return [
|
|
1155
|
+
"exec",
|
|
1156
|
+
...userArgs,
|
|
1157
|
+
prompt,
|
|
1158
|
+
"--json",
|
|
1159
|
+
"--output-schema",
|
|
1160
|
+
schemaPath,
|
|
1161
|
+
...userSpecifiedExecutionMode ? [] : ["--dangerously-bypass-approvals-and-sandbox"],
|
|
1162
|
+
"--color",
|
|
1163
|
+
"never"
|
|
1164
|
+
];
|
|
1165
|
+
}
|
|
1076
1166
|
var CodexAgent = class {
|
|
1077
1167
|
name = "codex";
|
|
1078
1168
|
bin;
|
|
1169
|
+
extraArgs;
|
|
1079
1170
|
platform;
|
|
1080
1171
|
schemaPath;
|
|
1081
1172
|
constructor(schemaPath, binOrDeps = {}) {
|
|
1082
1173
|
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
1083
1174
|
this.bin = deps.bin ?? "codex";
|
|
1175
|
+
this.extraArgs = deps.extraArgs;
|
|
1084
1176
|
this.platform = deps.platform ?? process.platform;
|
|
1085
1177
|
this.schemaPath = schemaPath;
|
|
1086
1178
|
}
|
|
@@ -1088,16 +1180,7 @@ var CodexAgent = class {
|
|
|
1088
1180
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
1089
1181
|
return new Promise((resolve, reject) => {
|
|
1090
1182
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
1091
|
-
const child = spawn(this.bin,
|
|
1092
|
-
"exec",
|
|
1093
|
-
prompt,
|
|
1094
|
-
"--json",
|
|
1095
|
-
"--output-schema",
|
|
1096
|
-
this.schemaPath,
|
|
1097
|
-
"--dangerously-bypass-approvals-and-sandbox",
|
|
1098
|
-
"--color",
|
|
1099
|
-
"never"
|
|
1100
|
-
], {
|
|
1183
|
+
const child = spawn(this.bin, buildCodexArgs(prompt, this.schemaPath, this.extraArgs), {
|
|
1101
1184
|
cwd,
|
|
1102
1185
|
shell: shouldUseWindowsShell$1(this.bin, this.platform),
|
|
1103
1186
|
stdio: [
|
|
@@ -1197,6 +1280,9 @@ function isAgentAbortError(error) {
|
|
|
1197
1280
|
function isAbortError$1(error) {
|
|
1198
1281
|
return error instanceof Error && error.name === "AbortError";
|
|
1199
1282
|
}
|
|
1283
|
+
function toNonEmptyString(value) {
|
|
1284
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1285
|
+
}
|
|
1200
1286
|
function getAvailablePort$1() {
|
|
1201
1287
|
return new Promise((resolve, reject) => {
|
|
1202
1288
|
const server = createServer();
|
|
@@ -1257,6 +1343,7 @@ function withTimeoutSignal$1(signal, timeoutMs) {
|
|
|
1257
1343
|
var OpenCodeAgent = class {
|
|
1258
1344
|
name = "opencode";
|
|
1259
1345
|
bin;
|
|
1346
|
+
extraArgs;
|
|
1260
1347
|
fetchFn;
|
|
1261
1348
|
getPortFn;
|
|
1262
1349
|
killProcessFn;
|
|
@@ -1266,6 +1353,7 @@ var OpenCodeAgent = class {
|
|
|
1266
1353
|
closingPromise = null;
|
|
1267
1354
|
constructor(deps = {}) {
|
|
1268
1355
|
this.bin = deps.bin ?? "opencode";
|
|
1356
|
+
this.extraArgs = deps.extraArgs;
|
|
1269
1357
|
this.fetchFn = deps.fetch ?? fetch;
|
|
1270
1358
|
this.getPortFn = deps.getPort ?? getAvailablePort$1;
|
|
1271
1359
|
this.killProcessFn = deps.killProcess ?? process.kill.bind(process);
|
|
@@ -1347,6 +1435,7 @@ var OpenCodeAgent = class {
|
|
|
1347
1435
|
const detached = !isWindows;
|
|
1348
1436
|
const child = this.spawnFn(this.bin, [
|
|
1349
1437
|
"serve",
|
|
1438
|
+
...this.extraArgs ?? [],
|
|
1350
1439
|
"--hostname",
|
|
1351
1440
|
"127.0.0.1",
|
|
1352
1441
|
"--port",
|
|
@@ -1508,51 +1597,6 @@ var OpenCodeAgent = class {
|
|
|
1508
1597
|
appendDebugLog("opencode:stream:no-body", { sessionId });
|
|
1509
1598
|
throw new Error("opencode returned no event stream body");
|
|
1510
1599
|
}
|
|
1511
|
-
const messagePostStartedAt = Date.now();
|
|
1512
|
-
appendDebugLog("opencode:message-post:start", {
|
|
1513
|
-
sessionId,
|
|
1514
|
-
promptLength: prompt.length
|
|
1515
|
-
});
|
|
1516
|
-
let messageRequestError = null;
|
|
1517
|
-
const messageRequest = (async () => {
|
|
1518
|
-
try {
|
|
1519
|
-
await this.request(server, `/session/${sessionId}/prompt_async`, {
|
|
1520
|
-
method: "POST",
|
|
1521
|
-
body: {
|
|
1522
|
-
role: "user",
|
|
1523
|
-
parts: [{
|
|
1524
|
-
type: "text",
|
|
1525
|
-
text: prompt
|
|
1526
|
-
}],
|
|
1527
|
-
format: STRUCTURED_OUTPUT_FORMAT
|
|
1528
|
-
},
|
|
1529
|
-
signal
|
|
1530
|
-
});
|
|
1531
|
-
appendDebugLog("opencode:message-post:end", {
|
|
1532
|
-
sessionId,
|
|
1533
|
-
elapsedMs: Date.now() - messagePostStartedAt
|
|
1534
|
-
});
|
|
1535
|
-
return {
|
|
1536
|
-
ok: true,
|
|
1537
|
-
body: ""
|
|
1538
|
-
};
|
|
1539
|
-
} catch (error) {
|
|
1540
|
-
messageRequestError = error;
|
|
1541
|
-
appendDebugLog("opencode:message-post:error", {
|
|
1542
|
-
sessionId,
|
|
1543
|
-
elapsedMs: Date.now() - messagePostStartedAt,
|
|
1544
|
-
error: serializeError(error),
|
|
1545
|
-
serverClosed: server.closed,
|
|
1546
|
-
serverStderr: server.stderr.slice(-2048),
|
|
1547
|
-
streamTelemetry: buildTelemetry()
|
|
1548
|
-
});
|
|
1549
|
-
streamAbortController.abort();
|
|
1550
|
-
return {
|
|
1551
|
-
ok: false,
|
|
1552
|
-
error
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
})();
|
|
1556
1600
|
const usage = {
|
|
1557
1601
|
inputTokens: 0,
|
|
1558
1602
|
outputTokens: 0,
|
|
@@ -1600,6 +1644,51 @@ var OpenCodeAgent = class {
|
|
|
1600
1644
|
currentPhase,
|
|
1601
1645
|
sawSessionIdle: (eventCounts["session.idle"] ?? 0) > 0
|
|
1602
1646
|
});
|
|
1647
|
+
const messagePostStartedAt = Date.now();
|
|
1648
|
+
appendDebugLog("opencode:message-post:start", {
|
|
1649
|
+
sessionId,
|
|
1650
|
+
promptLength: prompt.length
|
|
1651
|
+
});
|
|
1652
|
+
let messageRequestError = null;
|
|
1653
|
+
const messageRequest = (async () => {
|
|
1654
|
+
try {
|
|
1655
|
+
await this.request(server, `/session/${sessionId}/prompt_async`, {
|
|
1656
|
+
method: "POST",
|
|
1657
|
+
body: {
|
|
1658
|
+
role: "user",
|
|
1659
|
+
parts: [{
|
|
1660
|
+
type: "text",
|
|
1661
|
+
text: prompt
|
|
1662
|
+
}],
|
|
1663
|
+
format: STRUCTURED_OUTPUT_FORMAT
|
|
1664
|
+
},
|
|
1665
|
+
signal
|
|
1666
|
+
});
|
|
1667
|
+
appendDebugLog("opencode:message-post:end", {
|
|
1668
|
+
sessionId,
|
|
1669
|
+
elapsedMs: Date.now() - messagePostStartedAt
|
|
1670
|
+
});
|
|
1671
|
+
return {
|
|
1672
|
+
ok: true,
|
|
1673
|
+
body: ""
|
|
1674
|
+
};
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
messageRequestError = error;
|
|
1677
|
+
appendDebugLog("opencode:message-post:error", {
|
|
1678
|
+
sessionId,
|
|
1679
|
+
elapsedMs: Date.now() - messagePostStartedAt,
|
|
1680
|
+
error: serializeError(error),
|
|
1681
|
+
serverClosed: server.closed,
|
|
1682
|
+
serverStderr: server.stderr.slice(-2048),
|
|
1683
|
+
streamTelemetry: buildTelemetry()
|
|
1684
|
+
});
|
|
1685
|
+
streamAbortController.abort();
|
|
1686
|
+
return {
|
|
1687
|
+
ok: false,
|
|
1688
|
+
error
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
})();
|
|
1603
1692
|
const STALL_THRESHOLDS_MS = [
|
|
1604
1693
|
6e4,
|
|
1605
1694
|
12e4,
|
|
@@ -1817,20 +1906,21 @@ var OpenCodeAgent = class {
|
|
|
1817
1906
|
usage
|
|
1818
1907
|
};
|
|
1819
1908
|
}
|
|
1820
|
-
const outputText = lastFinalAnswerText ?? lastText;
|
|
1821
|
-
if (
|
|
1909
|
+
const outputText = toNonEmptyString(lastFinalAnswerText) ?? toNonEmptyString(lastText);
|
|
1910
|
+
if (outputText === null) {
|
|
1822
1911
|
appendDebugLog("opencode:output:missing", {
|
|
1823
1912
|
sessionId,
|
|
1824
1913
|
hasStructuredOutput: structuredOutputFromSSE !== null
|
|
1825
1914
|
});
|
|
1826
1915
|
throw new Error("opencode returned no text output");
|
|
1827
1916
|
}
|
|
1917
|
+
const finalOutputText = outputText;
|
|
1828
1918
|
try {
|
|
1829
|
-
const output = JSON.parse(
|
|
1919
|
+
const output = JSON.parse(finalOutputText);
|
|
1830
1920
|
appendDebugLog("opencode:output:structured", {
|
|
1831
1921
|
sessionId,
|
|
1832
1922
|
source: lastFinalAnswerText ? "final_answer" : "last_text",
|
|
1833
|
-
outputTextLength:
|
|
1923
|
+
outputTextLength: finalOutputText.length
|
|
1834
1924
|
});
|
|
1835
1925
|
return {
|
|
1836
1926
|
output,
|
|
@@ -1839,8 +1929,8 @@ var OpenCodeAgent = class {
|
|
|
1839
1929
|
} catch (error) {
|
|
1840
1930
|
appendDebugLog("opencode:output:parse-error", {
|
|
1841
1931
|
sessionId,
|
|
1842
|
-
outputTextLength:
|
|
1843
|
-
outputTextSample:
|
|
1932
|
+
outputTextLength: finalOutputText.length,
|
|
1933
|
+
outputTextSample: finalOutputText.slice(0, 512),
|
|
1844
1934
|
error: serializeError(error)
|
|
1845
1935
|
});
|
|
1846
1936
|
throw new Error(`Failed to parse opencode output: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2046,6 +2136,7 @@ async function delay(ms, signal) {
|
|
|
2046
2136
|
var RovoDevAgent = class {
|
|
2047
2137
|
name = "rovodev";
|
|
2048
2138
|
bin;
|
|
2139
|
+
extraArgs;
|
|
2049
2140
|
schemaPath;
|
|
2050
2141
|
fetchFn;
|
|
2051
2142
|
getPortFn;
|
|
@@ -2056,6 +2147,7 @@ var RovoDevAgent = class {
|
|
|
2056
2147
|
closingPromise = null;
|
|
2057
2148
|
constructor(schemaPath, deps = {}) {
|
|
2058
2149
|
this.bin = deps.bin ?? "acli";
|
|
2150
|
+
this.extraArgs = deps.extraArgs;
|
|
2059
2151
|
this.schemaPath = schemaPath;
|
|
2060
2152
|
this.fetchFn = deps.fetch ?? fetch;
|
|
2061
2153
|
this.getPortFn = deps.getPort ?? getAvailablePort;
|
|
@@ -2136,6 +2228,7 @@ var RovoDevAgent = class {
|
|
|
2136
2228
|
const child = this.spawnFn(this.bin, [
|
|
2137
2229
|
"rovodev",
|
|
2138
2230
|
"serve",
|
|
2231
|
+
...this.extraArgs ?? [],
|
|
2139
2232
|
"--disable-session-token",
|
|
2140
2233
|
String(port)
|
|
2141
2234
|
], {
|
|
@@ -2560,12 +2653,24 @@ function withTimeoutSignal(signal, timeoutMs) {
|
|
|
2560
2653
|
}
|
|
2561
2654
|
//#endregion
|
|
2562
2655
|
//#region src/core/agents/factory.ts
|
|
2563
|
-
function createAgent(name, runInfo, pathOverride) {
|
|
2656
|
+
function createAgent(name, runInfo, pathOverride, agentArgsOverride) {
|
|
2564
2657
|
switch (name) {
|
|
2565
|
-
case "claude": return new ClaudeAgent(
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2658
|
+
case "claude": return new ClaudeAgent({
|
|
2659
|
+
bin: pathOverride,
|
|
2660
|
+
extraArgs: agentArgsOverride
|
|
2661
|
+
});
|
|
2662
|
+
case "codex": return new CodexAgent(runInfo.schemaPath, {
|
|
2663
|
+
bin: pathOverride,
|
|
2664
|
+
extraArgs: agentArgsOverride
|
|
2665
|
+
});
|
|
2666
|
+
case "opencode": return new OpenCodeAgent({
|
|
2667
|
+
bin: pathOverride,
|
|
2668
|
+
extraArgs: agentArgsOverride
|
|
2669
|
+
});
|
|
2670
|
+
case "rovodev": return new RovoDevAgent(runInfo.schemaPath, {
|
|
2671
|
+
bin: pathOverride,
|
|
2672
|
+
extraArgs: agentArgsOverride
|
|
2673
|
+
});
|
|
2569
2674
|
}
|
|
2570
2675
|
}
|
|
2571
2676
|
//#endregion
|
|
@@ -2845,7 +2950,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2845
2950
|
};
|
|
2846
2951
|
return {
|
|
2847
2952
|
type: "completed",
|
|
2848
|
-
record: this.recordFailure(`[FAIL] ${result.output.summary}`, result.output.summary, result.output.key_learnings)
|
|
2953
|
+
record: this.recordFailure(`[FAIL] ${result.output.summary}`, result.output.summary, toStringArray(result.output.key_learnings))
|
|
2849
2954
|
};
|
|
2850
2955
|
} catch (err) {
|
|
2851
2956
|
const elapsedMs = Date.now() - agentStartedAt;
|
|
@@ -2884,7 +2989,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2884
2989
|
}
|
|
2885
2990
|
}
|
|
2886
2991
|
recordSuccess(output) {
|
|
2887
|
-
appendNotes(this.runInfo.notesPath, this.state.currentIteration, output.summary, output.key_changes_made, output.key_learnings);
|
|
2992
|
+
appendNotes(this.runInfo.notesPath, this.state.currentIteration, output.summary, toStringArray(output.key_changes_made), toStringArray(output.key_learnings));
|
|
2888
2993
|
commitAll(`gnhf #${this.state.currentIteration}: ${output.summary}`, this.cwd);
|
|
2889
2994
|
this.state.commitCount = getBranchCommitCount(this.runInfo.baseCommit, this.cwd);
|
|
2890
2995
|
this.state.successCount++;
|
|
@@ -2893,13 +2998,13 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2893
2998
|
number: this.state.currentIteration,
|
|
2894
2999
|
success: true,
|
|
2895
3000
|
summary: output.summary,
|
|
2896
|
-
keyChanges: output.key_changes_made,
|
|
2897
|
-
keyLearnings: output.key_learnings,
|
|
3001
|
+
keyChanges: toStringArray(output.key_changes_made),
|
|
3002
|
+
keyLearnings: toStringArray(output.key_learnings),
|
|
2898
3003
|
timestamp: /* @__PURE__ */ new Date()
|
|
2899
3004
|
};
|
|
2900
3005
|
}
|
|
2901
3006
|
recordFailure(notesSummary, recordSummary, learnings) {
|
|
2902
|
-
appendNotes(this.runInfo.notesPath, this.state.currentIteration, notesSummary, [], learnings);
|
|
3007
|
+
appendNotes(this.runInfo.notesPath, this.state.currentIteration, notesSummary, [], toStringArray(learnings));
|
|
2903
3008
|
resetHard(this.cwd);
|
|
2904
3009
|
this.state.failCount++;
|
|
2905
3010
|
this.state.consecutiveFailures++;
|
|
@@ -2908,7 +3013,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2908
3013
|
success: false,
|
|
2909
3014
|
summary: recordSummary,
|
|
2910
3015
|
keyChanges: [],
|
|
2911
|
-
keyLearnings: learnings,
|
|
3016
|
+
keyLearnings: toStringArray(learnings),
|
|
2912
3017
|
timestamp: /* @__PURE__ */ new Date()
|
|
2913
3018
|
};
|
|
2914
3019
|
}
|
|
@@ -3901,11 +4006,12 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
3901
4006
|
maxIterations: options.maxIterations,
|
|
3902
4007
|
maxTokens: options.maxTokens,
|
|
3903
4008
|
preventSleep: config.preventSleep,
|
|
4009
|
+
agentArgsOverride: config.agentArgsOverride?.[config.agent],
|
|
3904
4010
|
platform: process$1.platform,
|
|
3905
4011
|
nodeVersion: process$1.version,
|
|
3906
4012
|
gnhfVersion: packageVersion
|
|
3907
4013
|
});
|
|
3908
|
-
const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent]), runInfo, prompt, cwd, startIteration, {
|
|
4014
|
+
const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent], config.agentArgsOverride?.[config.agent]), runInfo, prompt, cwd, startIteration, {
|
|
3909
4015
|
maxIterations: options.maxIterations,
|
|
3910
4016
|
maxTokens: options.maxTokens
|
|
3911
4017
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnhf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Before I go to bed, I tell my agents: good night, have fun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"lint": "eslint src",
|
|
14
14
|
"format": "prettier --write src",
|
|
15
15
|
"format:check": "prettier --check src",
|
|
16
|
+
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
|
|
16
17
|
"test": "npm run build && vitest run",
|
|
17
18
|
"test:e2e": "npm run build && vitest run test/e2e.test.ts",
|
|
18
19
|
"test:coverage": "vitest run --coverage --exclude test/e2e.test.ts"
|