@tiens.nguyen/gonext-local-worker 1.0.199 → 1.0.201

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 CHANGED
@@ -206,6 +206,19 @@ const ask = async (q) => {
206
206
  rl.setPrompt(PROMPT);
207
207
  return (a ?? "").trim();
208
208
  };
209
+ // Yes/no prompt with an explicit default. Empty input (just Enter) → the default;
210
+ // otherwise the FIRST non-space character decides (y*→yes, n*→no), and anything else
211
+ // re-uses the default. This replaces a fragile `(await ask()) || "y"` idiom where any
212
+ // leftover character (a stray escape byte, a stray space) silently defeated the
213
+ // default — the reported "[Y/n] + Enter registered as No" bug.
214
+ const askYesNo = async (question, defaultYes) => {
215
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
216
+ const raw = (await ask(question + suffix)).trim().toLowerCase();
217
+ if (!raw) return defaultYes; // just Enter → the default
218
+ if (raw[0] === "y") return true;
219
+ if (raw[0] === "n") return false;
220
+ return defaultYes; // unrecognized → default (never silently flip it)
221
+ };
209
222
 
210
223
  // ---------- workspace registration for the current folder ----------
211
224
  async function readWorkspaces() {
@@ -229,16 +242,18 @@ async function ensureWorkspace() {
229
242
  }
230
243
  if (!existsSync(cwd) || !statSync(cwd).isDirectory()) return;
231
244
  console.log(`This folder is not a registered workspace:\n ${cwd}`);
232
- const reg = (await ask("Register it so the agent can read/edit code here? [Y/n] ")) || "y";
233
- if (!/^y(es)?$/i.test(reg)) {
245
+ const register = await askYesNo(
246
+ "Register it so the agent can read/edit code here?", true /* default Yes */
247
+ );
248
+ if (!register) {
234
249
  console.log(dim("Skipped — the agent will not be able to touch files here."));
235
250
  return;
236
251
  }
237
- const trust = (await ask(
252
+ const allowRun = await askYesNo(
238
253
  "Do you trust this folder — allow the agent to RUN build/test commands\n" +
239
- "(npm test, mvn, pytest…)? [y/N] "
240
- )) || "n";
241
- const allowRun = /^y(es)?$/i.test(trust);
254
+ "(npm test, mvn, pytest…)?",
255
+ false /* default No — running commands is the riskier grant */
256
+ );
242
257
  const next = list.filter((w) => w.path !== cwd);
243
258
  next.push({
244
259
  name: basename(cwd),
@@ -659,6 +659,36 @@ def _is_trivial_chat(text: str) -> bool:
659
659
  return bool(_TRIVIAL_CHAT.match(t))
660
660
 
661
661
 
662
+ # The greeting/smalltalk set MINUS the coding-ambiguous words: in a registered
663
+ # workspace (the `gonext` terminal), a bare "test"/"testing"/"ping" leans toward a
664
+ # command ("run the tests"), so it should reach the agent, not be answered as chat.
665
+ # This is the ONLY escape from workspace-mode's default-to-agent routing, so it must
666
+ # stay tight — anything not obviously conversational is better handled as a task.
667
+ _GREETING_SMALLTALK = re.compile(
668
+ r"^\s*(?:"
669
+ r"h(?:i+|ello+|ey+|iya|owdy)|yo|sup|wass?up|"
670
+ r"good\s*(?:morning|afternoon|evening|night|day)|greetings|"
671
+ r"thanks?(?:\s*you)?|thank\s*you|thx|ty|cheers|much\s*appreciated|"
672
+ r"bye+|goodbye|see\s*(?:ya|you)|cya|later|good\s*night|"
673
+ r"ok(?:ay)?|k|cool|nice|great|awesome|perfect|sounds\s*good|got\s*it|"
674
+ r"how\s*(?:are|r)\s*(?:you|u|ya)(?:\s*doing)?|how'?s\s*it\s*going|what'?s\s*up|"
675
+ r"who\s*(?:are|r)\s*(?:you|u)|what\s*(?:can|do)\s*you\s*do|what\s*are\s*you"
676
+ r")"
677
+ r"(?:\s+(?:there|everyone|all|bot|assistant|gonext|man|dude|buddy))?"
678
+ r"[\s!.?,'\"]*$",
679
+ re.IGNORECASE,
680
+ )
681
+
682
+
683
+ def _is_greeting_smalltalk(text: str) -> bool:
684
+ """Tighter than _is_trivial_chat (no test/ping) — the single chat escape from
685
+ workspace-mode default-to-agent routing."""
686
+ t = (text or "").strip()
687
+ if not t or len(t) > 60:
688
+ return False
689
+ return bool(_GREETING_SMALLTALK.match(t))
690
+
691
+
662
692
  # A weak/confused coding model sometimes "thinks out loud" in plain prose instead of
663
693
  # emitting the next tool call — e.g. a rambling, self-correcting scratchpad ("Wait,
664
694
  # actually... Let's try... Actually, let's just...") that can run thousands of chars.
@@ -2514,14 +2544,31 @@ def run_agent_chat(cfg):
2514
2544
  and not _HOWTO_PREFIX_RE.match(t)
2515
2545
  and _WS_ACTION_RE.search(t))
2516
2546
 
2517
- # Route: does this task need HTTP tool use? A trivial greeting/thank-you/"who are you"
2518
- # is caught LOCALLY (no model classifier call, no tool preamble) → straight to a fast
2519
- # small-prompt reply. Otherwise ask the model classifier.
2520
- if _is_trivial_chat(latest_user_text) and not _AGENT_KEYWORDS.search(latest_user_text or ""):
2521
- _log("router NO (trivial greeting/smalltalklocal fast-path, no tool preamble)")
2522
- _emit({"type": "step", "text": "Routing your request…"})
2547
+ # Routing. Two fundamentally different contexts:
2548
+ #
2549
+ # (A) WORKSPACE MODE (a folder is registered — i.e. the `gonext` terminal, or web
2550
+ # with a workspace): DEFAULT TO THE AGENT. The user opened a code folder and
2551
+ # registered it for the agent to read/edit/runeverything they type is
2552
+ # presumptively a task to DO, not a question to classify. We do NOT ask a model
2553
+ # classifier "does this need tools?" here: on a weak local model that guess is
2554
+ # unreliable, and every wrong guess sends an action request ("create a reactjs
2555
+ # project and start it", "do it for me") to plain chat, which can only hand back
2556
+ # instructions — the exact instability the user rejected. The ONLY escape is the
2557
+ # deterministic greeting/thanks fast-path, so "hi"/"thanks" stay instant instead
2558
+ # of spinning up the coding model. Anything else → agent, which still just
2559
+ # answers via final_answer when no tool is needed.
2560
+ #
2561
+ # (B) NO WORKSPACE (plain web chat): keep the keyword + model classifier — there are
2562
+ # no file/run tools to act with, so chat-vs-tool routing still earns its keep.
2563
+ _emit({"type": "step", "text": "Routing your request…"})
2564
+ if _is_greeting_smalltalk(latest_user_text) and not _AGENT_KEYWORDS.search(latest_user_text or ""):
2565
+ _log("router → NO (greeting/smalltalk — local fast-path)")
2523
2566
  _emit({"type": "step", "text": "→ Chat reply"})
2524
2567
  needs_agent = False
2568
+ elif _WS_AVAILABLE:
2569
+ _log("router → YES (workspace registered → act by default, no classifier)")
2570
+ _emit({"type": "step", "text": "→ Agent mode (workspace)"})
2571
+ needs_agent = True
2525
2572
  else:
2526
2573
  needs_agent = _route(latest_user_text, agent_base_url, agent_api_key, agent_model_id)
2527
2574
  # A pending email awaiting 'confirm' must reach the agent even though a bare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.199",
3
+ "version": "1.0.201",
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",