@nanhara/hara 0.112.0 → 0.112.1
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 +10 -0
- package/dist/exec/jobs.js +24 -0
- package/dist/tui/App.js +8 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.112.1 — the background-job indicator is now LIVE (even at idle)
|
|
9
|
+
|
|
10
|
+
- **`⚙ N bg running` updates in real time — including when hara is idle.** In 0.112.0 the indicator only
|
|
11
|
+
refreshed while a turn was running, so once the agent finished but a background task (a preview server,
|
|
12
|
+
a watcher, a long render) was still going, the idle prompt looked exactly like "nothing running" — and
|
|
13
|
+
the user reads that as *"why did it just stop?"*. Now `jobs.ts` emits on every start / self-exit / kill
|
|
14
|
+
and the status row subscribes, so the indicator appears the moment a task starts, updates as tasks
|
|
15
|
+
finish on their own, and clears when the last one ends — event-driven, no polling. (`/jobs` remains the
|
|
16
|
+
on-demand detailed view.)
|
|
17
|
+
|
|
8
18
|
## 0.112.0 — /jobs: see (and manage) what's running in the background
|
|
9
19
|
|
|
10
20
|
- **`/jobs` — a user-facing view of the agent's background shell jobs** (dev servers, watchers, long
|
package/dist/exec/jobs.js
CHANGED
|
@@ -7,6 +7,26 @@ import { shellCommand } from "../sandbox.js";
|
|
|
7
7
|
const MAX_BUF = 64 * 1024; // retain only the tail of each job's combined output
|
|
8
8
|
const jobs = new Map();
|
|
9
9
|
let seq = 0;
|
|
10
|
+
const jobsListeners = new Set();
|
|
11
|
+
/** Subscribe to job start/exit/kill. Lets the UI show "a background task is running" LIVE — crucially at
|
|
12
|
+
* IDLE too: a turn ending doesn't mean the background work did, and without this the user reads the idle
|
|
13
|
+
* prompt as "it stopped". Returns an unsubscribe. */
|
|
14
|
+
export function onJobsChange(fn) {
|
|
15
|
+
jobsListeners.add(fn);
|
|
16
|
+
return () => {
|
|
17
|
+
jobsListeners.delete(fn);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function emitJobsChange() {
|
|
21
|
+
for (const fn of jobsListeners) {
|
|
22
|
+
try {
|
|
23
|
+
fn();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* a listener must never break job bookkeeping */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
10
30
|
let hooked = false;
|
|
11
31
|
function ensureExitCleanup() {
|
|
12
32
|
if (hooked)
|
|
@@ -29,6 +49,7 @@ export function startJob(command, cwd, mode) {
|
|
|
29
49
|
if (job.status === "running") {
|
|
30
50
|
job.status = "exited";
|
|
31
51
|
job.code = code;
|
|
52
|
+
emitJobsChange(); // a job finishing ON ITS OWN (no user action) must update the UI — this is the idle case
|
|
32
53
|
}
|
|
33
54
|
});
|
|
34
55
|
child.on("error", (e) => {
|
|
@@ -36,9 +57,11 @@ export function startJob(command, cwd, mode) {
|
|
|
36
57
|
job.status = "exited";
|
|
37
58
|
job.code = -1;
|
|
38
59
|
job.buf = (job.buf + `\n[spawn error] ${e.message}`).slice(-MAX_BUF);
|
|
60
|
+
emitJobsChange();
|
|
39
61
|
}
|
|
40
62
|
});
|
|
41
63
|
jobs.set(job.id, job);
|
|
64
|
+
emitJobsChange();
|
|
42
65
|
return job.id;
|
|
43
66
|
}
|
|
44
67
|
export function listJobs() {
|
|
@@ -64,6 +87,7 @@ export function killJob(id) {
|
|
|
64
87
|
/* already gone */
|
|
65
88
|
}
|
|
66
89
|
j.status = "killed";
|
|
90
|
+
emitJobsChange();
|
|
67
91
|
return true;
|
|
68
92
|
}
|
|
69
93
|
/** Terminate every running job — registered on process exit so dev servers don't outlive hara. */
|
package/dist/tui/App.js
CHANGED
|
@@ -18,7 +18,7 @@ import { accent } from "./theme.js";
|
|
|
18
18
|
import { renderMarkdown } from "../md.js";
|
|
19
19
|
import { clearTodos, currentTodos, onTodosChange } from "../tools/todo.js";
|
|
20
20
|
import { onTurnPhase, turnPhase } from "../agent/phase.js";
|
|
21
|
-
import { listJobs } from "../exec/jobs.js";
|
|
21
|
+
import { listJobs, onJobsChange } from "../exec/jobs.js";
|
|
22
22
|
import { ModelPicker } from "./model-picker.js";
|
|
23
23
|
let _id = 0;
|
|
24
24
|
const nid = () => ++_id;
|
|
@@ -273,7 +273,7 @@ function StatusRow({ working, todos, queued }) {
|
|
|
273
273
|
// watcher) without asking. Live while working (spinner ticks re-render); best-effort at idle (/jobs is
|
|
274
274
|
// the authoritative on-demand view). Re-read each render.
|
|
275
275
|
const bg = listJobs().filter((j) => j.status === "running").length;
|
|
276
|
-
const bgTag = bg ? ` · ⚙ ${bg} bg (/jobs)` : "";
|
|
276
|
+
const bgTag = bg ? ` · ⚙ ${bg} bg running (/jobs)` : "";
|
|
277
277
|
if (!working) {
|
|
278
278
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: ` ${IDLE_HINTS}${bgTag}` }) }));
|
|
279
279
|
}
|
|
@@ -389,6 +389,12 @@ export function App({ initialStatus, model, cwd, header, onSubmit, cycleApproval
|
|
|
389
389
|
});
|
|
390
390
|
return unsub;
|
|
391
391
|
}, []);
|
|
392
|
+
// Subscribe to background-job start/exit/kill so the `⚙ N bg` indicator is LIVE — crucially at idle.
|
|
393
|
+
// A job finishing on its own, or a preview server still running after a turn ends, now refreshes the
|
|
394
|
+
// status row; without it the idle prompt reads as "it stopped". Bumping a nonce forces the re-render;
|
|
395
|
+
// StatusRow re-reads listJobs().
|
|
396
|
+
const [, setJobsTick] = useState(0);
|
|
397
|
+
useEffect(() => onJobsChange(() => setJobsTick((n) => n + 1)), []);
|
|
392
398
|
// Reconcile the synchronously-mutated live buffer into React state, at most once per ~33ms. First
|
|
393
399
|
// append any finalized blocks to <Static> (once, in order), then publish the remaining live tail.
|
|
394
400
|
// The array identities are fresh each flush so React/ink re-render exactly the minimal live region.
|