@tiens.nguyen/gonext-local-worker 1.0.252 → 1.0.253

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.
@@ -1984,6 +1984,8 @@ async function runAgentChatJob(job) {
1984
1984
  // each python "tokens" event, persisted to the job doc (Mongo) so the REPL can show
1985
1985
  // it live and it survives on the completed job.
1986
1986
  let latestCodeTokens = 0;
1987
+ // Task #84: peak single-request prompt-token count seen this turn (not the sum).
1988
+ let latestMaxCodeTokens = 0;
1987
1989
  // Raw model-stream tokens (event.type === "stream") are Python code + `Thought:`
1988
1990
  // prose + <code>/<|"|> fragments. The web Thought panel renders reasoning as
1989
1991
  // markdown, which turns `#`-prefixed code comments into <h1> headings and
@@ -2111,16 +2113,24 @@ async function runAgentChatJob(job) {
2111
2113
  enqueueText(event.text);
2112
2114
  answerStreamed = true;
2113
2115
  } else if (event.type === "tokens" && Number.isFinite(event.codeInput)) {
2114
- // Prompt tokens sent to the agent CODE model (task #83). Persist the running
2115
- // total to the job doc so it's in Mongo and the REPL can render it live. Steps
2116
- // are seconds apart, so a fire-and-forget PATCH per event is cheap; it only
2117
- // touches agentCodeTokens (never resultText/jobStatus), so it can't race the
2118
- // chunk/completed writes.
2119
- if (event.codeInput > latestCodeTokens) {
2120
- latestCodeTokens = event.codeInput;
2116
+ // Prompt tokens sent to the agent CODE model (tasks #83 accumulated + #84 peak).
2117
+ // Persist both to the job doc so they're in Mongo and the REPL can render them
2118
+ // live. Steps are seconds apart, so a fire-and-forget PATCH per event is cheap;
2119
+ // it only touches agentCodeTokens/agentMaxCodeTokens (never resultText/jobStatus),
2120
+ // so it can't race the chunk/completed writes.
2121
+ const nextTotal = Math.max(latestCodeTokens, event.codeInput);
2122
+ const nextMax = Number.isFinite(event.codeMax)
2123
+ ? Math.max(latestMaxCodeTokens, event.codeMax)
2124
+ : latestMaxCodeTokens;
2125
+ if (nextTotal !== latestCodeTokens || nextMax !== latestMaxCodeTokens) {
2126
+ latestCodeTokens = nextTotal;
2127
+ latestMaxCodeTokens = nextMax;
2121
2128
  workerFetch(`/api/worker/jobs/${jobId}`, {
2122
2129
  method: "PATCH",
2123
- body: JSON.stringify({ agentCodeTokens: latestCodeTokens }),
2130
+ body: JSON.stringify({
2131
+ agentCodeTokens: latestCodeTokens,
2132
+ agentMaxCodeTokens: latestMaxCodeTokens,
2133
+ }),
2124
2134
  }).catch(() => {});
2125
2135
  }
2126
2136
  } else if (event.type === "final" && typeof event.text === "string") {
@@ -2174,6 +2184,7 @@ async function runAgentChatJob(job) {
2174
2184
  resultText: answerStreamed ? fullText : finalText || fullText,
2175
2185
  tokenCount: Math.max(1, Math.ceil((finalText || fullText).length / 4)),
2176
2186
  ...(latestCodeTokens > 0 ? { agentCodeTokens: latestCodeTokens } : {}),
2187
+ ...(latestMaxCodeTokens > 0 ? { agentMaxCodeTokens: latestMaxCodeTokens } : {}),
2177
2188
  totalTimeSeconds,
2178
2189
  }),
2179
2190
  });
package/gonext-repl.mjs CHANGED
@@ -122,6 +122,7 @@ const SPINNERS = [
122
122
  // green, cyan, blue, indigo, pink, amber).
123
123
  const WAIT_COLORS_256 = [120, 87, 111, 147, 218, 221];
124
124
  const color256 = (n, s) => `\x1b[38;5;${n}m${s}\x1b[0m`;
125
+ const orange = (s) => color256(208, s); // peak-token label (task #84)
125
126
  // Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
126
127
  const fmtTokens = (n) => {
127
128
  if (n < 1000) return String(n);
@@ -564,6 +565,16 @@ async function loadSessionServer(cwd) {
564
565
  }
565
566
  }
566
567
 
568
+ // Peak single-request agent-code-model token count remembered per folder (task #84).
569
+ async function loadSessionMaxCodeTokens(cwd) {
570
+ try {
571
+ const raw = JSON.parse(await readFile(sessionFilePath(cwd), "utf8"));
572
+ return Number.isFinite(raw?.maxCodeTokens) ? raw.maxCodeTokens : 0;
573
+ } catch {
574
+ return 0;
575
+ }
576
+ }
577
+
567
578
  async function saveSession(cwd, history) {
568
579
  try {
569
580
  await mkdir(SESSIONS_DIR, { recursive: true });
@@ -576,6 +587,7 @@ async function saveSession(cwd, history) {
576
587
  updatedAt: new Date().toISOString(),
577
588
  testAuto: sessionTestAuto,
578
589
  deployServer: selectedServer, // remember the /server pick per folder (task #69)
590
+ maxCodeTokens: sessionMaxCodeTokens, // peak per-request tokens (task #84)
579
591
  history: trimmed,
580
592
  },
581
593
  null,
@@ -919,6 +931,10 @@ let alwaysExtendOnMaxStep = false;
919
931
  // Deploy target chosen via /server (task #69): {name, host, user} or null. Session-scoped;
920
932
  // sent to /agent-ask as deployServer so the agent deploys to this host with KEY auth.
921
933
  let selectedServer = null;
934
+ // Task #84: peak single-request agent-code-model token count for THIS workspace, kept
935
+ // as a max across turns and persisted per folder in the session file. Shown in orange
936
+ // next to the live token count.
937
+ let sessionMaxCodeTokens = 0;
922
938
 
923
939
  // While a turn runs, mute readline's own echo/redraws so keystrokes can't corrupt the
924
940
  // live status display and there's no way to type a new question — only Ctrl+C works.
@@ -1148,10 +1164,12 @@ async function runAgentTurn(history) {
1148
1164
  // Line 2 (indented): the SPINNER + the playful word together, both in the cycling
1149
1165
  // color — the spinner belongs with the word (dot on the label line, spinner leading
1150
1166
  // the flavor line).
1151
- // Task #83: after the playful word, ALWAYS show the running agent-code-model token
1152
- // count (dim), e.g. " ⠙ Caffeinating… · 12.3k tok" starts at "· 0 tok" and climbs
1153
- // as steps send more, so it's visible from the first thinking frame.
1154
- const tokLabel = dim(` · ${fmtTokens(latestCodeTokens)} tok`);
1167
+ // Task #83/#84: after the playful word, show the running agent-code-model token
1168
+ // count (dim) plus the per-workspace PEAK single-request count (orange), e.g.
1169
+ // " ⠙ Caffeinating… · 12.3k tok · 8.1k max". Both start at 0 and climb.
1170
+ const tokLabel =
1171
+ dim(` · ${fmtTokens(latestCodeTokens)} tok`) +
1172
+ orange(` · ${fmtTokens(sessionMaxCodeTokens)} max`);
1155
1173
  process.stdout.write(
1156
1174
  `${green(BULLET)} ${green(`${fit(primary)}… (${secs}s)`)}` +
1157
1175
  "\n" + ` ${color256(wc, `${glyph} ${thinkWord}…`)}${tokLabel}`
@@ -1424,6 +1442,10 @@ async function runAgentTurn(history) {
1424
1442
  if (Number.isFinite(job.agentCodeTokens) && job.agentCodeTokens > latestCodeTokens) {
1425
1443
  latestCodeTokens = job.agentCodeTokens;
1426
1444
  }
1445
+ // Peak single-request tokens (task #84): keep the per-workspace max across turns.
1446
+ if (Number.isFinite(job.agentMaxCodeTokens) && job.agentMaxCodeTokens > sessionMaxCodeTokens) {
1447
+ sessionMaxCodeTokens = job.agentMaxCodeTokens;
1448
+ }
1427
1449
  const text = typeof job.resultText === "string" ? job.resultText : "";
1428
1450
  if (job.jobStatus === "completed") {
1429
1451
  // The job can flip to "completed" between polls with authoritative resultText that
@@ -1541,6 +1563,7 @@ async function main() {
1541
1563
  // reflect them (both survive a fresh terminal in the same project).
1542
1564
  sessionTestAuto = await loadSessionTestAuto(resolve(process.cwd()));
1543
1565
  selectedServer = await loadSessionServer(resolve(process.cwd()));
1566
+ sessionMaxCodeTokens = await loadSessionMaxCodeTokens(resolve(process.cwd()));
1544
1567
  // Show which models will answer, straight from user settings (also validates auth).
1545
1568
  try {
1546
1569
  const probe = await fetchAgentPayload([{ role: "user", content: "ping" }]);
@@ -3940,6 +3940,9 @@ def run_agent_chat(cfg):
3940
3940
  # turn (task #83). Only this wrapper counts — the chat/brain helper calls
3941
3941
  # (_chat_completion) don't route through here, so this is "code model only".
3942
3942
  self._code_tokens_total = 0
3943
+ # Largest SINGLE-request prompt-token count seen this turn (task #84) — the
3944
+ # peak context size, not the sum. The REPL keeps a per-workspace max of this.
3945
+ self._code_tokens_max = 0
3943
3946
  # stream_agent: request the completion with stream=True and reassemble the
3944
3947
  # deltas into one message. Enabled for Ollama, where a long single-shot
3945
3948
  # (non-stream) generation sends nothing until the very end — a reverse proxy
@@ -4182,6 +4185,19 @@ def run_agent_chat(cfg):
4182
4185
  # _mark_first), read by the heartbeat below to switch its wording.
4183
4186
  self._first_token_seen = False
4184
4187
 
4188
+ # Task #83: emit the PROMPT-token estimate NOW, at request time — so the
4189
+ # code-model token count climbs the instant a step is SENT, not only after a
4190
+ # slow step returns usage (a cold gemma4 can wait minutes for the first token,
4191
+ # during which the count would otherwise sit at 0). We reconcile to the exact
4192
+ # server-reported prompt_tokens after the call when it's higher.
4193
+ _step_est = self._estimate_prompt_tokens(args, kwargs)
4194
+ if _step_est > 0:
4195
+ self._code_tokens_total += _step_est
4196
+ if _step_est > self._code_tokens_max:
4197
+ self._code_tokens_max = _step_est
4198
+ _emit({"type": "tokens", "codeInput": self._code_tokens_total,
4199
+ "codeMax": self._code_tokens_max, "stepInput": _step_est})
4200
+
4185
4201
  def _heartbeat():
4186
4202
  waited = 0
4187
4203
  while not hb_stop.wait(45):
@@ -4226,20 +4242,21 @@ def run_agent_chat(cfg):
4226
4242
  raise last_err
4227
4243
  finally:
4228
4244
  hb_stop.set()
4229
- # Task #83: tally PROMPT tokens sent to the code model this step, then emit
4230
- # the running total so the worker can persist it (Mongo) and the REPL can
4231
- # show it live. Best-effort never let accounting break the run.
4245
+ # Task #83: reconcile the pre-call estimate with the server's EXACT
4246
+ # prompt_tokens when it's higher — keeps the running total monotonic (the
4247
+ # estimate already showed the count climbing at request time). Best-effort.
4232
4248
  try:
4233
4249
  tu = getattr(msg, "token_usage", None)
4234
- step_in = int(getattr(tu, "input_tokens", 0) or 0) if tu else 0
4235
- if step_in <= 0:
4236
- step_in = self._estimate_prompt_tokens(args, kwargs)
4237
- if step_in > 0:
4238
- self._code_tokens_total += step_in
4250
+ exact = int(getattr(tu, "input_tokens", 0) or 0) if tu else 0
4251
+ if exact > _step_est:
4252
+ self._code_tokens_total += (exact - _step_est)
4253
+ if exact > self._code_tokens_max:
4254
+ self._code_tokens_max = exact
4239
4255
  _emit({
4240
4256
  "type": "tokens",
4241
4257
  "codeInput": self._code_tokens_total,
4242
- "stepInput": step_in,
4258
+ "codeMax": self._code_tokens_max,
4259
+ "stepInput": exact,
4243
4260
  })
4244
4261
  except Exception: # noqa: BLE001
4245
4262
  pass
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.252",
3
+ "version": "1.0.253",
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",