@tiens.nguyen/gonext-local-worker 1.0.198 → 1.0.199
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 +84 -28
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -54,11 +54,16 @@ const CLEAR_LINE = "\r\x1b[K"; // carriage-return + erase-to-end-of-line
|
|
|
54
54
|
// also drop the worker's ~~~ stream fences (they only matter to the web renderer).
|
|
55
55
|
const isHeartbeatLine = (line) => /…\s?\(\d+s\)\s*$/.test(line);
|
|
56
56
|
const isFenceLine = (line) => line.trim() === "~~~";
|
|
57
|
-
// The model's raw Python code blob is delimited by <code>/</code>
|
|
58
|
-
// the
|
|
57
|
+
// The model's raw Python code blob is delimited by <code>/</code> tags. We don't print
|
|
58
|
+
// the tags themselves, but the code IN BETWEEN prints in a distinct grey (codeColor)
|
|
59
59
|
// instead of dim prose, so it visually separates from the "Thought:" reasoning.
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Tolerant matching, NOT exact-line: the raw token stream often mangles the tags —
|
|
61
|
+
// glued to content ("<codecreate_folder(...)"), missing the ">", duplicated, or the
|
|
62
|
+
// close tag stuck on the code's last line — and exact matching printed those
|
|
63
|
+
// fragments literally in mixed colors (user-reported).
|
|
64
|
+
const CODE_OPEN_RE = /^<code(?:\s*>)?/; // applied to line.trim()
|
|
65
|
+
const CODE_CLOSE_RE = /<\/code>?\s*$/;
|
|
66
|
+
const isBareCloseTag = (t) => /^<\/code>?$/.test(t);
|
|
62
67
|
// The step summary the worker emits right after a tool call finishes (e.g.
|
|
63
68
|
// "unzip_file(...) | → Unzipped 256 files…") duplicates what's already visible in the
|
|
64
69
|
// gonext-local-worker daemon's own terminal — swallow it here to keep the REPL terse.
|
|
@@ -142,16 +147,19 @@ if (!workerKey || !apiBase) {
|
|
|
142
147
|
// for piped/scripted input, harmless for interactive TTYs. Both ask() and the main
|
|
143
148
|
// loop consume from this queue (never rl.question, to avoid double-capture).
|
|
144
149
|
//
|
|
145
|
-
// terminal:
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
const
|
|
150
|
+
// terminal: true (on real TTYs) with readline OWNING the prompt via setPrompt/prompt().
|
|
151
|
+
// History of this setting: plain terminal:true with a MANUALLY-written ">> " once caused
|
|
152
|
+
// a stray "[" artifact — readline redrew the input line using its own (empty, never-set)
|
|
153
|
+
// prompt and cursor tracking that our un-announced writes had made stale. The interim
|
|
154
|
+
// fix (terminal:false) traded that for something worse: the OS's canonical line
|
|
155
|
+
// discipline echoes raw CSI bytes, so arrows/Home/End showed literal "^[[D" and mid-line
|
|
156
|
+
// editing didn't work at all (user-reported). Giving readline the prompt makes its
|
|
157
|
+
// redraw math correct — rl.prompt() resets the cursor state after every burst of our
|
|
158
|
+
// output — and raw mode means backspace/arrows/↑-history all behave like a normal shell.
|
|
159
|
+
const IS_TTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
160
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: IS_TTY });
|
|
161
|
+
const PROMPT = cyan(">> ");
|
|
162
|
+
rl.setPrompt(PROMPT);
|
|
155
163
|
// Side effect of terminal:false above: readline no longer intercepts arrow keys for
|
|
156
164
|
// command-history recall, so pressing ↑/↓/→/← (or Home/End/Delete) sends the RAW escape
|
|
157
165
|
// bytes straight through as literal input — the OS's own canonical line discipline
|
|
@@ -189,8 +197,13 @@ const nextLine = () =>
|
|
|
189
197
|
? Promise.resolve(null)
|
|
190
198
|
: new Promise((res) => (_lineWaiter = res));
|
|
191
199
|
const ask = async (q) => {
|
|
192
|
-
|
|
200
|
+
// Route the question through readline's own prompt so its redraw logic stays
|
|
201
|
+
// consistent (a raw stdout write here would re-create the stale-cursor artifact
|
|
202
|
+
// that terminal:true mode is designed to avoid). Restored to ">> " afterwards.
|
|
203
|
+
rl.setPrompt(q);
|
|
204
|
+
rl.prompt();
|
|
193
205
|
const a = await nextLine();
|
|
206
|
+
rl.setPrompt(PROMPT);
|
|
194
207
|
return (a ?? "").trim();
|
|
195
208
|
};
|
|
196
209
|
|
|
@@ -510,14 +523,48 @@ async function runAgentTurn(history) {
|
|
|
510
523
|
}
|
|
511
524
|
inEditCard = false; // card ended — fall through to normal handling
|
|
512
525
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
526
|
+
{
|
|
527
|
+
const t = line.trim();
|
|
528
|
+
const openM = !inCode || t.startsWith("<code") ? CODE_OPEN_RE.exec(t) : null;
|
|
529
|
+
if (openM) {
|
|
530
|
+
// Enter code mode (or stay in it on a duplicate tag). Anything glued after
|
|
531
|
+
// the tag on the same line is code — print it, minus a glued close tag.
|
|
532
|
+
stampIfThinking(); // hold/refresh the stamp; the code body commits it below
|
|
533
|
+
inCode = true;
|
|
534
|
+
let rest = t.slice(openM[0].length).trim();
|
|
535
|
+
if (CODE_CLOSE_RE.test(rest)) {
|
|
536
|
+
inCode = false;
|
|
537
|
+
rest = rest.replace(CODE_CLOSE_RE, "").trim();
|
|
538
|
+
}
|
|
539
|
+
if (rest) {
|
|
540
|
+
onContent();
|
|
541
|
+
process.stdout.write(codeColor(rest) + "\n");
|
|
542
|
+
lastWasBlank = false;
|
|
543
|
+
}
|
|
544
|
+
lastContentAt = Date.now();
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (!inCode && isBareCloseTag(t)) {
|
|
548
|
+
// Stray close tag with no open in sight — swallow, never show it.
|
|
549
|
+
lastContentAt = Date.now();
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
517
552
|
}
|
|
518
553
|
if (inCode) {
|
|
519
|
-
|
|
520
|
-
if (
|
|
554
|
+
const t = line.trim();
|
|
555
|
+
if (CODE_CLOSE_RE.test(t)) {
|
|
556
|
+
// Close tag, possibly with the code's last line glued in front of it.
|
|
557
|
+
const before = t.replace(CODE_CLOSE_RE, "").trim();
|
|
558
|
+
if (before) {
|
|
559
|
+
onContent();
|
|
560
|
+
process.stdout.write(codeColor(before) + "\n");
|
|
561
|
+
lastWasBlank = false;
|
|
562
|
+
}
|
|
563
|
+
inCode = false;
|
|
564
|
+
lastContentAt = Date.now();
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (t === "") { lastContentAt = Date.now(); continue; } // skip blank lines in code
|
|
521
568
|
onContent();
|
|
522
569
|
process.stdout.write(codeColor(line) + "\n");
|
|
523
570
|
lastWasBlank = false;
|
|
@@ -637,20 +684,29 @@ async function main() {
|
|
|
637
684
|
dim(`Resumed session: ${turns} prior turn(s) from last time in this folder — /reset to start fresh.\n`)
|
|
638
685
|
);
|
|
639
686
|
}
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
process.
|
|
687
|
+
// Ctrl-C arrives on ONE of two paths depending on the readline mode: with
|
|
688
|
+
// terminal:true (real TTY) readline's raw-mode byte scanning intercepts \x03 and
|
|
689
|
+
// emits rl "SIGINT" (no OS signal is delivered); with terminal:false (piped input)
|
|
690
|
+
// the OS delivers a real SIGINT to the process. Attach the same handler to both —
|
|
691
|
+
// only one can ever fire for a given mode, so there's no double-handling.
|
|
692
|
+
const onInterrupt = () => {
|
|
644
693
|
if (following) {
|
|
645
694
|
followAborted = true;
|
|
646
695
|
process.stdout.write(red("\n^C "));
|
|
647
696
|
} else {
|
|
648
|
-
process.stdout.write(dim("\n(/exit to quit)\n")
|
|
697
|
+
process.stdout.write(dim("\n(/exit to quit)\n"));
|
|
698
|
+
// Discard anything half-typed (Ctrl-C at a prompt means "never mind") and
|
|
699
|
+
// redraw through readline so its cursor tracking stays right.
|
|
700
|
+
rl.line = "";
|
|
701
|
+
rl.cursor = 0;
|
|
702
|
+
rl.prompt();
|
|
649
703
|
}
|
|
650
|
-
}
|
|
704
|
+
};
|
|
705
|
+
process.on("SIGINT", onInterrupt);
|
|
706
|
+
rl.on("SIGINT", onInterrupt);
|
|
651
707
|
|
|
652
708
|
for (;;) {
|
|
653
|
-
|
|
709
|
+
rl.prompt();
|
|
654
710
|
// Absorb blank Enter-presses SILENTLY under the same prompt — without this, lines
|
|
655
711
|
// typed while the agent was busy on a long turn (queued, not yet read) all drain at
|
|
656
712
|
// once the moment the turn ends, each re-printing ">> " with no newline between them
|
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.199",
|
|
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",
|