@tiens.nguyen/gonext-local-worker 1.0.316 → 1.0.318
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 +28 -9
- package/gonext_agent_chat.py +56 -20
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -1182,6 +1182,10 @@ let jobIdForApprovalRace = "";
|
|
|
1182
1182
|
// Per-session coding-model override chosen via /model (empty = use the account default).
|
|
1183
1183
|
// Sent to /agent-ask as codingModelOverride; the API validates it against the whitelist.
|
|
1184
1184
|
let sessionCodingModel = "";
|
|
1185
|
+
// The default coding model id (from the startup agent-payload probe) — shown in the
|
|
1186
|
+
// per-turn token footer next to the GLOBAL output total so it's clear which model that
|
|
1187
|
+
// lifetime count belongs to. The /model override (sessionCodingModel) wins when set.
|
|
1188
|
+
let defaultCodingModel = "";
|
|
1185
1189
|
// Auto-test mode (/test-auto): when on, the agent verifies its work (build/run/curl/…)
|
|
1186
1190
|
// and fixes → re-tests until it passes before finishing. Default off; remembered per
|
|
1187
1191
|
// folder in the session file; sent to /agent-ask as autoTest so python drives the loop.
|
|
@@ -1937,13 +1941,20 @@ async function runAgentTurn(history) {
|
|
|
1937
1941
|
}
|
|
1938
1942
|
}
|
|
1939
1943
|
clearStatus();
|
|
1940
|
-
// Task #104: expose this turn's code-model INPUT +
|
|
1941
|
-
//
|
|
1944
|
+
// Task #104: expose this turn's code-model INPUT + the CUMULATIVE output-token
|
|
1945
|
+
// total (all turns for this user + coding server) so the caller can print a
|
|
1946
|
+
// persistent footer. Output is the running total (globalOutputTokens already holds
|
|
1947
|
+
// every prior turn; latestOutputTokens is THIS turn, not yet folded in because the
|
|
1948
|
+
// addOutputTokens POST above is async) — the same figure the live "↓" showed during
|
|
1949
|
+
// the turn, so it doesn't drop back to just this turn's count at the prompt (#112).
|
|
1950
|
+
// `turnOutputTokens` is kept separately so the caller's footer guard reflects whether
|
|
1951
|
+
// THIS turn used the code model (a plain-reply greeting = 0 → no footer).
|
|
1942
1952
|
return {
|
|
1943
1953
|
text: answerFrom(text),
|
|
1944
1954
|
shown: answerShownLive,
|
|
1945
1955
|
inputTokens: latestCodeTokens,
|
|
1946
|
-
outputTokens: latestOutputTokens,
|
|
1956
|
+
outputTokens: globalOutputTokens + latestOutputTokens,
|
|
1957
|
+
turnOutputTokens: latestOutputTokens,
|
|
1947
1958
|
};
|
|
1948
1959
|
}
|
|
1949
1960
|
if (text.length > shownChars) {
|
|
@@ -2053,6 +2064,8 @@ async function main() {
|
|
|
2053
2064
|
try {
|
|
2054
2065
|
const probe = await fetchAgentPayload([{ role: "user", content: "ping" }]);
|
|
2055
2066
|
const p = probe?.payload ?? {};
|
|
2067
|
+
// Remember the default coder id for the token footer's global-total label.
|
|
2068
|
+
if (p.codingModelId) defaultCodingModel = p.codingModelId;
|
|
2056
2069
|
// RAG storage choice (task #97): ask ONCE per folder, and only when RAG is actually
|
|
2057
2070
|
// enabled in Settings. Default Yes = LOCAL (kept on this machine, no S3 needed).
|
|
2058
2071
|
if (p.ragEnabled && sessionRagMode === null) {
|
|
@@ -2270,7 +2283,7 @@ async function main() {
|
|
|
2270
2283
|
}
|
|
2271
2284
|
history.push({ role: "user", content: taskLine });
|
|
2272
2285
|
try {
|
|
2273
|
-
const { text: answer, shown, cancelled, inputTokens, outputTokens } =
|
|
2286
|
+
const { text: answer, shown, cancelled, inputTokens, outputTokens, turnOutputTokens } =
|
|
2274
2287
|
await runAgentTurn(history);
|
|
2275
2288
|
if (answer) {
|
|
2276
2289
|
history.push({ role: "assistant", content: answer });
|
|
@@ -2281,15 +2294,21 @@ async function main() {
|
|
|
2281
2294
|
// A plain-reply answer already streamed live in the loop above — printing it
|
|
2282
2295
|
// again here would just duplicate it; a blank line is enough for spacing.
|
|
2283
2296
|
console.log(shown ? "" : `\n\n${answer}\n`);
|
|
2284
|
-
// Task #104: persistent
|
|
2285
|
-
//
|
|
2286
|
-
//
|
|
2287
|
-
|
|
2297
|
+
// Task #104: persistent token footer at the bottom of the screen — this turn's
|
|
2298
|
+
// code-model INPUT + this turn's OUTPUT (↓ out), then the model's GLOBAL lifetime
|
|
2299
|
+
// output total (all turns for this user + coding server), labelled with the model
|
|
2300
|
+
// name so it's clear which coder that running total belongs to. Guard on THIS turn's
|
|
2301
|
+
// activity so a plain-reply greeting that did no code-model work shows nothing (#112).
|
|
2302
|
+
if ((inputTokens ?? 0) > 0 || (turnOutputTokens ?? 0) > 0) {
|
|
2303
|
+
const coder = sessionCodingModel || defaultCodingModel;
|
|
2288
2304
|
console.log(
|
|
2289
2305
|
dim("tokens · ↑ in ") +
|
|
2290
2306
|
fmtTokens(inputTokens ?? 0) +
|
|
2291
2307
|
dim(" · ") +
|
|
2292
|
-
cyan(`↓ out ${fmtTokens(
|
|
2308
|
+
cyan(`↓ out ${fmtTokens(turnOutputTokens ?? 0)}`) +
|
|
2309
|
+
dim(" · ") +
|
|
2310
|
+
dim(`${coder ? coder + " " : ""}total `) +
|
|
2311
|
+
cyan(`↓ ${fmtTokens(outputTokens ?? 0)}`)
|
|
2293
2312
|
);
|
|
2294
2313
|
}
|
|
2295
2314
|
} else {
|
package/gonext_agent_chat.py
CHANGED
|
@@ -3177,15 +3177,32 @@ def run_agent_chat(cfg):
|
|
|
3177
3177
|
# Multi-step ReAct loop (thinking agent): the agent may take several
|
|
3178
3178
|
# Thought → tool → Observation steps and then call final_answer() itself. This
|
|
3179
3179
|
# replaced the old strict single-shot (max_steps=1) once a stronger coding model
|
|
3180
|
-
# (Qwen3-14B class) made real multi-step reasoning reliable.
|
|
3181
|
-
#
|
|
3182
|
-
#
|
|
3180
|
+
# (Qwen3-14B class) made real multi-step reasoning reliable. The default budget is
|
|
3181
|
+
# BACKEND-AWARE (a cloud OpenAI-compatible coder is stronger and cheaper-per-step than
|
|
3182
|
+
# a small local model, so it gets more room; a local Ollama coder gets a middle budget;
|
|
3183
|
+
# local MLX keeps the original tight default). Overridable via cfg.maxSteps.
|
|
3184
|
+
# The provide_final_answer override below is only the exhaustion fallback.
|
|
3185
|
+
#
|
|
3186
|
+
# Resolve the effective backend the SAME way as the model setup further down
|
|
3187
|
+
# (explicit kind wins; Auto sniffs the URL) so the budgets match the coder that runs.
|
|
3188
|
+
if coding_kind == "openai":
|
|
3189
|
+
_budget_is_ollama = False
|
|
3190
|
+
elif coding_kind == "ollama":
|
|
3191
|
+
_budget_is_ollama = True
|
|
3192
|
+
else:
|
|
3193
|
+
_budget_is_ollama = _is_ollama_server(coding_base_url)
|
|
3194
|
+
if coding_kind == "openai":
|
|
3195
|
+
_default_budget, _ws_budget = 20, 40 # OpenAI-compatible coder
|
|
3196
|
+
elif _budget_is_ollama:
|
|
3197
|
+
_default_budget, _ws_budget = 10, 20 # Ollama coder
|
|
3198
|
+
else:
|
|
3199
|
+
_default_budget, _ws_budget = 5, 12 # local MLX / Auto — original defaults
|
|
3183
3200
|
try:
|
|
3184
|
-
max_steps = int(cfg.get("maxSteps") or
|
|
3201
|
+
max_steps = int(cfg.get("maxSteps") or _default_budget)
|
|
3185
3202
|
except (TypeError, ValueError):
|
|
3186
|
-
max_steps =
|
|
3203
|
+
max_steps = _default_budget
|
|
3187
3204
|
if max_steps < 1:
|
|
3188
|
-
max_steps =
|
|
3205
|
+
max_steps = _default_budget
|
|
3189
3206
|
|
|
3190
3207
|
# Max web_search + fetch_url calls per task (user-configurable in web Settings →
|
|
3191
3208
|
# Agent). Default 10; past it the retrieval tools refuse and steer to finish.
|
|
@@ -3392,11 +3409,12 @@ def run_agent_chat(cfg):
|
|
|
3392
3409
|
_log(f"active workspace ignored (error resolving '{_aw_raw}'): {_e}")
|
|
3393
3410
|
if _WS_AVAILABLE:
|
|
3394
3411
|
_log(f"workspaces: {[(w['name'], w['path'], w['allowRun']) for w in _WS_ROOTS]}")
|
|
3395
|
-
# Coding tasks are edit→test→fix loops —
|
|
3396
|
-
# budget
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3412
|
+
# Coding tasks are edit→test→fix loops — the normal budget is too tight. Bump to
|
|
3413
|
+
# the backend-aware workspace budget (openai 40 / ollama 20 / MLX 12) when
|
|
3414
|
+
# workspaces are registered, unless the payload set one explicitly.
|
|
3415
|
+
if not cfg.get("maxSteps") and max_steps < _ws_budget:
|
|
3416
|
+
max_steps = _ws_budget
|
|
3417
|
+
_log(f"workspace registered → step budget raised to {_ws_budget} (no explicit maxSteps)")
|
|
3400
3418
|
# Auto-test adds a verify → fix → re-test cycle on top of the work itself, so give
|
|
3401
3419
|
# it more room (unless the user pinned a budget). Kept modest so a stuck test loop
|
|
3402
3420
|
# still terminates rather than burning a huge budget.
|
|
@@ -5163,14 +5181,27 @@ def run_agent_chat(cfg):
|
|
|
5163
5181
|
# Ollama honors it). Harmless if the server ignores it — usage stays 0.
|
|
5164
5182
|
completion_kwargs["stream_options"] = {"include_usage": True}
|
|
5165
5183
|
content, role, in_tok, out_tok, reasoning_len = self._run_stream(completion_kwargs)
|
|
5166
|
-
# Empty-turn retry: reasoning models (gemma4 on Ollama) sometimes spend the
|
|
5167
|
-
# WHOLE turn in the reasoning channel and emit no content → smolagents parses an
|
|
5168
|
-
# empty tool call and WASTES the step (seen live: 2 of 5 steps burned this way).
|
|
5169
5184
|
# Retry ONCE with a hard "emit the code block now" directive appended to the
|
|
5170
5185
|
# already-prepared API messages (plain dicts — no smolagents format guessing).
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5186
|
+
# Two triggers:
|
|
5187
|
+
# (1) EMPTY content — reasoning models (gemma4 on Ollama) sometimes spend the
|
|
5188
|
+
# WHOLE turn in the reasoning channel and emit no message.
|
|
5189
|
+
# (2) BROKEN code attempt — content has a dangling <code>/</code> tag but NO
|
|
5190
|
+
# valid pair, and #111 recovery found nothing in reasoning either (seen
|
|
5191
|
+
# live with Kimi K3: content = "Thought: …</code>", reasoning is plain
|
|
5192
|
+
# planning prose with no code). Without this, smolagents shows the ugly
|
|
5193
|
+
# "regex pattern <code> was not found" error and burns the step before its
|
|
5194
|
+
# own retry recovers. A finished PROSE answer has NO code tags, so it never
|
|
5195
|
+
# trips this — it flows to the auto-wrap path in generate() untouched.
|
|
5196
|
+
_pair_re = r"<code[^>]*>[\s\S]*?</code>"
|
|
5197
|
+
_has_pair = re.search(_pair_re, content or "", re.I) is not None
|
|
5198
|
+
_broken_code = (not _has_pair) and (
|
|
5199
|
+
re.search(r"</?code[\s>/]", content or "", re.I) is not None
|
|
5200
|
+
)
|
|
5201
|
+
if not content.strip() or _broken_code:
|
|
5202
|
+
why = "empty-content" if not content.strip() else "dangling code tag, no valid block"
|
|
5203
|
+
_log(f"{why} turn (reasoning={reasoning_len} chars) → retrying once with a "
|
|
5204
|
+
"'code now' directive")
|
|
5174
5205
|
_emit({"type": "step",
|
|
5175
5206
|
"text": "Model produced only analysis — nudging it to write the action…"})
|
|
5176
5207
|
retry_kwargs = dict(completion_kwargs)
|
|
@@ -5178,10 +5209,15 @@ def run_agent_chat(cfg):
|
|
|
5178
5209
|
{"role": "user", "content": self._CODE_NOW_DIRECTIVE}
|
|
5179
5210
|
]
|
|
5180
5211
|
r_content, r_role, r_in, r_out, _ = self._run_stream(retry_kwargs)
|
|
5181
|
-
|
|
5212
|
+
r_has_pair = re.search(_pair_re, r_content or "", re.I) is not None
|
|
5213
|
+
# Use the retry when it yields a real code block, or (empty original) any
|
|
5214
|
+
# content. If the retry is still broken and we HAD a Thought, keep the
|
|
5215
|
+
# original and let smolagents' own retry take it from here — no regression.
|
|
5216
|
+
if r_has_pair or (not content.strip() and r_content.strip()):
|
|
5182
5217
|
content, role = r_content, r_role
|
|
5183
|
-
|
|
5184
|
-
|
|
5218
|
+
in_tok = r_in or in_tok
|
|
5219
|
+
# Count the retry call's output either way — it was generated/billed (#112).
|
|
5220
|
+
out_tok += r_out
|
|
5185
5221
|
if stop_sequences and not self.supports_stop_parameter:
|
|
5186
5222
|
from smolagents.models import remove_content_after_stop_sequences
|
|
5187
5223
|
content = remove_content_after_stop_sequences(content, stop_sequences)
|
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.318",
|
|
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",
|