@tiens.nguyen/gonext-local-worker 1.0.160 → 1.0.162

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.
@@ -2325,6 +2325,26 @@ def run_agent_chat(cfg):
2325
2325
  in_tok = out_tok = reasoning_len = 0
2326
2326
  first_token_at = None
2327
2327
 
2328
+ # Runaway guard: a degenerate model (seen live: gemma4:26b spewing
2329
+ # "<channel|><thought>" forever) will stream garbage until the client times
2330
+ # out — a multi-minute hang. Abort early if the output either (a) balloons
2331
+ # past a hard char cap, or (b) collapses into a repeating short line. On abort
2332
+ # we return "" so the empty-turn retry re-prompts with the CODE-NOW directive.
2333
+ _MAX_STREAM_CHARS = 16000 # a single agent step never legitimately needs this
2334
+ _stream_buf = [] # every streamed piece (content + reasoning)
2335
+ _stream_chars = 0
2336
+ _runaway = False
2337
+
2338
+ def _looks_runaway() -> bool:
2339
+ tail = "".join(_stream_buf)[-1600:]
2340
+ lines = [ln.strip() for ln in tail.splitlines() if ln.strip()]
2341
+ # 12+ of the last lines are the same short string → degenerate repetition.
2342
+ if len(lines) >= 12:
2343
+ last = lines[-12:]
2344
+ if len(set(last)) <= 2 and all(len(ln) <= 80 for ln in last):
2345
+ return True
2346
+ return False
2347
+
2328
2348
  def _mark_first():
2329
2349
  # First streamed byte (content OR reasoning). The gap t0→here is pure
2330
2350
  # prompt-eval time (server sent nothing before this); everything after is
@@ -2362,13 +2382,33 @@ def run_agent_chat(cfg):
2362
2382
  if rpiece:
2363
2383
  _mark_first()
2364
2384
  reasoning_len += len(rpiece)
2385
+ _stream_buf.append(rpiece)
2386
+ _stream_chars += len(rpiece)
2365
2387
  _emit({"type": "stream", "text": rpiece})
2366
2388
  piece = getattr(delta, "content", None)
2367
2389
  if piece:
2368
2390
  _mark_first()
2369
2391
  parts.append(piece)
2392
+ _stream_buf.append(piece)
2393
+ _stream_chars += len(piece)
2370
2394
  _emit({"type": "stream", "text": piece})
2395
+ # Check the runaway guard periodically (cheap) once enough has streamed.
2396
+ if _stream_chars > 500 and (_stream_chars > _MAX_STREAM_CHARS or _looks_runaway()):
2397
+ _runaway = True
2398
+ reason = "char cap" if _stream_chars > _MAX_STREAM_CHARS else "repeating output"
2399
+ _log(f"streamed generate: ABORTING ({reason}) after {_stream_chars} chars "
2400
+ "— model degenerated; closing stream.")
2401
+ try:
2402
+ stream.close()
2403
+ except Exception: # noqa: BLE001
2404
+ pass
2405
+ break
2371
2406
  content = "".join(parts)
2407
+ if _runaway:
2408
+ # Discard the garbage; "" makes _streamed_generate run the CODE-NOW retry.
2409
+ _emit({"type": "step", "text": "Model output ran away — restarting this step…"})
2410
+ _log(f"streamed generate: discarded {len(content)} runaway chars → empty content")
2411
+ return "", role, in_tok, out_tok, reasoning_len
2372
2412
  _log(f"streamed generate assembled {len(content)} chars "
2373
2413
  f"(in={in_tok} out={out_tok} tokens, reasoning={reasoning_len} chars)")
2374
2414
  return content, role, in_tok, out_tok, reasoning_len
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.160",
3
+ "version": "1.0.162",
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",