@tiens.nguyen/gu-cli 1.0.750 → 1.0.751
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_agent_chat.py +39 -5
- package/package.json +3 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -6948,6 +6948,35 @@ def _ws_distill_output(out: str, cap_head: int = 1200, cap_tail: int = 4000) ->
|
|
|
6948
6948
|
+ out[-cap_tail:])
|
|
6949
6949
|
|
|
6950
6950
|
|
|
6951
|
+
# READ CAPS SCALE WITH THE WINDOW (issue #49). A flat 3,000 chars (~750 tokens, ~60-80 lines,
|
|
6952
|
+
# middle cut) was right for #63's weak models with no prompt cache. Measured over 625 Claude
|
|
6953
|
+
# steps it cut a read in 23 of 37 turns (>=14.6% of steps), and each cut is a follow-up read:
|
|
6954
|
+
# d6ad7211 walked a 6,665-line file in slices for ~40 steps. With cache reads at 0.1x a bigger
|
|
6955
|
+
# read costs about what the extra step did, and steps are what we save. Claude Code's own Read
|
|
6956
|
+
# allows 25k tokens. Small or unknown windows keep 3,000 — a 16k local Ollama is unchanged.
|
|
6957
|
+
_OBS_CAP_FLOOR = 3000
|
|
6958
|
+
_OBS_CAP_READ_MAX = 24_000 # ≈ read_file_lines' own 400-line limit
|
|
6959
|
+
_OBS_CAP_SEARCH_MAX = 12_000 # ≈ grep_repo's 100 hits / list_dir's 300 entries
|
|
6960
|
+
_OBS_CAP_WINDOW_SHARE = 0.03 # one read may take ~3% of the window
|
|
6961
|
+
_OBS_CAPS = {"read": _OBS_CAP_FLOOR, "search": _OBS_CAP_FLOOR}
|
|
6962
|
+
|
|
6963
|
+
|
|
6964
|
+
def _obs_caps_for_window(window_tokens: int) -> dict:
|
|
6965
|
+
"""{read, search} character caps for a model with this context window (0 = unknown)."""
|
|
6966
|
+
if not window_tokens or window_tokens <= 0:
|
|
6967
|
+
return {"read": _OBS_CAP_FLOOR, "search": _OBS_CAP_FLOOR}
|
|
6968
|
+
read = int(window_tokens * 4 * _OBS_CAP_WINDOW_SHARE) # ~4 chars per token
|
|
6969
|
+
read = max(_OBS_CAP_FLOOR, min(_OBS_CAP_READ_MAX, read))
|
|
6970
|
+
search = max(_OBS_CAP_FLOOR, min(_OBS_CAP_SEARCH_MAX, read // 2))
|
|
6971
|
+
return {"read": read, "search": search}
|
|
6972
|
+
|
|
6973
|
+
|
|
6974
|
+
def _set_obs_caps(window_tokens: int) -> dict:
|
|
6975
|
+
"""Set this turn's caps (the worker runs one turn per process)."""
|
|
6976
|
+
_OBS_CAPS.update(_obs_caps_for_window(window_tokens))
|
|
6977
|
+
return dict(_OBS_CAPS)
|
|
6978
|
+
|
|
6979
|
+
|
|
6951
6980
|
def _ws_cap_obs(text: str, cap: int = 3000) -> str:
|
|
6952
6981
|
"""Cap a READ tool's observation (read_file_lines / list_dir / read_text_file /
|
|
6953
6982
|
grep_repo) before it's returned to the CodeAgent. Every observation stays in the
|
|
@@ -9007,6 +9036,8 @@ def run_agent_chat(cfg):
|
|
|
9007
9036
|
_trim_state: dict = {"next_at": _TRIM_FIRST_AT_CHARS}
|
|
9008
9037
|
_ctx_window = _model_context_window(coding_base_url, coding_model_id, coding_api_key)
|
|
9009
9038
|
_compact_at = _compact_threshold(_ctx_window)
|
|
9039
|
+
_caps = _set_obs_caps(_ctx_window)
|
|
9040
|
+
_log(f"read caps: {_caps['read']} chars per read, {_caps['search']} per search (issue #49)")
|
|
9010
9041
|
if _ctx_window:
|
|
9011
9042
|
# SAY WHICH SOURCE ANSWERED. A measured window and a remembered one are not the same
|
|
9012
9043
|
# claim, and a table entry presented as fact is how a stale number survives unquestioned.
|
|
@@ -10811,7 +10842,8 @@ def run_agent_chat(cfg):
|
|
|
10811
10842
|
entries.sort()
|
|
10812
10843
|
more = " …(truncated)" if len(entries) >= 300 else ""
|
|
10813
10844
|
out = _ws_cap_obs(
|
|
10814
|
-
f"{len(entries)} files under {root}:\n" + "\n".join(entries) + more
|
|
10845
|
+
f"{len(entries)} files under {root}:\n" + "\n".join(entries) + more,
|
|
10846
|
+
cap=_OBS_CAPS["search"])
|
|
10815
10847
|
_emit_read("Listing files", path, f"{len(entries)} file(s)")
|
|
10816
10848
|
_last_obs["text"] = out
|
|
10817
10849
|
return out
|
|
@@ -10850,7 +10882,7 @@ def run_agent_chat(cfg):
|
|
|
10850
10882
|
return _rb
|
|
10851
10883
|
with open(rp, "r", encoding="utf-8", errors="replace") as fh:
|
|
10852
10884
|
data = fh.read(max(500, min(int(max_chars or 20000), 100000)))
|
|
10853
|
-
out = _ws_cap_obs(data or "(empty file)")
|
|
10885
|
+
out = _ws_cap_obs(data or "(empty file)", cap=_OBS_CAPS["read"])
|
|
10854
10886
|
_emit_read("Reading file", path, f"{data.count(chr(10)) + 1} line(s)"
|
|
10855
10887
|
if data else "empty")
|
|
10856
10888
|
_last_obs["text"] = out
|
|
@@ -11509,7 +11541,8 @@ def run_agent_chat(cfg):
|
|
|
11509
11541
|
_last_obs["text"] = msg
|
|
11510
11542
|
return msg
|
|
11511
11543
|
more = "\n…(capped at 100 hits)" if len(hits) >= 100 else ""
|
|
11512
|
-
out = _ws_cap_obs(f"Matches under {root}:\n" + "\n".join(hits) + more
|
|
11544
|
+
out = _ws_cap_obs(f"Matches under {root}:\n" + "\n".join(hits) + more,
|
|
11545
|
+
cap=_OBS_CAPS["search"])
|
|
11513
11546
|
_last_obs["text"] = out
|
|
11514
11547
|
return out
|
|
11515
11548
|
except Exception as e: # noqa: BLE001
|
|
@@ -11595,7 +11628,7 @@ def run_agent_chat(cfg):
|
|
|
11595
11628
|
_emit({"type": "step",
|
|
11596
11629
|
"text": f"Locating symbol → {name} ({len(defs)} def, "
|
|
11597
11630
|
f"{len(calls)} call, {len(tests)} test)"})
|
|
11598
|
-
res = _ws_cap_obs("\n".join(out))
|
|
11631
|
+
res = _ws_cap_obs("\n".join(out), cap=_OBS_CAPS["search"])
|
|
11599
11632
|
_last_obs["text"] = res
|
|
11600
11633
|
return res
|
|
11601
11634
|
except Exception as ex: # noqa: BLE001
|
|
@@ -11634,7 +11667,8 @@ def run_agent_chat(cfg):
|
|
|
11634
11667
|
_last_obs["text"] = _rb
|
|
11635
11668
|
return _rb
|
|
11636
11669
|
body = "".join(f"{i}: {lines[i - 1]}" for i in range(s, e + 1))
|
|
11637
|
-
out = _ws_cap_obs(f"{rp} (lines {s}-{e} of {len(lines)}):\n{body}"
|
|
11670
|
+
out = _ws_cap_obs(f"{rp} (lines {s}-{e} of {len(lines)}):\n{body}",
|
|
11671
|
+
cap=_OBS_CAPS["read"])
|
|
11638
11672
|
_search_tally_bump("read")
|
|
11639
11673
|
_emit_read("Reading lines", f"{path}:{s}-{e}", f"of {len(lines)} line(s)")
|
|
11640
11674
|
_last_obs["text"] = out
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.751",
|
|
4
4
|
"description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"test:cache-e2e": "GONEXT_OLLAMA1_LIVE=1 node --test --test-timeout=1800000 tests/prefix-cache-e2e.test.mjs",
|
|
38
38
|
"test:tokens": "python3 -m unittest discover tests -p \"test_cached_tokens.py\" -v && node --test tests/prefix-cache.test.mjs tests/agent-turn-shape.test.mjs",
|
|
39
39
|
"test:selection": "node --test tests/selection-scrollback.test.mjs tests/selection-while-thinking.test.mjs tests/terminal-selection.test.mjs tests/terminal-copy.test.mjs tests/terminal-viewport.test.mjs tests/hover-repaint.test.mjs",
|
|
40
|
+
"test:steps": "python3 scripts/py-tap.py tests/test_read_caps.py tests/test_trim_payback.py tests/test_explore.py tests/test_and_chains.py tests/test_edit_file_primary.py tests/test_bare_code_rescue.py tests/test_batching_rule.py tests/test_no_pdf_routing.py",
|
|
41
|
+
"test:skills": "python3 scripts/py-tap.py tests/test_skills_mcp_turn.py tests/test_hooks_turn.py tests/test_mcp_client.py && node --test tests/skills.test.mjs tests/mcp-config.test.mjs tests/hooks-config.test.mjs tests/slash-menu.test.mjs tests/conversation-archive.test.mjs",
|
|
40
42
|
"capture:turn": "GONEXT_OLLAMA1_LIVE=1 node scripts/capture-turn-fixture.mjs",
|
|
41
43
|
"test:bearer": "node --test tests/bearer-routing.test.mjs tests/bearer-verify.test.mjs",
|
|
42
44
|
"test:bearer-e2e": "bash scripts/bearer-e2e.sh"
|