@nanhara/hara 0.124.3 → 0.124.4
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/CHANGELOG.md +8 -0
- package/dist/agent/loop.js +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.124.4 — 2026-07-18 — deadline-aware task checkpoints
|
|
9
|
+
|
|
10
|
+
- **The 80% run-budget warning now reaches the Agent, not only the user.** Before the next model turn,
|
|
11
|
+
Hara injects a hidden checkpoint instruction to finish the current atomic step, persist artifacts,
|
|
12
|
+
update the checklist, and defer a new multi-minute stage to `/continue`. A video/image batch, full
|
|
13
|
+
validation, preview, render, install, or deployment should no longer begin with too little turn budget
|
|
14
|
+
and then appear to stop mysteriously at the 30-minute safety pause.
|
|
15
|
+
|
|
8
16
|
## 0.124.3 — 2026-07-17 — project-aware resume and runtime hardening
|
|
9
17
|
|
|
10
18
|
- **Saved sessions now resume in the project they belong to.** `hara resume <id>` validates the persisted
|
package/dist/agent/loop.js
CHANGED
|
@@ -149,6 +149,13 @@ function composeSystem(cwd, projectContext, override, memory, continuationSessio
|
|
|
149
149
|
}
|
|
150
150
|
const RUN_STOPPED = Symbol("agent-run-stopped");
|
|
151
151
|
const REPEATED_FAILURE_LIMIT = 3;
|
|
152
|
+
export function deadlineCheckpointReminder(timeoutMs) {
|
|
153
|
+
return (`Turn budget checkpoint: about 20% remains before the ${formatAgentDuration(timeoutMs)} safety pause. ` +
|
|
154
|
+
"Stop expanding scope. Finish only the current atomic step, persist any usable artifact, update todo_write, " +
|
|
155
|
+
"and reply with the completed checkpoint plus the next exact step. Do not start another generation batch, " +
|
|
156
|
+
"install, full validation suite, preview, render, deployment, or other multi-minute stage in this turn. " +
|
|
157
|
+
"The user can run /continue to start that next stage with a fresh bounded budget.");
|
|
158
|
+
}
|
|
152
159
|
function showRunNotice(opts, message, critical = false) {
|
|
153
160
|
if (opts.quiet)
|
|
154
161
|
return;
|
|
@@ -164,6 +171,13 @@ function showRunNotice(opts, message, critical = false) {
|
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
173
|
}
|
|
174
|
+
function requestRunCheckpoint(opts, life) {
|
|
175
|
+
if (life.disposed || life.checkpointDue || life.signal.aborted)
|
|
176
|
+
return;
|
|
177
|
+
life.checkpointDue = true;
|
|
178
|
+
const remainingMs = Math.max(0, life.timeoutMs - (Date.now() - life.startedAt));
|
|
179
|
+
showRunNotice(opts, `⚠ agent turn nearing its safety pause: ${formatAgentDuration(remainingMs)} remains. The agent will be told to finish the current atomic step and checkpoint; use \`/continue\` for the next expensive stage.`);
|
|
180
|
+
}
|
|
167
181
|
function warnRun(opts, life) {
|
|
168
182
|
if (life.disposed || life.warned || life.signal.aborted)
|
|
169
183
|
return;
|
|
@@ -191,6 +205,9 @@ function createRunLifecycle(opts) {
|
|
|
191
205
|
const warningDelay = Math.min(5 * 60_000, Math.max(250, Math.floor(timeoutMs * 0.8)));
|
|
192
206
|
const warningTimer = setTimeout(() => warnRun(opts, life), warningDelay);
|
|
193
207
|
warningTimer.unref?.();
|
|
208
|
+
const checkpointDelay = Math.max(250, Math.floor(timeoutMs * 0.8));
|
|
209
|
+
const checkpointTimer = setTimeout(() => requestRunCheckpoint(opts, life), checkpointDelay);
|
|
210
|
+
checkpointTimer.unref?.();
|
|
194
211
|
let removeStopListener = () => { };
|
|
195
212
|
const stopPromise = new Promise((resolveStopped) => {
|
|
196
213
|
const stopped = () => resolveStopped(RUN_STOPPED);
|
|
@@ -205,6 +222,7 @@ function createRunLifecycle(opts) {
|
|
|
205
222
|
timeoutController,
|
|
206
223
|
timeoutTimer,
|
|
207
224
|
warningTimer,
|
|
225
|
+
checkpointTimer,
|
|
208
226
|
stopPromise,
|
|
209
227
|
removeStopListener,
|
|
210
228
|
startedAt,
|
|
@@ -213,6 +231,8 @@ function createRunLifecycle(opts) {
|
|
|
213
231
|
rounds: 0,
|
|
214
232
|
timedOut: false,
|
|
215
233
|
warned: false,
|
|
234
|
+
checkpointDue: false,
|
|
235
|
+
checkpointInjected: false,
|
|
216
236
|
limitAnnounced: false,
|
|
217
237
|
disposed: false,
|
|
218
238
|
failedCalls: new Map(),
|
|
@@ -223,6 +243,7 @@ function disposeRunLifecycle(life) {
|
|
|
223
243
|
life.disposed = true;
|
|
224
244
|
clearTimeout(life.timeoutTimer);
|
|
225
245
|
clearTimeout(life.warningTimer);
|
|
246
|
+
clearTimeout(life.checkpointTimer);
|
|
226
247
|
life.removeStopListener();
|
|
227
248
|
}
|
|
228
249
|
function hardStop(opts, life, kind, detail) {
|
|
@@ -325,6 +346,15 @@ async function runAgentInner(history, opts, life) {
|
|
|
325
346
|
life.rounds += 1;
|
|
326
347
|
if (life.rounds >= Math.ceil(life.maxRounds * 0.75))
|
|
327
348
|
warnRun(opts, life);
|
|
349
|
+
if (Date.now() - life.startedAt >= Math.floor(life.timeoutMs * 0.8))
|
|
350
|
+
requestRunCheckpoint(opts, life);
|
|
351
|
+
if (!opts.quiet && life.checkpointDue && !life.checkpointInjected) {
|
|
352
|
+
life.checkpointInjected = true;
|
|
353
|
+
history.push({
|
|
354
|
+
role: "user",
|
|
355
|
+
content: wrapReminders([deadlineCheckpointReminder(life.timeoutMs)]),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
328
358
|
// Type-ahead steering: fold in anything the user submitted while the previous step ran, so it
|
|
329
359
|
// reaches the model on this next call (drained after the last tool round; empty on the 1st pass).
|
|
330
360
|
if (opts.pendingInput && !runSignal.aborted) {
|