@tiens.nguyen/gonext-local-worker 1.0.239 → 1.0.241
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-local-worker.mjs +32 -11
- package/gonext-repl.mjs +37 -7
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1830,6 +1830,32 @@ async function runAgentChatJob(job) {
|
|
|
1830
1830
|
(process.env.GONEXT_PROBE_PYTHON ?? process.env.GONEXT_MLX_LM_PYTHON ?? "")
|
|
1831
1831
|
.trim() || "python3";
|
|
1832
1832
|
const scriptPath = join(WORKER_DIR, "gonext_agent_chat.py");
|
|
1833
|
+
|
|
1834
|
+
// Workspace scope depends on WHERE the job came from (task #75). The `gonext`
|
|
1835
|
+
// terminal always sends its launch cwd as activeWorkspace; the web app never does —
|
|
1836
|
+
// that's the discriminator.
|
|
1837
|
+
// • Terminal: the real `gonext-local-worker workspace add` registry + that cwd.
|
|
1838
|
+
// • Web agent chat: ONE isolated temp scratch folder — NOT the Mac's registered
|
|
1839
|
+
// project folders. Inheriting them leaked their paths into the web prompt and
|
|
1840
|
+
// let terminal state drive web routing. run_command stays off (not a code repo).
|
|
1841
|
+
const terminalActiveWs = String(payload?.activeWorkspace ?? "").trim();
|
|
1842
|
+
let jobWorkspaces;
|
|
1843
|
+
let jobActiveWorkspace;
|
|
1844
|
+
if (terminalActiveWs) {
|
|
1845
|
+
jobWorkspaces = await readFile(join(homedir(), ".gonext", "workspaces.json"), "utf8")
|
|
1846
|
+
.then((raw) => {
|
|
1847
|
+
const parsed = JSON.parse(raw);
|
|
1848
|
+
return Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
|
|
1849
|
+
})
|
|
1850
|
+
.catch(() => []);
|
|
1851
|
+
jobActiveWorkspace = terminalActiveWs;
|
|
1852
|
+
} else {
|
|
1853
|
+
const webWork = join(homedir(), ".gonext", "web-agent-work");
|
|
1854
|
+
await mkdir(webWork, { recursive: true }).catch(() => {});
|
|
1855
|
+
jobWorkspaces = [{ name: "web", path: webWork, allowRun: false }];
|
|
1856
|
+
jobActiveWorkspace = webWork;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1833
1859
|
const input = JSON.stringify({
|
|
1834
1860
|
messages: payload?.messages ?? [],
|
|
1835
1861
|
agentBaseURL: payload?.agentBaseURL ?? "",
|
|
@@ -1889,17 +1915,12 @@ async function runAgentChatJob(job) {
|
|
|
1889
1915
|
ragEmbedModel: payload?.ragEmbedModel ?? "",
|
|
1890
1916
|
ragEmbedUrl: payload?.ragEmbedUrl ?? "",
|
|
1891
1917
|
ragTopK: payload?.ragTopK ?? 6,
|
|
1892
|
-
// Workspaces the agent may read/edit/test code in
|
|
1893
|
-
//
|
|
1894
|
-
workspaces:
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
})
|
|
1899
|
-
.catch(() => []),
|
|
1900
|
-
// The `gonext` terminal REPL's launch folder — download_file/unzip_file output
|
|
1901
|
-
// lands here (must be a registered workspace; the python side re-validates).
|
|
1902
|
-
activeWorkspace: payload?.activeWorkspace ?? "",
|
|
1918
|
+
// Workspaces the agent may read/edit/test code in (see the terminal-vs-web split
|
|
1919
|
+
// computed above): the real registry for the terminal, a lone temp folder for web.
|
|
1920
|
+
workspaces: jobWorkspaces,
|
|
1921
|
+
// download_file/unzip_file output lands here (a registered workspace; python
|
|
1922
|
+
// re-validates). Terminal = its launch cwd; web = the temp scratch folder.
|
|
1923
|
+
activeWorkspace: jobActiveWorkspace,
|
|
1903
1924
|
});
|
|
1904
1925
|
// 30 min max for an agent run: multi-step ReAct on a 14B/31B with a cold prompt
|
|
1905
1926
|
// cache — or a remote Ollama box on slow GPU cold-loading a big model — can run
|
package/gonext-repl.mjs
CHANGED
|
@@ -106,8 +106,22 @@ const THINKING_WORDS = (() => {
|
|
|
106
106
|
})();
|
|
107
107
|
const pickWord = () =>
|
|
108
108
|
THINKING_WORDS[Math.floor(Math.random() * THINKING_WORDS.length)] || "Thinking";
|
|
109
|
-
//
|
|
110
|
-
|
|
109
|
+
// In-progress ("blinking") spinner for the step being worked on. 30 styles that rotate
|
|
110
|
+
// ~every 8s so a long CPU-bound wait stays interesting — mirrors the web agent's set
|
|
111
|
+
// (keep in sync with the .spin-N @keyframes in web/src/App.css). Single-width glyphs so
|
|
112
|
+
// they never shift the text after them.
|
|
113
|
+
const SPINNERS = [
|
|
114
|
+
"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", "│╱─╲", "▁▃▄▅▆▇▆▅▄▃", "←↖↑↗→↘↓↙", "▏▎▍▌▋▊▉▊▋▌▍▎",
|
|
115
|
+
"◴◷◶◵", "◐◓◑◒", "⣾⣽⣻⢿⡿⣟⣯⣷", "▖▘▝▗", "✶✸✹✺✹✷",
|
|
116
|
+
"⠁⠂⠄⡀⢀⠠⠐⠈", "dqpb", "◢◣◤◥", "✦✧", "◜◝◞◟",
|
|
117
|
+
"▌▀▐▄", "◰◳◲◱", "┤┘┴└├┌┬┐", "▘▝▗▖", "▸▹",
|
|
118
|
+
"⢄⢂⢁⡁⡈⡐⡠", "▙▛▜▟", "▂▃▄▅▆▇█▇▆▅▄▃", "⣿⣷⣯⣟⡿⢿⣻⣽", "⎺⎻⎼⎽",
|
|
119
|
+
"•◦", "░▒▓█▓▒", "◇◈◆", "·✢✳✽", "◠◡",
|
|
120
|
+
].map((s) => [...s]);
|
|
121
|
+
// The playful word's color cycles ~every 2.5s (xterm-256 palette matching the web accents:
|
|
122
|
+
// green, cyan, blue, indigo, pink, amber).
|
|
123
|
+
const WAIT_COLORS_256 = [120, 87, 111, 147, 218, 221];
|
|
124
|
+
const color256 = (n, s) => `\x1b[38;5;${n}m${s}\x1b[0m`;
|
|
111
125
|
const BULLET = "●"; // a completed action
|
|
112
126
|
|
|
113
127
|
// ---------- flags ----------
|
|
@@ -1014,6 +1028,7 @@ async function runAgentTurn(history) {
|
|
|
1014
1028
|
let thinking = false;
|
|
1015
1029
|
let thinkingSince = 0;
|
|
1016
1030
|
let thinkWord = pickWord(); // playful word shown UNDER the in-progress line (flavor)
|
|
1031
|
+
let lastWordBucket = -1; // 12s window index → rotate the word once per window (task: 30-spinner)
|
|
1017
1032
|
let almostDone = false; // set from the worker's "…almost completed thinking…" heartbeat
|
|
1018
1033
|
let runningCmd = null; // the command currently executing (from a "Running → …" event)
|
|
1019
1034
|
// The model's CURRENT Thought (task #64): while it streams its reasoning inside the
|
|
@@ -1042,10 +1057,20 @@ async function runAgentTurn(history) {
|
|
|
1042
1057
|
// silent once any of THIS line has already been shown (see carryFlushedLen).
|
|
1043
1058
|
if (plainReplyFlow && carryFlushedLen > 0) return;
|
|
1044
1059
|
// Count from when this phase went quiet, so the seconds reflect the current wait/run.
|
|
1045
|
-
if (!thinking) {
|
|
1060
|
+
if (!thinking) {
|
|
1061
|
+
thinking = true; thinkingSince = lastContentAt; thinkWord = pickWord(); lastWordBucket = 0;
|
|
1062
|
+
}
|
|
1063
|
+
const now = Date.now();
|
|
1046
1064
|
const secs = thinkSecs();
|
|
1047
|
-
|
|
1048
|
-
|
|
1065
|
+
// Rotate the playful word once per 12s window (guarded so the fast ticker doesn't
|
|
1066
|
+
// re-roll it many times within the same second).
|
|
1067
|
+
const wordBucket = Math.floor((now - thinkingSince) / 12000);
|
|
1068
|
+
if (wordBucket !== lastWordBucket) { lastWordBucket = wordBucket; thinkWord = pickWord(); }
|
|
1069
|
+
// Spinner: STYLE rotates ~every 8s, FRAME advances ~every 120ms, color cycles ~every
|
|
1070
|
+
// 2.5s — all off the wall clock so they keep moving smoothly across phases.
|
|
1071
|
+
const frames = SPINNERS[Math.floor(now / 8000) % SPINNERS.length];
|
|
1072
|
+
const glyph = frames[Math.floor(now / 120) % frames.length];
|
|
1073
|
+
const wc = WAIT_COLORS_256[Math.floor(now / 2500) % WAIT_COLORS_256.length];
|
|
1049
1074
|
// Line 1 = WHAT is happening now: the running command, else the phase (Thinking /
|
|
1050
1075
|
// Almost done). Line 2 = the playful word (flavor). Truncate so neither wraps.
|
|
1051
1076
|
// Priority: a live command > the model's current Thought clause (task #64, so a
|
|
@@ -1057,12 +1082,17 @@ async function runAgentTurn(history) {
|
|
|
1057
1082
|
const max = Math.max(24, (process.stdout.columns || 80) - 14);
|
|
1058
1083
|
const fit = (s) => (s.length > max ? s.slice(0, max - 1) + "…" : s);
|
|
1059
1084
|
clearStatus();
|
|
1085
|
+
// Line 1: cycling-color spinner + yellow phase/seconds. Line 2: the word in the same
|
|
1086
|
+
// cycling color (the web colors the spinner + word together; primary stays the theme).
|
|
1060
1087
|
process.stdout.write(
|
|
1061
|
-
|
|
1088
|
+
`${color256(wc, glyph)} ${yellow(`${fit(primary)}… (${secs}s)`)}` +
|
|
1089
|
+
"\n" + color256(wc, ` ${thinkWord}…`)
|
|
1062
1090
|
);
|
|
1063
1091
|
statusShown = true;
|
|
1064
1092
|
};
|
|
1065
|
-
|
|
1093
|
+
// 120ms so the spinner animates smoothly (the frame math above uses the wall clock, so
|
|
1094
|
+
// seconds still tick once/sec). Only redraws when the model has gone quiet >QUIET_MS.
|
|
1095
|
+
const ticker = setInterval(tick, 120);
|
|
1066
1096
|
|
|
1067
1097
|
// Called right before any real (bullet/edit-card/answer) content prints: drop the
|
|
1068
1098
|
// status lines so content lands cleanly, and end the current phase (so the next phase's
|
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.241",
|
|
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",
|