@tiens.nguyen/gonext-local-worker 1.0.246 → 1.0.247
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 +3 -0
- package/gonext-repl.mjs +34 -3
- package/gonext_agent_chat.py +34 -1
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1879,6 +1879,9 @@ async function runAgentChatJob(job) {
|
|
|
1879
1879
|
// REPL's "I can show a picker" signal (else python keeps hard-blocking, as on web).
|
|
1880
1880
|
jobId,
|
|
1881
1881
|
interactiveApproval: payload?.interactiveApproval === true,
|
|
1882
|
+
// Session opt-in (task #80): auto-extend past the step budget without re-prompting
|
|
1883
|
+
// (the REPL sets this once the user picks "yes, don't ask again").
|
|
1884
|
+
alwaysExtendOnMaxStep: payload?.alwaysExtendOnMaxStep === true,
|
|
1882
1885
|
// Auto-test mode (/test-auto): the agent verifies its work and fixes until it passes.
|
|
1883
1886
|
autoTest: payload?.autoTest === true,
|
|
1884
1887
|
// Deploy target chosen via the terminal /server picker (task #69). Host/user only.
|
package/gonext-repl.mjs
CHANGED
|
@@ -778,8 +778,25 @@ function onApprovalKey(key) {
|
|
|
778
778
|
// Show the picker and resolve to the user's choice. Non-TTY (piped) can't pick → deny
|
|
779
779
|
// (safe). Auto-denies after ~170s so an absent user never hangs the turn (and stays
|
|
780
780
|
// under the python side's 180s wait); Enter is instant since Yes is preselected.
|
|
781
|
-
|
|
782
|
-
|
|
781
|
+
// The command carries a "__MAXSTEP__::<message>" sentinel when it's the step-budget
|
|
782
|
+
// continue prompt (task #80) rather than a risky-command approval. That case shows a
|
|
783
|
+
// 3-option picker (Yes / Yes-don't-ask-again / No); a normal command shows Yes/No.
|
|
784
|
+
// Returns { allow, dontAsk } — dontAsk is only ever true for the max-step "don't ask
|
|
785
|
+
// again this session" choice (the caller then stops sending the prompt).
|
|
786
|
+
const MAXSTEP_PREFIX = "__MAXSTEP__::";
|
|
787
|
+
async function approvalPrompt(command) {
|
|
788
|
+
const isMax = command.startsWith(MAXSTEP_PREFIX);
|
|
789
|
+
if (isMax) {
|
|
790
|
+
const label = command.slice(MAXSTEP_PREFIX.length);
|
|
791
|
+
if (!process.stdin.isTTY) return { allow: false, dontAsk: false };
|
|
792
|
+
process.stdout.write("\n" + yellow("⏭ " + label) + "\n");
|
|
793
|
+
const idx = await pickFromList(
|
|
794
|
+
["Yes, keep going", "Yes, and don't ask again this session", "No, stop here"],
|
|
795
|
+
0
|
|
796
|
+
);
|
|
797
|
+
return { allow: idx === 0 || idx === 1, dontAsk: idx === 1 };
|
|
798
|
+
}
|
|
799
|
+
const allow = await new Promise((resolve) => {
|
|
783
800
|
if (!process.stdin.isTTY) {
|
|
784
801
|
process.stdout.write(dim(`\n(approval needed for: ${command} — no interactive terminal, skipping)\n`));
|
|
785
802
|
resolve(false);
|
|
@@ -799,6 +816,7 @@ function approvalPrompt(command) {
|
|
|
799
816
|
orig(v);
|
|
800
817
|
};
|
|
801
818
|
});
|
|
819
|
+
return { allow, dontAsk: false };
|
|
802
820
|
}
|
|
803
821
|
// Generic arrow-key list picker (↑/↓ + Enter) — used by /server so you navigate instead
|
|
804
822
|
// of typing a number. Same mechanism as the Yes/No approval picker above, generalized to
|
|
@@ -860,6 +878,10 @@ let sessionCodingModel = "";
|
|
|
860
878
|
// and fixes → re-tests until it passes before finishing. Default off; remembered per
|
|
861
879
|
// folder in the session file; sent to /agent-ask as autoTest so python drives the loop.
|
|
862
880
|
let sessionTestAuto = false;
|
|
881
|
+
// Step-budget opt-out (task #80): set once the user picks "yes, don't ask again" at a
|
|
882
|
+
// max-step prompt. Session-scoped (not persisted per folder); sent to /agent-ask each turn
|
|
883
|
+
// so the agent auto-extends past the step budget without prompting again.
|
|
884
|
+
let alwaysExtendOnMaxStep = false;
|
|
863
885
|
// Deploy target chosen via /server (task #69): {name, host, user} or null. Session-scoped;
|
|
864
886
|
// sent to /agent-ask as deployServer so the agent deploys to this host with KEY auth.
|
|
865
887
|
let selectedServer = null;
|
|
@@ -965,6 +987,9 @@ async function runAgentTurn(history) {
|
|
|
965
987
|
// The terminal can show a Yes/No picker → the agent PAUSES on a risky command and
|
|
966
988
|
// asks instead of hard-blocking it (interactive command-approval gate).
|
|
967
989
|
interactiveApproval: true,
|
|
990
|
+
// Step-budget opt-out (task #80): once the user chose "don't ask again", auto-extend
|
|
991
|
+
// past the step budget without prompting.
|
|
992
|
+
...(alwaysExtendOnMaxStep ? { alwaysExtendOnMaxStep: true } : {}),
|
|
968
993
|
// Auto-test mode (/test-auto): the agent verifies its work and fixes until it passes.
|
|
969
994
|
...(sessionTestAuto ? { autoTest: true } : {}),
|
|
970
995
|
// Deploy target chosen via /server — host/user only (no secret), for key-auth deploys.
|
|
@@ -1409,7 +1434,13 @@ async function runAgentTurn(history) {
|
|
|
1409
1434
|
) {
|
|
1410
1435
|
lastApprovalId = job.pendingApproval.id;
|
|
1411
1436
|
clearStatus();
|
|
1412
|
-
const allow = await approvalPrompt(job.pendingApproval.command || "");
|
|
1437
|
+
const { allow, dontAsk } = await approvalPrompt(job.pendingApproval.command || "");
|
|
1438
|
+
// "Yes, don't ask again this session" (max-step prompt only): remember it so every
|
|
1439
|
+
// later turn sends alwaysExtendOnMaxStep and the agent auto-extends silently (#80).
|
|
1440
|
+
if (dontAsk) {
|
|
1441
|
+
alwaysExtendOnMaxStep = true;
|
|
1442
|
+
process.stdout.write(dim(" (won't ask again about the step budget this session)\n"));
|
|
1443
|
+
}
|
|
1413
1444
|
try {
|
|
1414
1445
|
await fetch(`${apiBase}/api/worker/jobs/${jobId}/approval`, {
|
|
1415
1446
|
method: "POST",
|
package/gonext_agent_chat.py
CHANGED
|
@@ -2340,7 +2340,10 @@ def _ws_request_approval(api_base, worker_key, job_id, command, reason):
|
|
|
2340
2340
|
except Exception as e: # noqa: BLE001
|
|
2341
2341
|
_log(f"approval-request failed: {e} — treating as deny")
|
|
2342
2342
|
return False
|
|
2343
|
-
|
|
2343
|
+
if command.startswith("__MAXSTEP__::"):
|
|
2344
|
+
_emit({"type": "step", "text": "Awaiting your choice: continue past the step budget?"})
|
|
2345
|
+
else:
|
|
2346
|
+
_emit({"type": "step", "text": f"Awaiting your approval to run: {command[:70]}"})
|
|
2344
2347
|
deadline = _t.time() + 180 # 3 min; absent user → deny (never auto-run something risky)
|
|
2345
2348
|
while _t.time() < deadline:
|
|
2346
2349
|
_t.sleep(1.2)
|
|
@@ -5396,8 +5399,38 @@ def run_agent_chat(cfg):
|
|
|
5396
5399
|
_log(f"compact system prompt unavailable ({_e}); using smolagents default")
|
|
5397
5400
|
agent = _ToolAgent(**agent_kwargs)
|
|
5398
5401
|
_agent_ref["a"] = agent # let step_callback trim this agent's step memory (#63)
|
|
5402
|
+
# At the step budget the run ends WITHOUT final_answer (_maxsteps_partial hit). Offer
|
|
5403
|
+
# to keep going instead of dead-ending (task #80): a terminal user says Yes → extend
|
|
5404
|
+
# the budget to a large ceiling and CONTINUE with the existing memory (reset=False,
|
|
5405
|
+
# so the model keeps all prior observations). smolagents captures max_steps as a
|
|
5406
|
+
# LOCAL at run() time, so we can't bump it mid-loop — we re-enter run() with a raised
|
|
5407
|
+
# agent.max_steps instead. The real bound is the 30-min worker job cap, so 1000 just
|
|
5408
|
+
# means "run to completion". 'Yes, don't ask again' is handled REPL-side: it flips a
|
|
5409
|
+
# session flag so the NEXT turn arrives with alwaysExtendOnMaxStep and we auto-extend
|
|
5410
|
+
# without prompting.
|
|
5411
|
+
_MAXSTEP_CEILING = 1000
|
|
5412
|
+
_always_extend = bool(cfg.get("alwaysExtendOnMaxStep"))
|
|
5399
5413
|
with contextlib.redirect_stdout(sys.stderr):
|
|
5400
5414
|
result = agent.run(task_with_hint)
|
|
5415
|
+
while _maxsteps_partial.get("hit"):
|
|
5416
|
+
if _always_extend:
|
|
5417
|
+
_log(f"max-steps reached → auto-extending to {_MAXSTEP_CEILING} "
|
|
5418
|
+
"(don't-ask-again session flag)")
|
|
5419
|
+
_do_extend = True
|
|
5420
|
+
elif _interactive_approval and _job_id and pdf_api_base and pdf_worker_key:
|
|
5421
|
+
_do_extend = _ws_request_approval(
|
|
5422
|
+
pdf_api_base, pdf_worker_key, _job_id,
|
|
5423
|
+
f"__MAXSTEP__::Reached the step budget ({agent.max_steps}). "
|
|
5424
|
+
"Keep going?", "max-steps")
|
|
5425
|
+
else:
|
|
5426
|
+
_do_extend = False # web / no interactive channel → current wrap-up
|
|
5427
|
+
if not _do_extend:
|
|
5428
|
+
break
|
|
5429
|
+
_maxsteps_partial["hit"] = False
|
|
5430
|
+
agent.max_steps = _MAXSTEP_CEILING
|
|
5431
|
+
_emit({"type": "step", "text": "Continuing — step budget extended…"})
|
|
5432
|
+
_log(f"continuing run with raised budget {agent.max_steps}")
|
|
5433
|
+
result = agent.run(task_with_hint, reset=False)
|
|
5401
5434
|
# Final formatting — NO extra summarizer model call. In multi-step mode the
|
|
5402
5435
|
# agent's own final_answer() (or the max-steps fallback above) already holds the
|
|
5403
5436
|
# synthesized answer; we just strip the internal hint tags we appended to tool
|
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.247",
|
|
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",
|