gnhf 0.1.22 → 0.1.23

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 +2 -1
  2. package/dist/cli.mjs +28 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -136,7 +136,7 @@ npm link
136
136
  ```
137
137
 
138
138
  - **Incremental commits** — each successful iteration is a separate git commit, so you can cherry-pick or revert individual changes
139
- - **Runtime caps** `--max-iterations` stops before the next iteration begins, while `--max-tokens` can abort mid-iteration once reported usage reaches the cap; 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
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
142
  - **Resume support** — run `gnhf` while on an existing `gnhf/` branch to pick up where a previous run left off
@@ -172,6 +172,7 @@ Pass `--worktree` to run each agent in an isolated [git worktree](https://git-sc
172
172
  | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, or `opencode`) | config file (`claude`) |
173
173
  | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
174
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 |
175
176
  | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
176
177
  | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
177
178
  | `--version` | Show version | |
package/dist/cli.mjs CHANGED
@@ -440,7 +440,8 @@ const AGENT_OUTPUT_SCHEMA = {
440
440
  key_learnings: {
441
441
  type: "array",
442
442
  items: { type: "string" }
443
- }
443
+ },
444
+ should_fully_stop: { type: "boolean" }
444
445
  },
445
446
  required: [
446
447
  "success",
@@ -2779,6 +2780,14 @@ function createAgent(name, runInfo, pathOverride, agentArgsOverride) {
2779
2780
  //#endregion
2780
2781
  //#region src/templates/iteration-prompt.ts
2781
2782
  function buildIterationPrompt(params) {
2783
+ const outputFields = [
2784
+ "- success: whether you were able to make a meaningful contribution that got us closer towards the objective. setting this to false means any code change you made should be discarded",
2785
+ "- summary: a concise one-sentence summary of the accomplishment in this iteration",
2786
+ "- key_changes_made: an array of descriptions for key changes you made. don't group this by file - group by logical units of work. don't describe activities - describe material outcomes",
2787
+ "- key_learnings: an array of new learnings that were surprising, weren't captured by previous notes and would be informative for future iterations"
2788
+ ];
2789
+ 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).` : "";
2782
2791
  return `You are working autonomously towards an objective given below.
2783
2792
  This is iteration ${params.n}. Each iteration aims to make an incremental step forward, not to complete the entire objective.
2784
2793
 
@@ -2792,10 +2801,7 @@ This is iteration ${params.n}. Each iteration aims to make an incremental step f
2792
2801
 
2793
2802
  ## Output
2794
2803
 
2795
- - success: whether you were able to make a meaningful contribution that got us closer towards the objective. setting this to false means any code change you made should be discarded
2796
- - summary: a concise one-sentence summary of the accomplishment in this iteration
2797
- - key_changes_made: an array of descriptions for key changes you made. don't group this by file - group by logical units of work. don't describe activities - describe material outcomes
2798
- - key_learnings: an array of new learnings that were surprising, weren't captured by previous notes and would be informative for future iterations
2804
+ ${outputFields.join("\n")}${stopConditionSection}
2799
2805
 
2800
2806
  ## Objective
2801
2807
 
@@ -2910,7 +2916,8 @@ var Orchestrator = class extends EventEmitter {
2910
2916
  const iterationPrompt = buildIterationPrompt({
2911
2917
  n: this.state.currentIteration,
2912
2918
  runId: this.runInfo.runId,
2913
- prompt: this.prompt
2919
+ prompt: this.prompt,
2920
+ stopWhen: this.limits.stopWhen
2914
2921
  });
2915
2922
  appendDebugLog("iteration:start", {
2916
2923
  iteration: this.state.currentIteration,
@@ -2957,6 +2964,10 @@ var Orchestrator = class extends EventEmitter {
2957
2964
  totalOutputTokens: this.state.totalOutputTokens,
2958
2965
  commitCount: this.state.commitCount
2959
2966
  });
2967
+ if (this.limits.stopWhen !== void 0 && result.shouldFullyStop) {
2968
+ this.abort("stop condition met");
2969
+ break;
2970
+ }
2960
2971
  const postIterationAbortReason = this.getPostIterationAbortReason();
2961
2972
  if (postIterationAbortReason) {
2962
2973
  this.abort(postIterationAbortReason);
@@ -3053,13 +3064,16 @@ var Orchestrator = class extends EventEmitter {
3053
3064
  cacheCreationTokens: result.usage.cacheCreationTokens
3054
3065
  });
3055
3066
  if (this.stopRequested) return { type: "stopped" };
3067
+ const shouldFullyStop = result.output.should_fully_stop === true;
3056
3068
  if (result.output.success) return {
3057
3069
  type: "completed",
3058
- record: this.recordSuccess(result.output)
3070
+ record: this.recordSuccess(result.output),
3071
+ shouldFullyStop
3059
3072
  };
3060
3073
  return {
3061
3074
  type: "completed",
3062
- record: this.recordFailure(`[FAIL] ${result.output.summary}`, result.output.summary, toStringArray(result.output.key_learnings))
3075
+ record: this.recordFailure(`[FAIL] ${result.output.summary}`, result.output.summary, toStringArray(result.output.key_learnings)),
3076
+ shouldFullyStop
3063
3077
  };
3064
3078
  } catch (err) {
3065
3079
  const elapsedMs = Date.now() - agentStartedAt;
@@ -3090,7 +3104,8 @@ var Orchestrator = class extends EventEmitter {
3090
3104
  const summary = err instanceof Error ? err.message : String(err);
3091
3105
  return {
3092
3106
  type: "completed",
3093
- record: this.recordFailure(`[ERROR] ${summary}`, summary, [])
3107
+ record: this.recordFailure(`[ERROR] ${summary}`, summary, []),
3108
+ shouldFullyStop: false
3094
3109
  };
3095
3110
  } finally {
3096
3111
  this.activeAbortController = null;
@@ -4081,7 +4096,7 @@ function readReexecStdinPrompt(env) {
4081
4096
  }
4082
4097
  }
4083
4098
  const program = new Command();
4084
- 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 (claude, codex, rovodev, or opencode)").option("--max-iterations <n>", "Abort after N total iterations", parseNonNegativeInteger).option("--max-tokens <n>", "Abort after N total input+output tokens", parseNonNegativeInteger).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) => {
4099
+ 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 (claude, codex, rovodev, or opencode)").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 the loop when the agent reports this natural-language condition is met").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) => {
4085
4100
  if (options.mock) {
4086
4101
  const mock = new MockOrchestrator();
4087
4102
  enterAltScreen();
@@ -4193,6 +4208,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4193
4208
  startIteration,
4194
4209
  maxIterations: options.maxIterations,
4195
4210
  maxTokens: options.maxTokens,
4211
+ stopWhen: options.stopWhen,
4196
4212
  preventSleep: config.preventSleep,
4197
4213
  agentArgsOverride: config.agentArgsOverride?.[config.agent],
4198
4214
  worktree: options.worktree,
@@ -4203,7 +4219,8 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
4203
4219
  });
4204
4220
  const orchestrator = new Orchestrator(config, createAgent(config.agent, runInfo, config.agentPathOverride[config.agent], config.agentArgsOverride?.[config.agent]), runInfo, prompt, effectiveCwd, startIteration, {
4205
4221
  maxIterations: options.maxIterations,
4206
- maxTokens: options.maxTokens
4222
+ maxTokens: options.maxTokens,
4223
+ stopWhen: options.stopWhen
4207
4224
  });
4208
4225
  let shutdownSignal = null;
4209
4226
  enterAltScreen();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {