@tiens.nguyen/gonext-local-worker 1.0.179 → 1.0.180
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/gonext-repl.mjs +47 -2
- package/gonext_agent_chat.py +8 -2
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -35,6 +35,14 @@ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
|
35
35
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
36
36
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
37
37
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
38
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
39
|
+
const CLEAR_LINE = "\r\x1b[K"; // carriage-return + erase-to-end-of-line
|
|
40
|
+
|
|
41
|
+
// The worker's playful "still thinking" heartbeat lines look like "Caffeinating… (90s)"
|
|
42
|
+
// (a word/phrase, an ellipsis, then the elapsed seconds). They're ephemeral progress —
|
|
43
|
+
// we render them on a single in-place status line (yellow) that the next one overwrites
|
|
44
|
+
// and that gets erased once real output arrives, so they never pile up in the scrollback.
|
|
45
|
+
const isHeartbeatLine = (line) => /…\s?\(\d+s\)\s*$/.test(line);
|
|
38
46
|
|
|
39
47
|
// ---------- flags ----------
|
|
40
48
|
const argv = process.argv.slice(2);
|
|
@@ -219,10 +227,42 @@ async function runAgentTurn(history) {
|
|
|
219
227
|
followAborted = false;
|
|
220
228
|
const startedAt = Date.now();
|
|
221
229
|
let shownChars = 0;
|
|
230
|
+
let carry = ""; // trailing partial line held until its newline arrives
|
|
231
|
+
let statusShown = false; // a transient heartbeat status line is on screen
|
|
222
232
|
let warnedPending = false;
|
|
233
|
+
|
|
234
|
+
// Erase the transient heartbeat status line (if any) before printing real output.
|
|
235
|
+
const clearStatus = () => {
|
|
236
|
+
if (statusShown) {
|
|
237
|
+
process.stdout.write(CLEAR_LINE);
|
|
238
|
+
statusShown = false;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
// Feed newly-arrived stream text through the line splitter: heartbeat lines become an
|
|
242
|
+
// in-place status line (never committed to scrollback); everything else commits dim.
|
|
243
|
+
const consume = (chunk) => {
|
|
244
|
+
carry += stripThinkTags(chunk);
|
|
245
|
+
let nl;
|
|
246
|
+
while ((nl = carry.indexOf("\n")) >= 0) {
|
|
247
|
+
const line = carry.slice(0, nl);
|
|
248
|
+
carry = carry.slice(nl + 1);
|
|
249
|
+
if (isHeartbeatLine(line)) {
|
|
250
|
+
// Overwrite the previous heartbeat in place — no newline, so it stays transient.
|
|
251
|
+
process.stdout.write(CLEAR_LINE + yellow(line));
|
|
252
|
+
statusShown = true;
|
|
253
|
+
} else if (line.trim() === "") {
|
|
254
|
+
// Swallow the blank line the worker appends after a heartbeat; keep real spacing.
|
|
255
|
+
if (!statusShown) process.stdout.write("\n");
|
|
256
|
+
} else {
|
|
257
|
+
clearStatus();
|
|
258
|
+
process.stdout.write(dim(line) + "\n");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
223
262
|
try {
|
|
224
263
|
for (;;) {
|
|
225
264
|
if (followAborted) {
|
|
265
|
+
clearStatus();
|
|
226
266
|
process.stdout.write(
|
|
227
267
|
dim("\n(stopped following — the job keeps running on the worker)\n")
|
|
228
268
|
);
|
|
@@ -235,14 +275,17 @@ async function runAgentTurn(history) {
|
|
|
235
275
|
if (!jr.ok) throw new Error(job?.error || `job poll failed (HTTP ${jr.status})`);
|
|
236
276
|
const text = typeof job.resultText === "string" ? job.resultText : "";
|
|
237
277
|
if (job.jobStatus === "completed") {
|
|
238
|
-
//
|
|
278
|
+
// Drop the transient heartbeat line and any partial reasoning tail; the bright
|
|
279
|
+
// answer prints right after via the caller.
|
|
280
|
+
clearStatus();
|
|
239
281
|
return answerFrom(text);
|
|
240
282
|
}
|
|
241
283
|
if (text.length > shownChars) {
|
|
242
|
-
|
|
284
|
+
consume(text.slice(shownChars));
|
|
243
285
|
shownChars = text.length;
|
|
244
286
|
}
|
|
245
287
|
if (job.jobStatus === "failed" || job.jobStatus === "cancelled") {
|
|
288
|
+
clearStatus();
|
|
246
289
|
throw new Error(job.errorMessage || `job ${job.jobStatus}`);
|
|
247
290
|
}
|
|
248
291
|
if (
|
|
@@ -251,6 +294,7 @@ async function runAgentTurn(history) {
|
|
|
251
294
|
Date.now() - startedAt > 6000
|
|
252
295
|
) {
|
|
253
296
|
warnedPending = true;
|
|
297
|
+
clearStatus();
|
|
254
298
|
process.stdout.write(
|
|
255
299
|
red("\n⚠ no worker has claimed the job — is `gonext-local-worker` running?\n")
|
|
256
300
|
);
|
|
@@ -258,6 +302,7 @@ async function runAgentTurn(history) {
|
|
|
258
302
|
await sleep(700);
|
|
259
303
|
}
|
|
260
304
|
} finally {
|
|
305
|
+
clearStatus();
|
|
261
306
|
following = false;
|
|
262
307
|
}
|
|
263
308
|
}
|
package/gonext_agent_chat.py
CHANGED
|
@@ -1481,10 +1481,16 @@ _WS_ACTIVE: str = ""
|
|
|
1481
1481
|
def _rag_read_allowed(path: str) -> str:
|
|
1482
1482
|
"""Resolve `path` and ensure it stays within a safe root (worker cwd, temp dir,
|
|
1483
1483
|
~/.gonext, or a registered workspace) — so the agent's file tools can't read
|
|
1484
|
-
arbitrary files like ~/.ssh.
|
|
1484
|
+
arbitrary files like ~/.ssh. Relative paths anchor to the terminal workspace (where
|
|
1485
|
+
downloads/unzips land), NOT the worker's process cwd (the home dir), because the
|
|
1486
|
+
model routinely passes bare names like "tools-hook" or "tools-hook/README.md"."""
|
|
1485
1487
|
import os
|
|
1486
1488
|
import tempfile
|
|
1487
|
-
|
|
1489
|
+
expanded = os.path.expanduser((path or "").strip())
|
|
1490
|
+
if not os.path.isabs(expanded):
|
|
1491
|
+
base = _WS_ACTIVE or (_WS_ROOTS[0]["path"] if _WS_ROOTS else os.getcwd())
|
|
1492
|
+
expanded = os.path.join(base, expanded)
|
|
1493
|
+
rp = os.path.realpath(expanded)
|
|
1488
1494
|
roots = [
|
|
1489
1495
|
os.path.realpath(os.getcwd()),
|
|
1490
1496
|
os.path.realpath(tempfile.gettempdir()),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.180",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|