gnhf 0.1.23 → 0.1.24

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 +14 -10
  2. package/dist/cli.mjs +133 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -139,7 +139,7 @@ npm link
139
139
  - **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; 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
140
140
  - **Shared memory** — the agent reads `notes.md` (built up from prior iterations) to communicate across iterations
141
141
  - **Local run metadata** — gnhf stores prompt, notes, and resume metadata under `.gnhf/runs/` and ignores it locally, so your branch only contains intentional work
142
- - **Resume support** — run `gnhf` while on an existing `gnhf/` branch to pick up where a previous run left off
142
+ - **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 overwrite the saved prompt, start a new branch, or quit
143
143
 
144
144
  ### Worktree Mode
145
145
 
@@ -165,17 +165,19 @@ Pass `--worktree` to run each agent in an isolated [git worktree](https://git-sc
165
165
  | `echo "<prompt>" \| gnhf` | Pipe prompt via stdin |
166
166
  | `cat prd.md \| gnhf` | Pipe a large spec or PRD via stdin |
167
167
 
168
+ If you run `gnhf` on an existing `gnhf/` branch with a different prompt, gnhf asks whether to overwrite the saved prompt, start a new branch, or quit. When the prompt came from stdin, that confirmation is read from the controlling terminal, so it must be available.
169
+
168
170
  ### Flags
169
171
 
170
- | Flag | Description | Default |
171
- | ------------------------ | --------------------------------------------------------------------- | ---------------------- |
172
- | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, or `opencode`) | config file (`claude`) |
173
- | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
174
- | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
175
- | `--stop-when <cond>` | End the loop when the agent reports this natural-language condition is met | unlimited |
176
- | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
177
- | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
178
- | `--version` | Show version | |
172
+ | Flag | Description | Default |
173
+ | ------------------------ | -------------------------------------------------------------------------- | ---------------------- |
174
+ | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, or `opencode`) | config file (`claude`) |
175
+ | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
176
+ | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
177
+ | `--stop-when <cond>` | End the loop when the agent reports this natural-language condition is met | unlimited |
178
+ | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
179
+ | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
180
+ | `--version` | Show version | |
179
181
 
180
182
  ## Configuration
181
183
 
@@ -249,6 +251,8 @@ Including a snippet of `gnhf.log` is the single most useful thing you can attach
249
251
 
250
252
  ## Development
251
253
 
254
+ If you want to contribute changes back to this repo, see [`CONTRIBUTING.md`](./CONTRIBUTING.md). Human-authored PRs targeting `main` must be opened via `git push no-mistakes` so the required `Require no-mistakes` check passes.
255
+
252
256
  ```sh
253
257
  npm run build # Build with tsdown
254
258
  npm run dev # Watch mode
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { appendFileSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
2
+ import { appendFileSync, closeSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import process$1 from "node:process";
@@ -427,10 +427,8 @@ function removeWorktree(baseCwd, worktreePath) {
427
427
  }
428
428
  //#endregion
429
429
  //#region src/core/agents/types.ts
430
- const AGENT_OUTPUT_SCHEMA = {
431
- type: "object",
432
- additionalProperties: false,
433
- properties: {
430
+ function buildAgentOutputSchema(opts) {
431
+ const properties = {
434
432
  success: { type: "boolean" },
435
433
  summary: { type: "string" },
436
434
  key_changes_made: {
@@ -440,21 +438,30 @@ const AGENT_OUTPUT_SCHEMA = {
440
438
  key_learnings: {
441
439
  type: "array",
442
440
  items: { type: "string" }
443
- },
444
- should_fully_stop: { type: "boolean" }
445
- },
446
- required: [
441
+ }
442
+ };
443
+ const required = [
447
444
  "success",
448
445
  "summary",
449
446
  "key_changes_made",
450
447
  "key_learnings"
451
- ]
452
- };
448
+ ];
449
+ if (opts.includeStopField) {
450
+ properties.should_fully_stop = { type: "boolean" };
451
+ required.push("should_fully_stop");
452
+ }
453
+ return {
454
+ type: "object",
455
+ additionalProperties: false,
456
+ properties,
457
+ required
458
+ };
459
+ }
453
460
  //#endregion
454
461
  //#region src/core/run.ts
455
462
  const LOG_FILENAME = "gnhf.log";
456
- function writeSchemaFile(schemaPath) {
457
- writeFileSync(schemaPath, JSON.stringify(AGENT_OUTPUT_SCHEMA, null, 2), "utf-8");
463
+ function writeSchemaFile(schemaPath, includeStopField) {
464
+ writeFileSync(schemaPath, JSON.stringify(buildAgentOutputSchema({ includeStopField }), null, 2), "utf-8");
458
465
  }
459
466
  function ensureRunMetadataIgnored(cwd) {
460
467
  const excludePath = execFileSync("git", [
@@ -474,7 +481,7 @@ function ensureRunMetadataIgnored(cwd) {
474
481
  appendFileSync(resolved, `${content.length > 0 && !content.endsWith("\n") ? "\n" : ""}${entry}\n`, "utf-8");
475
482
  } else writeFileSync(resolved, `${entry}\n`, "utf-8");
476
483
  }
477
- function setupRun(runId, prompt, baseCommit, cwd) {
484
+ function setupRun(runId, prompt, baseCommit, cwd, schemaOptions) {
478
485
  ensureRunMetadataIgnored(cwd);
479
486
  const runDir = join(cwd, ".gnhf", "runs", runId);
480
487
  mkdirSync(runDir, { recursive: true });
@@ -483,7 +490,7 @@ function setupRun(runId, prompt, baseCommit, cwd) {
483
490
  const notesPath = join(runDir, "notes.md");
484
491
  writeFileSync(notesPath, `# gnhf run: ${runId}\n\nObjective: ${prompt}\n\n## Iteration Log\n`, "utf-8");
485
492
  const schemaPath = join(runDir, "output-schema.json");
486
- writeSchemaFile(schemaPath);
493
+ writeSchemaFile(schemaPath, schemaOptions.includeStopField);
487
494
  const logPath = join(runDir, LOG_FILENAME);
488
495
  const baseCommitPath = join(runDir, "base-commit");
489
496
  const hasStoredBaseCommit = existsSync(baseCommitPath);
@@ -500,13 +507,13 @@ function setupRun(runId, prompt, baseCommit, cwd) {
500
507
  baseCommitPath
501
508
  };
502
509
  }
503
- function resumeRun(runId, cwd) {
510
+ function resumeRun(runId, cwd, schemaOptions) {
504
511
  const runDir = join(cwd, ".gnhf", "runs", runId);
505
512
  if (!existsSync(runDir)) throw new Error(`Run directory not found: ${runDir}`);
506
513
  const promptPath = join(runDir, "prompt.md");
507
514
  const notesPath = join(runDir, "notes.md");
508
515
  const schemaPath = join(runDir, "output-schema.json");
509
- writeSchemaFile(schemaPath);
516
+ writeSchemaFile(schemaPath, schemaOptions.includeStopField);
510
517
  const logPath = join(runDir, LOG_FILENAME);
511
518
  const baseCommitPath = join(runDir, "base-commit");
512
519
  return {
@@ -1068,7 +1075,7 @@ function terminateClaudeProcess(child, platform) {
1068
1075
  }
1069
1076
  child.kill("SIGTERM");
1070
1077
  }
1071
- function buildClaudeArgs(prompt, extraArgs) {
1078
+ function buildClaudeArgs(prompt, schema, extraArgs) {
1072
1079
  const userArgs = extraArgs ?? [];
1073
1080
  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="));
1074
1081
  return [
@@ -1079,7 +1086,7 @@ function buildClaudeArgs(prompt, extraArgs) {
1079
1086
  "--output-format",
1080
1087
  "stream-json",
1081
1088
  "--json-schema",
1082
- JSON.stringify(AGENT_OUTPUT_SCHEMA),
1089
+ JSON.stringify(schema),
1083
1090
  ...userSpecifiedPermissionMode ? [] : ["--dangerously-skip-permissions"]
1084
1091
  ];
1085
1092
  }
@@ -1102,17 +1109,19 @@ var ClaudeAgent = class {
1102
1109
  bin;
1103
1110
  extraArgs;
1104
1111
  platform;
1112
+ schema;
1105
1113
  constructor(binOrDeps = {}) {
1106
1114
  const deps = typeof binOrDeps === "string" ? { bin: binOrDeps } : binOrDeps;
1107
1115
  this.bin = deps.bin ?? "claude";
1108
1116
  this.extraArgs = deps.extraArgs;
1109
1117
  this.platform = deps.platform ?? process.platform;
1118
+ this.schema = deps.schema ?? buildAgentOutputSchema({ includeStopField: false });
1110
1119
  }
1111
1120
  run(prompt, cwd, options) {
1112
1121
  const { onUsage, onMessage, signal, logPath } = options ?? {};
1113
1122
  return new Promise((resolve, reject) => {
1114
1123
  const logStream = logPath ? createWriteStream(logPath) : null;
1115
- const child = spawn(this.bin, buildClaudeArgs(prompt, this.extraArgs), {
1124
+ const child = spawn(this.bin, buildClaudeArgs(prompt, this.schema, this.extraArgs), {
1116
1125
  cwd,
1117
1126
  shell: shouldUseWindowsShell$2(this.bin, this.platform),
1118
1127
  stdio: [
@@ -1339,25 +1348,27 @@ const BLANKET_PERMISSION_RULESET = [{
1339
1348
  pattern: "*",
1340
1349
  action: "allow"
1341
1350
  }];
1342
- const STRUCTURED_OUTPUT_FORMAT = {
1343
- type: "json_schema",
1344
- schema: AGENT_OUTPUT_SCHEMA,
1345
- retryCount: 1
1346
- };
1351
+ function buildStructuredOutputFormat(schema) {
1352
+ return {
1353
+ type: "json_schema",
1354
+ schema,
1355
+ retryCount: 1
1356
+ };
1357
+ }
1347
1358
  function buildOpencodeChildEnv() {
1348
1359
  const env = { ...process.env };
1349
1360
  delete env.OPENCODE_SERVER_USERNAME;
1350
1361
  delete env.OPENCODE_SERVER_PASSWORD;
1351
1362
  return env;
1352
1363
  }
1353
- function buildPrompt(prompt) {
1364
+ function buildPrompt(prompt, schema) {
1354
1365
  return [
1355
1366
  prompt,
1356
1367
  "",
1357
1368
  "When you finish, reply with only valid JSON.",
1358
1369
  "Do not wrap the JSON in markdown fences.",
1359
1370
  "Do not include any prose before or after the JSON.",
1360
- `The JSON must match this schema exactly: ${JSON.stringify(AGENT_OUTPUT_SCHEMA)}`
1371
+ `The JSON must match this schema exactly: ${JSON.stringify(schema)}`
1361
1372
  ].join("\n");
1362
1373
  }
1363
1374
  /**
@@ -1452,6 +1463,7 @@ var OpenCodeAgent = class {
1452
1463
  getPortFn;
1453
1464
  killProcessFn;
1454
1465
  platform;
1466
+ schema;
1455
1467
  spawnFn;
1456
1468
  server = null;
1457
1469
  closingPromise = null;
@@ -1462,6 +1474,7 @@ var OpenCodeAgent = class {
1462
1474
  this.getPortFn = deps.getPort ?? getAvailablePort$1;
1463
1475
  this.killProcessFn = deps.killProcess ?? process.kill.bind(process);
1464
1476
  this.platform = deps.platform ?? process.platform;
1477
+ this.schema = deps.schema ?? buildAgentOutputSchema({ includeStopField: false });
1465
1478
  this.spawnFn = deps.spawn ?? spawn;
1466
1479
  }
1467
1480
  async run(prompt, cwd, options) {
@@ -1487,7 +1500,7 @@ var OpenCodeAgent = class {
1487
1500
  try {
1488
1501
  const server = await this.ensureServer(cwd, runController.signal);
1489
1502
  sessionId = await this.createSession(server, cwd, runController.signal);
1490
- const result = await this.streamMessage(server, sessionId, buildPrompt(prompt), runController.signal, logStream, onUsage, onMessage);
1503
+ const result = await this.streamMessage(server, sessionId, buildPrompt(prompt, this.schema), runController.signal, logStream, onUsage, onMessage);
1491
1504
  appendDebugLog("opencode:run:end", {
1492
1505
  sessionId,
1493
1506
  elapsedMs: Date.now() - runStartedAt,
@@ -1764,7 +1777,7 @@ var OpenCodeAgent = class {
1764
1777
  type: "text",
1765
1778
  text: prompt
1766
1779
  }],
1767
- format: STRUCTURED_OUTPUT_FORMAT
1780
+ format: buildStructuredOutputFormat(this.schema)
1768
1781
  },
1769
1782
  signal
1770
1783
  });
@@ -2757,11 +2770,13 @@ function withTimeoutSignal(signal, timeoutMs) {
2757
2770
  }
2758
2771
  //#endregion
2759
2772
  //#region src/core/agents/factory.ts
2760
- function createAgent(name, runInfo, pathOverride, agentArgsOverride) {
2773
+ function createAgent(name, runInfo, pathOverride, agentArgsOverride, options) {
2774
+ const schema = buildAgentOutputSchema({ includeStopField: options.includeStopField });
2761
2775
  switch (name) {
2762
2776
  case "claude": return new ClaudeAgent({
2763
2777
  bin: pathOverride,
2764
- extraArgs: agentArgsOverride
2778
+ extraArgs: agentArgsOverride,
2779
+ schema
2765
2780
  });
2766
2781
  case "codex": return new CodexAgent(runInfo.schemaPath, {
2767
2782
  bin: pathOverride,
@@ -2769,7 +2784,8 @@ function createAgent(name, runInfo, pathOverride, agentArgsOverride) {
2769
2784
  });
2770
2785
  case "opencode": return new OpenCodeAgent({
2771
2786
  bin: pathOverride,
2772
- extraArgs: agentArgsOverride
2787
+ extraArgs: agentArgsOverride,
2788
+ schema
2773
2789
  });
2774
2790
  case "rovodev": return new RovoDevAgent(runInfo.schemaPath, {
2775
2791
  bin: pathOverride,
@@ -2787,7 +2803,7 @@ function buildIterationPrompt(params) {
2787
2803
  "- key_learnings: an array of new learnings that were surprising, weren't captured by previous notes and would be informative for future iterations"
2788
2804
  ];
2789
2805
  if (params.stopWhen !== void 0) outputFields.push("- should_fully_stop: set to true ONLY when the stop condition below is fully met and the entire loop should end. default to false");
2790
- const stopConditionSection = params.stopWhen !== void 0 ? `\n\n## Stop Condition\n\nThe user has configured a condition to end the loop: ${params.stopWhen}\nIf this condition is fully met after this iteration's work, set should_fully_stop=true in your output. Otherwise set it to false (or omit it).` : "";
2806
+ const stopConditionSection = params.stopWhen !== void 0 ? `\n\n## Stop Condition\n\nThe user has configured a condition to end the loop: ${params.stopWhen}\nIf this condition is fully met after this iteration's work, set should_fully_stop=true in your output. Otherwise set it to false.` : "";
2791
2807
  return `You are working autonomously towards an objective given below.
2792
2808
  This is iteration ${params.n}. Each iteration aims to make an incremental step forward, not to complete the entire objective.
2793
2809
 
@@ -3998,6 +4014,12 @@ const GNHF_REEXEC_STDIN_PROMPT = "GNHF_REEXEC_STDIN_PROMPT";
3998
4014
  const GNHF_REEXEC_STDIN_PROMPT_FILE = "GNHF_REEXEC_STDIN_PROMPT_FILE";
3999
4015
  const GNHF_REEXEC_STDIN_PROMPT_DIR_PREFIX = "gnhf-stdin-";
4000
4016
  const GNHF_REEXEC_STDIN_PROMPT_FILENAME = "prompt.txt";
4017
+ var PromptSignalError = class extends Error {
4018
+ constructor(signal) {
4019
+ super(signal);
4020
+ this.signal = signal;
4021
+ }
4022
+ };
4001
4023
  function parseNonNegativeInteger(value) {
4002
4024
  if (!/^\d+$/.test(value)) throw new InvalidArgumentError("must be a non-negative integer");
4003
4025
  const parsed = Number.parseInt(value, 10);
@@ -4013,15 +4035,15 @@ function humanizeErrorMessage(message) {
4013
4035
  if (message.includes("not a git repository")) return "This command must be run inside a Git repository. Change into a repo or run \"git init\" first.";
4014
4036
  return message;
4015
4037
  }
4016
- function initializeNewBranch(prompt, cwd) {
4038
+ function initializeNewBranch(prompt, cwd, schemaOptions) {
4017
4039
  ensureCleanWorkingTree(cwd);
4018
4040
  const baseCommit = getHeadCommit(cwd);
4019
4041
  const branchName = slugifyPrompt(prompt);
4020
4042
  createBranch(branchName, cwd);
4021
4043
  const runId = branchName.split("/")[1];
4022
- return setupRun(runId, prompt, baseCommit, cwd);
4044
+ return setupRun(runId, prompt, baseCommit, cwd, schemaOptions);
4023
4045
  }
4024
- function initializeWorktreeRun(prompt, cwd) {
4046
+ function initializeWorktreeRun(prompt, cwd, schemaOptions) {
4025
4047
  const repoRoot = getRepoRootDir(cwd);
4026
4048
  const baseCommit = getHeadCommit(cwd);
4027
4049
  const branchName = slugifyPrompt(prompt);
@@ -4029,19 +4051,80 @@ function initializeWorktreeRun(prompt, cwd) {
4029
4051
  const worktreePath = join(dirname(repoRoot), `${basename(repoRoot)}-gnhf-worktrees`, runId);
4030
4052
  createWorktree(repoRoot, worktreePath, branchName);
4031
4053
  return {
4032
- runInfo: setupRun(runId, prompt, baseCommit, worktreePath),
4054
+ runInfo: setupRun(runId, prompt, baseCommit, worktreePath, schemaOptions),
4033
4055
  worktreePath,
4034
4056
  effectiveCwd: worktreePath
4035
4057
  };
4036
4058
  }
4037
- function ask(question) {
4038
- const rl = createInterface({
4059
+ function openPromptTerminal() {
4060
+ if (process$1.stdin.isTTY) return {
4039
4061
  input: process$1.stdin,
4040
- output: process$1.stderr
4062
+ output: process$1.stderr,
4063
+ cleanup: () => {}
4064
+ };
4065
+ const inputPath = process$1.platform === "win32" ? "CONIN$" : "/dev/tty";
4066
+ const outputPath = process$1.platform === "win32" ? "CONOUT$" : "/dev/tty";
4067
+ const inputFd = openSync(inputPath, "r");
4068
+ try {
4069
+ const outputFd = openSync(outputPath, "w");
4070
+ try {
4071
+ const input = createReadStream("", {
4072
+ autoClose: true,
4073
+ fd: inputFd
4074
+ });
4075
+ const output = createWriteStream("", {
4076
+ autoClose: true,
4077
+ fd: outputFd
4078
+ });
4079
+ return {
4080
+ input,
4081
+ output,
4082
+ cleanup: () => {
4083
+ input.destroy();
4084
+ output.destroy();
4085
+ }
4086
+ };
4087
+ } catch (error) {
4088
+ closeSync(outputFd);
4089
+ throw error;
4090
+ }
4091
+ } catch (error) {
4092
+ closeSync(inputFd);
4093
+ throw error;
4094
+ }
4095
+ }
4096
+ function ask(question, closeMessage, unavailableMessage) {
4097
+ let terminal;
4098
+ try {
4099
+ terminal = openPromptTerminal();
4100
+ } catch {
4101
+ throw new Error(unavailableMessage);
4102
+ }
4103
+ const rl = createInterface({
4104
+ input: terminal.input,
4105
+ output: terminal.output
4041
4106
  });
4042
- return new Promise((resolve) => {
4107
+ return new Promise((resolve, reject) => {
4108
+ const handleClose = () => {
4109
+ terminal.cleanup();
4110
+ rl.off("close", handleClose);
4111
+ rl.off("SIGINT", handleSigInt);
4112
+ reject(new Error(closeMessage));
4113
+ };
4114
+ const handleSigInt = () => {
4115
+ rl.off("close", handleClose);
4116
+ rl.off("SIGINT", handleSigInt);
4117
+ rl.close();
4118
+ terminal.cleanup();
4119
+ reject(new PromptSignalError("SIGINT"));
4120
+ };
4121
+ rl.once("close", handleClose);
4122
+ rl.once("SIGINT", handleSigInt);
4043
4123
  rl.question(question, (answer) => {
4124
+ rl.off("close", handleClose);
4125
+ rl.off("SIGINT", handleSigInt);
4044
4126
  rl.close();
4127
+ terminal.cleanup();
4045
4128
  resolve(answer.trim().toLowerCase());
4046
4129
  });
4047
4130
  });
@@ -4135,6 +4218,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4135
4218
  let worktreeCleanup = null;
4136
4219
  const currentBranch = getCurrentBranch(cwd);
4137
4220
  const onGnhfBranch = currentBranch.startsWith("gnhf/");
4221
+ const schemaOptions = { includeStopField: options.stopWhen !== void 0 };
4138
4222
  let runInfo;
4139
4223
  let startIteration = 0;
4140
4224
  if (options.worktree) {
@@ -4146,7 +4230,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4146
4230
  console.error("Cannot use --worktree from a gnhf branch. Switch to the base branch first.");
4147
4231
  process$1.exit(1);
4148
4232
  }
4149
- const wt = initializeWorktreeRun(prompt, cwd);
4233
+ const wt = initializeWorktreeRun(prompt, cwd, schemaOptions);
4150
4234
  runInfo = wt.runInfo;
4151
4235
  effectiveCwd = wt.effectiveCwd;
4152
4236
  worktreePath = wt.worktreePath;
@@ -4161,18 +4245,18 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4161
4245
  });
4162
4246
  } else if (onGnhfBranch) {
4163
4247
  const existingRunId = currentBranch.slice(5);
4164
- const existing = resumeRun(existingRunId, cwd);
4248
+ const existing = resumeRun(existingRunId, cwd, schemaOptions);
4165
4249
  const existingPrompt = readFileSync(existing.promptPath, "utf-8");
4166
4250
  if (!prompt || prompt === existingPrompt) {
4167
4251
  prompt = existingPrompt;
4168
4252
  runInfo = existing;
4169
4253
  startIteration = getLastIterationNumber(existing);
4170
4254
  } else {
4171
- const answer = await ask(`You are on gnhf branch "${currentBranch}".\n (o) Overwrite current run with new prompt\n (n) Start a new branch on top of this one\n (q) Quit\nChoose [o/n/q]: `);
4255
+ const answer = await ask(`You are on gnhf branch "${currentBranch}".\n (o) Overwrite current run with new prompt\n (n) Start a new branch on top of this one\n (q) Quit\nChoose [o/n/q]: `, "The overwrite prompt closed before a choice was entered. Re-run gnhf from an interactive terminal and choose o, n, or q.", "Cannot show the overwrite prompt because stdin is not interactive. Re-run gnhf from an interactive terminal and choose o, n, or q.");
4172
4256
  if (answer === "o") {
4173
4257
  ensureCleanWorkingTree(cwd);
4174
- runInfo = setupRun(existingRunId, prompt, existing.baseCommit, cwd);
4175
- } else if (answer === "n") runInfo = initializeNewBranch(prompt, cwd);
4258
+ runInfo = setupRun(existingRunId, prompt, existing.baseCommit, cwd, schemaOptions);
4259
+ } else if (answer === "n") runInfo = initializeNewBranch(prompt, cwd, schemaOptions);
4176
4260
  else process$1.exit(0);
4177
4261
  }
4178
4262
  } else {
@@ -4180,7 +4264,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4180
4264
  program.help();
4181
4265
  return;
4182
4266
  }
4183
- runInfo = initializeNewBranch(prompt, cwd);
4267
+ runInfo = initializeNewBranch(prompt, cwd, schemaOptions);
4184
4268
  }
4185
4269
  let sleepPreventionCleanup = null;
4186
4270
  if (config.preventSleep) {
@@ -4217,7 +4301,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4217
4301
  nodeVersion: process$1.version,
4218
4302
  gnhfVersion: packageVersion
4219
4303
  });
4220
- const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent], config.agentArgsOverride?.[config.agent]), runInfo, prompt, effectiveCwd, startIteration, {
4304
+ const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent], config.agentArgsOverride?.[config.agent], schemaOptions), runInfo, prompt, effectiveCwd, startIteration, {
4221
4305
  maxIterations: options.maxIterations,
4222
4306
  maxTokens: options.maxTokens,
4223
4307
  stopWhen: options.stopWhen
@@ -4301,6 +4385,7 @@ function die(message) {
4301
4385
  try {
4302
4386
  await program.parseAsync();
4303
4387
  } catch (err) {
4388
+ if (err instanceof PromptSignalError) process$1.exit(getSignalExitCode(err.signal));
4304
4389
  die(err instanceof Error ? err.message : String(err));
4305
4390
  }
4306
4391
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {