@tiens.nguyen/gonext-local-worker 1.0.255 β 1.0.257
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 +15 -0
- package/gonext_agent_chat.py +238 -9
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -803,7 +803,22 @@ function onApprovalKey(key) {
|
|
|
803
803
|
// again this session" choice (the caller then stops sending the prompt).
|
|
804
804
|
const MAXSTEP_PREFIX = "__MAXSTEP__::";
|
|
805
805
|
const TIMEOUT_PREFIX = "__TIMEOUT__::";
|
|
806
|
+
const COMPACT_PREFIX = "__COMPACT__::";
|
|
806
807
|
async function approvalPrompt(command) {
|
|
808
|
+
// Context-compaction prompt (task #87 rec#3): the running context got large. Ask whether
|
|
809
|
+
// to aggressively compact older steps. No answer in 30s = No (keep full detail β the safe
|
|
810
|
+
// default; the always-on auto-trim still bounds things).
|
|
811
|
+
if (command.startsWith(COMPACT_PREFIX)) {
|
|
812
|
+
const label = command.slice(COMPACT_PREFIX.length);
|
|
813
|
+
if (!process.stdin.isTTY) return { allow: false, dontAsk: false };
|
|
814
|
+
process.stdout.write("\n" + yellow("π " + label) + dim(" (no answer in 30s = keep full)") + "\n");
|
|
815
|
+
const idx = await pickFromList(
|
|
816
|
+
["Yes, compact older steps", "No, keep full detail"],
|
|
817
|
+
0,
|
|
818
|
+
{ timeoutMs: 30000, timeoutIndex: 1 }
|
|
819
|
+
);
|
|
820
|
+
return { allow: idx === 0, dontAsk: false };
|
|
821
|
+
}
|
|
807
822
|
// Time-budget prompt (task #81): the agent hit its wall-clock budget. Ask whether to
|
|
808
823
|
// keep waiting; auto-answer No after 60s of no input so the run can't hang forever.
|
|
809
824
|
if (command.startsWith(TIMEOUT_PREFIX)) {
|
package/gonext_agent_chat.py
CHANGED
|
@@ -2342,6 +2342,8 @@ def _ws_request_approval(api_base, worker_key, job_id, command, reason):
|
|
|
2342
2342
|
return False
|
|
2343
2343
|
if command.startswith("__MAXSTEP__::"):
|
|
2344
2344
|
_emit({"type": "step", "text": "Awaiting your choice: continue past the step budget?"})
|
|
2345
|
+
elif command.startswith("__COMPACT__::"):
|
|
2346
|
+
_emit({"type": "step", "text": "Awaiting your choice: compact the context?"})
|
|
2345
2347
|
else:
|
|
2346
2348
|
_emit({"type": "step", "text": f"Awaiting your approval to run: {command[:70]}"})
|
|
2347
2349
|
deadline = _t.time() + 180 # 3 min; absent user β deny (never auto-run something risky)
|
|
@@ -2485,6 +2487,32 @@ def _ws_save_servers(servers: list) -> None:
|
|
|
2485
2487
|
_log(f"servers.json write failed: {e}")
|
|
2486
2488
|
|
|
2487
2489
|
|
|
2490
|
+
def _ws_cap_servers(max_live: int = 3) -> list:
|
|
2491
|
+
"""Server lifecycle (task #90 Phase 2): prune dead registry entries and STOP the
|
|
2492
|
+
oldest background servers beyond max_live, so dev servers leaked from earlier
|
|
2493
|
+
turns can't accumulate and squat every default port (the root cause of the
|
|
2494
|
+
'port 3000/3001/3005 busy' punt seen live). Only ever touches processes in OUR
|
|
2495
|
+
registry (started by run_command β never arbitrary user processes). Persists the
|
|
2496
|
+
pruned registry and returns the live list, oldest first."""
|
|
2497
|
+
import os
|
|
2498
|
+
import signal
|
|
2499
|
+
servers = _ws_load_servers() # prune=True drops already-dead entries
|
|
2500
|
+
if len(servers) > max_live:
|
|
2501
|
+
for s in servers[:-max_live]: # registry is append-ordered β oldest first
|
|
2502
|
+
try:
|
|
2503
|
+
pgid = int(s.get("pgid", 0))
|
|
2504
|
+
if pgid <= 0:
|
|
2505
|
+
continue
|
|
2506
|
+
os.killpg(pgid, signal.SIGTERM)
|
|
2507
|
+
_log(f"server lifecycle: stopped old background server pid={s.get('pid')} "
|
|
2508
|
+
f"ports={s.get('ports')} ('{str(s.get('command'))[:40]}')")
|
|
2509
|
+
except (OSError, ValueError) as e: # already gone / no permission
|
|
2510
|
+
_log(f"server lifecycle: stop skipped pid={s.get('pid')}: {e}")
|
|
2511
|
+
servers = servers[-max_live:]
|
|
2512
|
+
_ws_save_servers(servers)
|
|
2513
|
+
return servers
|
|
2514
|
+
|
|
2515
|
+
|
|
2488
2516
|
def _ws_scrubbed_env() -> dict:
|
|
2489
2517
|
"""Minimal child env for run_command. The worker process env holds secrets
|
|
2490
2518
|
(GONEXT_WORKER_KEY etc. via worker.env) β a repo's test script must NEVER see
|
|
@@ -3121,13 +3149,53 @@ def run_agent_chat(cfg):
|
|
|
3121
3149
|
# the on-disk folder overview change between turns (the agent creates/edits files),
|
|
3122
3150
|
# so they must NOT sit inside the static tool_hint β they'd bust the prefix cache
|
|
3123
3151
|
# mid-reference every turn. They join the variable tail of task_with_hint instead.
|
|
3152
|
+
# Task #90 Phase 2 β environment visibility: prune/cap the background-server
|
|
3153
|
+
# registry once per turn (stops the oldest beyond 3 β kills the leak class), then
|
|
3154
|
+
# SHOW the survivors to the model so a 'port busy' is instantly explicable and
|
|
3155
|
+
# reuse/stop_server is actionable. Variable (server state changes) β tail.
|
|
3156
|
+
_ws_servers_line = ""
|
|
3157
|
+
if _WS_AVAILABLE:
|
|
3158
|
+
try:
|
|
3159
|
+
import os as _os_srv
|
|
3160
|
+
_live_srv = _ws_cap_servers()
|
|
3161
|
+
if _live_srv:
|
|
3162
|
+
_descs = ", ".join(
|
|
3163
|
+
":" + ",".join(str(p) for p in (s.get("ports") or ["?"]))
|
|
3164
|
+
+ f" ('{str(s.get('command'))[:30]}' in "
|
|
3165
|
+
+ f"{_os_srv.path.basename(str(s.get('workdir') or '')) or '?'})"
|
|
3166
|
+
for s in _live_srv)
|
|
3167
|
+
_ws_servers_line = (
|
|
3168
|
+
f" Background servers YOU already started (still running): {_descs}. "
|
|
3169
|
+
"REUSE one if it fits the task, or stop_server(<port>) β their ports "
|
|
3170
|
+
"are TAKEN, so never start a duplicate on one of them.\n")
|
|
3171
|
+
except Exception as _e: # noqa: BLE001
|
|
3172
|
+
_log(f"server context skip: {_e}")
|
|
3124
3173
|
_ws_context_block = (
|
|
3125
3174
|
_ws_current_line
|
|
3126
3175
|
+ f" Other registered workspaces (use only if the user names them): {_ws_names}\n"
|
|
3127
|
-
|
|
3176
|
+
+ _ws_servers_line
|
|
3177
|
+
+ f" Current folder contents:\n"
|
|
3128
3178
|
+ "\n".join(f" {line}" for line in _ws_overview.splitlines()) + "\n"
|
|
3129
3179
|
) if _WS_AVAILABLE else ""
|
|
3130
3180
|
_ws_tool_block = (
|
|
3181
|
+
# Task #90 (root-cause rev): senior-dev autonomy as GENERAL principles β outcome
|
|
3182
|
+
# over exit code, own the environment, evidence before finishing, switch approach.
|
|
3183
|
+
# Static (cached, per #86); deliberately not tied to any specific failure case.
|
|
3184
|
+
"WORK LIKE A SENIOR DEVELOPER β you OWN the task until it DEMONSTRABLY works:\n"
|
|
3185
|
+
" * OUTCOME over exit code: a command exiting 0 is NOT the goal. After any "
|
|
3186
|
+
"state-changing step (start/build/install/edit), VERIFY the intended EFFECT "
|
|
3187
|
+
"(server responds, artifact exists, test green, file contains the change) before "
|
|
3188
|
+
"moving on or finishing.\n"
|
|
3189
|
+
" * On failure: read the error, fix the ROOT CAUSE (edit the code/config, "
|
|
3190
|
+
"install the missing dependency, adjust the environment), re-run to confirm. If "
|
|
3191
|
+
"the SAME fix fails twice, SWITCH APPROACH β never repeat it, never just give up.\n"
|
|
3192
|
+
" * The environment is YOURS to manage: a busy port, missing package, or stale "
|
|
3193
|
+
"process you started earlier is yours to RESOLVE with the tools (e.g. start on a "
|
|
3194
|
+
"free port via 'env VAR=value cmd', stop_server your own old server) β not to "
|
|
3195
|
+
"report back as the user's problem.\n"
|
|
3196
|
+
" * final_answer needs EVIDENCE: say what you verified and how. If a thing "
|
|
3197
|
+
"truly cannot be done with these tools, state plainly WHAT failed and WHY β but "
|
|
3198
|
+
"NEVER end with 'you can do it yourself' when a tool could have done it.\n"
|
|
3131
3199
|
" - list_dir(path) / read_text_file(path) β browse and read files.\n"
|
|
3132
3200
|
" - grep_repo(pattern, path='', glob='') β search code, returns file:line hits. "
|
|
3133
3201
|
"Use this to find the EXACT lines to change.\n"
|
|
@@ -3349,7 +3417,13 @@ def run_agent_chat(cfg):
|
|
|
3349
3417
|
"if the task needs a DIFFERENT one.)\n"
|
|
3350
3418
|
+ _deploy_block
|
|
3351
3419
|
+ _ws_context_block +
|
|
3352
|
-
|
|
3420
|
+
# Task #90: ~25-token echo of the autonomy contract at the highest-attention spot
|
|
3421
|
+
# (recency β right next to the task), reinforcing the full directive that lives in
|
|
3422
|
+
# the cached reference above. Workspace/coding mode only.
|
|
3423
|
+
+ ("Remember: FINISH it yourself β fix blockers, VERIFY the outcome, then "
|
|
3424
|
+
"final_answer with evidence; never hand the task back.\n"
|
|
3425
|
+
if _WS_AVAILABLE else "")
|
|
3426
|
+
+ "\nTASK (answer THIS, choose the tool that fits it):\n"
|
|
3353
3427
|
f"{task_text}\n"
|
|
3354
3428
|
)
|
|
3355
3429
|
|
|
@@ -3873,6 +3947,81 @@ def run_agent_chat(cfg):
|
|
|
3873
3947
|
# agent's memory to trim old observations (task #63, keeps the prompt from growing
|
|
3874
3948
|
# O(nΒ²) as big file reads pile up in the step trace).
|
|
3875
3949
|
_agent_ref: dict = {"a": None}
|
|
3950
|
+
# Task #87 rec#3: when the context grows large in an INTERACTIVE terminal turn, ask
|
|
3951
|
+
# the user Yes/No before aggressively compacting it (rather than deciding silently).
|
|
3952
|
+
# decision: None = not yet asked this turn; "always" = user said yes, keep compacting
|
|
3953
|
+
# each step without re-asking; "never" = user said no, don't ask again this turn (the
|
|
3954
|
+
# normal auto-trim above still runs). Threshold in prompt tokens for the last request.
|
|
3955
|
+
_compact_state: dict = {"decision": None}
|
|
3956
|
+
_COMPACT_ASK_TOKENS = 14000
|
|
3957
|
+
|
|
3958
|
+
# Task #90 Phase 3 β completion gate (code-enforced, complements the prompt). Fires at
|
|
3959
|
+
# most ONCE per turn: refuse the first final_answer that either hands the task back to
|
|
3960
|
+
# the user, or lands right after a state-changing command that was never verified β with
|
|
3961
|
+
# a verify-or-explain nudge fed back to the model (smolagents final_answer_checks: a
|
|
3962
|
+
# raised message becomes the check-failure the model sees and retries on). After one
|
|
3963
|
+
# nudge it ACCEPTS unconditionally β no loop; an honest "here's what failed" is valid.
|
|
3964
|
+
_completion_gate: dict = {"nudged": False}
|
|
3965
|
+
# Punt phrases: handing the CORE task back ("β¦start it yourself", "you'll need to run").
|
|
3966
|
+
# Deliberately narrow so a helpful tip ("you can also run npm test") does NOT trigger it.
|
|
3967
|
+
_PUNT_RE = re.compile(
|
|
3968
|
+
r"\b(?:do|run|start|complete|finish|build) it yourself\b"
|
|
3969
|
+
r"|\byou can navigate to\b"
|
|
3970
|
+
r"|\bstart (?:it|the (?:app|server|project|dev server)) yourself\b"
|
|
3971
|
+
r"|\byou'?ll (?:have to|need to) (?:run|start|do|build)\b",
|
|
3972
|
+
re.I)
|
|
3973
|
+
|
|
3974
|
+
def _final_answer_gate(final_answer, memory, agent=None):
|
|
3975
|
+
"""smolagents final_answer_check. True = accept; raising = reject with the message
|
|
3976
|
+
the model then sees. Only nudges when there's real evidence of an unfinished job."""
|
|
3977
|
+
if _completion_gate["nudged"]:
|
|
3978
|
+
return True # already nudged once this turn β accept, never loop
|
|
3979
|
+
ans = str(final_answer if final_answer is not None else "")
|
|
3980
|
+
punt = bool(_PUNT_RE.search(ans))
|
|
3981
|
+
# The most-recent tool result: a command that finished but whose EFFECT was never
|
|
3982
|
+
# verified (Phase-1 'EXITED 0 (β¦VERIFYβ¦)' marker), with no server/HTTP confirmation.
|
|
3983
|
+
last_unverified = False
|
|
3984
|
+
try:
|
|
3985
|
+
for _st in reversed(getattr(memory, "steps", []) or []):
|
|
3986
|
+
_obs = getattr(_st, "observations", None)
|
|
3987
|
+
if isinstance(_obs, str) and _obs.strip():
|
|
3988
|
+
last_unverified = ("EXITED 0 (" in _obs) and not any(
|
|
3989
|
+
k in _obs for k in
|
|
3990
|
+
("RUNNING:", "listening", "HTTP 2", "http://localhost"))
|
|
3991
|
+
break
|
|
3992
|
+
except Exception: # noqa: BLE001
|
|
3993
|
+
pass
|
|
3994
|
+
if punt or last_unverified:
|
|
3995
|
+
_completion_gate["nudged"] = True
|
|
3996
|
+
raise ValueError(
|
|
3997
|
+
"NOT DONE YET β you ran commands or changed files but have NOT shown the "
|
|
3998
|
+
"goal actually works. Do ONE of these, then call final_answer:\n"
|
|
3999
|
+
" 1) VERIFY it β curl/open the server, run the test, or re-read the file to "
|
|
4000
|
+
"confirm the change took β and answer WITH that evidence; OR\n"
|
|
4001
|
+
" 2) if it genuinely cannot be done with these tools, state plainly WHAT "
|
|
4002
|
+
"failed and WHY.\n"
|
|
4003
|
+
"Do NOT hand an unfinished task back to the user.")
|
|
4004
|
+
return True
|
|
4005
|
+
|
|
4006
|
+
def _aggressive_compact(steps):
|
|
4007
|
+
"""User-approved deep compaction: keep only the LAST step in full; for every
|
|
4008
|
+
older step shrink the observation to a stub and the model_output to just its
|
|
4009
|
+
Thought head. More aggressive than the always-on auto-trim (keep-last-2). Returns
|
|
4010
|
+
chars saved. Idempotent β already-short fields fall under the caps and are skipped."""
|
|
4011
|
+
saved = 0
|
|
4012
|
+
for _st in steps[:-1]:
|
|
4013
|
+
_obs = getattr(_st, "observations", None)
|
|
4014
|
+
if isinstance(_obs, str) and len(_obs) > 200:
|
|
4015
|
+
_b = len(_obs)
|
|
4016
|
+
_st.observations = _obs[:150].rstrip() + "\nβ¦[compacted β re-read if needed]"
|
|
4017
|
+
saved += _b - len(_st.observations)
|
|
4018
|
+
_mo = getattr(_st, "model_output", None)
|
|
4019
|
+
if isinstance(_mo, str) and len(_mo) > 220:
|
|
4020
|
+
_b = len(_mo)
|
|
4021
|
+
_head = re.split(r"<code[\s>]|```", _mo, maxsplit=1)[0].rstrip()
|
|
4022
|
+
_st.model_output = (_head[:200].rstrip() or _mo[:150].rstrip()) + "\nβ¦[compacted]"
|
|
4023
|
+
saved += _b - len(_st.model_output)
|
|
4024
|
+
return saved
|
|
3876
4025
|
|
|
3877
4026
|
def step_callback(step_log):
|
|
3878
4027
|
step_num = getattr(step_log, "step_number", "?")
|
|
@@ -3890,22 +4039,82 @@ def run_agent_chat(cfg):
|
|
|
3890
4039
|
except Exception: # noqa: BLE001
|
|
3891
4040
|
pass
|
|
3892
4041
|
|
|
3893
|
-
# (B) Trim OLD, LARGE
|
|
3894
|
-
#
|
|
3895
|
-
#
|
|
3896
|
-
#
|
|
3897
|
-
#
|
|
4042
|
+
# (B) Trim OLD, LARGE step content out of the CodeAgent's step memory so it isn't
|
|
4043
|
+
# re-sent to the model on every subsequent step (the #63 O(nΒ²) token blow-up: an
|
|
4044
|
+
# uncapped dump cost ~5-6k tokens PER remaining step). Keep the last 2 steps intact
|
|
4045
|
+
# (the model just acted on them); shrink older long content. write_memory_to_messages
|
|
4046
|
+
# rebuilds the prompt from memory.steps on EVERY request (no cache), so mutating the
|
|
4047
|
+
# step objects here takes effect on the next request; old steps are only rendered
|
|
4048
|
+
# into history, never re-parsed for execution, so trimming them is safe.
|
|
4049
|
+
# - observations = the tool's OUTPUT (a file/dir dump). Trimmed since #63.
|
|
4050
|
+
# - model_output = the model's own Thought + <code> block (task #87). For an OLD
|
|
4051
|
+
# step the code ALREADY ran and its result is in the observation, so re-sending
|
|
4052
|
+
# the code verbatim every step is pure waste β keep the Thought, drop the code.
|
|
4053
|
+
# Both trims are IDEMPOTENT (a re-trimmed field falls under the size threshold, so a
|
|
4054
|
+
# later step_callback skips it β the counter below only logs NEW trims).
|
|
3898
4055
|
try:
|
|
3899
4056
|
_mem = getattr(_agent_ref.get("a"), "memory", None)
|
|
3900
4057
|
_steps = getattr(_mem, "steps", None)
|
|
3901
4058
|
if _steps and len(_steps) > 2:
|
|
4059
|
+
_trim_n, _trim_saved = 0, 0
|
|
3902
4060
|
for _st in _steps[:-2]:
|
|
3903
4061
|
_obs = getattr(_st, "observations", None)
|
|
3904
4062
|
if isinstance(_obs, str) and len(_obs) > 900:
|
|
4063
|
+
_before = len(_obs)
|
|
3905
4064
|
_st.observations = (
|
|
3906
4065
|
_obs[:400].rstrip()
|
|
3907
4066
|
+ "\nβ¦[earlier observation trimmed to save context β "
|
|
3908
4067
|
"re-read the file/dir with a small range if you still need it]")
|
|
4068
|
+
_trim_n += 1
|
|
4069
|
+
_trim_saved += _before - len(_st.observations)
|
|
4070
|
+
_mo = getattr(_st, "model_output", None)
|
|
4071
|
+
if isinstance(_mo, str) and len(_mo) > 700:
|
|
4072
|
+
_before = len(_mo)
|
|
4073
|
+
# Keep the Thought (everything before the code block); the code is
|
|
4074
|
+
# redundant with the observation that already came back.
|
|
4075
|
+
_head = re.split(r"<code[\s>]|```", _mo, maxsplit=1)[0].rstrip()
|
|
4076
|
+
_kept = (_head[:500].rstrip() if _head else _mo[:300].rstrip())
|
|
4077
|
+
_new = _kept + "\nβ¦[code trimmed to save context β its result is in the Observation]"
|
|
4078
|
+
if len(_new) < _before:
|
|
4079
|
+
_st.model_output = _new
|
|
4080
|
+
_trim_n += 1
|
|
4081
|
+
_trim_saved += _before - len(_new)
|
|
4082
|
+
if _trim_n:
|
|
4083
|
+
_log(f"context trim: shrank {_trim_n} old step field(s), "
|
|
4084
|
+
f"saved ~{_trim_saved} chars (#63/#87)")
|
|
4085
|
+
|
|
4086
|
+
# (C) Task #87 rec#3 β INTERACTIVE compaction consent. When the context is
|
|
4087
|
+
# genuinely large and we're in a terminal that can prompt, ask the user
|
|
4088
|
+
# Yes/No before deep-compacting (the auto-trim above is always-on; THIS is
|
|
4089
|
+
# the extra, opt-in aggressive pass). Only in the terminal (interactive) β
|
|
4090
|
+
# web has no picker, so it just keeps the auto-trim.
|
|
4091
|
+
if (_interactive_approval and _job_id and pdf_api_base and pdf_worker_key
|
|
4092
|
+
and _compact_state["decision"] != "never"):
|
|
4093
|
+
# Size of the last request in prompt tokens (exact if the server
|
|
4094
|
+
# reported it, else ~chars/4 of the current step memory).
|
|
4095
|
+
_tu = getattr(step_log, "token_usage", None)
|
|
4096
|
+
_in_tok = int(getattr(_tu, "input_tokens", 0) or 0) if _tu else 0
|
|
4097
|
+
if _in_tok <= 0:
|
|
4098
|
+
_chars = sum(len(getattr(_s, "model_output", "") or "")
|
|
4099
|
+
+ len(getattr(_s, "observations", "") or "")
|
|
4100
|
+
for _s in _steps)
|
|
4101
|
+
_in_tok = _chars // 4 + 7000 # + fixed system/reference baseline
|
|
4102
|
+
if _in_tok >= _COMPACT_ASK_TOKENS and len(_steps) > 2:
|
|
4103
|
+
if _compact_state["decision"] != "always":
|
|
4104
|
+
_kt = _in_tok // 1000
|
|
4105
|
+
_allow = _ws_request_approval(
|
|
4106
|
+
pdf_api_base, pdf_worker_key, _job_id,
|
|
4107
|
+
f"__COMPACT__::The context is getting large (~{_kt}k tokens "
|
|
4108
|
+
"per request). Compact older steps to keep it fast?",
|
|
4109
|
+
"compact-context")
|
|
4110
|
+
_compact_state["decision"] = "always" if _allow else "never"
|
|
4111
|
+
_emit({"type": "step", "text": (
|
|
4112
|
+
"Compacting contextβ¦" if _allow
|
|
4113
|
+
else "Keeping full context (won't ask again this turn).")})
|
|
4114
|
+
if _compact_state["decision"] == "always":
|
|
4115
|
+
_cs = _aggressive_compact(_steps)
|
|
4116
|
+
if _cs:
|
|
4117
|
+
_log(f"context compact (user-approved): saved ~{_cs} chars")
|
|
3909
4118
|
except Exception as _e: # noqa: BLE001
|
|
3910
4119
|
_log(f"memory-trim skip: {_e}")
|
|
3911
4120
|
|
|
@@ -5177,8 +5386,23 @@ def run_agent_chat(cfg):
|
|
|
5177
5386
|
rc = proc.poll()
|
|
5178
5387
|
if rc is not None:
|
|
5179
5388
|
out = _ws_distill_output(_read_log())
|
|
5180
|
-
|
|
5181
|
-
|
|
5389
|
+
# Task #90 Phase 1 β truthful perception: never claim success the
|
|
5390
|
+
# tool didn't observe. exit 0 is a FACT about the process, not
|
|
5391
|
+
# proof of the GOAL (e.g. `npm start` exits 0 when its port is
|
|
5392
|
+
# busy and the server never started β the old "PASSED (exit 0)"
|
|
5393
|
+
# label told the model it succeeded, 3Γ in one live run). The
|
|
5394
|
+
# RUNNING/LAUNCHED paths below stay β those ARE observed outcomes.
|
|
5395
|
+
# General (no command-name or error-string matching): the label
|
|
5396
|
+
# states the fact and hands verification to the model, which the
|
|
5397
|
+
# senior-dev directive tells it to do.
|
|
5398
|
+
status = (
|
|
5399
|
+
"EXITED 0 (the command finished β exit code alone does NOT "
|
|
5400
|
+
"prove the intended effect happened; check the output below "
|
|
5401
|
+
"and VERIFY before relying on it)"
|
|
5402
|
+
if rc == 0 else f"FAILED (exit {rc})"
|
|
5403
|
+
)
|
|
5404
|
+
_emit({"type": "step", "text":
|
|
5405
|
+
f"Command {'finished' if rc == 0 else 'failed'} β {command[:50]}"})
|
|
5182
5406
|
result = f"{status}\n{out}" if out.strip() else status
|
|
5183
5407
|
_last_obs["text"] = result
|
|
5184
5408
|
return result
|
|
@@ -5450,6 +5674,11 @@ def run_agent_chat(cfg):
|
|
|
5450
5674
|
max_steps=max_steps,
|
|
5451
5675
|
step_callbacks=[step_callback],
|
|
5452
5676
|
)
|
|
5677
|
+
# Task #90 Phase 3: the completion gate only makes sense in coding/workspace mode
|
|
5678
|
+
# (state-changing commands + file edits); retrieval turns never trip it anyway, but
|
|
5679
|
+
# gating keeps plain-chat/research answers untouched.
|
|
5680
|
+
if _WS_AVAILABLE:
|
|
5681
|
+
agent_kwargs["final_answer_checks"] = [_final_answer_gate]
|
|
5453
5682
|
if _AgentBase is CodeAgent:
|
|
5454
5683
|
# Only the CodeAgent runs a Python executor; ToolCallingAgent takes neither.
|
|
5455
5684
|
# 660s, NOT 60 (task #74): the wall clock must exceed the LARGEST tool-internal
|
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.257",
|
|
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",
|