@tiens.nguyen/gonext-local-worker 1.0.313 → 1.0.314

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 CHANGED
@@ -948,6 +948,11 @@ let cancelRequested = false; // a cancel POST has been sent for the current turn
948
948
  let approvalActive = false;
949
949
  let approvalSel = 0; // 0 = Yes (default highlighted), 1 = No
950
950
  let approvalResolve = null;
951
+ // The active turn's live-status wiper (its local clearStatus), or null between turns. A
952
+ // picker calls this before drawing so the "Almost done…/thought/token" block is gone
953
+ // first — otherwise the 120ms ticker keeps redrawing that block on top of the picker and
954
+ // its relative-cursor math lands on the wrong rows (duplicated/interleaved options).
955
+ let clearActiveStatus = null;
951
956
  function drawApprovalOptions(redraw) {
952
957
  // Two option lines, redrawn in place. On redraw the cursor sits one row below "No";
953
958
  // step up 2 rows, clear+rewrite both, landing back where we started.
@@ -986,6 +991,10 @@ const MAXSTEP_PREFIX = "__MAXSTEP__::";
986
991
  const TIMEOUT_PREFIX = "__TIMEOUT__::";
987
992
  const COMPACT_PREFIX = "__COMPACT__::";
988
993
  async function approvalPrompt(command) {
994
+ // Wipe the live "thinking" status block BEFORE any picker draws, and stop the ticker from
995
+ // redrawing it (the tick early-returns while a picker is active) — otherwise the block
996
+ // reappears under the options and corrupts the layout.
997
+ if (clearActiveStatus) clearActiveStatus();
989
998
  // Context-compaction prompt (task #87 rec#3): the running context got large. Ask whether
990
999
  // to aggressively compact older steps. No answer in 30s = No (keep full detail — the safe
991
1000
  // default; the always-on auto-trim still bounds things).
@@ -1463,10 +1472,11 @@ async function runAgentTurn(history) {
1463
1472
  process.stdout.write(seq);
1464
1473
  statusShown = false;
1465
1474
  };
1475
+ clearActiveStatus = clearStatus; // let pickers wipe this turn's status block before they draw
1466
1476
 
1467
1477
  const tick = () => {
1468
1478
  if (!following || followAborted || !jobRunning) return;
1469
- if (approvalActive) return; // a Yes/No picker owns the screen — don't draw over it
1479
+ if (approvalActive || listPickerActive) return; // a picker owns the screen — don't draw over it
1470
1480
  if (Date.now() - lastContentAt < QUIET_MS) return; // tokens still flowing
1471
1481
  // A plain-reply answer streams onto a line with NO trailing newline until it's fully
1472
1482
  // done — overwriting the ticker there corrupts the visible answer mid-word. Stay
@@ -2009,6 +2019,7 @@ async function runAgentTurn(history) {
2009
2019
  } finally {
2010
2020
  clearInterval(ticker);
2011
2021
  clearStatus();
2022
+ clearActiveStatus = null; // no turn owns the status wiper between turns
2012
2023
  // Task #96: stop mouse-reporting the moment the turn ends (also on cancel/error — this
2013
2024
  // finally always runs), and detach the click parser, so the user's shell selection is
2014
2025
  // never captured between turns.
@@ -5071,20 +5071,40 @@ def run_agent_chat(cfg):
5071
5071
  """Recover a <code>…</code> tool call that a reasoning model placed in the
5072
5072
  REASONING channel instead of the visible message (task #111). No-op when the
5073
5073
  visible content already has a usable code block, or when there is no reasoning
5074
- text (e.g. Ollama with reasoning_effort='none') — so the Ollama path is unchanged."""
5074
+ text (e.g. Ollama with reasoning_effort='none') — so the Ollama path is unchanged.
5075
+
5076
+ Kimi K3 @ Moonshot splits its output two ways when it fails: (a) the full
5077
+ <code>…</code> block lands in reasoning and only prose leaks to content; or
5078
+ (b) the OPENER + code land in reasoning while the CLOSING </code> leaks into
5079
+ content (seen live: content = 'Thought: …contains.</code>'). Handle both."""
5075
5080
  _pair = r"<code[^>]*>[\s\S]*?</code>"
5076
5081
  if re.search(_pair, content or "", re.I):
5077
- return content # already parseable
5078
- if not (reasoning or "").strip():
5082
+ return content # content already has a usable block — never override it
5083
+ reasoning = reasoning or ""
5084
+ if not reasoning.strip():
5079
5085
  return content # nothing to recover from (Ollama / non-reasoning model)
5080
- # Take the LAST code block in the combined stream (reasoning usually holds it).
5081
- combined = (reasoning or "") + "\n" + (content or "")
5086
+ code = None
5087
+ # (a) Prefer a COMPLETE block already inside reasoning (the model drafts the
5088
+ # whole tool call there) — take the LAST one, ignoring any stray </code> in content.
5082
5089
  last = None
5083
- for last in re.finditer(r"<code[^>]*>([\s\S]*?)</code>", combined, re.I):
5090
+ for last in re.finditer(r"<code[^>]*>([\s\S]*?)</code>", reasoning, re.I):
5084
5091
  pass
5085
- if not last:
5092
+ if last:
5093
+ code = last.group(1)
5094
+ else:
5095
+ # (b) Opener + code in reasoning, closer leaked to content → take everything
5096
+ # after the LAST opener in reasoning (its </code> is the one now in content).
5097
+ m = None
5098
+ for m in re.finditer(r"<code[^>]*>([\s\S]*)$", reasoning, re.I):
5099
+ pass
5100
+ if m:
5101
+ code = m.group(1)
5102
+ if code is None:
5086
5103
  return content
5087
- code = (last.group(1) or "").strip()
5104
+ # A leaked 'Thought:' line (or a stray </code>) can ride along after the code —
5105
+ # cut it so only the executable snippet remains.
5106
+ code = re.split(r"\n\s*Thought\s*:\s", code, maxsplit=1)[0]
5107
+ code = re.sub(r"</?code[^>]*>", "", code, flags=re.I).strip()
5088
5108
  if not code:
5089
5109
  return content
5090
5110
  # Keep the visible Thought prose (strip any stray/unbalanced code tags), then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.313",
3
+ "version": "1.0.314",
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",