@tiens.nguyen/gonext-local-worker 1.0.259 → 1.0.261
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 +30 -2
- package/gonext_agent_chat.py +164 -15
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -1025,13 +1025,20 @@ const normalizeThought = (buf) => {
|
|
|
1025
1025
|
const t = String(buf || "");
|
|
1026
1026
|
const ci = t.search(/<code[\s>]/i);
|
|
1027
1027
|
const head = ci >= 0 ? t.slice(0, ci) : t;
|
|
1028
|
-
// 1) a genuine Thought prose line (before the code) — only if it isn't itself code
|
|
1028
|
+
// 1) a genuine Thought prose line (before the code) — only if it isn't itself code AND
|
|
1029
|
+
// reads like an actual clause. A streaming token boundary can leave the first
|
|
1030
|
+
// non-empty line as a single bare word (e.g. "string", when the model is rambling
|
|
1031
|
+
// about "multi-line strings"); shown alone on the blinking line it's meaningless and,
|
|
1032
|
+
// because liveThought only updates on a truthy value, it can FREEZE there through a
|
|
1033
|
+
// long silent prompt-eval ("string… (777s)"). Require ≥2 words and a little length so
|
|
1034
|
+
// a lone fragment falls through to the tool label / "Thinking…" instead.
|
|
1029
1035
|
const prose = head
|
|
1030
1036
|
.replace(/^\s*Thought:\s*/i, "")
|
|
1031
1037
|
.split(/\n/)
|
|
1032
1038
|
.map((x) => x.trim())
|
|
1033
1039
|
.find(Boolean);
|
|
1034
|
-
|
|
1040
|
+
const proseIsClause = !!prose && prose.length >= 6 && prose.trim().split(/\s+/).length >= 2;
|
|
1041
|
+
if (proseIsClause && !_thoughtLooksCodey(prose)) return prose.slice(0, 100);
|
|
1035
1042
|
// 2) otherwise name the tool being called (from the code part).
|
|
1036
1043
|
const codePart = ci >= 0 ? t.slice(ci) : t;
|
|
1037
1044
|
const m = /(?:<code>\s*)?\b([a-z_]\w*)\s*\(\s*(?:path\s*=\s*)?["']?([^"'\n,)]*)/i.exec(codePart);
|
|
@@ -1117,6 +1124,7 @@ async function runAgentTurn(history) {
|
|
|
1117
1124
|
// "◐ working… (7s)" — instead of dumping the model's rambling reasoning and stamping a
|
|
1118
1125
|
// "✔ completed thought" on every token-stream micro-pause (the reported clutter).
|
|
1119
1126
|
const QUIET_MS = 700; // stream idle this long ⇒ we're waiting on the model
|
|
1127
|
+
const STALE_THOUGHT_MS = 25000; // no new tokens this long ⇒ the current Thought clause is stale (#95 A)
|
|
1120
1128
|
let lastContentAt = Date.now();
|
|
1121
1129
|
let thinking = false;
|
|
1122
1130
|
let thinkingSince = 0;
|
|
@@ -1154,6 +1162,11 @@ async function runAgentTurn(history) {
|
|
|
1154
1162
|
thinking = true; thinkingSince = lastContentAt; thinkWord = pickWord(); lastWordBucket = 0;
|
|
1155
1163
|
}
|
|
1156
1164
|
const now = Date.now();
|
|
1165
|
+
// Age out a stale Thought clause: if the model has produced NO new stream content for a
|
|
1166
|
+
// while (e.g. a 150s+ silent prompt-eval on a huge context), the last clause is no longer
|
|
1167
|
+
// "what it's doing now" — drop it so the line falls back to the playful word / "Thinking…"
|
|
1168
|
+
// instead of freezing on an old thought while the timer climbs ("string… (777s)", #95 A).
|
|
1169
|
+
if (liveThought && now - lastContentAt > STALE_THOUGHT_MS) liveThought = "";
|
|
1157
1170
|
const secs = thinkSecs();
|
|
1158
1171
|
// Rotate the playful word once per 12s window (guarded so the fast ticker doesn't
|
|
1159
1172
|
// re-roll it many times within the same second).
|
|
@@ -1750,6 +1763,18 @@ async function main() {
|
|
|
1750
1763
|
console.log(shown ? "" : `\n\n${answer}\n`);
|
|
1751
1764
|
} else {
|
|
1752
1765
|
history.pop(); // aborted/failed/cancelled turn — don't poison the next request
|
|
1766
|
+
// A turn that ended WITHOUT an answer (Ctrl+C cancel, or an abort/failure) must not
|
|
1767
|
+
// silently launch another turn. Any keystrokes the user typed DURING the dead turn
|
|
1768
|
+
// sit in `_lineQueue` — they land there (not ignored) whenever `following` was still
|
|
1769
|
+
// false, e.g. during the agent-ask round-trip at the very start of the turn, or
|
|
1770
|
+
// while the user was mashing "continue"/Enter. If left queued, `nextLine()` drains
|
|
1771
|
+
// them back-to-back on the next loop iterations, each auto-firing a fresh turn that
|
|
1772
|
+
// a straggler Ctrl+C then lands on — the reported "every turn immediately shows
|
|
1773
|
+
// (cancelled)" loop. Drop that buffered input so the NEXT turn only starts when the
|
|
1774
|
+
// user deliberately types something new (bug #92). Resume via "continue" still works:
|
|
1775
|
+
// that's typed AFTER the cancel note, so it arrives through the live waiter, not the
|
|
1776
|
+
// queue.
|
|
1777
|
+
if (_lineQueue.length > 0) _lineQueue.length = 0;
|
|
1753
1778
|
// Ctrl+C cancel already printed its own "(cancelled)" note in runAgentTurn — a
|
|
1754
1779
|
// second "(no answer)" line here would be redundant/misleading.
|
|
1755
1780
|
if (cancelled) {
|
|
@@ -1763,6 +1788,9 @@ async function main() {
|
|
|
1763
1788
|
}
|
|
1764
1789
|
} catch (err) {
|
|
1765
1790
|
history.pop();
|
|
1791
|
+
// Same reasoning as the no-answer branch (bug #92): don't let input buffered during a
|
|
1792
|
+
// turn that just threw auto-fire the next one.
|
|
1793
|
+
if (_lineQueue.length > 0) _lineQueue.length = 0;
|
|
1766
1794
|
console.error(red(`\ngonext: ${err.message}\n`));
|
|
1767
1795
|
}
|
|
1768
1796
|
}
|
package/gonext_agent_chat.py
CHANGED
|
@@ -1077,6 +1077,35 @@ def _plain_reply(messages: list, base_url: str, api_key: str, model_id: str,
|
|
|
1077
1077
|
raise RuntimeError(f"couldn't get a response ({_clip(str(last_err), 160)})")
|
|
1078
1078
|
|
|
1079
1079
|
|
|
1080
|
+
# A model-server call can fail for two very different reasons, and they deserve
|
|
1081
|
+
# different handling:
|
|
1082
|
+
# • the BACKEND is unavailable — an nginx 502/503/504 (the upstream Ollama/MLX box is
|
|
1083
|
+
# down, restarting, still loading the weights, or overloaded past the proxy's read
|
|
1084
|
+
# timeout), or the socket refused/timed out. These are INFRA outages: retrying a bit
|
|
1085
|
+
# harder can ride out a blip, and if it persists the honest thing is to SAY the
|
|
1086
|
+
# coding backend is down — never to hallucinate a generic answer as if we succeeded.
|
|
1087
|
+
# • a genuine model/agent error — worth the plain-reply degrade.
|
|
1088
|
+
# `str(e)` for a proxy error is the raw nginx HTML ("<html>…502 Bad Gateway…"), so match
|
|
1089
|
+
# on the status text/codes and the common connection-failure phrases.
|
|
1090
|
+
def _is_backend_unavailable(err) -> bool:
|
|
1091
|
+
s = str(err or "").lower()
|
|
1092
|
+
return (
|
|
1093
|
+
"502 bad gateway" in s
|
|
1094
|
+
or "503 service" in s
|
|
1095
|
+
or "504 gateway" in s
|
|
1096
|
+
or "bad gateway" in s
|
|
1097
|
+
or "gateway time-out" in s
|
|
1098
|
+
or "gateway timeout" in s
|
|
1099
|
+
or "connection refused" in s
|
|
1100
|
+
or "connection reset" in s
|
|
1101
|
+
or "connection aborted" in s
|
|
1102
|
+
or "failed to establish a new connection" in s
|
|
1103
|
+
or "max retries exceeded" in s
|
|
1104
|
+
or "read timed out" in s
|
|
1105
|
+
or "timed out" in s
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
|
|
1080
1109
|
def _synthesize_document(gathered: list, task: str, base_url: str, api_key: str,
|
|
1081
1110
|
model_id: str) -> str:
|
|
1082
1111
|
"""Turn raw gathered research observations into ONE clean, organized document body
|
|
@@ -1179,11 +1208,19 @@ def _normalize_code_tags(text: str) -> str:
|
|
|
1179
1208
|
return fixed
|
|
1180
1209
|
|
|
1181
1210
|
|
|
1182
|
-
def _escape_raw_newlines_in_strings(code: str) -> str:
|
|
1211
|
+
def _escape_raw_newlines_in_strings(code: str, defuse_tags: bool = False) -> str:
|
|
1183
1212
|
"""Turn RAW newlines/tabs that sit INSIDE a single/double-quoted string literal into
|
|
1184
1213
|
\\n/\\t escapes. Triple-quoted strings, comments, and everything outside a string are
|
|
1185
1214
|
copied verbatim. A tiny hand-rolled scanner (the code doesn't parse, so ast/tokenize
|
|
1186
|
-
can't help). Used
|
|
1215
|
+
can't help). Used by _repair_unterminated_string below.
|
|
1216
|
+
|
|
1217
|
+
defuse_tags: also rewrite a literal <code>/</code> that appears INSIDE a string literal
|
|
1218
|
+
(e.g. the file content the model is writing legitimately contains React's default
|
|
1219
|
+
"Edit <code>src/App.js</code>") to <co\\x64e>/</co\\x64e>. The `\\x64` is Python's hex
|
|
1220
|
+
escape for 'd', so at RUNTIME the string is still exactly "<code>"/"</code>" — but the
|
|
1221
|
+
literal source no longer contains the token smolagents uses as its code-block delimiter,
|
|
1222
|
+
so its parser can't mis-close the block on content. Only touches tags inside strings;
|
|
1223
|
+
the real closing </code> lives OUTSIDE any string and is left intact."""
|
|
1187
1224
|
out = []
|
|
1188
1225
|
i, n = 0, len(code)
|
|
1189
1226
|
quote = None # the delimiter of the string we're inside, or None
|
|
@@ -1196,7 +1233,10 @@ def _escape_raw_newlines_in_strings(code: str) -> str:
|
|
|
1196
1233
|
end = code.find(three, i + 3)
|
|
1197
1234
|
if end == -1:
|
|
1198
1235
|
out.append(code[i:]); break
|
|
1199
|
-
|
|
1236
|
+
seg = code[i:end + 3]
|
|
1237
|
+
if defuse_tags:
|
|
1238
|
+
seg = seg.replace("</code>", "</co\\x64e>").replace("<code>", "<co\\x64e>")
|
|
1239
|
+
out.append(seg); i = end + 3; continue
|
|
1200
1240
|
if ch == "#": # comment — copy to end of line
|
|
1201
1241
|
j = code.find("\n", i)
|
|
1202
1242
|
if j == -1:
|
|
@@ -1212,6 +1252,11 @@ def _escape_raw_newlines_in_strings(code: str) -> str:
|
|
|
1212
1252
|
out.append(ch); escaped = True; i += 1; continue
|
|
1213
1253
|
if ch == quote:
|
|
1214
1254
|
out.append(ch); quote = None; i += 1; continue
|
|
1255
|
+
# Defuse a delimiter-colliding tag INSIDE the string (see docstring).
|
|
1256
|
+
if defuse_tags and code.startswith("</code>", i):
|
|
1257
|
+
out.append("</co\\x64e>"); i += 7; continue
|
|
1258
|
+
if defuse_tags and code.startswith("<code>", i):
|
|
1259
|
+
out.append("<co\\x64e>"); i += 6; continue
|
|
1215
1260
|
out.append({"\n": "\\n", "\r": "\\r", "\t": "\\t"}.get(ch, ch)); i += 1
|
|
1216
1261
|
return "".join(out)
|
|
1217
1262
|
|
|
@@ -1242,15 +1287,42 @@ def _repair_unterminated_string(code: str) -> str:
|
|
|
1242
1287
|
def _repair_code_block_strings(text: str) -> str:
|
|
1243
1288
|
"""Apply _repair_unterminated_string to the python inside a <code>…</code> block
|
|
1244
1289
|
(leaving the surrounding Thought prose untouched). No-op when there's no block or the
|
|
1245
|
-
block already parses.
|
|
1246
|
-
|
|
1247
|
-
|
|
1290
|
+
block already parses.
|
|
1291
|
+
|
|
1292
|
+
Two-stage rescue:
|
|
1293
|
+
1. NON-GREEDY span (first <code> → first </code>) + raw-newline escaping — the common
|
|
1294
|
+
case (multi-line file content with unescaped newlines).
|
|
1295
|
+
2. If that still won't compile AND the content itself contains extra </code> tags
|
|
1296
|
+
(the model is writing a file whose CONTENT legitimately has <code>/</code> — e.g.
|
|
1297
|
+
React's default App.js "Edit <code>src/App.js</code>"), the non-greedy match closed
|
|
1298
|
+
the block early on that inner tag → a truncated, unterminatable fragment. Retry with
|
|
1299
|
+
the GREEDY span (first <code> → LAST </code>) and defuse the in-string tags so the
|
|
1300
|
+
only real </code> left is the true closer. This is the fix for the create_file loop
|
|
1301
|
+
where the coder burned minutes/100k+ tokens on 'unterminated string literal'."""
|
|
1302
|
+
om = re.search(r"<code>", text)
|
|
1303
|
+
if not om:
|
|
1248
1304
|
return text
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
if
|
|
1305
|
+
open_end = om.end()
|
|
1306
|
+
cm = re.search(r"</code>", text[open_end:])
|
|
1307
|
+
if not cm:
|
|
1252
1308
|
return text
|
|
1253
|
-
|
|
1309
|
+
close_start = open_end + cm.start()
|
|
1310
|
+
inner = text[open_end:close_start]
|
|
1311
|
+
fixed = _repair_unterminated_string(inner)
|
|
1312
|
+
if fixed != inner:
|
|
1313
|
+
return text[:open_end] + fixed + text[close_start:]
|
|
1314
|
+
# Stage 2 — only worth trying when there's MORE than one </code> (extra tags in content).
|
|
1315
|
+
last = text.rfind("</code>")
|
|
1316
|
+
if last <= close_start:
|
|
1317
|
+
return text # single </code>; nothing a greedy re-read would change
|
|
1318
|
+
greedy = text[open_end:last]
|
|
1319
|
+
sanitized = _escape_raw_newlines_in_strings(greedy, defuse_tags=True)
|
|
1320
|
+
try:
|
|
1321
|
+
compile(sanitized, "<gonext-code>", "exec")
|
|
1322
|
+
except Exception: # noqa: BLE001
|
|
1323
|
+
return text # still not parseable — leave it for the loop-breaker / model retry
|
|
1324
|
+
# Rebuild so the FIRST literal </code> is now the true closer (inner ones are defused).
|
|
1325
|
+
return text[:open_end] + sanitized + text[last:]
|
|
1254
1326
|
|
|
1255
1327
|
|
|
1256
1328
|
def _strip_think(text: str) -> str:
|
|
@@ -4173,6 +4245,12 @@ def run_agent_chat(cfg):
|
|
|
4173
4245
|
# Largest SINGLE-request prompt-token count seen this turn (task #84) — the
|
|
4174
4246
|
# peak context size, not the sum. The REPL keeps a per-workspace max of this.
|
|
4175
4247
|
self._code_tokens_max = 0
|
|
4248
|
+
# Loop-breaker (#95 B1): how many CONSECUTIVE generations have produced a code
|
|
4249
|
+
# block that STILL won't compile after our repair. A weak coder can get stuck
|
|
4250
|
+
# re-emitting the same unparseable tool call (seen live: create_file with a
|
|
4251
|
+
# multi-line string, ~10 identical retries, 863s/133k tokens). After a few in a
|
|
4252
|
+
# row we stop feeding smolagents an un-runnable block and finish honestly.
|
|
4253
|
+
self._parse_fail_streak = 0
|
|
4176
4254
|
# stream_agent: request the completion with stream=True and reassemble the
|
|
4177
4255
|
# deltas into one message. Enabled for Ollama, where a long single-shot
|
|
4178
4256
|
# (non-stream) generation sends nothing until the very end — a reverse proxy
|
|
@@ -4445,9 +4523,16 @@ def run_agent_chat(cfg):
|
|
|
4445
4523
|
hb.start()
|
|
4446
4524
|
last_err = None
|
|
4447
4525
|
try:
|
|
4448
|
-
|
|
4526
|
+
# A gateway outage (502/503/504, refused/reset/timeout) is transient infra —
|
|
4527
|
+
# the remote box may just be cold or the proxy briefly down — so give it MORE
|
|
4528
|
+
# attempts and a longer backoff to ride out a blip, instead of the 3 quick
|
|
4529
|
+
# tries a normal error gets. The attempt count is decided per-error: we start
|
|
4530
|
+
# with the generic budget and extend it the moment we see a gateway error.
|
|
4531
|
+
attempt, max_attempts = 0, 3
|
|
4532
|
+
while attempt < max_attempts:
|
|
4449
4533
|
try:
|
|
4450
4534
|
msg = self._generate_once(*args, **kwargs)
|
|
4535
|
+
last_err = None
|
|
4451
4536
|
break
|
|
4452
4537
|
except Exception as e: # noqa: BLE001
|
|
4453
4538
|
emsg = str(e)
|
|
@@ -4465,10 +4550,19 @@ def run_agent_chat(cfg):
|
|
|
4465
4550
|
"names (for Ollama use the tag, e.g. qwen3:14b)."
|
|
4466
4551
|
) from e
|
|
4467
4552
|
last_err = e
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4553
|
+
gateway = _is_backend_unavailable(e)
|
|
4554
|
+
if gateway and max_attempts < 6:
|
|
4555
|
+
max_attempts = 6 # stretch the budget for a flaky backend
|
|
4556
|
+
attempt += 1
|
|
4557
|
+
if attempt < max_attempts:
|
|
4558
|
+
# Longer, capped backoff for gateway errors (up to ~8s) so a
|
|
4559
|
+
# cold/loading model has time to come back; short for the rest.
|
|
4560
|
+
delay = min(8.0, 2.0 * attempt) if gateway else 1.5 * attempt
|
|
4561
|
+
kind = "gateway/backend unavailable" if gateway else "transient"
|
|
4562
|
+
_log(f"generate {kind} error (attempt {attempt}/{max_attempts}): "
|
|
4563
|
+
f"{_clip(emsg, 160)} — retrying in {delay:.0f}s")
|
|
4564
|
+
time.sleep(delay)
|
|
4565
|
+
if last_err is not None:
|
|
4472
4566
|
raise last_err
|
|
4473
4567
|
finally:
|
|
4474
4568
|
hb_stop.set()
|
|
@@ -4532,7 +4626,42 @@ def run_agent_chat(cfg):
|
|
|
4532
4626
|
_log("repaired unterminated string literal in tool-call code")
|
|
4533
4627
|
if repaired != content:
|
|
4534
4628
|
msg.content = repaired
|
|
4629
|
+
# Loop-breaker (#95 B1): if the block STILL won't compile after the
|
|
4630
|
+
# repair, the model is stuck emitting an unparseable tool call. Count
|
|
4631
|
+
# consecutive failures; after a few, finish honestly rather than burn
|
|
4632
|
+
# the whole step budget — each retry is a full (often 150s+) model
|
|
4633
|
+
# call on an ever-growing context. B2's greedy/defuse fix means the
|
|
4634
|
+
# <code>-in-content collision now compiles here, so this only trips on
|
|
4635
|
+
# genuinely un-fixable code (e.g. a truncated generation).
|
|
4636
|
+
_cb = re.search(r"<code>([\s\S]*?)</code>", msg.content or "")
|
|
4637
|
+
_still_bad = False
|
|
4638
|
+
if _cb:
|
|
4639
|
+
try:
|
|
4640
|
+
compile(_cb.group(1), "<gonext-code>", "exec")
|
|
4641
|
+
except Exception: # noqa: BLE001
|
|
4642
|
+
_still_bad = True
|
|
4643
|
+
if _still_bad:
|
|
4644
|
+
self._parse_fail_streak += 1
|
|
4645
|
+
_log("tool-call code still unparseable after repair "
|
|
4646
|
+
f"(streak {self._parse_fail_streak}/3)")
|
|
4647
|
+
if self._parse_fail_streak >= 3:
|
|
4648
|
+
_log("parse-fail streak hit limit → ending the turn honestly "
|
|
4649
|
+
"instead of looping on an unparseable tool call")
|
|
4650
|
+
_stuck_msg = (
|
|
4651
|
+
"I got stuck on a code-formatting error: my edit to the "
|
|
4652
|
+
"file kept failing to parse (a string/escaping issue in the "
|
|
4653
|
+
"tool call), so I stopped instead of retrying in a loop. "
|
|
4654
|
+
"Any dev server I started is still running. Please try "
|
|
4655
|
+
"again, or tell me the specific change to make and I'll "
|
|
4656
|
+
"keep it to a small, single-line edit."
|
|
4657
|
+
)
|
|
4658
|
+
msg.content = ("<code>\nfinal_answer("
|
|
4659
|
+
+ repr(_stuck_msg) + ")\n</code>")
|
|
4660
|
+
self._parse_fail_streak = 0
|
|
4661
|
+
else:
|
|
4662
|
+
self._parse_fail_streak = 0
|
|
4535
4663
|
else:
|
|
4664
|
+
self._parse_fail_streak = 0 # a prose turn isn't a parse failure
|
|
4536
4665
|
stripped = content.strip()
|
|
4537
4666
|
looks_final = (
|
|
4538
4667
|
len(stripped) >= 400 or re.search(
|
|
@@ -5778,6 +5907,26 @@ def run_agent_chat(cfg):
|
|
|
5778
5907
|
_log(f"agent config error (not degrading): {cfg_err}")
|
|
5779
5908
|
_emit({"type": "final", "text": f"⚠️ {cfg_err}"})
|
|
5780
5909
|
return
|
|
5910
|
+
# Coding-model BACKEND outage (502/503/504, refused/timeout): the tool-capable
|
|
5911
|
+
# loop never got to run because its code model was unreachable. Do NOT fall through
|
|
5912
|
+
# to _plain_reply — a bare chat model would stream a confident GENERIC how-to (e.g.
|
|
5913
|
+
# "to create a React app, run npx create-react-app…") that looks like a real answer
|
|
5914
|
+
# but did NOTHING the user asked (seen live: "create a reactjs and start it" → the
|
|
5915
|
+
# coder 502'd 3× → generic instructions, no warning). That's worse than an honest
|
|
5916
|
+
# failure: it hides the outage and, being persisted to history, misleads the next
|
|
5917
|
+
# turn. Emit a clear, standalone "backend is down, retry" and stop — the checkpoint
|
|
5918
|
+
# is kept (not cleared) so a later "continue" resumes once the box is back.
|
|
5919
|
+
if _is_backend_unavailable(e):
|
|
5920
|
+
_log(f"agent error (coding-model backend unavailable, NOT degrading): {_clip(str(e), 160)}")
|
|
5921
|
+
_emit({"type": "final", "text": (
|
|
5922
|
+
"⚠️ The coding model backend is currently unavailable (the server returned "
|
|
5923
|
+
"a gateway error / timed out), so I couldn't run the tools to do this. It's "
|
|
5924
|
+
"usually starting up, reloading the model, or briefly overloaded — not a "
|
|
5925
|
+
"problem with your request. Please try again in a moment; if it keeps "
|
|
5926
|
+
"happening, check that the coding model server is up. (I didn't make any "
|
|
5927
|
+
"changes.)"
|
|
5928
|
+
)})
|
|
5929
|
+
return
|
|
5781
5930
|
# An unexpected failure inside the agent loop should still return a useful
|
|
5782
5931
|
# answer from the conversation rather than surfacing a raw error to the user —
|
|
5783
5932
|
# but only when the fallback ACTUALLY has something useful to say.
|
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.261",
|
|
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",
|