@tiens.nguyen/gonext-local-worker 1.0.240 → 1.0.242

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.
Files changed (2) hide show
  1. package/gonext-repl.mjs +38 -7
  2. package/package.json +1 -1
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
- // Rotating half-circle = an in-progress ("blinking") bullet for the step being worked on.
110
- const SPINNER = ["◐", "◓", "◑", "◒"];
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) { thinking = true; thinkingSince = lastContentAt; thinkWord = pickWord(); }
1060
+ if (!thinking) {
1061
+ thinking = true; thinkingSince = lastContentAt; thinkWord = pickWord(); lastWordBucket = 0;
1062
+ }
1063
+ const now = Date.now();
1046
1064
  const secs = thinkSecs();
1047
- if (secs % 12 === 0) thinkWord = pickWord(); // rotate the word on long waits
1048
- const glyph = SPINNER[secs % SPINNER.length]; // rotates → reads as "in progress"
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,18 @@ 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: green ● + yellow phase/seconds. Line 2 (indented): the SPINNER + the playful
1086
+ // word together, both in the cycling color — the spinner belongs with the word, like
1087
+ // the web (dot on the label line, spinner leading the flavor line).
1060
1088
  process.stdout.write(
1061
- yellow(`${glyph} ${fit(primary)}… (${secs}s)`) + "\n" + dim(` ${thinkWord}…`)
1089
+ `${green(BULLET)} ${yellow(`${fit(primary)}… (${secs}s)`)}` +
1090
+ "\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}`
1062
1091
  );
1063
1092
  statusShown = true;
1064
1093
  };
1065
- const ticker = setInterval(tick, 1000);
1094
+ // 120ms so the spinner animates smoothly (the frame math above uses the wall clock, so
1095
+ // seconds still tick once/sec). Only redraws when the model has gone quiet >QUIET_MS.
1096
+ const ticker = setInterval(tick, 120);
1066
1097
 
1067
1098
  // Called right before any real (bullet/edit-card/answer) content prints: drop the
1068
1099
  // 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.240",
3
+ "version": "1.0.242",
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",