gnhf 0.1.15 → 0.1.17
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 +175 -80
- 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
|
}
|
|
@@ -964,29 +1020,37 @@ function terminateClaudeProcess(child, platform) {
|
|
|
964
1020
|
}
|
|
965
1021
|
child.kill("SIGTERM");
|
|
966
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
|
+
}
|
|
967
1038
|
var ClaudeAgent = class {
|
|
968
1039
|
name = "claude";
|
|
969
1040
|
bin;
|
|
1041
|
+
extraArgs;
|
|
970
1042
|
platform;
|
|
971
1043
|
constructor(binOrDeps = {}) {
|
|
972
1044
|
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
973
1045
|
this.bin = deps.bin ?? "claude";
|
|
1046
|
+
this.extraArgs = deps.extraArgs;
|
|
974
1047
|
this.platform = deps.platform ?? process.platform;
|
|
975
1048
|
}
|
|
976
1049
|
run(prompt, cwd, options) {
|
|
977
1050
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
978
1051
|
return new Promise((resolve, reject) => {
|
|
979
1052
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
980
|
-
const child = spawn(this.bin,
|
|
981
|
-
"-p",
|
|
982
|
-
prompt,
|
|
983
|
-
"--verbose",
|
|
984
|
-
"--output-format",
|
|
985
|
-
"stream-json",
|
|
986
|
-
"--json-schema",
|
|
987
|
-
JSON.stringify(AGENT_OUTPUT_SCHEMA),
|
|
988
|
-
"--dangerously-skip-permissions"
|
|
989
|
-
], {
|
|
1053
|
+
const child = spawn(this.bin, buildClaudeArgs(prompt, this.extraArgs), {
|
|
990
1054
|
cwd,
|
|
991
1055
|
shell: shouldUseWindowsShell$2(this.bin, this.platform),
|
|
992
1056
|
stdio: [
|
|
@@ -1084,14 +1148,31 @@ function terminateCodexProcess(child, platform) {
|
|
|
1084
1148
|
}
|
|
1085
1149
|
child.kill("SIGTERM");
|
|
1086
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
|
+
}
|
|
1087
1166
|
var CodexAgent = class {
|
|
1088
1167
|
name = "codex";
|
|
1089
1168
|
bin;
|
|
1169
|
+
extraArgs;
|
|
1090
1170
|
platform;
|
|
1091
1171
|
schemaPath;
|
|
1092
1172
|
constructor(schemaPath, binOrDeps = {}) {
|
|
1093
1173
|
const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
|
|
1094
1174
|
this.bin = deps.bin ?? "codex";
|
|
1175
|
+
this.extraArgs = deps.extraArgs;
|
|
1095
1176
|
this.platform = deps.platform ?? process.platform;
|
|
1096
1177
|
this.schemaPath = schemaPath;
|
|
1097
1178
|
}
|
|
@@ -1099,16 +1180,7 @@ var CodexAgent = class {
|
|
|
1099
1180
|
const { onUsage, onMessage, signal, logPath } = options ?? {};
|
|
1100
1181
|
return new Promise((resolve, reject) => {
|
|
1101
1182
|
const logStream = logPath ? createWriteStream(logPath) : null;
|
|
1102
|
-
const child = spawn(this.bin,
|
|
1103
|
-
"exec",
|
|
1104
|
-
prompt,
|
|
1105
|
-
"--json",
|
|
1106
|
-
"--output-schema",
|
|
1107
|
-
this.schemaPath,
|
|
1108
|
-
"--dangerously-bypass-approvals-and-sandbox",
|
|
1109
|
-
"--color",
|
|
1110
|
-
"never"
|
|
1111
|
-
], {
|
|
1183
|
+
const child = spawn(this.bin, buildCodexArgs(prompt, this.schemaPath, this.extraArgs), {
|
|
1112
1184
|
cwd,
|
|
1113
1185
|
shell: shouldUseWindowsShell$1(this.bin, this.platform),
|
|
1114
1186
|
stdio: [
|
|
@@ -1208,6 +1280,9 @@ function isAgentAbortError(error) {
|
|
|
1208
1280
|
function isAbortError$1(error) {
|
|
1209
1281
|
return error instanceof Error && error.name === "AbortError";
|
|
1210
1282
|
}
|
|
1283
|
+
function toNonEmptyString(value) {
|
|
1284
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1285
|
+
}
|
|
1211
1286
|
function getAvailablePort$1() {
|
|
1212
1287
|
return new Promise((resolve, reject) => {
|
|
1213
1288
|
const server = createServer();
|
|
@@ -1268,6 +1343,7 @@ function withTimeoutSignal$1(signal, timeoutMs) {
|
|
|
1268
1343
|
var OpenCodeAgent = class {
|
|
1269
1344
|
name = "opencode";
|
|
1270
1345
|
bin;
|
|
1346
|
+
extraArgs;
|
|
1271
1347
|
fetchFn;
|
|
1272
1348
|
getPortFn;
|
|
1273
1349
|
killProcessFn;
|
|
@@ -1277,6 +1353,7 @@ var OpenCodeAgent = class {
|
|
|
1277
1353
|
closingPromise = null;
|
|
1278
1354
|
constructor(deps = {}) {
|
|
1279
1355
|
this.bin = deps.bin ?? "opencode";
|
|
1356
|
+
this.extraArgs = deps.extraArgs;
|
|
1280
1357
|
this.fetchFn = deps.fetch ?? fetch;
|
|
1281
1358
|
this.getPortFn = deps.getPort ?? getAvailablePort$1;
|
|
1282
1359
|
this.killProcessFn = deps.killProcess ?? process.kill.bind(process);
|
|
@@ -1358,6 +1435,7 @@ var OpenCodeAgent = class {
|
|
|
1358
1435
|
const detached = !isWindows;
|
|
1359
1436
|
const child = this.spawnFn(this.bin, [
|
|
1360
1437
|
"serve",
|
|
1438
|
+
...this.extraArgs ?? [],
|
|
1361
1439
|
"--hostname",
|
|
1362
1440
|
"127.0.0.1",
|
|
1363
1441
|
"--port",
|
|
@@ -1519,51 +1597,6 @@ var OpenCodeAgent = class {
|
|
|
1519
1597
|
appendDebugLog("opencode:stream:no-body", { sessionId });
|
|
1520
1598
|
throw new Error("opencode returned no event stream body");
|
|
1521
1599
|
}
|
|
1522
|
-
const messagePostStartedAt = Date.now();
|
|
1523
|
-
appendDebugLog("opencode:message-post:start", {
|
|
1524
|
-
sessionId,
|
|
1525
|
-
promptLength: prompt.length
|
|
1526
|
-
});
|
|
1527
|
-
let messageRequestError = null;
|
|
1528
|
-
const messageRequest = (async () => {
|
|
1529
|
-
try {
|
|
1530
|
-
await this.request(server, `/session/${sessionId}/prompt_async`, {
|
|
1531
|
-
method: "POST",
|
|
1532
|
-
body: {
|
|
1533
|
-
role: "user",
|
|
1534
|
-
parts: [{
|
|
1535
|
-
type: "text",
|
|
1536
|
-
text: prompt
|
|
1537
|
-
}],
|
|
1538
|
-
format: STRUCTURED_OUTPUT_FORMAT
|
|
1539
|
-
},
|
|
1540
|
-
signal
|
|
1541
|
-
});
|
|
1542
|
-
appendDebugLog("opencode:message-post:end", {
|
|
1543
|
-
sessionId,
|
|
1544
|
-
elapsedMs: Date.now() - messagePostStartedAt
|
|
1545
|
-
});
|
|
1546
|
-
return {
|
|
1547
|
-
ok: true,
|
|
1548
|
-
body: ""
|
|
1549
|
-
};
|
|
1550
|
-
} catch (error) {
|
|
1551
|
-
messageRequestError = error;
|
|
1552
|
-
appendDebugLog("opencode:message-post:error", {
|
|
1553
|
-
sessionId,
|
|
1554
|
-
elapsedMs: Date.now() - messagePostStartedAt,
|
|
1555
|
-
error: serializeError(error),
|
|
1556
|
-
serverClosed: server.closed,
|
|
1557
|
-
serverStderr: server.stderr.slice(-2048),
|
|
1558
|
-
streamTelemetry: buildTelemetry()
|
|
1559
|
-
});
|
|
1560
|
-
streamAbortController.abort();
|
|
1561
|
-
return {
|
|
1562
|
-
ok: false,
|
|
1563
|
-
error
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
})();
|
|
1567
1600
|
const usage = {
|
|
1568
1601
|
inputTokens: 0,
|
|
1569
1602
|
outputTokens: 0,
|
|
@@ -1611,6 +1644,51 @@ var OpenCodeAgent = class {
|
|
|
1611
1644
|
currentPhase,
|
|
1612
1645
|
sawSessionIdle: (eventCounts["session.idle"] ?? 0) > 0
|
|
1613
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
|
+
})();
|
|
1614
1692
|
const STALL_THRESHOLDS_MS = [
|
|
1615
1693
|
6e4,
|
|
1616
1694
|
12e4,
|
|
@@ -1828,20 +1906,21 @@ var OpenCodeAgent = class {
|
|
|
1828
1906
|
usage
|
|
1829
1907
|
};
|
|
1830
1908
|
}
|
|
1831
|
-
const outputText = lastFinalAnswerText ?? lastText;
|
|
1832
|
-
if (
|
|
1909
|
+
const outputText = toNonEmptyString(lastFinalAnswerText) ?? toNonEmptyString(lastText);
|
|
1910
|
+
if (outputText === null) {
|
|
1833
1911
|
appendDebugLog("opencode:output:missing", {
|
|
1834
1912
|
sessionId,
|
|
1835
1913
|
hasStructuredOutput: structuredOutputFromSSE !== null
|
|
1836
1914
|
});
|
|
1837
1915
|
throw new Error("opencode returned no text output");
|
|
1838
1916
|
}
|
|
1917
|
+
const finalOutputText = outputText;
|
|
1839
1918
|
try {
|
|
1840
|
-
const output = JSON.parse(
|
|
1919
|
+
const output = JSON.parse(finalOutputText);
|
|
1841
1920
|
appendDebugLog("opencode:output:structured", {
|
|
1842
1921
|
sessionId,
|
|
1843
1922
|
source: lastFinalAnswerText ? "final_answer" : "last_text",
|
|
1844
|
-
outputTextLength:
|
|
1923
|
+
outputTextLength: finalOutputText.length
|
|
1845
1924
|
});
|
|
1846
1925
|
return {
|
|
1847
1926
|
output,
|
|
@@ -1850,8 +1929,8 @@ var OpenCodeAgent = class {
|
|
|
1850
1929
|
} catch (error) {
|
|
1851
1930
|
appendDebugLog("opencode:output:parse-error", {
|
|
1852
1931
|
sessionId,
|
|
1853
|
-
outputTextLength:
|
|
1854
|
-
outputTextSample:
|
|
1932
|
+
outputTextLength: finalOutputText.length,
|
|
1933
|
+
outputTextSample: finalOutputText.slice(0, 512),
|
|
1855
1934
|
error: serializeError(error)
|
|
1856
1935
|
});
|
|
1857
1936
|
throw new Error(`Failed to parse opencode output: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2057,6 +2136,7 @@ async function delay(ms, signal) {
|
|
|
2057
2136
|
var RovoDevAgent = class {
|
|
2058
2137
|
name = "rovodev";
|
|
2059
2138
|
bin;
|
|
2139
|
+
extraArgs;
|
|
2060
2140
|
schemaPath;
|
|
2061
2141
|
fetchFn;
|
|
2062
2142
|
getPortFn;
|
|
@@ -2067,6 +2147,7 @@ var RovoDevAgent = class {
|
|
|
2067
2147
|
closingPromise = null;
|
|
2068
2148
|
constructor(schemaPath, deps = {}) {
|
|
2069
2149
|
this.bin = deps.bin ?? "acli";
|
|
2150
|
+
this.extraArgs = deps.extraArgs;
|
|
2070
2151
|
this.schemaPath = schemaPath;
|
|
2071
2152
|
this.fetchFn = deps.fetch ?? fetch;
|
|
2072
2153
|
this.getPortFn = deps.getPort ?? getAvailablePort;
|
|
@@ -2147,6 +2228,7 @@ var RovoDevAgent = class {
|
|
|
2147
2228
|
const child = this.spawnFn(this.bin, [
|
|
2148
2229
|
"rovodev",
|
|
2149
2230
|
"serve",
|
|
2231
|
+
...this.extraArgs ?? [],
|
|
2150
2232
|
"--disable-session-token",
|
|
2151
2233
|
String(port)
|
|
2152
2234
|
], {
|
|
@@ -2571,12 +2653,24 @@ function withTimeoutSignal(signal, timeoutMs) {
|
|
|
2571
2653
|
}
|
|
2572
2654
|
//#endregion
|
|
2573
2655
|
//#region src/core/agents/factory.ts
|
|
2574
|
-
function createAgent(name, runInfo, pathOverride) {
|
|
2656
|
+
function createAgent(name, runInfo, pathOverride, agentArgsOverride) {
|
|
2575
2657
|
switch (name) {
|
|
2576
|
-
case "claude": return new ClaudeAgent(
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
+
});
|
|
2580
2674
|
}
|
|
2581
2675
|
}
|
|
2582
2676
|
//#endregion
|
|
@@ -2587,7 +2681,7 @@ This is iteration ${params.n}. Each iteration aims to make an incremental step f
|
|
|
2587
2681
|
|
|
2588
2682
|
## Instructions
|
|
2589
2683
|
|
|
2590
|
-
1. Read .gnhf/runs/${params.runId}/notes.md first to understand what has been done in previous iterations
|
|
2684
|
+
1. Read .gnhf/runs/${params.runId}/notes.md first to understand what has been done in previous iterations. Do NOT write to or modify notes.md - it is maintained automatically by the gnhf orchestrator
|
|
2591
2685
|
2. Identify the next smallest logical unit of work that's individually verifiable and would make incremental progress towards the objective, and treat that as the scope of this iteration
|
|
2592
2686
|
3. If you attempted a solution and it didn't end up moving the needle on the objective, document learnings and record success=false, then conclude the iteration rather than continuously pivoting
|
|
2593
2687
|
4. If you made code changes, run build/tests/linters/formatters if available to validate your work. Do NOT make any git commits - that will be handled automatically by the gnhf orchestrator
|
|
@@ -2856,7 +2950,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2856
2950
|
};
|
|
2857
2951
|
return {
|
|
2858
2952
|
type: "completed",
|
|
2859
|
-
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))
|
|
2860
2954
|
};
|
|
2861
2955
|
} catch (err) {
|
|
2862
2956
|
const elapsedMs = Date.now() - agentStartedAt;
|
|
@@ -3912,11 +4006,12 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
3912
4006
|
maxIterations: options.maxIterations,
|
|
3913
4007
|
maxTokens: options.maxTokens,
|
|
3914
4008
|
preventSleep: config.preventSleep,
|
|
4009
|
+
agentArgsOverride: config.agentArgsOverride?.[config.agent],
|
|
3915
4010
|
platform: process$1.platform,
|
|
3916
4011
|
nodeVersion: process$1.version,
|
|
3917
4012
|
gnhfVersion: packageVersion
|
|
3918
4013
|
});
|
|
3919
|
-
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, {
|
|
3920
4015
|
maxIterations: options.maxIterations,
|
|
3921
4016
|
maxTokens: options.maxTokens
|
|
3922
4017
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnhf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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"
|