@tiens.nguyen/gonext-local-worker 1.0.221 → 1.0.223
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-local-worker.mjs +15 -2
- package/gonext_agent_chat.py +54 -5
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1249,8 +1249,15 @@ async function correctOcrTextViaOpenAI(extractedText, base, model) {
|
|
|
1249
1249
|
model,
|
|
1250
1250
|
stream: false,
|
|
1251
1251
|
temperature: 0,
|
|
1252
|
+
// Qwen3 (and other reasoning models) emit a <think> trace by default. Over the
|
|
1253
|
+
// OpenAI /v1 API there's no `think:false` flag, so use Qwen3's `/no_think` SOFT
|
|
1254
|
+
// SWITCH (honored by its chat template, same as the agent loop) to suppress it —
|
|
1255
|
+
// otherwise the model spends its whole token budget reasoning and returns no
|
|
1256
|
+
// corrected text (seen live: correction was a no-op, chars unchanged). Generous
|
|
1257
|
+
// max_tokens so a long OCR page isn't truncated.
|
|
1258
|
+
max_tokens: 4096,
|
|
1252
1259
|
messages: [
|
|
1253
|
-
{ role: "system", content: OCR_CORRECT_SYSTEM_PROMPT },
|
|
1260
|
+
{ role: "system", content: `${OCR_CORRECT_SYSTEM_PROMPT}\n/no_think` },
|
|
1254
1261
|
{ role: "user", content: `Text:\n${extractedText}` },
|
|
1255
1262
|
],
|
|
1256
1263
|
}),
|
|
@@ -1261,7 +1268,13 @@ async function correctOcrTextViaOpenAI(extractedText, base, model) {
|
|
|
1261
1268
|
}
|
|
1262
1269
|
const json = await res.json();
|
|
1263
1270
|
let corrected = json?.choices?.[0]?.message?.content?.trim() || "";
|
|
1264
|
-
|
|
1271
|
+
// Strip a <think> trace if `/no_think` wasn't honored: closed pairs, a template-opened
|
|
1272
|
+
// leading …</think>, and a trailing UNCLOSED <think> (truncated by max_tokens).
|
|
1273
|
+
corrected = corrected
|
|
1274
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
1275
|
+
.replace(/^[\s\S]*?<\/think>/i, "")
|
|
1276
|
+
.replace(/<think>[\s\S]*$/i, "")
|
|
1277
|
+
.trim();
|
|
1265
1278
|
return normalizeCorrection(corrected, extractedText);
|
|
1266
1279
|
}
|
|
1267
1280
|
|
package/gonext_agent_chat.py
CHANGED
|
@@ -2202,12 +2202,23 @@ def _ws_record_change(action: str, path: str, backup) -> None:
|
|
|
2202
2202
|
_log(f"workspace manifest write failed: {e}")
|
|
2203
2203
|
|
|
2204
2204
|
|
|
2205
|
-
# Command prefixes run_command may execute (first token after shlex split).
|
|
2206
|
-
#
|
|
2205
|
+
# Command prefixes run_command may execute (first token after shlex split). This is a
|
|
2206
|
+
# STEERING mechanism, not a sandbox: python3 (allowed) can subprocess anything, and
|
|
2207
|
+
# ssh/rsync flags (ProxyCommand, -e) can local-exec too. A run-enabled workspace means
|
|
2208
|
+
# the user trusts the agent with local-user-level execution — the list exists to keep
|
|
2209
|
+
# the model on productive paths (build/test/deploy), not to contain a hostile one.
|
|
2210
|
+
# Still excluded on purpose: bare shells, package publishes, sudo, and bare `pip`
|
|
2211
|
+
# (the model must use `python3 -m pip`, which pip itself recommends).
|
|
2207
2212
|
_WS_RUN_ALLOWED = {
|
|
2208
2213
|
"npm", "npx", "yarn", "pnpm", "node", "mvn", "./mvnw", "gradle", "./gradlew",
|
|
2209
2214
|
"pytest", "python", "python3", "go", "cargo", "make", "dotnet", "swift",
|
|
2210
2215
|
"rspec", "bundle", "php", "composer", "tsc", "jest", "vitest",
|
|
2216
|
+
# Deployment (task #57): transfer files + run remote commands. Auth uses the user's
|
|
2217
|
+
# own SSH key/agent (SSH_AUTH_SOCK passes through _ws_scrubbed_env); the model is
|
|
2218
|
+
# told to use non-interactive KEY auth (BatchMode) and NEVER hardcode passwords (a
|
|
2219
|
+
# stdin password prompt EOFErrors here). No `sftp`: it's interactive by default
|
|
2220
|
+
# (would hang → timeout kill); scp/rsync cover the same ground batch-safely.
|
|
2221
|
+
"ssh", "scp", "rsync",
|
|
2211
2222
|
}
|
|
2212
2223
|
|
|
2213
2224
|
# ---- background servers (behavior-detected, command-agnostic) ----
|
|
@@ -2339,7 +2350,10 @@ def _ws_scrubbed_env() -> dict:
|
|
|
2339
2350
|
them, so we pass an explicit whitelist instead of inheriting."""
|
|
2340
2351
|
import os
|
|
2341
2352
|
keep = ("PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "SHELL", "USER", "TERM",
|
|
2342
|
-
"JAVA_HOME", "GOPATH", "CARGO_HOME", "NVM_DIR"
|
|
2353
|
+
"JAVA_HOME", "GOPATH", "CARGO_HOME", "NVM_DIR",
|
|
2354
|
+
# SSH agent socket so ssh/scp/rsync can authenticate with the user's KEY
|
|
2355
|
+
# (no password in the model's context) for deploys (task #57).
|
|
2356
|
+
"SSH_AUTH_SOCK", "SSH_AGENT_PID")
|
|
2343
2357
|
env = {k: os.environ[k] for k in keep if k in os.environ}
|
|
2344
2358
|
# CI=1 makes watch-mode test runners (CRA's `npm test`, jest --watch) run ONCE and
|
|
2345
2359
|
# exit instead of sitting in interactive watch mode until the timeout kills them.
|
|
@@ -2927,6 +2941,21 @@ def run_agent_chat(cfg):
|
|
|
2927
2941
|
"left RUNNING (its window opens on the user's Mac); stop_server closes it. An "
|
|
2928
2942
|
"interactive input() CLI can't be driven here (EOFError) — make it non-interactive "
|
|
2929
2943
|
"or give the user the command.\n"
|
|
2944
|
+
f" ALLOWED runners (first word only): {', '.join(sorted(_WS_RUN_ALLOWED))}. "
|
|
2945
|
+
"Anything else is rejected — do NOT retry a blocked command a different way. "
|
|
2946
|
+
"For Python packages use `python3 -m pip install <pkg>` (bare `pip` is NOT allowed).\n"
|
|
2947
|
+
" DEPLOY (ssh/scp/rsync are allowed): transfer files + run remote commands "
|
|
2948
|
+
"directly, e.g. `rsync -az build/ user@host:/path` then `ssh user@host '…'`. Use "
|
|
2949
|
+
"NON-INTERACTIVE KEY auth: add `-o BatchMode=yes` (and "
|
|
2950
|
+
"`-o StrictHostKeyChecking=accept-new`) so it fails fast instead of hanging on a "
|
|
2951
|
+
"prompt. NEVER put a password in a command, file, or your reply — there is no "
|
|
2952
|
+
"stdin here. If the server only accepts a PASSWORD (BatchMode fails with "
|
|
2953
|
+
"'Permission denied (publickey,password)'), STOP and tell the user to set up key "
|
|
2954
|
+
"auth once: `ssh-copy-id user@host` — then retry. Do not write paramiko/expect "
|
|
2955
|
+
"scripts with hardcoded credentials. REMOTE sudo usually FAILS over ssh (no TTY: "
|
|
2956
|
+
"'a terminal is required') — deploy to a directory the remote user can write, "
|
|
2957
|
+
"and if a root step is unavoidable (mv to /var/www, nginx config/reload), "
|
|
2958
|
+
"final_answer the exact one-liner for the user to run on the server themselves.\n"
|
|
2930
2959
|
" NOTE: plain `import os`/`open()` is blocked — use ONLY these tools for files.\n"
|
|
2931
2960
|
" WRITING FILE CONTENT: multi-line content MUST use \\n for newlines (or a "
|
|
2932
2961
|
"triple-quoted string). NEVER put a raw line break inside a normal \"...\" — it "
|
|
@@ -4496,7 +4525,7 @@ def run_agent_chat(cfg):
|
|
|
4496
4525
|
|
|
4497
4526
|
@tool
|
|
4498
4527
|
def run_command(command: str, workdir: str = "", timeout_seconds: int = 180) -> str:
|
|
4499
|
-
"""Run a command inside a workspace that has run permission (e.g. 'npm test', 'npm run build', 'npm start', 'mvn test', 'python3 app.py'). One-shot commands return their output when they exit. If the command turns out to be a SERVER (keeps running and listens on a port), it is left RUNNING and its URL is reported. If it is a DESKTOP/GUI app (opens a window — tkinter, pygame, PyQt…), it is LAUNCHED and left running: its window appears on the user's Mac; tell them it's open. Both are stoppable via stop_server. Note: an interactive CLI that reads stdin (input()) cannot be driven here — it will fail with EOFError; make it non-interactive or give the user the command to run themselves.
|
|
4528
|
+
"""Run a command inside a workspace that has run permission (e.g. 'npm test', 'npm run build', 'npm start', 'mvn test', 'python3 app.py'). One-shot commands return their output when they exit. If the command turns out to be a SERVER (keeps running and listens on a port), it is left RUNNING and its URL is reported. If it is a DESKTOP/GUI app (opens a window — tkinter, pygame, PyQt…), it is LAUNCHED and left running: its window appears on the user's Mac; tell them it's open. Both are stoppable via stop_server. Also allows DEPLOY commands (ssh/scp/rsync) — use non-interactive KEY auth (-o BatchMode=yes); never put a password in the command (there is no stdin). Note: an interactive CLI that reads stdin (input()) cannot be driven here — it will fail with EOFError; make it non-interactive or give the user the command to run themselves.
|
|
4500
4529
|
|
|
4501
4530
|
Args:
|
|
4502
4531
|
command: the command line (allowlisted runners only; no shells or sudo).
|
|
@@ -4509,7 +4538,27 @@ def run_agent_chat(cfg):
|
|
|
4509
4538
|
import signal
|
|
4510
4539
|
import subprocess
|
|
4511
4540
|
import time as _t
|
|
4512
|
-
|
|
4541
|
+
# workdir="" → the active workspace. Guard the common model mistake of
|
|
4542
|
+
# passing the workspace's OWN name (e.g. workdir="t13" while the active
|
|
4543
|
+
# folder is /…/t13), which would double-nest to /…/t13/t13
|
|
4544
|
+
# (FileNotFoundError). EXISTENCE-FIRST: a real subdir always wins — only
|
|
4545
|
+
# strip the redundant leading segment when the nested path does NOT exist
|
|
4546
|
+
# (so a legit repo/repo layout like web/web is never misrouted). Handles
|
|
4547
|
+
# both the bare form ("t13") and the prefixed form ("t13/my-react-app").
|
|
4548
|
+
_wd_in = (workdir or "").strip()
|
|
4549
|
+
if _wd_in and not os.path.isabs(os.path.expanduser(_wd_in)):
|
|
4550
|
+
_active = _default_ws_root()
|
|
4551
|
+
if _wd_in in (".", "./"):
|
|
4552
|
+
_wd_in = ""
|
|
4553
|
+
elif not os.path.isdir(os.path.join(_active, _wd_in)):
|
|
4554
|
+
_base = os.path.basename(_active)
|
|
4555
|
+
if _wd_in == _base:
|
|
4556
|
+
_wd_in = ""
|
|
4557
|
+
elif _wd_in.startswith(_base + os.sep):
|
|
4558
|
+
_stripped = _wd_in[len(_base) + 1:]
|
|
4559
|
+
if os.path.isdir(os.path.join(_active, _stripped)):
|
|
4560
|
+
_wd_in = _stripped
|
|
4561
|
+
wd = _rag_read_allowed(_wd_in or _default_ws_root())
|
|
4513
4562
|
w = _ws_for_path(wd)
|
|
4514
4563
|
if w is None or not w.get("allowRun"):
|
|
4515
4564
|
msg = ("Error: running commands is not enabled for this workspace. "
|
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.223",
|
|
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",
|