@tiens.nguyen/gonext-local-worker 1.0.186 → 1.0.187
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 +52 -17
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -36,6 +36,7 @@ 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
38
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
39
|
+
const blue = (s) => `\x1b[34m${s}\x1b[0m`;
|
|
39
40
|
const CLEAR_LINE = "\r\x1b[K"; // carriage-return + erase-to-end-of-line
|
|
40
41
|
|
|
41
42
|
// The worker's "still thinking" heartbeat lines look like "Caffeinating… (90s)" — we
|
|
@@ -43,11 +44,15 @@ const CLEAR_LINE = "\r\x1b[K"; // carriage-return + erase-to-end-of-line
|
|
|
43
44
|
// also drop the worker's ~~~ stream fences (they only matter to the web renderer).
|
|
44
45
|
const isHeartbeatLine = (line) => /…\s?\(\d+s\)\s*$/.test(line);
|
|
45
46
|
const isFenceLine = (line) => line.trim() === "~~~";
|
|
46
|
-
// The model's
|
|
47
|
-
//
|
|
48
|
-
//
|
|
47
|
+
// The model's raw Python code blob is delimited by <code>/</code> lines. We don't print
|
|
48
|
+
// the tag lines themselves, but the code IN BETWEEN prints in a distinct color (blue)
|
|
49
|
+
// instead of dim prose, so it visually separates from the "Thought:" reasoning.
|
|
49
50
|
const isCodeOpenLine = (line) => line.trim() === "<code>";
|
|
50
51
|
const isCodeCloseLine = (line) => line.trim() === "</code>";
|
|
52
|
+
// The step summary the worker emits right after a tool call finishes (e.g.
|
|
53
|
+
// "unzip_file(...) | → Unzipped 256 files…") duplicates what's already visible in the
|
|
54
|
+
// gonext-local-worker daemon's own terminal — swallow it here to keep the REPL terse.
|
|
55
|
+
const isToolStepSummary = (line) => / \| → /.test(line);
|
|
51
56
|
|
|
52
57
|
// Playful words for the local "thinking…" ticker, reused verbatim from the worker's
|
|
53
58
|
// thinking_words.txt sibling (falls back to a tiny built-in list if it's missing).
|
|
@@ -282,22 +287,44 @@ async function runAgentTurn(history) {
|
|
|
282
287
|
};
|
|
283
288
|
const ticker = setInterval(tick, 1000);
|
|
284
289
|
|
|
285
|
-
// Close out a "thinking…" phase right before real output prints.
|
|
290
|
+
// Close out a "thinking…" phase right before real output prints. A model streams in
|
|
291
|
+
// small bursts with natural micro-pauses between them (token batching, network jitter);
|
|
292
|
+
// each pause >700ms would otherwise stamp its OWN "✔ completed thought" line, stacking
|
|
293
|
+
// many near-duplicates through a single Thought/code generation. `stampIfThinking()` —
|
|
294
|
+
// called ONLY at the <code> tag transition, where nothing prints right after it — HOLDS
|
|
295
|
+
// the stamp as the current status line (like the ticker): a later completion overwrites
|
|
296
|
+
// it in place instead of stacking a new line. `onContent()` — called immediately before
|
|
297
|
+
// any real content prints — always COMMITS whatever is pending (a fresh stamp or a held
|
|
298
|
+
// one) with a trailing newline first, so content never lands on the same line as a stamp.
|
|
299
|
+
let pendingStamp = false;
|
|
300
|
+
const stampIfThinking = () => {
|
|
301
|
+
if (thinking) {
|
|
302
|
+
process.stdout.write(CLEAR_LINE + dim(`✔ completed thought (${thinkSecs()}s)`));
|
|
303
|
+
thinking = false;
|
|
304
|
+
statusShown = true;
|
|
305
|
+
pendingStamp = true;
|
|
306
|
+
}
|
|
307
|
+
lastContentAt = Date.now();
|
|
308
|
+
};
|
|
286
309
|
const onContent = () => {
|
|
287
310
|
if (thinking) {
|
|
288
311
|
process.stdout.write(CLEAR_LINE + dim(`✔ completed thought (${thinkSecs()}s)`) + "\n");
|
|
289
312
|
thinking = false;
|
|
290
|
-
|
|
313
|
+
} else if (pendingStamp) {
|
|
314
|
+
process.stdout.write("\n"); // commit the held stamp; content prints right after
|
|
291
315
|
} else {
|
|
292
316
|
clearStatus();
|
|
293
317
|
}
|
|
318
|
+
pendingStamp = false;
|
|
319
|
+
statusShown = false;
|
|
294
320
|
lastContentAt = Date.now();
|
|
295
321
|
};
|
|
296
322
|
|
|
297
|
-
// Split newly-arrived text into lines: heartbeats + ~~~ fences
|
|
298
|
-
//
|
|
299
|
-
// the
|
|
300
|
-
//
|
|
323
|
+
// Split newly-arrived text into lines: heartbeats + ~~~ fences + the post-tool-call step
|
|
324
|
+
// summary ("tool(...) | → result", redundant with the daemon's own terminal) are
|
|
325
|
+
// swallowed; the model's <code>/</code> TAG lines are hidden but the code body between
|
|
326
|
+
// them prints in blue (not dim, so it stands out from "Thought:" prose); blank lines
|
|
327
|
+
// keep spacing; everything else commits dim to scrollback.
|
|
301
328
|
let inCode = false;
|
|
302
329
|
const consume = (chunk) => {
|
|
303
330
|
carry += stripThinkTags(chunk);
|
|
@@ -305,17 +332,18 @@ async function runAgentTurn(history) {
|
|
|
305
332
|
while ((nl = carry.indexOf("\n")) >= 0) {
|
|
306
333
|
const line = carry.slice(0, nl);
|
|
307
334
|
carry = carry.slice(nl + 1);
|
|
308
|
-
if (isHeartbeatLine(line) || isFenceLine(line)) continue; // swallow
|
|
335
|
+
if (isHeartbeatLine(line) || isFenceLine(line)) continue; // swallow, no trace
|
|
336
|
+
if (isToolStepSummary(line)) { lastContentAt = Date.now(); continue; } // swallow
|
|
309
337
|
if (!inCode && isCodeOpenLine(line)) {
|
|
310
|
-
//
|
|
311
|
-
// then suppress the code body itself.
|
|
312
|
-
onContent();
|
|
338
|
+
stampIfThinking(); // hold/refresh the stamp; the code body commits it below
|
|
313
339
|
inCode = true;
|
|
314
340
|
continue;
|
|
315
341
|
}
|
|
316
342
|
if (inCode) {
|
|
317
|
-
lastContentAt = Date.now();
|
|
318
|
-
if (
|
|
343
|
+
if (isCodeCloseLine(line)) { inCode = false; lastContentAt = Date.now(); continue; }
|
|
344
|
+
if (line.trim() === "") { lastContentAt = Date.now(); continue; } // skip blank lines in code
|
|
345
|
+
onContent();
|
|
346
|
+
process.stdout.write(blue(line) + "\n");
|
|
319
347
|
continue;
|
|
320
348
|
}
|
|
321
349
|
if (line.trim() === "") {
|
|
@@ -413,10 +441,17 @@ async function main() {
|
|
|
413
441
|
|
|
414
442
|
for (;;) {
|
|
415
443
|
process.stdout.write(cyan(">> "));
|
|
416
|
-
|
|
444
|
+
// Absorb blank Enter-presses SILENTLY under the same prompt — without this, lines
|
|
445
|
+
// typed while the agent was busy on a long turn (queued, not yet read) all drain at
|
|
446
|
+
// once the moment the turn ends, each re-printing ">> " with no newline between them
|
|
447
|
+
// (e.g. ">> >> >> "). Only re-print the prompt for a genuine stdin close or input.
|
|
448
|
+
let lineRaw;
|
|
449
|
+
for (;;) {
|
|
450
|
+
lineRaw = await nextLine();
|
|
451
|
+
if (lineRaw === null || lineRaw.trim()) break;
|
|
452
|
+
}
|
|
417
453
|
if (lineRaw === null) break; // stdin closed
|
|
418
454
|
const line = lineRaw.trim();
|
|
419
|
-
if (!line) continue;
|
|
420
455
|
if (line === "/exit" || line === "/quit") break;
|
|
421
456
|
if (line === "/help") {
|
|
422
457
|
console.log(dim(" /exit — quit · /revert [runId] — undo the agent's file edits (latest run)"));
|
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.187",
|
|
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",
|