gnhf 0.1.32 → 0.1.34
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 +28 -24
- package/dist/cli.mjs +395 -49
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ 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 request stop or a configured runtime cap is reached
|
|
48
48
|
- **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries; retryable hard agent errors back off exponentially while agent-reported failures continue immediately
|
|
49
49
|
- **Live terminal title** — interactive runs keep your terminal title updated with live status, token totals, and commit count, then clear or restore it on exit depending on terminal support; token totals prefixed with `~` are estimates
|
|
50
|
+
- **Exit summary**: every run ends with a permanent summary covering elapsed time, branch, iterations, tokens, branch diff stats, local notes/log paths, and review commands
|
|
50
51
|
- **Agent-agnostic**: works with Claude Code, Codex, Rovo Dev, OpenCode, GitHub Copilot CLI, Pi, or ACP targets out of the box
|
|
51
52
|
|
|
52
53
|
## Quick Start
|
|
@@ -142,6 +143,7 @@ npm link
|
|
|
142
143
|
- **Runtime caps** - `--max-iterations` stops before the next iteration begins, `--max-tokens` can abort mid-iteration once reported usage reaches the cap, and `--stop-when` ends the loop after an iteration whose agent output reports the natural-language condition is met; resumed runs reuse the saved stop condition unless you pass a new value, or `--stop-when ""` to clear it; uncommitted work is rolled back in either case, and in the interactive TUI the final state remains visible until you press Ctrl+C to exit
|
|
143
144
|
- **Iteration finalization** - agents are expected to finish validation, stop any background processes they started, and only then emit the final JSON result for the iteration
|
|
144
145
|
- **Graceful interrupts** - in the interactive TUI, the first Ctrl+C requests a graceful stop and lets the current iteration finish (or ends backoff early), the second Ctrl+C force-stops immediately, and `SIGTERM` also force-stops immediately
|
|
146
|
+
- **Exit summary** - after shutdown cleanup, gnhf prints a permanent stdout summary with the final branch, elapsed time, iteration and token totals, branch diff stats, notes/debug-log paths, and review commands
|
|
145
147
|
- **Shared memory** — the agent reads `notes.md` (built up from prior iterations) to communicate across iterations
|
|
146
148
|
- **Local run metadata** — gnhf stores prompt, notes, stop conditions, and commit-message convention metadata under `.gnhf/runs/` and ignores it locally, so your branch only contains intentional work
|
|
147
149
|
- **Resume support** — run `gnhf` while on an existing `gnhf/` branch to pick up where a previous run left off; if you provide a different prompt, gnhf asks whether to update the saved prompt and continue with the existing history, start a new branch, or quit. New runs whose generated branch already exists use a numeric suffix such as `gnhf/<slug>-1`.
|
|
@@ -175,22 +177,22 @@ If you run `gnhf` on an existing `gnhf/` branch with a different prompt, gnhf as
|
|
|
175
177
|
|
|
176
178
|
### Flags
|
|
177
179
|
|
|
178
|
-
| Flag | Description
|
|
179
|
-
| ------------------------ |
|
|
180
|
-
| `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, `pi`, or `acp:<target>`) | config file (`claude`) |
|
|
181
|
-
| `--max-iterations <n>` | Abort after `n` total iterations
|
|
182
|
-
| `--max-tokens <n>` | Abort after `n` total input+output tokens
|
|
183
|
-
| `--stop-when <cond>` | End the loop when the agent reports this condition; persists across resume
|
|
184
|
-
| `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`)
|
|
185
|
-
| `--worktree` | Run in a separate git worktree (enables multiple agents concurrently)
|
|
186
|
-
| `--version` | Show version
|
|
180
|
+
| Flag | Description | Default |
|
|
181
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------ | ---------------------- |
|
|
182
|
+
| `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, `pi`, or `acp:<target-or-command>`) | config file (`claude`) |
|
|
183
|
+
| `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
|
|
184
|
+
| `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
|
|
185
|
+
| `--stop-when <cond>` | End the loop when the agent reports this condition; persists across resume | unlimited |
|
|
186
|
+
| `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
|
|
187
|
+
| `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
|
|
188
|
+
| `--version` | Show version | |
|
|
187
189
|
|
|
188
190
|
## Configuration
|
|
189
191
|
|
|
190
192
|
Config lives at `~/.gnhf/config.yml`:
|
|
191
193
|
|
|
192
194
|
```yaml
|
|
193
|
-
# Agent to use by default (claude, codex, rovodev, opencode, copilot, pi, or acp:<target>)
|
|
195
|
+
# Agent to use by default (claude, codex, rovodev, opencode, copilot, pi, or acp:<target-or-command>)
|
|
194
196
|
agent: claude
|
|
195
197
|
|
|
196
198
|
# Custom paths to native agent binaries (optional)
|
|
@@ -225,7 +227,7 @@ agent: claude
|
|
|
225
227
|
# staging: "node /opt/staging/agent.mjs"
|
|
226
228
|
|
|
227
229
|
# Commit message convention (optional)
|
|
228
|
-
# Defaults to: gnhf
|
|
230
|
+
# Defaults to: gnhf <iteration>: <summary>
|
|
229
231
|
# Use the conventional preset for semantic-release compatible headers:
|
|
230
232
|
# commitMessage:
|
|
231
233
|
# preset: conventional
|
|
@@ -245,6 +247,7 @@ The iteration and token caps are runtime-only flags and are not persisted in `co
|
|
|
245
247
|
`agentArgsOverride.<name>` lets you pass through extra CLI flags for native agents (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, or `pi`).
|
|
246
248
|
ACP targets do not support path or arg overrides in this version.
|
|
247
249
|
Use `acpRegistryOverrides` to map `acp:<target>` names to custom spawn commands for local, forked, or beta ACP agents.
|
|
250
|
+
You can also pass a raw custom ACP server command directly as a quoted `acp:` spec, for example `gnhf --agent 'acp:./bin/dev-acp --profile ci' "fix the tests"`.
|
|
248
251
|
|
|
249
252
|
- Use it for agent-specific options like models, profiles, or reasoning settings without adding a dedicated `gnhf` config field for each one.
|
|
250
253
|
- For `codex`, `claude`, and `copilot`, `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.
|
|
@@ -252,7 +255,7 @@ Use `acpRegistryOverrides` to map `acp:<target>` names to custom spawn commands
|
|
|
252
255
|
|
|
253
256
|
`commitMessage` controls the subject line that gnhf uses for each successful iteration commit.
|
|
254
257
|
|
|
255
|
-
- Omit it to keep the default `gnhf
|
|
258
|
+
- Omit it to keep the default `gnhf <iteration>: <summary>` format.
|
|
256
259
|
- Set `preset: conventional` to ask the agent for `type` and optional `scope`, then commit as `type(scope): summary` for semantic-release style workflows. Valid types are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `test`, and `chore`; invalid or missing types fall back to `chore`, and empty scopes are omitted.
|
|
257
260
|
- The resolved commit-message convention is saved per run, so resuming a `gnhf/` branch keeps the original subject format even if `config.yml` changes later.
|
|
258
261
|
|
|
@@ -273,7 +276,8 @@ When sleep prevention is enabled, `gnhf` uses the native mechanism for your OS:
|
|
|
273
276
|
|
|
274
277
|
## Debug Logs
|
|
275
278
|
|
|
276
|
-
Every run writes a JSONL debug log to `.gnhf/runs/<runId>/gnhf.log` alongside `notes.md`. Lifecycle events for the orchestrator, agent, and HTTP requests are captured with elapsed timings and (for failures) the full `error.cause` chain
|
|
279
|
+
Every run writes a JSONL debug log to `.gnhf/runs/<runId>/gnhf.log` alongside `notes.md`. Lifecycle events for the orchestrator, agent, and HTTP requests are captured with elapsed timings and (for failures) the full `error.cause` chain, which is what you need to tell a bare `TypeError: fetch failed` apart from an undici `UND_ERR_HEADERS_TIMEOUT`. The agent's own streaming output still goes to the per-iteration `iteration-<n>.jsonl` file next to it.
|
|
280
|
+
Raw ACP command specs are redacted as `acp:custom`/`custom` in debug logs and related errors, so local paths or secrets in custom commands are not written to `gnhf.log`.
|
|
277
281
|
|
|
278
282
|
Including a snippet of `gnhf.log` is the single most useful thing you can attach when filing an issue.
|
|
279
283
|
|
|
@@ -285,17 +289,17 @@ Set `GNHF_TELEMETRY=0` to turn it off.
|
|
|
285
289
|
|
|
286
290
|
## Agents
|
|
287
291
|
|
|
288
|
-
`gnhf` supports six native agents plus ACP targets. ACP support is powered by [`acpx`](https://github.com/openclaw/acpx), which is bundled with `gnhf` and provides the runtime and agent registry for `acp:<target>` specs.
|
|
289
|
-
|
|
290
|
-
| Agent | Flag
|
|
291
|
-
| ------------------ |
|
|
292
|
-
| Claude Code | `--agent claude`
|
|
293
|
-
| Codex | `--agent codex`
|
|
294
|
-
| GitHub Copilot CLI | `--agent copilot`
|
|
295
|
-
| Pi | `--agent pi`
|
|
296
|
-
| Rovo Dev | `--agent rovodev`
|
|
297
|
-
| OpenCode | `--agent opencode`
|
|
298
|
-
| ACP target | `--agent acp:<target>` | Install and authenticate the target supported by the bundled [`acpx`](https://github.com/openclaw/acpx) registry, such as `acp:gemini
|
|
292
|
+
`gnhf` supports six native agents plus ACP targets. ACP support is powered by [`acpx`](https://github.com/openclaw/acpx), which is bundled with `gnhf` and provides the runtime and agent registry for `acp:<target-or-command>` specs.
|
|
293
|
+
|
|
294
|
+
| Agent | Flag | Requirements | Notes |
|
|
295
|
+
| ------------------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
296
|
+
| Claude Code | `--agent claude` | Install Anthropic's `claude` CLI and sign in first. | `gnhf` invokes `claude` directly in non-interactive mode. After Claude emits a successful structured result, `gnhf` treats that result as final and shuts down any lingering Claude process tree after a short grace period. |
|
|
297
|
+
| Codex | `--agent codex` | Install OpenAI's `codex` CLI and sign in first. | `gnhf` invokes `codex exec` directly in non-interactive mode. |
|
|
298
|
+
| GitHub Copilot CLI | `--agent copilot` | Install GitHub Copilot CLI and sign in first. | `gnhf` invokes `copilot` directly in non-interactive JSONL mode. Copilot currently exposes assistant output tokens, but not full input/cache token totals; see https://github.com/github/copilot-cli/issues/1152. |
|
|
299
|
+
| Pi | `--agent pi` | Install the `pi` CLI and configure a usable provider/model first. | `gnhf` invokes `pi` directly in JSON mode, appends the final output schema to the prompt, and disables Pi session persistence with `--no-session`. |
|
|
300
|
+
| Rovo Dev | `--agent rovodev` | Install Atlassian's `acli` and authenticate it with Rovo Dev first. | `gnhf` starts a local `acli rovodev serve --disable-session-token <port>` process automatically in the repo workspace. |
|
|
301
|
+
| OpenCode | `--agent opencode` | Install `opencode` and configure at least one usable model provider first. | `gnhf` starts a local `opencode serve --hostname 127.0.0.1 --port <port> --print-logs` process automatically, creates a per-run session, and applies a blanket allow rule so tool calls do not block on prompts. |
|
|
302
|
+
| ACP target | `--agent acp:<target-or-command>` | Install and authenticate the target supported by the bundled [`acpx`](https://github.com/openclaw/acpx) registry, such as `acp:gemini`, or pass a quoted custom ACP server command. | `gnhf` runs the target through ACP with a persistent per-run session under `.gnhf/runs/<runId>/acp-sessions`; token usage and `--max-tokens` use ACP `used` deltas when available, with prompt-length plus tool-call estimates as a fallback, and `agentPathOverride` and `agentArgsOverride` do not apply. |
|
|
299
303
|
|
|
300
304
|
## Development
|
|
301
305
|
|
package/dist/cli.mjs
CHANGED
|
@@ -37,12 +37,21 @@ const AGENT_NAMES = [
|
|
|
37
37
|
"copilot",
|
|
38
38
|
"pi"
|
|
39
39
|
];
|
|
40
|
-
const ACP_SPEC_PATTERN = /^acp:[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
41
40
|
function isAgentName$1(name) {
|
|
42
|
-
return AGENT_NAMES.includes(name);
|
|
41
|
+
return typeof name === "string" && AGENT_NAMES.includes(name);
|
|
42
|
+
}
|
|
43
|
+
function hasDisallowedAcpTargetChar(target) {
|
|
44
|
+
for (let i = 0; i < target.length; i += 1) {
|
|
45
|
+
const code = target.charCodeAt(i);
|
|
46
|
+
if (code < 32 || code === 127) return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
43
49
|
}
|
|
44
50
|
function isAcpSpec(spec) {
|
|
45
|
-
|
|
51
|
+
if (typeof spec !== "string") return false;
|
|
52
|
+
if (!spec.startsWith("acp:")) return false;
|
|
53
|
+
const target = spec.slice(4);
|
|
54
|
+
return target.length > 0 && target.trim() === target && !hasDisallowedAcpTargetChar(target);
|
|
46
55
|
}
|
|
47
56
|
function isAgentSpec(spec) {
|
|
48
57
|
return isAgentName$1(spec) || isAcpSpec(spec);
|
|
@@ -50,6 +59,16 @@ function isAgentSpec(spec) {
|
|
|
50
59
|
function getAcpTarget(spec) {
|
|
51
60
|
return spec.slice(4);
|
|
52
61
|
}
|
|
62
|
+
function isNamedAcpTarget(target) {
|
|
63
|
+
return ACP_TARGET_NAME_PATTERN.test(target);
|
|
64
|
+
}
|
|
65
|
+
function redactAcpTargetForLogs(target) {
|
|
66
|
+
return isNamedAcpTarget(target) ? target : "custom";
|
|
67
|
+
}
|
|
68
|
+
function redactAgentSpecForLogs(spec) {
|
|
69
|
+
if (!spec.startsWith("acp:")) return spec;
|
|
70
|
+
return `acp:${redactAcpTargetForLogs(spec.slice(4))}`;
|
|
71
|
+
}
|
|
53
72
|
const DEFAULT_CONFIG = {
|
|
54
73
|
agent: "claude",
|
|
55
74
|
agentPathOverride: {},
|
|
@@ -199,12 +218,19 @@ function serializeAgentArgsOverride(agentArgsOverride) {
|
|
|
199
218
|
sortKeys: false
|
|
200
219
|
}).trimEnd();
|
|
201
220
|
}
|
|
221
|
+
function serializeAgent(agent) {
|
|
222
|
+
return yaml.dump({ agent }, {
|
|
223
|
+
lineWidth: -1,
|
|
224
|
+
noRefs: true,
|
|
225
|
+
sortKeys: false
|
|
226
|
+
}).trimEnd();
|
|
227
|
+
}
|
|
202
228
|
function serializeConfig(config) {
|
|
203
229
|
const agentPathOverrideSection = serializeAgentPathOverride(config.agentPathOverride);
|
|
204
230
|
const agentArgsOverrideSection = serializeAgentArgsOverride(config.agentArgsOverride);
|
|
205
231
|
const lines = [
|
|
206
|
-
"# Agent to use by default: native agent name or acp:<target>",
|
|
207
|
-
|
|
232
|
+
"# Agent to use by default: native agent name or acp:<target-or-command>",
|
|
233
|
+
serializeAgent(config.agent),
|
|
208
234
|
"",
|
|
209
235
|
"# Custom paths to native agent binaries (optional)",
|
|
210
236
|
"# Paths may be absolute, bare executable names on PATH,",
|
|
@@ -237,14 +263,14 @@ function serializeConfig(config) {
|
|
|
237
263
|
"# - high",
|
|
238
264
|
"",
|
|
239
265
|
"# Custom ACP target commands (optional)",
|
|
240
|
-
"# Maps acp:<target> names to spawn commands. Useful for
|
|
266
|
+
"# Maps acp:<target> names to spawn commands. Useful for naming a",
|
|
241
267
|
"# local or beta build of an ACP agent.",
|
|
242
268
|
"# acpRegistryOverrides:",
|
|
243
269
|
"# my-fork: \"/usr/local/bin/my-claude-code-fork --acp\"",
|
|
244
270
|
"# staging: \"node /opt/staging/agent.mjs\"",
|
|
245
271
|
"",
|
|
246
272
|
"# Commit message convention (optional)",
|
|
247
|
-
"# Defaults to: gnhf
|
|
273
|
+
"# Defaults to: gnhf <iteration>: <summary>",
|
|
248
274
|
"# Use Conventional Commits semantic-release headers:",
|
|
249
275
|
"# commitMessage:",
|
|
250
276
|
"# preset: conventional"
|
|
@@ -479,6 +505,62 @@ function getBranchCommitCount(baseCommit, cwd) {
|
|
|
479
505
|
`${baseCommit}..HEAD`
|
|
480
506
|
], cwd), 10);
|
|
481
507
|
}
|
|
508
|
+
function emptyBranchDiffStats$1() {
|
|
509
|
+
return {
|
|
510
|
+
commits: 0,
|
|
511
|
+
filesChanged: 0,
|
|
512
|
+
filesAdded: 0,
|
|
513
|
+
filesUpdated: 0,
|
|
514
|
+
filesDeleted: 0,
|
|
515
|
+
filesRenamed: 0,
|
|
516
|
+
binaryFiles: 0,
|
|
517
|
+
linesAdded: 0,
|
|
518
|
+
linesDeleted: 0
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function getBranchDiffStats(baseCommit, cwd) {
|
|
522
|
+
if (!baseCommit) return emptyBranchDiffStats$1();
|
|
523
|
+
const range = `${baseCommit}..HEAD`;
|
|
524
|
+
const stats = emptyBranchDiffStats$1();
|
|
525
|
+
stats.commits = Number.parseInt(git([
|
|
526
|
+
"rev-list",
|
|
527
|
+
"--count",
|
|
528
|
+
"--first-parent",
|
|
529
|
+
range
|
|
530
|
+
], cwd), 10);
|
|
531
|
+
const nameStatus = git([
|
|
532
|
+
"diff",
|
|
533
|
+
"--name-status",
|
|
534
|
+
range
|
|
535
|
+
], cwd);
|
|
536
|
+
for (const line of nameStatus.split("\n")) {
|
|
537
|
+
if (!line) continue;
|
|
538
|
+
const [status] = line.split(" ");
|
|
539
|
+
stats.filesChanged++;
|
|
540
|
+
if (status === "A") stats.filesAdded++;
|
|
541
|
+
else if (status === "D") stats.filesDeleted++;
|
|
542
|
+
else if (status?.startsWith("R")) {
|
|
543
|
+
stats.filesUpdated++;
|
|
544
|
+
stats.filesRenamed++;
|
|
545
|
+
} else stats.filesUpdated++;
|
|
546
|
+
}
|
|
547
|
+
const numstat = git([
|
|
548
|
+
"diff",
|
|
549
|
+
"--numstat",
|
|
550
|
+
range
|
|
551
|
+
], cwd);
|
|
552
|
+
for (const line of numstat.split("\n")) {
|
|
553
|
+
if (!line) continue;
|
|
554
|
+
const [added, deleted] = line.split(" ");
|
|
555
|
+
if (added === "-" || deleted === "-") {
|
|
556
|
+
stats.binaryFiles++;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
stats.linesAdded += Number.parseInt(added ?? "0", 10) || 0;
|
|
560
|
+
stats.linesDeleted += Number.parseInt(deleted ?? "0", 10) || 0;
|
|
561
|
+
}
|
|
562
|
+
return stats;
|
|
563
|
+
}
|
|
482
564
|
function commitAll(message, cwd) {
|
|
483
565
|
git(["add", "-A"], cwd);
|
|
484
566
|
try {
|
|
@@ -660,7 +742,7 @@ function resolveConventionalScope(value) {
|
|
|
660
742
|
return scope === "" ? "" : `(${scope})`;
|
|
661
743
|
}
|
|
662
744
|
function buildCommitMessage(config, output, context) {
|
|
663
|
-
if (config === void 0) return collapseHeader(`gnhf
|
|
745
|
+
if (config === void 0) return collapseHeader(`gnhf ${context.iteration}: ${output.summary}`);
|
|
664
746
|
const commitOutput = output;
|
|
665
747
|
return collapseHeader(`${resolveConventionalType(commitOutput.type)}${resolveConventionalScope(commitOutput.scope)}: ${output.summary}`);
|
|
666
748
|
}
|
|
@@ -13461,6 +13543,40 @@ function isAbortError$2(error) {
|
|
|
13461
13543
|
function createAbortError$2() {
|
|
13462
13544
|
return /* @__PURE__ */ new Error("Agent was aborted");
|
|
13463
13545
|
}
|
|
13546
|
+
function redactRawAcpTargetInString(text, target) {
|
|
13547
|
+
const redacted = redactAcpTargetForLogs(target);
|
|
13548
|
+
if (redacted === target) return text;
|
|
13549
|
+
return text.split(target).join(redacted);
|
|
13550
|
+
}
|
|
13551
|
+
function redactRawAcpTargetInValue(value, target) {
|
|
13552
|
+
if (typeof value === "string") return redactRawAcpTargetInString(value, target);
|
|
13553
|
+
if (Array.isArray(value)) return value.map((item) => redactRawAcpTargetInValue(item, target));
|
|
13554
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, redactRawAcpTargetInValue(entry, target)]));
|
|
13555
|
+
return value;
|
|
13556
|
+
}
|
|
13557
|
+
function serializeAcpErrorForLog(error, target) {
|
|
13558
|
+
return redactRawAcpTargetInValue(serializeError(error), target);
|
|
13559
|
+
}
|
|
13560
|
+
function redactAcpErrorForThrow(error, target) {
|
|
13561
|
+
if (redactAcpTargetForLogs(target) === target) return error;
|
|
13562
|
+
if (error instanceof PermanentAgentError) return new PermanentAgentError(redactRawAcpTargetInString(error.message, target), redactRawAcpTargetInString(error.detail, target));
|
|
13563
|
+
if (error instanceof Error) {
|
|
13564
|
+
let cause;
|
|
13565
|
+
try {
|
|
13566
|
+
cause = "cause" in error ? error.cause : void 0;
|
|
13567
|
+
} catch {
|
|
13568
|
+
cause = void 0;
|
|
13569
|
+
}
|
|
13570
|
+
const redactedCause = cause === void 0 ? void 0 : redactAcpErrorForThrow(cause, target);
|
|
13571
|
+
const redactedError = new Error(redactRawAcpTargetInString(error.message, target), redactedCause === void 0 ? void 0 : { cause: redactedCause });
|
|
13572
|
+
redactedError.name = error.name;
|
|
13573
|
+
if (typeof error.stack === "string") redactedError.stack = redactRawAcpTargetInString(error.stack, target);
|
|
13574
|
+
const code = error.code;
|
|
13575
|
+
if (code !== void 0) redactedError.code = code;
|
|
13576
|
+
return redactedError;
|
|
13577
|
+
}
|
|
13578
|
+
return redactRawAcpTargetInValue(error, target);
|
|
13579
|
+
}
|
|
13464
13580
|
function estimateTokens(charCount) {
|
|
13465
13581
|
if (charCount <= 0) return 0;
|
|
13466
13582
|
return Math.ceil(charCount / 4);
|
|
@@ -13493,30 +13609,47 @@ var AcpAgent = class {
|
|
|
13493
13609
|
const { signal, onMessage, onUsage, logPath } = options ?? {};
|
|
13494
13610
|
if (signal?.aborted) throw createAbortError$2();
|
|
13495
13611
|
const runtime = this.ensureRuntime(cwd);
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13612
|
+
let handle;
|
|
13613
|
+
try {
|
|
13614
|
+
handle = await runtime.ensureSession({
|
|
13615
|
+
sessionKey: this.runId,
|
|
13616
|
+
agent: this.target,
|
|
13617
|
+
mode: "persistent",
|
|
13618
|
+
cwd
|
|
13619
|
+
});
|
|
13620
|
+
} catch (error) {
|
|
13621
|
+
throw redactAcpErrorForThrow(error, this.target);
|
|
13622
|
+
}
|
|
13502
13623
|
this.handle = handle;
|
|
13503
13624
|
const requestId = randomUUID();
|
|
13504
13625
|
appendDebugLog("acp:turn:start", {
|
|
13505
|
-
target: this.target,
|
|
13626
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13506
13627
|
sessionKey: this.runId,
|
|
13507
13628
|
requestId,
|
|
13508
13629
|
cwd
|
|
13509
13630
|
});
|
|
13510
13631
|
const acpPrompt = buildAcpPrompt(prompt, this.schema);
|
|
13511
13632
|
const promptTokenEstimate = estimateTokens(acpPrompt.length);
|
|
13512
|
-
const turn = runtime.startTurn({
|
|
13513
|
-
handle,
|
|
13514
|
-
text: acpPrompt,
|
|
13515
|
-
mode: "prompt",
|
|
13516
|
-
requestId,
|
|
13517
|
-
signal
|
|
13518
|
-
});
|
|
13519
13633
|
const startedAt = Date.now();
|
|
13634
|
+
const turn = (() => {
|
|
13635
|
+
try {
|
|
13636
|
+
return runtime.startTurn({
|
|
13637
|
+
handle,
|
|
13638
|
+
text: acpPrompt,
|
|
13639
|
+
mode: "prompt",
|
|
13640
|
+
requestId,
|
|
13641
|
+
signal
|
|
13642
|
+
});
|
|
13643
|
+
} catch (error) {
|
|
13644
|
+
appendDebugLog("acp:turn:start-error", {
|
|
13645
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13646
|
+
requestId,
|
|
13647
|
+
elapsedMs: Date.now() - startedAt,
|
|
13648
|
+
error: serializeAcpErrorForLog(error, this.target)
|
|
13649
|
+
});
|
|
13650
|
+
throw redactAcpErrorForThrow(error, this.target);
|
|
13651
|
+
}
|
|
13652
|
+
})();
|
|
13520
13653
|
const iterationStartUsed = this.lastReportedUsed;
|
|
13521
13654
|
let latestUsed = iterationStartUsed;
|
|
13522
13655
|
let usageUpdateReceived = iterationStartUsed > 0;
|
|
@@ -13587,23 +13720,23 @@ var AcpAgent = class {
|
|
|
13587
13720
|
if (signal?.aborted || isAbortError$2(error)) {
|
|
13588
13721
|
await turn.cancel({ reason: "gnhf-aborted" }).catch(() => void 0);
|
|
13589
13722
|
appendDebugLog("acp:turn:aborted", {
|
|
13590
|
-
target: this.target,
|
|
13723
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13591
13724
|
requestId,
|
|
13592
13725
|
elapsedMs: Date.now() - startedAt
|
|
13593
13726
|
});
|
|
13594
13727
|
throw createAbortError$2();
|
|
13595
13728
|
}
|
|
13596
13729
|
appendDebugLog("acp:turn:stream-error", {
|
|
13597
|
-
target: this.target,
|
|
13730
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13598
13731
|
requestId,
|
|
13599
13732
|
elapsedMs: Date.now() - startedAt,
|
|
13600
|
-
error:
|
|
13733
|
+
error: serializeAcpErrorForLog(error, this.target)
|
|
13601
13734
|
});
|
|
13602
|
-
throw error;
|
|
13735
|
+
throw redactAcpErrorForThrow(error, this.target);
|
|
13603
13736
|
}
|
|
13604
13737
|
const result = await turn.result;
|
|
13605
13738
|
appendDebugLog("acp:turn:result", {
|
|
13606
|
-
target: this.target,
|
|
13739
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13607
13740
|
requestId,
|
|
13608
13741
|
status: result.status,
|
|
13609
13742
|
stopReason: result.status === "completed" || result.status === "cancelled" ? result.stopReason : void 0,
|
|
@@ -13614,7 +13747,7 @@ var AcpAgent = class {
|
|
|
13614
13747
|
});
|
|
13615
13748
|
if (result.status === "cancelled") throw createAbortError$2();
|
|
13616
13749
|
if (result.status === "failed") {
|
|
13617
|
-
const message = result.error.message || "ACP turn failed";
|
|
13750
|
+
const message = redactRawAcpTargetInString(result.error.message || "ACP turn failed", this.target);
|
|
13618
13751
|
if (result.error.retryable === false) throw new PermanentAgentError(message, result.error.code ?? "ACP_TURN_FAILED");
|
|
13619
13752
|
throw new Error(message);
|
|
13620
13753
|
}
|
|
@@ -13658,11 +13791,11 @@ var AcpAgent = class {
|
|
|
13658
13791
|
handle,
|
|
13659
13792
|
reason: "gnhf-shutdown"
|
|
13660
13793
|
});
|
|
13661
|
-
appendDebugLog("acp:close", { target: this.target });
|
|
13794
|
+
appendDebugLog("acp:close", { target: redactAcpTargetForLogs(this.target) });
|
|
13662
13795
|
} catch (error) {
|
|
13663
13796
|
appendDebugLog("acp:close-error", {
|
|
13664
|
-
target: this.target,
|
|
13665
|
-
error:
|
|
13797
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13798
|
+
error: serializeAcpErrorForLog(error, this.target)
|
|
13666
13799
|
});
|
|
13667
13800
|
}
|
|
13668
13801
|
}
|
|
@@ -13677,7 +13810,7 @@ var AcpAgent = class {
|
|
|
13677
13810
|
});
|
|
13678
13811
|
this.runtime = runtime;
|
|
13679
13812
|
appendDebugLog("acp:runtime:created", {
|
|
13680
|
-
target: this.target,
|
|
13813
|
+
target: redactAcpTargetForLogs(this.target),
|
|
13681
13814
|
sessionStateDir: this.sessionStateDir,
|
|
13682
13815
|
cwd
|
|
13683
13816
|
});
|
|
@@ -16343,7 +16476,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
16343
16476
|
this.state.status = "running";
|
|
16344
16477
|
this.emit("state", this.getState());
|
|
16345
16478
|
appendDebugLog("orchestrator:start", {
|
|
16346
|
-
agent: this.agent.name,
|
|
16479
|
+
agent: redactAgentSpecForLogs(this.agent.name),
|
|
16347
16480
|
runId: this.runInfo.runId,
|
|
16348
16481
|
startIteration: this.state.currentIteration,
|
|
16349
16482
|
maxIterations: this.limits.maxIterations,
|
|
@@ -16502,7 +16635,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
16502
16635
|
const agentStartedAt = Date.now();
|
|
16503
16636
|
appendDebugLog("agent:run:start", {
|
|
16504
16637
|
iteration: this.state.currentIteration,
|
|
16505
|
-
agent: this.agent.name,
|
|
16638
|
+
agent: redactAgentSpecForLogs(this.agent.name),
|
|
16506
16639
|
logPath
|
|
16507
16640
|
});
|
|
16508
16641
|
try {
|
|
@@ -16707,6 +16840,158 @@ var Orchestrator = class extends EventEmitter {
|
|
|
16707
16840
|
}
|
|
16708
16841
|
};
|
|
16709
16842
|
//#endregion
|
|
16843
|
+
//#region src/utils/tokens.ts
|
|
16844
|
+
function formatTokens(count) {
|
|
16845
|
+
if (count >= 0xe8d4a51000) return `${(count / 0xe8d4a51000).toFixed(1)}T`;
|
|
16846
|
+
if (count >= 1e9) return `${(count / 1e9).toFixed(1)}B`;
|
|
16847
|
+
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
16848
|
+
if (count >= 1e3) return `${Math.round(count / 1e3)}K`;
|
|
16849
|
+
return String(count);
|
|
16850
|
+
}
|
|
16851
|
+
//#endregion
|
|
16852
|
+
//#region src/core/exit-summary.ts
|
|
16853
|
+
const MIN_CARD_WIDTH = 62;
|
|
16854
|
+
const LABEL_WIDTH = 16;
|
|
16855
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
16856
|
+
const ANSI_TOKEN_RE = /\x1b\[[0-9;]*m/g;
|
|
16857
|
+
const NO_MISTAKES_URL = "https://github.com/kunchenguid/no-mistakes";
|
|
16858
|
+
function stripExitSummaryAnsi(text) {
|
|
16859
|
+
return text.replace(ANSI_RE, "");
|
|
16860
|
+
}
|
|
16861
|
+
function makeStyles(color) {
|
|
16862
|
+
const wrap = (open, text) => color ? `${open}${text}\x1b[0m` : text;
|
|
16863
|
+
return {
|
|
16864
|
+
dim: (text) => wrap("\x1B[2m", text),
|
|
16865
|
+
bold: (text) => wrap("\x1B[1m", text),
|
|
16866
|
+
cyan: (text) => wrap("\x1B[36m", text),
|
|
16867
|
+
yellow: (text) => wrap("\x1B[33m", text),
|
|
16868
|
+
green: (text) => wrap("\x1B[32m", text),
|
|
16869
|
+
red: (text) => wrap("\x1B[31m", text),
|
|
16870
|
+
magenta: (text) => wrap("\x1B[35m", text),
|
|
16871
|
+
blueUnderline: (text) => wrap("\x1B[34;4m", text)
|
|
16872
|
+
};
|
|
16873
|
+
}
|
|
16874
|
+
function visibleLength(text) {
|
|
16875
|
+
return stripExitSummaryAnsi(text).length;
|
|
16876
|
+
}
|
|
16877
|
+
function padVisible(text, width) {
|
|
16878
|
+
return text + " ".repeat(Math.max(0, width - visibleLength(text)));
|
|
16879
|
+
}
|
|
16880
|
+
function truncateVisible(text, width) {
|
|
16881
|
+
if (visibleLength(text) <= width) return text;
|
|
16882
|
+
if (width <= 0) return "";
|
|
16883
|
+
const targetWidth = Math.max(0, width - 1);
|
|
16884
|
+
let output = "";
|
|
16885
|
+
let visible = 0;
|
|
16886
|
+
let index = 0;
|
|
16887
|
+
let hasActiveStyle = false;
|
|
16888
|
+
const finish = () => `${output}…${hasActiveStyle ? "\x1B[0m" : ""}`;
|
|
16889
|
+
for (const match of text.matchAll(ANSI_TOKEN_RE)) {
|
|
16890
|
+
const chunk = text.slice(index, match.index);
|
|
16891
|
+
for (const char of chunk) {
|
|
16892
|
+
if (visible >= targetWidth) return finish();
|
|
16893
|
+
output += char;
|
|
16894
|
+
visible += 1;
|
|
16895
|
+
}
|
|
16896
|
+
output += match[0];
|
|
16897
|
+
hasActiveStyle = match[0] !== "\x1B[0m";
|
|
16898
|
+
index = match.index + match[0].length;
|
|
16899
|
+
}
|
|
16900
|
+
for (const char of text.slice(index)) {
|
|
16901
|
+
if (visible >= targetWidth) return finish();
|
|
16902
|
+
output += char;
|
|
16903
|
+
visible += 1;
|
|
16904
|
+
}
|
|
16905
|
+
return finish();
|
|
16906
|
+
}
|
|
16907
|
+
function formatDuration(ms) {
|
|
16908
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
16909
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
16910
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
16911
|
+
const seconds = totalSeconds % 60;
|
|
16912
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
16913
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
16914
|
+
return `${seconds}s`;
|
|
16915
|
+
}
|
|
16916
|
+
function formatNumber(value) {
|
|
16917
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
16918
|
+
}
|
|
16919
|
+
function formatTokenCount$1(value, suffix, estimated) {
|
|
16920
|
+
return `${estimated ? "~" : ""}${formatTokens(value)} ${suffix}`;
|
|
16921
|
+
}
|
|
16922
|
+
function plural(value, singular, pluralText = `${singular}s`) {
|
|
16923
|
+
return `${formatNumber(value)} ${value === 1 ? singular : pluralText}`;
|
|
16924
|
+
}
|
|
16925
|
+
function metricLine(label, columns) {
|
|
16926
|
+
return ` ${padVisible(`${padVisible(label, LABEL_WIDTH)}${columns[0] ?? ""}`, 30)}${padVisible(columns[1] ?? "", 13)}${columns[2] ?? ""}`;
|
|
16927
|
+
}
|
|
16928
|
+
function commandLine(label, command) {
|
|
16929
|
+
return ` ${padVisible(label, LABEL_WIDTH)}${command}`;
|
|
16930
|
+
}
|
|
16931
|
+
function continuationLine(text) {
|
|
16932
|
+
return ` ${"".padEnd(LABEL_WIDTH)}${text}`;
|
|
16933
|
+
}
|
|
16934
|
+
function resolveCardWidth(contents, terminalColumns) {
|
|
16935
|
+
const contentWidth = Math.max(...contents.map(visibleLength));
|
|
16936
|
+
const desiredWidth = Math.max(MIN_CARD_WIDTH, contentWidth + 4);
|
|
16937
|
+
const columns = terminalColumns && terminalColumns > 0 ? terminalColumns : void 0;
|
|
16938
|
+
return Math.max(4, columns ? Math.min(desiredWidth, columns) : desiredWidth);
|
|
16939
|
+
}
|
|
16940
|
+
function cardBorder(left, right, width, dim) {
|
|
16941
|
+
return dim(`${left}${"─".repeat(Math.max(0, width - 2))}${right}`);
|
|
16942
|
+
}
|
|
16943
|
+
function cardLine(content, width, dim) {
|
|
16944
|
+
const contentWidth = Math.max(0, width - 4);
|
|
16945
|
+
return `${dim("│ ")}${padVisible(truncateVisible(content, contentWidth), contentWidth)}${dim(" │")}`;
|
|
16946
|
+
}
|
|
16947
|
+
function renderExitSummary(options) {
|
|
16948
|
+
const s = makeStyles(options.color);
|
|
16949
|
+
const elapsed = formatDuration(options.elapsedMs);
|
|
16950
|
+
const stopped = options.status === "aborted";
|
|
16951
|
+
const title = stopped ? `${s.red("×")} ${s.bold("gnhf stopped")}` : `${s.cyan("✦")} ${s.bold("gnhf wrapped")}`;
|
|
16952
|
+
const subtitle = stopped ? `${s.cyan(options.agentName)} ran for ${s.yellow(elapsed)} before: ${options.abortReason ?? options.status}` : `${s.cyan(options.agentName)} worked for ${s.yellow(elapsed)} on ${s.magenta(options.branchName)}`;
|
|
16953
|
+
const cardWidth = resolveCardWidth([title, ` ${subtitle}`], options.terminalColumns);
|
|
16954
|
+
const rolledBack = `${options.failCount} rolled back`;
|
|
16955
|
+
const inputTokens = formatTokenCount$1(options.totalInputTokens, "in", options.tokensEstimated);
|
|
16956
|
+
const outputTokens = formatTokenCount$1(options.totalOutputTokens, "out", options.tokensEstimated);
|
|
16957
|
+
const commits = plural(options.commitCount, "commit");
|
|
16958
|
+
const linesAdded = `+${formatNumber(options.diffStats.linesAdded)}`;
|
|
16959
|
+
const linesDeleted = `-${formatNumber(options.diffStats.linesDeleted)}`;
|
|
16960
|
+
return `\n${[
|
|
16961
|
+
cardBorder("╭", "╮", cardWidth, s.dim),
|
|
16962
|
+
cardLine(title, cardWidth, s.dim),
|
|
16963
|
+
cardLine(` ${subtitle}`, cardWidth, s.dim),
|
|
16964
|
+
cardBorder("╰", "╯", cardWidth, s.dim),
|
|
16965
|
+
"",
|
|
16966
|
+
metricLine(s.dim("iterations"), [
|
|
16967
|
+
`${s.bold(String(options.iterations))} total`,
|
|
16968
|
+
s.green(`${options.successCount} good`),
|
|
16969
|
+
stopped ? s.red(rolledBack) : s.yellow(rolledBack)
|
|
16970
|
+
]),
|
|
16971
|
+
metricLine(s.dim("tokens"), [s.bold(inputTokens), s.bold(outputTokens)]),
|
|
16972
|
+
metricLine(s.dim("branch diff"), [
|
|
16973
|
+
s.bold(commits),
|
|
16974
|
+
s.green(linesAdded),
|
|
16975
|
+
s.red(linesDeleted)
|
|
16976
|
+
]),
|
|
16977
|
+
metricLine(s.dim("files"), [
|
|
16978
|
+
`${options.diffStats.filesAdded} added`,
|
|
16979
|
+
`${options.diffStats.filesUpdated} updated`,
|
|
16980
|
+
`${options.diffStats.filesDeleted} deleted`
|
|
16981
|
+
]),
|
|
16982
|
+
"",
|
|
16983
|
+
commandLine(s.dim("notes"), options.notesPath),
|
|
16984
|
+
commandLine(s.dim("debug log"), options.logPath),
|
|
16985
|
+
"",
|
|
16986
|
+
commandLine(s.dim("next steps"), s.cyan(`git log --oneline ${options.baseRef}..HEAD`)),
|
|
16987
|
+
continuationLine(s.cyan(`git diff --stat ${options.baseRef}..HEAD`)),
|
|
16988
|
+
continuationLine(s.cyan("gh pr create")),
|
|
16989
|
+
"",
|
|
16990
|
+
commandLine(s.dim("too much"), `${s.cyan("git push no-mistakes")}:`),
|
|
16991
|
+
commandLine(s.dim("to review?"), s.blueUnderline(NO_MISTAKES_URL))
|
|
16992
|
+
].join("\n")}\n`;
|
|
16993
|
+
}
|
|
16994
|
+
//#endregion
|
|
16710
16995
|
//#region src/mock-orchestrator.ts
|
|
16711
16996
|
function mockIter(n, success, summary, agoMs) {
|
|
16712
16997
|
return {
|
|
@@ -16920,15 +17205,6 @@ function formatElapsed(ms) {
|
|
|
16920
17205
|
return `${String(Math.floor(s / 3600)).padStart(2, "0")}:${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;
|
|
16921
17206
|
}
|
|
16922
17207
|
//#endregion
|
|
16923
|
-
//#region src/utils/tokens.ts
|
|
16924
|
-
function formatTokens(count) {
|
|
16925
|
-
if (count >= 0xe8d4a51000) return `${(count / 0xe8d4a51000).toFixed(1)}T`;
|
|
16926
|
-
if (count >= 1e9) return `${(count / 1e9).toFixed(1)}B`;
|
|
16927
|
-
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
16928
|
-
if (count >= 1e3) return `${Math.round(count / 1e3)}K`;
|
|
16929
|
-
return String(count);
|
|
16930
|
-
}
|
|
16931
|
-
//#endregion
|
|
16932
17208
|
//#region src/utils/terminal-width.ts
|
|
16933
17209
|
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
16934
17210
|
const MARK_REGEX = /\p{Mark}/u;
|
|
@@ -17548,7 +17824,7 @@ const GNHF_REEXEC_STDIN_PROMPT_FILE = "GNHF_REEXEC_STDIN_PROMPT_FILE";
|
|
|
17548
17824
|
const GNHF_REEXEC_STDIN_PROMPT_DIR_PREFIX = "gnhf-stdin-";
|
|
17549
17825
|
const GNHF_REEXEC_STDIN_PROMPT_FILENAME = "prompt.txt";
|
|
17550
17826
|
const AGENT_NAME_SET = new Set(AGENT_NAMES);
|
|
17551
|
-
const AGENT_SPEC_LIST = `${`"${AGENT_NAMES.slice(0, -1).join("\", \"")}", or "${AGENT_NAMES[AGENT_NAMES.length - 1]}"`}, or "acp:<target>" (e.g. acp:gemini)`;
|
|
17827
|
+
const AGENT_SPEC_LIST = `${`"${AGENT_NAMES.slice(0, -1).join("\", \"")}", or "${AGENT_NAMES[AGENT_NAMES.length - 1]}"`}, or "acp:<target-or-command>" (e.g. acp:gemini)`;
|
|
17552
17828
|
var PromptSignalError = class extends Error {
|
|
17553
17829
|
constructor(signal) {
|
|
17554
17830
|
super(signal);
|
|
@@ -17576,6 +17852,42 @@ function isAgentName(name) {
|
|
|
17576
17852
|
function getNativeAgentName(spec) {
|
|
17577
17853
|
return isAgentName(spec) ? spec : void 0;
|
|
17578
17854
|
}
|
|
17855
|
+
function getTelemetryAgent(spec) {
|
|
17856
|
+
return redactAgentSpecForLogs(spec);
|
|
17857
|
+
}
|
|
17858
|
+
function shouldUseColor() {
|
|
17859
|
+
return process$1.stdout.isTTY === true && process$1.env.NO_COLOR === void 0 && process$1.env.TERM !== "dumb";
|
|
17860
|
+
}
|
|
17861
|
+
function emptyBranchDiffStats(commitCount) {
|
|
17862
|
+
return {
|
|
17863
|
+
commits: commitCount,
|
|
17864
|
+
filesChanged: 0,
|
|
17865
|
+
filesAdded: 0,
|
|
17866
|
+
filesUpdated: 0,
|
|
17867
|
+
filesDeleted: 0,
|
|
17868
|
+
filesRenamed: 0,
|
|
17869
|
+
binaryFiles: 0,
|
|
17870
|
+
linesAdded: 0,
|
|
17871
|
+
linesDeleted: 0
|
|
17872
|
+
};
|
|
17873
|
+
}
|
|
17874
|
+
function redactDebugArgs(args) {
|
|
17875
|
+
const redacted = [...args];
|
|
17876
|
+
for (let i = 0; i < redacted.length; i += 1) {
|
|
17877
|
+
const arg = redacted[i];
|
|
17878
|
+
if (arg === "--") break;
|
|
17879
|
+
if (arg === "--agent") {
|
|
17880
|
+
const next = redacted[i + 1];
|
|
17881
|
+
if (next !== void 0) {
|
|
17882
|
+
redacted[i + 1] = redactAgentSpecForLogs(next);
|
|
17883
|
+
i += 1;
|
|
17884
|
+
}
|
|
17885
|
+
continue;
|
|
17886
|
+
}
|
|
17887
|
+
if (arg?.startsWith("--agent=")) redacted[i] = `--agent=${redactAgentSpecForLogs(arg.slice(8))}`;
|
|
17888
|
+
}
|
|
17889
|
+
return redacted;
|
|
17890
|
+
}
|
|
17579
17891
|
function buildSchemaOptions(stopWhen, commitMessage) {
|
|
17580
17892
|
const commitFields = getCommitMessageSchemaFields(commitMessage);
|
|
17581
17893
|
return {
|
|
@@ -17797,7 +18109,7 @@ function readReexecStdinPrompt(env) {
|
|
|
17797
18109
|
}
|
|
17798
18110
|
}
|
|
17799
18111
|
const program = new Command();
|
|
17800
|
-
program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(packageVersion).argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", `Agent to use (${AGENT_NAMES.join(", ")}, or acp:<target>)`).option("--max-iterations <n>", "Abort after N total iterations", parseNonNegativeInteger).option("--max-tokens <n>", "Abort after N total input+output tokens", parseNonNegativeInteger).option("--stop-when <condition>", "End when the agent reports this condition; resumes reuse it, pass a new value to overwrite or \"\" to clear").option("--prevent-sleep <mode>", "Prevent system sleep during the run (\"on\" or \"off\")", parseOnOffBoolean).option("--worktree", "Run in a separate git worktree (enables multiple agents on the same repo)", false).option("--mock", "", false).action(async (promptArg, options) => {
|
|
18112
|
+
program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(packageVersion).argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", `Agent to use (${AGENT_NAMES.join(", ")}, or acp:<target-or-command>)`).option("--max-iterations <n>", "Abort after N total iterations", parseNonNegativeInteger).option("--max-tokens <n>", "Abort after N total input+output tokens", parseNonNegativeInteger).option("--stop-when <condition>", "End when the agent reports this condition; resumes reuse it, pass a new value to overwrite or \"\" to clear").option("--prevent-sleep <mode>", "Prevent system sleep during the run (\"on\" or \"off\")", parseOnOffBoolean).option("--worktree", "Run in a separate git worktree (enables multiple agents on the same repo)", false).option("--mock", "", false).action(async (promptArg, options) => {
|
|
17801
18113
|
if (options.mock) {
|
|
17802
18114
|
const mock = new MockOrchestrator();
|
|
17803
18115
|
enterAltScreen();
|
|
@@ -17939,16 +18251,17 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
17939
18251
|
}
|
|
17940
18252
|
}
|
|
17941
18253
|
const runMode = options.worktree ? "worktree" : startIteration > 0 ? "resume" : "new";
|
|
18254
|
+
const telemetryAgent = getTelemetryAgent(config.agent);
|
|
17942
18255
|
telemetry.pageview("/run", {
|
|
17943
|
-
agent:
|
|
18256
|
+
agent: telemetryAgent,
|
|
17944
18257
|
mode: runMode
|
|
17945
18258
|
});
|
|
17946
18259
|
initDebugLog(runInfo.logPath);
|
|
17947
18260
|
appendDebugLog("run:start", {
|
|
17948
|
-
args: process$1.argv.slice(2),
|
|
18261
|
+
args: redactDebugArgs(process$1.argv.slice(2)),
|
|
17949
18262
|
runId: runInfo.runId,
|
|
17950
18263
|
runDir: runInfo.runDir,
|
|
17951
|
-
agent: config.agent,
|
|
18264
|
+
agent: redactAgentSpecForLogs(config.agent),
|
|
17952
18265
|
promptLength: prompt.length,
|
|
17953
18266
|
promptFromStdin,
|
|
17954
18267
|
startIteration,
|
|
@@ -18037,6 +18350,38 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
18037
18350
|
}
|
|
18038
18351
|
{
|
|
18039
18352
|
const finalState = orchestrator.getState();
|
|
18353
|
+
let finalBranchName = "HEAD";
|
|
18354
|
+
try {
|
|
18355
|
+
finalBranchName = getCurrentBranch(effectiveCwd);
|
|
18356
|
+
} catch (error) {
|
|
18357
|
+
appendDebugLog("summary:branch-error", { error: serializeError(error) });
|
|
18358
|
+
}
|
|
18359
|
+
let diffStats = emptyBranchDiffStats(finalState.commitCount);
|
|
18360
|
+
try {
|
|
18361
|
+
diffStats = getBranchDiffStats(runInfo.baseCommit, effectiveCwd);
|
|
18362
|
+
} catch (error) {
|
|
18363
|
+
appendDebugLog("summary:diff-stats-error", { error: serializeError(error) });
|
|
18364
|
+
}
|
|
18365
|
+
const exitSummary = renderExitSummary({
|
|
18366
|
+
agentName: redactAgentSpecForLogs(config.agent),
|
|
18367
|
+
branchName: finalBranchName,
|
|
18368
|
+
elapsedMs: Date.now() - finalState.startTime.getTime(),
|
|
18369
|
+
status: finalState.status,
|
|
18370
|
+
abortReason: finalState.lastAgentError ?? finalState.lastMessage,
|
|
18371
|
+
iterations: finalState.currentIteration,
|
|
18372
|
+
successCount: finalState.successCount,
|
|
18373
|
+
failCount: finalState.failCount,
|
|
18374
|
+
totalInputTokens: finalState.totalInputTokens,
|
|
18375
|
+
totalOutputTokens: finalState.totalOutputTokens,
|
|
18376
|
+
tokensEstimated: finalState.tokensEstimated,
|
|
18377
|
+
commitCount: finalState.commitCount,
|
|
18378
|
+
notesPath: runInfo.notesPath,
|
|
18379
|
+
logPath: runInfo.logPath,
|
|
18380
|
+
baseRef: runInfo.baseCommit.slice(0, 12) || runInfo.baseCommit,
|
|
18381
|
+
diffStats,
|
|
18382
|
+
color: shouldUseColor(),
|
|
18383
|
+
terminalColumns: process$1.stdout.columns
|
|
18384
|
+
});
|
|
18040
18385
|
appendDebugLog("run:complete", {
|
|
18041
18386
|
signal: shutdownSignal,
|
|
18042
18387
|
status: finalState.status,
|
|
@@ -18049,7 +18394,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
18049
18394
|
worktreePath
|
|
18050
18395
|
});
|
|
18051
18396
|
telemetry.track("run", {
|
|
18052
|
-
agent:
|
|
18397
|
+
agent: telemetryAgent,
|
|
18053
18398
|
mode: runMode,
|
|
18054
18399
|
status: finalState.status,
|
|
18055
18400
|
signal: shutdownSignal ?? void 0,
|
|
@@ -18074,6 +18419,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
|
|
|
18074
18419
|
worktreeCleanup = null;
|
|
18075
18420
|
appendDebugLog("worktree:cleaned-up", { worktreePath });
|
|
18076
18421
|
}
|
|
18422
|
+
process$1.stdout.write(exitSummary);
|
|
18077
18423
|
}
|
|
18078
18424
|
if (shutdownSignal) process$1.exit(getSignalExitCode(shutdownSignal));
|
|
18079
18425
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnhf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "Before I go to bed, I tell my agents: good night, have fun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"@types/js-yaml": "^4.0.9",
|
|
28
28
|
"@types/node": "^22.0.0",
|
|
29
29
|
"@vitest/coverage-v8": "^4.1.2",
|
|
30
|
+
"acp-mock": "^1.1.0",
|
|
30
31
|
"acpx": "^0.6.1",
|
|
31
32
|
"eslint": "^9.0.0",
|
|
32
33
|
"eslint-config-prettier": "^10.0.0",
|