localcode 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- localcode/__init__.py +1 -1
- localcode/agent/context.py +7 -7
- localcode/agent/recovery.py +7 -5
- localcode/tools/bash.py +52 -0
- localcode/tui/screens/chat.py +9 -5
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/METADATA +1 -1
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/RECORD +11 -11
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/WHEEL +0 -0
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/entry_points.txt +0 -0
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {localcode-0.3.2.dist-info → localcode-0.3.3.dist-info}/top_level.txt +0 -0
localcode/__init__.py
CHANGED
localcode/agent/context.py
CHANGED
|
@@ -246,10 +246,9 @@ def _semantic_tool_summary(content: str) -> str:
|
|
|
246
246
|
)
|
|
247
247
|
if is_error:
|
|
248
248
|
# Self-conditioning (arXiv:2509.09677): echoing an OLD error's text
|
|
249
|
-
# pushes the model to repeat the mistake
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
# ledger still records the failure fact, minus the conditioning text.
|
|
249
|
+
# pushes the model to repeat the mistake. Recent errors stay intact
|
|
250
|
+
# (kept above by keep_recent) so it can fix the immediate problem;
|
|
251
|
+
# older ones drop to a neutral note (the ledger still records the fact).
|
|
253
252
|
return "[an earlier tool call errored and was handled — details dropped]"
|
|
254
253
|
if facts:
|
|
255
254
|
return (
|
|
@@ -725,9 +724,10 @@ def build_progress_ledger(
|
|
|
725
724
|
return list(dict.fromkeys(s for s in seq if s))
|
|
726
725
|
|
|
727
726
|
lines = [
|
|
728
|
-
"##
|
|
729
|
-
"
|
|
730
|
-
"
|
|
727
|
+
"## A log of YOUR OWN tool calls so far this turn — NOT the user's work "
|
|
728
|
+
"and NOT pre-existing files on disk (leftover files you did not create are "
|
|
729
|
+
"not your progress or your task). Don't re-read/re-run anything below "
|
|
730
|
+
"unless you changed it; keep making NEW progress toward the user's goal.",
|
|
731
731
|
]
|
|
732
732
|
rd = _uniq(files_read)
|
|
733
733
|
if rd:
|
localcode/agent/recovery.py
CHANGED
|
@@ -390,9 +390,11 @@ def churn_nudge_for(signal: ChurnSignal) -> str:
|
|
|
390
390
|
)
|
|
391
391
|
# INVESTIGATION_SPIN
|
|
392
392
|
return (
|
|
393
|
-
"SYSTEM:
|
|
394
|
-
"
|
|
395
|
-
"
|
|
396
|
-
"
|
|
397
|
-
"
|
|
393
|
+
"SYSTEM: Several rounds of reading/listing/searching without WRITING "
|
|
394
|
+
"anything — you're going in circles. STOP exploring. If the task is to "
|
|
395
|
+
"build something, create the target file and write real code THIS round "
|
|
396
|
+
"with write_file/edit_file; do NOT read, ls, find, cat, or grep again. "
|
|
397
|
+
"Leftover files on disk are NOT your progress (may be an unrelated run) — "
|
|
398
|
+
"only files YOU write count. If you truly lack info only the user has, "
|
|
399
|
+
"ask ONE focused question; otherwise start writing now."
|
|
398
400
|
)
|
localcode/tools/bash.py
CHANGED
|
@@ -711,6 +711,50 @@ def _normalize_repo_root_variants(cmd: str, repo: str) -> str:
|
|
|
711
711
|
return pattern.sub(repo, cmd)
|
|
712
712
|
|
|
713
713
|
|
|
714
|
+
def _quoting_error_hint(output: str) -> str:
|
|
715
|
+
"""Turn a raw shell quoting failure into actionable guidance.
|
|
716
|
+
|
|
717
|
+
A path with spaces or parentheses (e.g. `Qwen 3.6 35B-A3B (Q8)`) that
|
|
718
|
+
isn't quoted breaks bash with `syntax error near unexpected token` — an
|
|
719
|
+
error a model can't act on, so it retries the same broken shape in a loop
|
|
720
|
+
(observed exactly this). Tell it precisely how to fix it.
|
|
721
|
+
"""
|
|
722
|
+
low = output.lower()
|
|
723
|
+
if "syntax error near unexpected token" not in low and "unexpected eof" not in low:
|
|
724
|
+
return ""
|
|
725
|
+
return (
|
|
726
|
+
"HINT: a path or argument with spaces or parentheses wasn't quoted, so "
|
|
727
|
+
"the shell mis-parsed it. Wrap paths in SINGLE quotes — e.g. "
|
|
728
|
+
"ls -la '/Users/you/My Dir (v2)'. Simpler and safer: use list_files or "
|
|
729
|
+
"read_file with the path as an argument — they take the raw path and "
|
|
730
|
+
"never need shell escaping."
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def _redirect_shell_dir_listing(cmd: str) -> str:
|
|
735
|
+
"""Route `ls <path>` to list_files, which can't break on spaces/parens.
|
|
736
|
+
|
|
737
|
+
Mirrors the `cat → read_file` guard. Only fires for a plain `ls` with a
|
|
738
|
+
PATH argument (no pipes/redirs/chains) — bare `ls`/`ls -la` in the cwd
|
|
739
|
+
can't hit the quoting failure, so it's left to run.
|
|
740
|
+
"""
|
|
741
|
+
_cd, body = _extract_leading_cd(cmd)
|
|
742
|
+
b = body.strip()
|
|
743
|
+
if any(sep in b for sep in ("|", ">", "<", "&&", "||", ";", "$(", "`")):
|
|
744
|
+
return ""
|
|
745
|
+
# Path arg must NOT start with '-' (else `ls -la` reads its own flags as a path).
|
|
746
|
+
m = re.match(r"^ls((?:\s+-[a-zA-Z]+)*)\s+([^-\s].*)$", b)
|
|
747
|
+
if m is None:
|
|
748
|
+
return "" # bare `ls`/`ls -la` (no path) — harmless, let it run
|
|
749
|
+
path = m.group(2).strip().strip('"').strip("'")
|
|
750
|
+
return (
|
|
751
|
+
f"REJECTED: use list_files(path='{path}') to inspect a directory, not "
|
|
752
|
+
"bash `ls <path>`. list_files takes the path as an argument, so it never "
|
|
753
|
+
"breaks on spaces or parentheses in a folder name — which is what fails "
|
|
754
|
+
"with bash here."
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
|
|
714
758
|
def execute(ctx: ToolContext, args: dict) -> str:
|
|
715
759
|
cmd = _normalize_repo_root_variants(args["command"], str(ctx.repo))
|
|
716
760
|
# Block ANY use of process-attaching debuggers — lldb / dtrace /
|
|
@@ -776,6 +820,9 @@ def execute(ctx: ToolContext, args: dict) -> str:
|
|
|
776
820
|
file_read_redirect = _redirect_shell_file_read(cmd, str(repo))
|
|
777
821
|
if file_read_redirect:
|
|
778
822
|
return file_read_redirect
|
|
823
|
+
dir_listing_redirect = _redirect_shell_dir_listing(cmd)
|
|
824
|
+
if dir_listing_redirect:
|
|
825
|
+
return dir_listing_redirect
|
|
779
826
|
file_write_redirect = _redirect_shell_file_write(cmd, str(repo))
|
|
780
827
|
if file_write_redirect:
|
|
781
828
|
return file_write_redirect
|
|
@@ -969,6 +1016,11 @@ def execute(ctx: ToolContext, args: dict) -> str:
|
|
|
969
1016
|
pass
|
|
970
1017
|
if r.returncode != 0:
|
|
971
1018
|
output = f"[exit code {r.returncode}]\n{output}"
|
|
1019
|
+
# Shell quoting failure (unquoted spaces/parens in a path) → give
|
|
1020
|
+
# the model an actionable fix instead of a raw syntax error it loops on.
|
|
1021
|
+
_qh = _quoting_error_hint(output)
|
|
1022
|
+
if _qh:
|
|
1023
|
+
output = _qh + "\n\n" + output
|
|
972
1024
|
# AirPlay-collision detector: if the model curls localhost:5000
|
|
973
1025
|
# or :7000, those are squatted by macOS AirPlay Receiver /
|
|
974
1026
|
# AirTunes — bare `curl -s` sees a 200 with empty body and
|
localcode/tui/screens/chat.py
CHANGED
|
@@ -411,12 +411,16 @@ class _ChatTextArea(TextArea):
|
|
|
411
411
|
& .text-area--cursor-line {
|
|
412
412
|
background: ansi_default;
|
|
413
413
|
}
|
|
414
|
-
/* The caret.
|
|
415
|
-
|
|
416
|
-
a
|
|
417
|
-
|
|
414
|
+
/* The caret. textual-ansi reports dark=False, so TextArea's own
|
|
415
|
+
`&:light .text-area--cursor` rule (background: $foreground 70%) wins
|
|
416
|
+
over a plain override and renders a dark/black block. Force it with
|
|
417
|
+
!important: color+background = ansi_default (terminal fg/bg) plus
|
|
418
|
+
`reverse` swaps them, giving a light block on a dark terminal and a
|
|
419
|
+
dark block on a light one — terminal-adaptive, no hardcoded hex. */
|
|
418
420
|
& .text-area--cursor {
|
|
419
|
-
|
|
421
|
+
color: ansi_default !important;
|
|
422
|
+
background: ansi_default !important;
|
|
423
|
+
text-style: reverse !important;
|
|
420
424
|
}
|
|
421
425
|
&:focus {
|
|
422
426
|
background-tint: transparent 0%;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
localcode/__init__.py,sha256=
|
|
1
|
+
localcode/__init__.py,sha256=bQESHtF45jqmHUASEOi1qA8k5Ze3-o_Uh926z32yitM,49
|
|
2
2
|
localcode/__main__.py,sha256=_4Tblte9F1KOpB-oGBQuTlaSN-IncLmhihqhBkpH3pU,69
|
|
3
3
|
localcode/_subproc_env.py,sha256=eltq_kKvqGj22pIFivB5BZft9-s5fziBW0lsV9FhEV4,2383
|
|
4
4
|
localcode/app.py,sha256=FSwpce6Jfn7zJmhL1ipaqTq0xKh0MxCMnEnMQuFV8L8,67820
|
|
@@ -67,14 +67,14 @@ localcode/voice.py,sha256=ZAh1GmJiQJF4cXQ2YCclJZB77Sa9yflox22MnDMl7t8,39178
|
|
|
67
67
|
localcode/agent/__init__.py,sha256=-aQIBiYIojvwl9nDf_wcyiT5UQTmcEQbEBwnBEsrjEU,7349
|
|
68
68
|
localcode/agent/app_tasks.py,sha256=e5tcxBZczgQyq5irGePfe5fPmNAK6AUJ_N2i11iXb5Q,5725
|
|
69
69
|
localcode/agent/constants.py,sha256=0zlgyqZYJs0MOoh70rOgrXfl_ZuU5kR29Sjzrrz7TlI,12350
|
|
70
|
-
localcode/agent/context.py,sha256=
|
|
70
|
+
localcode/agent/context.py,sha256=adV_WlFBHMDHsWdndRgrkZqRweDD9plyqMP_2X74WAw,37617
|
|
71
71
|
localcode/agent/goal.py,sha256=X87Q3rsdp70wOfkpbh35M4qFWSNyklX3RuRWjv-pCXs,6264
|
|
72
72
|
localcode/agent/helpers.py,sha256=vutFyCtnca0UnF0wgk7LdsqVWdiqyYm4YnFOxtnCJ_A,15004
|
|
73
73
|
localcode/agent/hooks.py,sha256=XadEbSM7gHtKlLrqSKnvcQFcoWoNUXdlMdIPS-EwBGA,15841
|
|
74
74
|
localcode/agent/loop.py,sha256=ZX3VS_7jCcRLX3VCSOsl8uMF51AnPICt18awXGk928s,99869
|
|
75
75
|
localcode/agent/prompt_context.py,sha256=abJPLMhTzuVcsW4AfMBIW-SEqmrU69DhI-0vGZllZVU,13707
|
|
76
76
|
localcode/agent/prompts.py,sha256=1VFrtzumZ2A7BaZ1gStpre-mZBs721FZi9hiB4qenZw,14639
|
|
77
|
-
localcode/agent/recovery.py,sha256=
|
|
77
|
+
localcode/agent/recovery.py,sha256=AA2qQwsmlgB_RV_JvQVwURj1xTxQa2c-34SljnFZeHk,16806
|
|
78
78
|
localcode/agent/sections.py,sha256=9hnwaNritQHgn7qIGFBZ3ChZgMQXQcqtmpdbR1wafA0,8998
|
|
79
79
|
localcode/agent/streaming.py,sha256=TEcKbmKfDGV1razrV-ceVrvIIxvS-GmLWGNDnF0gSTA,12872
|
|
80
80
|
localcode/agent/tool_execution.py,sha256=yfWZwOO3f5ICd2eqw6keg8sOKOIa3vFt4peFinmu5nM,14462
|
|
@@ -99,7 +99,7 @@ localcode/tools/__init__.py,sha256=6FknaKvS-0mo4dYFTegcJcyihcJpH42NzQnM-FkKCPk,1
|
|
|
99
99
|
localcode/tools/agent.py,sha256=aIcoGc3JiiSN4MSumwNc9TiKPZciyNXWKTYjFNir79s,8786
|
|
100
100
|
localcode/tools/append_file.py,sha256=GNjPBnnV7hpD32u0Jx6YxRmsOSr4uwvJgIcInH1qhSc,2015
|
|
101
101
|
localcode/tools/base.py,sha256=2QV-q1DzXWM4KL9mFKNPzlBiLOS2uKLXhlgGyeWEKCI,2064
|
|
102
|
-
localcode/tools/bash.py,sha256=
|
|
102
|
+
localcode/tools/bash.py,sha256=Yuuxo0lsX0JuFbHQX8OflmgZgq7uqBc1FUSGJRHlsB8,43380
|
|
103
103
|
localcode/tools/edit_diff.py,sha256=eJ1FyK3HecAmJNiM-XdHOQ7WCkyOuD6hwAlTVgYQGGk,3017
|
|
104
104
|
localcode/tools/edit_file.py,sha256=DhhQSi6wnD4_IwLEYoC-zBLUPaxhihZxBatNoerFLe4,13455
|
|
105
105
|
localcode/tools/facts.py,sha256=OqTLYOclKy6lpPQiU3aDrzY-Ho-jhWc8mvzLx6TFRCk,5576
|
|
@@ -119,7 +119,7 @@ localcode/tui/__init__.py,sha256=Yfy9bvw3l1iY4FDhRX8qZVgnZLuYiq8lc9SDYl4cWwc,61
|
|
|
119
119
|
localcode/tui/app.py,sha256=KjQzpGKnxkgh-0kVc3zcb63Q6a5LBuGreBqTUrasiqM,17700
|
|
120
120
|
localcode/tui/bridge.py,sha256=urPPCqC_nisAYawWk3juvMI8IaoJfv4g7USpGOZOqQc,2803
|
|
121
121
|
localcode/tui/screens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
122
|
-
localcode/tui/screens/chat.py,sha256=
|
|
122
|
+
localcode/tui/screens/chat.py,sha256=3eRHyiFNec7cbWEcClEEopzCDt3_SGP9lIkNH5yuSCc,193659
|
|
123
123
|
localcode/tui/screens/mode_picker.py,sha256=cK_xNo7ytOwJXJwzUevMTcb5dwQKtjMfh60of-ntRz4,2347
|
|
124
124
|
localcode/tui/screens/model_picker.py,sha256=VkhyREIT2W1J8A6tUe0ix5TbWXUa2XSe9qKzcXgjjz4,35991
|
|
125
125
|
localcode/tui/screens/setup.py,sha256=6sSKU1KagLyf5DtkQWOSBA7inPFZMTF570t-20PIwaE,40844
|
|
@@ -131,9 +131,9 @@ localcode/tui/widgets/chat_log.py,sha256=D5DUkrQXL_ilxyC8N1xIPLw4Pqeh0jSWt1mPgho
|
|
|
131
131
|
localcode/tui/widgets/voice_visualizer.py,sha256=zOhrIxaMcg-F2Y8LvHJTddq227phmiTjtEyRbkx1XxU,4709
|
|
132
132
|
localcode/tui/widgets/messages/__init__.py,sha256=952AZ1qVMGUIqPIry7mwxnYbMkgPlcY5FAwZtEa5YPg,967
|
|
133
133
|
localcode/tui/widgets/messages/diff.py,sha256=khDf_MThrQz0S62-t-m7i3OU5cdNolUk1TCeKMRPDQI,10910
|
|
134
|
-
localcode-0.3.
|
|
135
|
-
localcode-0.3.
|
|
136
|
-
localcode-0.3.
|
|
137
|
-
localcode-0.3.
|
|
138
|
-
localcode-0.3.
|
|
139
|
-
localcode-0.3.
|
|
134
|
+
localcode-0.3.3.dist-info/licenses/LICENSE,sha256=Ahg0cteZ-6bIRB7KC15WEtdqWnzy8HY_yzsZBoVRql0,11294
|
|
135
|
+
localcode-0.3.3.dist-info/METADATA,sha256=22UXuP5QSAS0stB-2Nb7uUKNUlwKJPnnN7CnWmo6v2Y,7813
|
|
136
|
+
localcode-0.3.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
137
|
+
localcode-0.3.3.dist-info/entry_points.txt,sha256=rE7elTGUvMsBd-Quy22cuTVJ-zGVOmx0nEuC6ni9Tg4,87
|
|
138
|
+
localcode-0.3.3.dist-info/top_level.txt,sha256=58eL3Rw8v0OGmYNxjx8uS4ERWatrzUbyCL5inlukkTo,10
|
|
139
|
+
localcode-0.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|