gnhf 0.1.32 → 0.1.33

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.
Files changed (3) hide show
  1. package/README.md +26 -24
  2. package/dist/cli.mjs +138 -40
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -175,22 +175,22 @@ If you run `gnhf` on an existing `gnhf/` branch with a different prompt, gnhf as
175
175
 
176
176
  ### Flags
177
177
 
178
- | Flag | Description | Default |
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 | unlimited |
182
- | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
183
- | `--stop-when <cond>` | End the loop when the agent reports this condition; persists across resume | unlimited |
184
- | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
185
- | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
186
- | `--version` | Show version | |
178
+ | Flag | Description | Default |
179
+ | ------------------------ | ------------------------------------------------------------------------------------------------------ | ---------------------- |
180
+ | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, `pi`, or `acp:<target-or-command>`) | config file (`claude`) |
181
+ | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
182
+ | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
183
+ | `--stop-when <cond>` | End the loop when the agent reports this condition; persists across resume | unlimited |
184
+ | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
185
+ | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
186
+ | `--version` | Show version | |
187
187
 
188
188
  ## Configuration
189
189
 
190
190
  Config lives at `~/.gnhf/config.yml`:
191
191
 
192
192
  ```yaml
193
- # Agent to use by default (claude, codex, rovodev, opencode, copilot, pi, or acp:<target>)
193
+ # Agent to use by default (claude, codex, rovodev, opencode, copilot, pi, or acp:<target-or-command>)
194
194
  agent: claude
195
195
 
196
196
  # Custom paths to native agent binaries (optional)
@@ -225,7 +225,7 @@ agent: claude
225
225
  # staging: "node /opt/staging/agent.mjs"
226
226
 
227
227
  # Commit message convention (optional)
228
- # Defaults to: gnhf #<iteration>: <summary>
228
+ # Defaults to: gnhf <iteration>: <summary>
229
229
  # Use the conventional preset for semantic-release compatible headers:
230
230
  # commitMessage:
231
231
  # preset: conventional
@@ -245,6 +245,7 @@ The iteration and token caps are runtime-only flags and are not persisted in `co
245
245
  `agentArgsOverride.<name>` lets you pass through extra CLI flags for native agents (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, or `pi`).
246
246
  ACP targets do not support path or arg overrides in this version.
247
247
  Use `acpRegistryOverrides` to map `acp:<target>` names to custom spawn commands for local, forked, or beta ACP agents.
248
+ 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
249
 
249
250
  - Use it for agent-specific options like models, profiles, or reasoning settings without adding a dedicated `gnhf` config field for each one.
250
251
  - 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 +253,7 @@ Use `acpRegistryOverrides` to map `acp:<target>` names to custom spawn commands
252
253
 
253
254
  `commitMessage` controls the subject line that gnhf uses for each successful iteration commit.
254
255
 
255
- - Omit it to keep the default `gnhf #<iteration>: <summary>` format.
256
+ - Omit it to keep the default `gnhf <iteration>: <summary>` format.
256
257
  - 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
258
  - 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
259
 
@@ -273,7 +274,8 @@ When sleep prevention is enabled, `gnhf` uses the native mechanism for your OS:
273
274
 
274
275
  ## Debug Logs
275
276
 
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 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.
277
+ 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.
278
+ 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
279
 
278
280
  Including a snippet of `gnhf.log` is the single most useful thing you can attach when filing an issue.
279
281
 
@@ -285,17 +287,17 @@ Set `GNHF_TELEMETRY=0` to turn it off.
285
287
 
286
288
  ## Agents
287
289
 
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 | Requirements | Notes |
291
- | ------------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
292
- | 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. |
293
- | Codex | `--agent codex` | Install OpenAI's `codex` CLI and sign in first. | `gnhf` invokes `codex exec` directly in non-interactive mode. |
294
- | 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. |
295
- | 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`. |
296
- | 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. |
297
- | 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. |
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`. | `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. |
290
+ `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.
291
+
292
+ | Agent | Flag | Requirements | Notes |
293
+ | ------------------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
294
+ | 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. |
295
+ | Codex | `--agent codex` | Install OpenAI's `codex` CLI and sign in first. | `gnhf` invokes `codex exec` directly in non-interactive mode. |
296
+ | 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. |
297
+ | 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`. |
298
+ | 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. |
299
+ | 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. |
300
+ | 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
301
 
300
302
  ## Development
301
303
 
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
- return ACP_SPEC_PATTERN.test(spec);
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
- `agent: ${config.agent}`,
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 pinning a",
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 #<iteration>: <summary>",
273
+ "# Defaults to: gnhf <iteration>: <summary>",
248
274
  "# Use Conventional Commits semantic-release headers:",
249
275
  "# commitMessage:",
250
276
  "# preset: conventional"
@@ -660,7 +686,7 @@ function resolveConventionalScope(value) {
660
686
  return scope === "" ? "" : `(${scope})`;
661
687
  }
662
688
  function buildCommitMessage(config, output, context) {
663
- if (config === void 0) return collapseHeader(`gnhf #${context.iteration}: ${output.summary}`);
689
+ if (config === void 0) return collapseHeader(`gnhf ${context.iteration}: ${output.summary}`);
664
690
  const commitOutput = output;
665
691
  return collapseHeader(`${resolveConventionalType(commitOutput.type)}${resolveConventionalScope(commitOutput.scope)}: ${output.summary}`);
666
692
  }
@@ -13461,6 +13487,40 @@ function isAbortError$2(error) {
13461
13487
  function createAbortError$2() {
13462
13488
  return /* @__PURE__ */ new Error("Agent was aborted");
13463
13489
  }
13490
+ function redactRawAcpTargetInString(text, target) {
13491
+ const redacted = redactAcpTargetForLogs(target);
13492
+ if (redacted === target) return text;
13493
+ return text.split(target).join(redacted);
13494
+ }
13495
+ function redactRawAcpTargetInValue(value, target) {
13496
+ if (typeof value === "string") return redactRawAcpTargetInString(value, target);
13497
+ if (Array.isArray(value)) return value.map((item) => redactRawAcpTargetInValue(item, target));
13498
+ if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, redactRawAcpTargetInValue(entry, target)]));
13499
+ return value;
13500
+ }
13501
+ function serializeAcpErrorForLog(error, target) {
13502
+ return redactRawAcpTargetInValue(serializeError(error), target);
13503
+ }
13504
+ function redactAcpErrorForThrow(error, target) {
13505
+ if (redactAcpTargetForLogs(target) === target) return error;
13506
+ if (error instanceof PermanentAgentError) return new PermanentAgentError(redactRawAcpTargetInString(error.message, target), redactRawAcpTargetInString(error.detail, target));
13507
+ if (error instanceof Error) {
13508
+ let cause;
13509
+ try {
13510
+ cause = "cause" in error ? error.cause : void 0;
13511
+ } catch {
13512
+ cause = void 0;
13513
+ }
13514
+ const redactedCause = cause === void 0 ? void 0 : redactAcpErrorForThrow(cause, target);
13515
+ const redactedError = new Error(redactRawAcpTargetInString(error.message, target), redactedCause === void 0 ? void 0 : { cause: redactedCause });
13516
+ redactedError.name = error.name;
13517
+ if (typeof error.stack === "string") redactedError.stack = redactRawAcpTargetInString(error.stack, target);
13518
+ const code = error.code;
13519
+ if (code !== void 0) redactedError.code = code;
13520
+ return redactedError;
13521
+ }
13522
+ return redactRawAcpTargetInValue(error, target);
13523
+ }
13464
13524
  function estimateTokens(charCount) {
13465
13525
  if (charCount <= 0) return 0;
13466
13526
  return Math.ceil(charCount / 4);
@@ -13493,30 +13553,47 @@ var AcpAgent = class {
13493
13553
  const { signal, onMessage, onUsage, logPath } = options ?? {};
13494
13554
  if (signal?.aborted) throw createAbortError$2();
13495
13555
  const runtime = this.ensureRuntime(cwd);
13496
- const handle = await runtime.ensureSession({
13497
- sessionKey: this.runId,
13498
- agent: this.target,
13499
- mode: "persistent",
13500
- cwd
13501
- });
13556
+ let handle;
13557
+ try {
13558
+ handle = await runtime.ensureSession({
13559
+ sessionKey: this.runId,
13560
+ agent: this.target,
13561
+ mode: "persistent",
13562
+ cwd
13563
+ });
13564
+ } catch (error) {
13565
+ throw redactAcpErrorForThrow(error, this.target);
13566
+ }
13502
13567
  this.handle = handle;
13503
13568
  const requestId = randomUUID();
13504
13569
  appendDebugLog("acp:turn:start", {
13505
- target: this.target,
13570
+ target: redactAcpTargetForLogs(this.target),
13506
13571
  sessionKey: this.runId,
13507
13572
  requestId,
13508
13573
  cwd
13509
13574
  });
13510
13575
  const acpPrompt = buildAcpPrompt(prompt, this.schema);
13511
13576
  const promptTokenEstimate = estimateTokens(acpPrompt.length);
13512
- const turn = runtime.startTurn({
13513
- handle,
13514
- text: acpPrompt,
13515
- mode: "prompt",
13516
- requestId,
13517
- signal
13518
- });
13519
13577
  const startedAt = Date.now();
13578
+ const turn = (() => {
13579
+ try {
13580
+ return runtime.startTurn({
13581
+ handle,
13582
+ text: acpPrompt,
13583
+ mode: "prompt",
13584
+ requestId,
13585
+ signal
13586
+ });
13587
+ } catch (error) {
13588
+ appendDebugLog("acp:turn:start-error", {
13589
+ target: redactAcpTargetForLogs(this.target),
13590
+ requestId,
13591
+ elapsedMs: Date.now() - startedAt,
13592
+ error: serializeAcpErrorForLog(error, this.target)
13593
+ });
13594
+ throw redactAcpErrorForThrow(error, this.target);
13595
+ }
13596
+ })();
13520
13597
  const iterationStartUsed = this.lastReportedUsed;
13521
13598
  let latestUsed = iterationStartUsed;
13522
13599
  let usageUpdateReceived = iterationStartUsed > 0;
@@ -13587,23 +13664,23 @@ var AcpAgent = class {
13587
13664
  if (signal?.aborted || isAbortError$2(error)) {
13588
13665
  await turn.cancel({ reason: "gnhf-aborted" }).catch(() => void 0);
13589
13666
  appendDebugLog("acp:turn:aborted", {
13590
- target: this.target,
13667
+ target: redactAcpTargetForLogs(this.target),
13591
13668
  requestId,
13592
13669
  elapsedMs: Date.now() - startedAt
13593
13670
  });
13594
13671
  throw createAbortError$2();
13595
13672
  }
13596
13673
  appendDebugLog("acp:turn:stream-error", {
13597
- target: this.target,
13674
+ target: redactAcpTargetForLogs(this.target),
13598
13675
  requestId,
13599
13676
  elapsedMs: Date.now() - startedAt,
13600
- error: serializeError(error)
13677
+ error: serializeAcpErrorForLog(error, this.target)
13601
13678
  });
13602
- throw error;
13679
+ throw redactAcpErrorForThrow(error, this.target);
13603
13680
  }
13604
13681
  const result = await turn.result;
13605
13682
  appendDebugLog("acp:turn:result", {
13606
- target: this.target,
13683
+ target: redactAcpTargetForLogs(this.target),
13607
13684
  requestId,
13608
13685
  status: result.status,
13609
13686
  stopReason: result.status === "completed" || result.status === "cancelled" ? result.stopReason : void 0,
@@ -13614,7 +13691,7 @@ var AcpAgent = class {
13614
13691
  });
13615
13692
  if (result.status === "cancelled") throw createAbortError$2();
13616
13693
  if (result.status === "failed") {
13617
- const message = result.error.message || "ACP turn failed";
13694
+ const message = redactRawAcpTargetInString(result.error.message || "ACP turn failed", this.target);
13618
13695
  if (result.error.retryable === false) throw new PermanentAgentError(message, result.error.code ?? "ACP_TURN_FAILED");
13619
13696
  throw new Error(message);
13620
13697
  }
@@ -13658,11 +13735,11 @@ var AcpAgent = class {
13658
13735
  handle,
13659
13736
  reason: "gnhf-shutdown"
13660
13737
  });
13661
- appendDebugLog("acp:close", { target: this.target });
13738
+ appendDebugLog("acp:close", { target: redactAcpTargetForLogs(this.target) });
13662
13739
  } catch (error) {
13663
13740
  appendDebugLog("acp:close-error", {
13664
- target: this.target,
13665
- error: serializeError(error)
13741
+ target: redactAcpTargetForLogs(this.target),
13742
+ error: serializeAcpErrorForLog(error, this.target)
13666
13743
  });
13667
13744
  }
13668
13745
  }
@@ -13677,7 +13754,7 @@ var AcpAgent = class {
13677
13754
  });
13678
13755
  this.runtime = runtime;
13679
13756
  appendDebugLog("acp:runtime:created", {
13680
- target: this.target,
13757
+ target: redactAcpTargetForLogs(this.target),
13681
13758
  sessionStateDir: this.sessionStateDir,
13682
13759
  cwd
13683
13760
  });
@@ -16343,7 +16420,7 @@ var Orchestrator = class extends EventEmitter {
16343
16420
  this.state.status = "running";
16344
16421
  this.emit("state", this.getState());
16345
16422
  appendDebugLog("orchestrator:start", {
16346
- agent: this.agent.name,
16423
+ agent: redactAgentSpecForLogs(this.agent.name),
16347
16424
  runId: this.runInfo.runId,
16348
16425
  startIteration: this.state.currentIteration,
16349
16426
  maxIterations: this.limits.maxIterations,
@@ -16502,7 +16579,7 @@ var Orchestrator = class extends EventEmitter {
16502
16579
  const agentStartedAt = Date.now();
16503
16580
  appendDebugLog("agent:run:start", {
16504
16581
  iteration: this.state.currentIteration,
16505
- agent: this.agent.name,
16582
+ agent: redactAgentSpecForLogs(this.agent.name),
16506
16583
  logPath
16507
16584
  });
16508
16585
  try {
@@ -17548,7 +17625,7 @@ const GNHF_REEXEC_STDIN_PROMPT_FILE = "GNHF_REEXEC_STDIN_PROMPT_FILE";
17548
17625
  const GNHF_REEXEC_STDIN_PROMPT_DIR_PREFIX = "gnhf-stdin-";
17549
17626
  const GNHF_REEXEC_STDIN_PROMPT_FILENAME = "prompt.txt";
17550
17627
  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)`;
17628
+ 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
17629
  var PromptSignalError = class extends Error {
17553
17630
  constructor(signal) {
17554
17631
  super(signal);
@@ -17576,6 +17653,26 @@ function isAgentName(name) {
17576
17653
  function getNativeAgentName(spec) {
17577
17654
  return isAgentName(spec) ? spec : void 0;
17578
17655
  }
17656
+ function getTelemetryAgent(spec) {
17657
+ return redactAgentSpecForLogs(spec);
17658
+ }
17659
+ function redactDebugArgs(args) {
17660
+ const redacted = [...args];
17661
+ for (let i = 0; i < redacted.length; i += 1) {
17662
+ const arg = redacted[i];
17663
+ if (arg === "--") break;
17664
+ if (arg === "--agent") {
17665
+ const next = redacted[i + 1];
17666
+ if (next !== void 0) {
17667
+ redacted[i + 1] = redactAgentSpecForLogs(next);
17668
+ i += 1;
17669
+ }
17670
+ continue;
17671
+ }
17672
+ if (arg?.startsWith("--agent=")) redacted[i] = `--agent=${redactAgentSpecForLogs(arg.slice(8))}`;
17673
+ }
17674
+ return redacted;
17675
+ }
17579
17676
  function buildSchemaOptions(stopWhen, commitMessage) {
17580
17677
  const commitFields = getCommitMessageSchemaFields(commitMessage);
17581
17678
  return {
@@ -17797,7 +17894,7 @@ function readReexecStdinPrompt(env) {
17797
17894
  }
17798
17895
  }
17799
17896
  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) => {
17897
+ 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
17898
  if (options.mock) {
17802
17899
  const mock = new MockOrchestrator();
17803
17900
  enterAltScreen();
@@ -17939,16 +18036,17 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
17939
18036
  }
17940
18037
  }
17941
18038
  const runMode = options.worktree ? "worktree" : startIteration > 0 ? "resume" : "new";
18039
+ const telemetryAgent = getTelemetryAgent(config.agent);
17942
18040
  telemetry.pageview("/run", {
17943
- agent: config.agent,
18041
+ agent: telemetryAgent,
17944
18042
  mode: runMode
17945
18043
  });
17946
18044
  initDebugLog(runInfo.logPath);
17947
18045
  appendDebugLog("run:start", {
17948
- args: process$1.argv.slice(2),
18046
+ args: redactDebugArgs(process$1.argv.slice(2)),
17949
18047
  runId: runInfo.runId,
17950
18048
  runDir: runInfo.runDir,
17951
- agent: config.agent,
18049
+ agent: redactAgentSpecForLogs(config.agent),
17952
18050
  promptLength: prompt.length,
17953
18051
  promptFromStdin,
17954
18052
  startIteration,
@@ -18049,7 +18147,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18049
18147
  worktreePath
18050
18148
  });
18051
18149
  telemetry.track("run", {
18052
- agent: config.agent,
18150
+ agent: telemetryAgent,
18053
18151
  mode: runMode,
18054
18152
  status: finalState.status,
18055
18153
  signal: shutdownSignal ?? void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
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",