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.
- package/README.md +2 -1
- package/dist/cli.mjs +28 -11
- 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**
|
|
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
|
-
|
|
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();
|