alpiecode 10.0.3__tar.gz → 10.5.1__tar.gz
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.
- {alpiecode-10.0.3 → alpiecode-10.5.1}/PKG-INFO +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.1}/pyproject.toml +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/SOURCES.txt +3 -15
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/__init__.py +1 -1
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/agent.py +2 -1
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/backends/local_backend.py +34 -11
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/backends/openai_backend.py +14 -3
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/cli.py +78 -14
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/compaction.py +122 -10
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/config.py +2 -2
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/context.py +43 -14
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/discovery.py +5 -0
- alpiecode-10.5.1/src/codeagent/dream_rsi.py +374 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/executor.py +17 -6
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/local_model.py +72 -11
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/orchestrator.py +154 -17
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/prompt.py +11 -3
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/system1.py +99 -18
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/tools.py +27 -0
- alpiecode-10.5.1/test/test_context_and_trajectory.py +337 -0
- alpiecode-10.5.1/test/test_dream_rsi.py +478 -0
- alpiecode-10.0.3/test/test_audit_fixes.py +0 -71
- alpiecode-10.0.3/test/test_benchmark_system1.py +0 -90
- alpiecode-10.0.3/test/test_cache_reads.py +0 -66
- alpiecode-10.0.3/test/test_chat_mode.py +0 -85
- alpiecode-10.0.3/test/test_claude_codex_capabilities.py +0 -111
- alpiecode-10.0.3/test/test_context_filter.py +0 -57
- alpiecode-10.0.3/test/test_math_tools.py +0 -214
- alpiecode-10.0.3/test/test_metrics.py +0 -46
- alpiecode-10.0.3/test/test_offline_mode.py +0 -314
- alpiecode-10.0.3/test/test_offline_opt.py +0 -59
- alpiecode-10.0.3/test/test_palindrome_check.py +0 -50
- alpiecode-10.0.3/test/test_phase1.py +0 -302
- alpiecode-10.0.3/test/test_phase2.py +0 -231
- alpiecode-10.0.3/test/test_phase3.py +0 -154
- alpiecode-10.0.3/test/test_system1.py +0 -84
- {alpiecode-10.0.3 → alpiecode-10.5.1}/README.md +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/setup.cfg +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode/__init__.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/backends/base.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/cache.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/client.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/doctor.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/git_ops.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/github.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/guardian.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/guardrails.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/media.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/memory.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/metrics.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/progress.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/rephraser.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/server.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/session.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/updater.py +0 -0
- {alpiecode-10.0.3 → alpiecode-10.5.1}/src/codeagent/vscode_installer.py +0 -0
|
@@ -17,6 +17,7 @@ src/codeagent/config.py
|
|
|
17
17
|
src/codeagent/context.py
|
|
18
18
|
src/codeagent/discovery.py
|
|
19
19
|
src/codeagent/doctor.py
|
|
20
|
+
src/codeagent/dream_rsi.py
|
|
20
21
|
src/codeagent/executor.py
|
|
21
22
|
src/codeagent/git_ops.py
|
|
22
23
|
src/codeagent/github.py
|
|
@@ -42,18 +43,5 @@ src/codeagent/backends/base.py
|
|
|
42
43
|
src/codeagent/backends/local_backend.py
|
|
43
44
|
src/codeagent/backends/openai_backend.py
|
|
44
45
|
src/codeagent/extension/alpiecode.vsix
|
|
45
|
-
test/
|
|
46
|
-
test/
|
|
47
|
-
test/test_cache_reads.py
|
|
48
|
-
test/test_chat_mode.py
|
|
49
|
-
test/test_claude_codex_capabilities.py
|
|
50
|
-
test/test_context_filter.py
|
|
51
|
-
test/test_math_tools.py
|
|
52
|
-
test/test_metrics.py
|
|
53
|
-
test/test_offline_mode.py
|
|
54
|
-
test/test_offline_opt.py
|
|
55
|
-
test/test_palindrome_check.py
|
|
56
|
-
test/test_phase1.py
|
|
57
|
-
test/test_phase2.py
|
|
58
|
-
test/test_phase3.py
|
|
59
|
-
test/test_system1.py
|
|
46
|
+
test/test_context_and_trajectory.py
|
|
47
|
+
test/test_dream_rsi.py
|
|
@@ -17,7 +17,7 @@ Supports:
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Optional, Any, Dict
|
|
19
19
|
|
|
20
|
-
__version__ = "10.
|
|
20
|
+
__version__ = "10.5.1"
|
|
21
21
|
|
|
22
22
|
def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
|
|
23
23
|
"""Run an autonomous coding task programmatically."""
|
|
@@ -340,7 +340,8 @@ def run_agent(
|
|
|
340
340
|
console.print(f"📺 URL: {url}", style="cyan")
|
|
341
341
|
|
|
342
342
|
elif event.type == "adaptive_mode" and verbose and HAS_RICH:
|
|
343
|
-
|
|
343
|
+
msg = event.data.get("message") or "Adaptive mode: reasoning trace adjusted."
|
|
344
|
+
console.print(f"⚡ [dim]{msg}[/dim]")
|
|
344
345
|
|
|
345
346
|
elif event.type == "status":
|
|
346
347
|
phase = event.data.get("phase")
|
|
@@ -91,17 +91,38 @@ class LocalBackend:
|
|
|
91
91
|
elif not isinstance(content, str):
|
|
92
92
|
content = str(content)
|
|
93
93
|
|
|
94
|
-
# For assistant messages
|
|
94
|
+
# For assistant messages, condense bulky write_file / edit_file payloads from content
|
|
95
|
+
if role == "assistant":
|
|
96
|
+
from ..compaction import compact_text_tool_calls
|
|
97
|
+
content = compact_text_tool_calls(content)
|
|
98
|
+
|
|
99
|
+
# For assistant messages with tool_calls, embed tool call info in content text ONLY if not already marked
|
|
95
100
|
if role == "assistant" and msg.get("tool_calls"):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
already_marked = (
|
|
102
|
+
"[Completed " in content
|
|
103
|
+
or "[Incomplete" in content
|
|
104
|
+
or "[Called tool:" in content
|
|
105
|
+
or "<tool_call>" in content
|
|
106
|
+
)
|
|
107
|
+
if not already_marked:
|
|
108
|
+
tc_descriptions = []
|
|
109
|
+
for tc in msg["tool_calls"]:
|
|
110
|
+
if isinstance(tc, dict):
|
|
111
|
+
fn = tc.get("function", {})
|
|
112
|
+
name = fn.get("name", "unknown")
|
|
113
|
+
args = fn.get("arguments", "{}")
|
|
114
|
+
# Do not duplicate full file content in context history
|
|
115
|
+
if name in ("write_file", "edit_file", "apply_patch"):
|
|
116
|
+
try:
|
|
117
|
+
import json as _json
|
|
118
|
+
a_dict = _json.loads(args) if isinstance(args, str) else args
|
|
119
|
+
p = a_dict.get("path") or a_dict.get("filename") or a_dict.get("file") or ""
|
|
120
|
+
args = f'{{"path": "{p}"}}'
|
|
121
|
+
except Exception:
|
|
122
|
+
args = "{...}"
|
|
123
|
+
tc_descriptions.append(f"[Called tool: {name}({args})]")
|
|
124
|
+
if tc_descriptions:
|
|
125
|
+
content = (content + "\n" + "\n".join(tc_descriptions)).strip()
|
|
105
126
|
# Do NOT include tool_calls key — GGUF template will crash on it
|
|
106
127
|
|
|
107
128
|
clean["content"] = content
|
|
@@ -247,7 +268,9 @@ class LocalBackend:
|
|
|
247
268
|
inside_suppressed_think = True
|
|
248
269
|
text = text.replace("<think>", "")
|
|
249
270
|
if inside_suppressed_think:
|
|
250
|
-
if "
|
|
271
|
+
if "<tool_call>" in text or "DONE:" in text or "<function=" in text:
|
|
272
|
+
inside_suppressed_think = False
|
|
273
|
+
elif "</think>" in text:
|
|
251
274
|
_, after = text.split("</think>", 1)
|
|
252
275
|
inside_suppressed_think = False
|
|
253
276
|
text = after
|
|
@@ -54,14 +54,20 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
|
|
|
54
54
|
# If trailing comma at end: `{"a": 1,` -> `{"a": 1`
|
|
55
55
|
repaired = re.sub(r",\s*$", "", repaired)
|
|
56
56
|
|
|
57
|
+
was_truncated = False
|
|
57
58
|
# Check unclosed quotes: count non-escaped double quotes
|
|
58
59
|
quote_count = len(re.findall(r'(?<!\\)"', repaired))
|
|
59
60
|
if quote_count % 2 != 0:
|
|
61
|
+
was_truncated = True
|
|
62
|
+
dangling_bs = len(repaired) - len(repaired.rstrip('\\'))
|
|
63
|
+
if dangling_bs % 2 != 0:
|
|
64
|
+
repaired = repaired[:-1]
|
|
60
65
|
repaired += '"'
|
|
61
66
|
|
|
62
|
-
# Balance unclosed brackets and braces
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
# Balance unclosed brackets and braces outside string literals
|
|
68
|
+
stripped = re.sub(r'"(?:\\.|[^"\\])*"', '""', repaired)
|
|
69
|
+
open_curly = stripped.count("{") - stripped.count("}")
|
|
70
|
+
open_square = stripped.count("[") - stripped.count("]")
|
|
65
71
|
|
|
66
72
|
if open_square > 0:
|
|
67
73
|
repaired += "]" * open_square
|
|
@@ -71,6 +77,8 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
|
|
|
71
77
|
try:
|
|
72
78
|
parsed = json.loads(repaired)
|
|
73
79
|
if isinstance(parsed, dict):
|
|
80
|
+
if was_truncated:
|
|
81
|
+
parsed["_was_truncated"] = True
|
|
74
82
|
return parsed
|
|
75
83
|
except Exception:
|
|
76
84
|
pass
|
|
@@ -91,6 +99,9 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
|
|
|
91
99
|
extracted[k] = v_lit
|
|
92
100
|
|
|
93
101
|
if extracted:
|
|
102
|
+
if "name" in extracted and "arguments" not in extracted:
|
|
103
|
+
args = {k: v for k, v in extracted.items() if k not in ("name", "id", "type")}
|
|
104
|
+
return {"name": extracted["name"], "arguments": args}
|
|
94
105
|
return extracted
|
|
95
106
|
|
|
96
107
|
logger.warning("Failed to parse tool call arguments: %s", raw_args[:200])
|
|
@@ -29,18 +29,30 @@ def _show_banner():
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _normalize_args():
|
|
32
|
-
known_commands = {"init", "serve", "run", "chat", "plan", "diff", "undo", "doctor", "explain", "version", "-h", "--help", "-v", "--version"}
|
|
32
|
+
known_commands = {"init", "serve", "run", "chat", "plan", "diff", "undo", "doctor", "explain", "dream", "version", "-h", "--help", "-v", "--version"}
|
|
33
33
|
subcommand = None
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
# Normalize common flag typos / aliases before parsing
|
|
36
|
+
for idx, arg in enumerate(sys.argv):
|
|
37
|
+
if arg in ("--offlie", "-offline"):
|
|
38
|
+
sys.argv[idx] = "--offline"
|
|
39
|
+
|
|
35
40
|
# Check if a subcommand is present
|
|
36
41
|
for arg in sys.argv[1:]:
|
|
37
42
|
if arg in known_commands:
|
|
38
43
|
subcommand = arg
|
|
39
44
|
break
|
|
40
|
-
|
|
45
|
+
|
|
41
46
|
if not subcommand and len(sys.argv) > 1:
|
|
42
|
-
|
|
43
|
-
sys.argv.
|
|
47
|
+
# Determine if any positional non-flag argument was provided
|
|
48
|
+
positional_args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
|
49
|
+
if not positional_args:
|
|
50
|
+
# Flags like --offline or --thinking given without a subcommand or task -> launch chat mode
|
|
51
|
+
subcommand = "chat"
|
|
52
|
+
sys.argv.insert(1, "chat")
|
|
53
|
+
else:
|
|
54
|
+
subcommand = "run"
|
|
55
|
+
sys.argv.insert(1, "run")
|
|
44
56
|
elif subcommand and subcommand not in ("-h", "--help", "-v", "--version") and sys.argv[1] != subcommand:
|
|
45
57
|
# Move subcommand to index 1 so subparsers parse arguments correctly without default override
|
|
46
58
|
sys.argv.remove(subcommand)
|
|
@@ -49,11 +61,11 @@ def _normalize_args():
|
|
|
49
61
|
if subcommand in ("run", "plan", "explain") and len(sys.argv) > 2:
|
|
50
62
|
cmd_idx = sys.argv.index(subcommand)
|
|
51
63
|
sub_args = sys.argv[cmd_idx + 1:]
|
|
52
|
-
|
|
64
|
+
|
|
53
65
|
flags = []
|
|
54
66
|
positionals = []
|
|
55
|
-
value_flags = {"--workdir", "--image", "--video", "--url", "--github", "--max-turns", "--host", "--port"}
|
|
56
|
-
|
|
67
|
+
value_flags = {"--workdir", "--image", "--video", "--url", "--github", "--max-turns", "--host", "--port", "--ctx-size"}
|
|
68
|
+
|
|
57
69
|
i = 0
|
|
58
70
|
while i < len(sub_args):
|
|
59
71
|
arg = sub_args[i]
|
|
@@ -67,7 +79,7 @@ def _normalize_args():
|
|
|
67
79
|
else:
|
|
68
80
|
positionals.append(arg)
|
|
69
81
|
i += 1
|
|
70
|
-
|
|
82
|
+
|
|
71
83
|
sys.argv = sys.argv[:cmd_idx + 1] + flags + positionals
|
|
72
84
|
|
|
73
85
|
|
|
@@ -87,7 +99,7 @@ def main():
|
|
|
87
99
|
common.add_argument("--quiet", action="store_true", help="Suppress per-turn logging")
|
|
88
100
|
common.add_argument("--debug", action="store_true", help="Show autonomous discovery and debug diagnostics")
|
|
89
101
|
common.add_argument("--offline", "--local", dest="offline", action="store_true", help="Run using local offline GGUF model on GPU without internet")
|
|
90
|
-
common.add_argument("--ctx-size", type=int, default=None, help="Context size in tokens (default:
|
|
102
|
+
common.add_argument("--ctx-size", type=int, default=None, help="Context size in tokens (default: 8192 offline, 262144 online)")
|
|
91
103
|
|
|
92
104
|
from . import __version__
|
|
93
105
|
parser = argparse.ArgumentParser(
|
|
@@ -103,7 +115,7 @@ def main():
|
|
|
103
115
|
sub.add_parser("init", help="Configure your VLM/OpenAI-compatible endpoint")
|
|
104
116
|
|
|
105
117
|
# ── serve ──
|
|
106
|
-
serve_p = sub.add_parser("serve", help="Start the AlpieCode API server")
|
|
118
|
+
serve_p = sub.add_parser("serve", help="Start the AlpieCode API server", parents=[common])
|
|
107
119
|
serve_p.add_argument("--host", default="127.0.0.1", help="Host address to bind to (default: 127.0.0.1)")
|
|
108
120
|
serve_p.add_argument("--port", type=int, default=7169, help="Port to listen on (default: 7169)")
|
|
109
121
|
|
|
@@ -125,12 +137,18 @@ def main():
|
|
|
125
137
|
undo_p = sub.add_parser("undo", help="Revert file changes made by the last AlpieCode session", parents=[common])
|
|
126
138
|
|
|
127
139
|
# ── doctor ──
|
|
128
|
-
sub.add_parser("doctor", help="Run system health checks (CUDA, Python, network, dev tools)")
|
|
140
|
+
sub.add_parser("doctor", help="Run system health checks (CUDA, Python, network, dev tools)", parents=[common])
|
|
129
141
|
|
|
130
142
|
# ── explain ──
|
|
131
143
|
explain_p = sub.add_parser("explain", help="Explain a file, function, or codebase concept", parents=[common])
|
|
132
144
|
explain_p.add_argument("target", help="File path or concept to explain")
|
|
133
145
|
|
|
146
|
+
# ?? dream ??
|
|
147
|
+
dream_p = sub.add_parser('dream', help='Dream-RSI: offline recursive self-improvement simulation', parents=[common])
|
|
148
|
+
dream_p.add_argument('--optimize', action='store_true', help='Run offline dreaming simulation to synthesize optimal search policy')
|
|
149
|
+
dream_p.add_argument('--stats', action='store_true', help='Show trajectory replay metrics and active policy status')
|
|
150
|
+
dream_p.add_argument('--clear', action='store_true', help='Clear historical trajectory replay pool')
|
|
151
|
+
|
|
134
152
|
args = parser.parse_args()
|
|
135
153
|
|
|
136
154
|
if not args.command:
|
|
@@ -145,6 +163,52 @@ def main():
|
|
|
145
163
|
interactive_init()
|
|
146
164
|
return
|
|
147
165
|
|
|
166
|
+
if args.command == 'dream':
|
|
167
|
+
import time
|
|
168
|
+
from .dream_rsi import TrajectoryStore, DreamOptimizer, load_active_dream_profile
|
|
169
|
+
store = TrajectoryStore()
|
|
170
|
+
optimizer = DreamOptimizer(store)
|
|
171
|
+
if getattr(args, 'clear', False):
|
|
172
|
+
count = store.clear_trajectories()
|
|
173
|
+
print(f'?? Cleared {count} historical trajectories from replay pool.')
|
|
174
|
+
return
|
|
175
|
+
if getattr(args, 'optimize', False):
|
|
176
|
+
print('?? Running Dream-RSI offline recursive self-improvement simulation...')
|
|
177
|
+
profile, res = optimizer.optimize()
|
|
178
|
+
if res.get('status') == 'no_trajectories':
|
|
179
|
+
print('?? No historical trajectories recorded yet. Run tasks with alpiecode run to populate the replay buffer.')
|
|
180
|
+
return
|
|
181
|
+
metrics = res.get('metrics', {})
|
|
182
|
+
print('\n? Dream-RSI Optimization Complete!')
|
|
183
|
+
print(f' ?? Trajectories Replayed: {metrics.get("total_trajectories", 0)}')
|
|
184
|
+
print(f' ?? Total Turns Saved: {metrics.get("total_turns_saved", 0)}')
|
|
185
|
+
print(f' ?? Loops Prevented: {metrics.get("total_loops_prevented", 0)}')
|
|
186
|
+
print(f' ?? Stalls Averted: {metrics.get("total_stalls_averted", 0)}')
|
|
187
|
+
print(f' ? Simulation Fitness: {metrics.get("average_score", 0.0)}')
|
|
188
|
+
print('\n?? Active Policy Updated:')
|
|
189
|
+
print(f' ? Syntax Loop Threshold: {profile.syntax_loop_threshold}')
|
|
190
|
+
print(f' ? General Loop Threshold: {profile.general_loop_threshold}')
|
|
191
|
+
print(f' ? Stall Trigger: {profile.stall_threshold}')
|
|
192
|
+
print(f' ? Single-File Enforcement: {profile.single_file_enforcement}')
|
|
193
|
+
return
|
|
194
|
+
trajectories = store.list_trajectories()
|
|
195
|
+
profile = load_active_dream_profile()
|
|
196
|
+
successes = sum(1 for t in trajectories if t.outcome == 'success')
|
|
197
|
+
failures = sum(1 for t in trajectories if t.outcome != 'success')
|
|
198
|
+
print('\n?? Dream-RSI Status & Replay Pool')
|
|
199
|
+
print(f' ?? Replay Buffer: {len(trajectories)} trajectories stored')
|
|
200
|
+
print(f' ? Successful Runs: {successes}')
|
|
201
|
+
print(f' ?? Failed/Stalled Runs: {failures}')
|
|
202
|
+
print('\n?? Active Policy Profile:')
|
|
203
|
+
print(f' ? Syntax Loop Threshold: {profile.syntax_loop_threshold}')
|
|
204
|
+
print(f' ? General Loop Threshold: {profile.general_loop_threshold}')
|
|
205
|
+
print(f' ? Stall Trigger: {profile.stall_threshold}')
|
|
206
|
+
print(f' ? Single-File Enforcement: {profile.single_file_enforcement}')
|
|
207
|
+
opt_time = time.ctime(profile.last_optimized_at) if profile.last_optimized_at else 'Never'
|
|
208
|
+
print(f' ? Last Optimized: {opt_time}')
|
|
209
|
+
print('\n?? Tip: Run alpiecode dream --optimize to run offline self-improvement.')
|
|
210
|
+
return
|
|
211
|
+
|
|
148
212
|
if args.command == "doctor":
|
|
149
213
|
from .doctor import run_doctor
|
|
150
214
|
sys.exit(run_doctor())
|
|
@@ -175,9 +239,9 @@ def main():
|
|
|
175
239
|
|
|
176
240
|
if getattr(args, "offline", False):
|
|
177
241
|
cfg.base_url = "offline"
|
|
178
|
-
#
|
|
242
|
+
# 8192 default fits 4.4GB model + KV cache 100% in 6GB VRAM (4.75GB / 6GB) with zero host-paging
|
|
179
243
|
import os
|
|
180
|
-
cfg.n_ctx = int(os.environ.get("ALPIECODE_CTX_SIZE",
|
|
244
|
+
cfg.n_ctx = int(os.environ.get("ALPIECODE_CTX_SIZE", 8192))
|
|
181
245
|
os.environ["ALPIECODE_BASE_URL"] = "offline"
|
|
182
246
|
|
|
183
247
|
if getattr(args, "ctx_size", None):
|
|
@@ -15,6 +15,7 @@ Strategy:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
|
+
import re
|
|
18
19
|
from typing import Any, Dict, List, Optional
|
|
19
20
|
|
|
20
21
|
# Our model's context window
|
|
@@ -219,27 +220,120 @@ def select_relevant_history(
|
|
|
219
220
|
return "### Relevant Historical Anchors:\n" + "\n".join(reversed(high_value_notes))
|
|
220
221
|
|
|
221
222
|
|
|
222
|
-
def
|
|
223
|
+
def compact_text_tool_calls(text: str) -> str:
|
|
224
|
+
"""Condense bulky file content in text-based tool calls."""
|
|
225
|
+
if not text or ("write_file" not in text and "edit_file" not in text and "apply_patch" not in text):
|
|
226
|
+
return text
|
|
227
|
+
|
|
228
|
+
def _replace_block(m):
|
|
229
|
+
raw = m.group(1).strip()
|
|
230
|
+
try:
|
|
231
|
+
from .backends.openai_backend import _repair_and_parse_json_arguments
|
|
232
|
+
data = _repair_and_parse_json_arguments(raw)
|
|
233
|
+
if isinstance(data, dict) and data.get("name") in ("write_file", "edit_file", "apply_patch"):
|
|
234
|
+
args = data.get("arguments", {})
|
|
235
|
+
if isinstance(args, dict):
|
|
236
|
+
p = args.get("path") or args.get("filename") or args.get("file") or "file"
|
|
237
|
+
return f"[Completed {data['name']} for '{p}']"
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
return m.group(0)
|
|
241
|
+
|
|
242
|
+
# 1. First condense complete/closed <tool_call>...</tool_call> blocks
|
|
243
|
+
text = re.sub(r"<tool_call>\s*(.*?)\s*</tool_call>", _replace_block, text, flags=re.DOTALL)
|
|
244
|
+
|
|
245
|
+
# 2. Also condense unclosed/truncated <tool_call> blocks (e.g. from token cutoff before </tool_call>)
|
|
246
|
+
if "<tool_call>" in text:
|
|
247
|
+
def _replace_unclosed(m):
|
|
248
|
+
raw = m.group(0)
|
|
249
|
+
t_name = "write_file"
|
|
250
|
+
p = "file"
|
|
251
|
+
name_m = re.search(r'"name":\s*"([^"]+)"', raw)
|
|
252
|
+
if name_m:
|
|
253
|
+
t_name = name_m.group(1)
|
|
254
|
+
path_m = re.search(r'"(?:path|filename|file)":\s*"([^"]+)"', raw)
|
|
255
|
+
if path_m:
|
|
256
|
+
p = path_m.group(1)
|
|
257
|
+
return f"[Incomplete/truncated {t_name} for '{p}']"
|
|
258
|
+
|
|
259
|
+
text = re.sub(r"<tool_call>(?:(?!</tool_call>).)*$", _replace_unclosed, text, flags=re.DOTALL)
|
|
260
|
+
|
|
261
|
+
return text
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def compact_tool_calls(tool_calls: List[Any]) -> List[Any]:
|
|
265
|
+
"""Condense bulky file content in historical tool calls to conserve context tokens."""
|
|
266
|
+
if not tool_calls:
|
|
267
|
+
return []
|
|
268
|
+
compacted = []
|
|
269
|
+
for tc in tool_calls:
|
|
270
|
+
if not isinstance(tc, dict):
|
|
271
|
+
compacted.append(tc)
|
|
272
|
+
continue
|
|
273
|
+
fn = tc.get("function", {})
|
|
274
|
+
if not isinstance(fn, dict):
|
|
275
|
+
compacted.append(tc)
|
|
276
|
+
continue
|
|
277
|
+
name = fn.get("name", "")
|
|
278
|
+
raw_args = fn.get("arguments", "")
|
|
279
|
+
if name in ("write_file", "edit_file", "apply_patch"):
|
|
280
|
+
try:
|
|
281
|
+
args = json.loads(raw_args) if isinstance(raw_args, str) else dict(raw_args)
|
|
282
|
+
content = args.get("content") or args.get("patch") or ""
|
|
283
|
+
path = args.get("path") or args.get("filename") or "file"
|
|
284
|
+
if isinstance(content, str) and len(content) > 150:
|
|
285
|
+
lines = content.splitlines()
|
|
286
|
+
args["content"] = f"[Content saved to {path} ({len(lines)} lines, {len(content)} bytes)]"
|
|
287
|
+
compact_tc = dict(tc)
|
|
288
|
+
compact_tc["function"] = dict(fn)
|
|
289
|
+
compact_tc["function"]["arguments"] = json.dumps(args) if isinstance(raw_args, str) else args
|
|
290
|
+
compacted.append(compact_tc)
|
|
291
|
+
continue
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
compacted.append(tc)
|
|
295
|
+
return compacted
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def compact_messages(messages: List[dict], keep_recent: int = KEEP_RECENT_TURNS) -> List[dict]:
|
|
223
299
|
"""
|
|
224
300
|
Compact a message list by summarizing older turns.
|
|
225
301
|
|
|
226
302
|
Preserves:
|
|
227
303
|
- System prompt (index 0)
|
|
228
304
|
- All user messages (verbatim)
|
|
229
|
-
- Last
|
|
305
|
+
- Last keep_recent messages (verbatim, with older tool arguments trimmed if bulky)
|
|
230
306
|
|
|
231
307
|
Summarizes:
|
|
232
308
|
- Older tool results (truncated)
|
|
233
309
|
- Older assistant reasoning (removed)
|
|
310
|
+
- Older write_file/edit_file payload content (condensed)
|
|
234
311
|
"""
|
|
235
|
-
if len(messages) <=
|
|
236
|
-
|
|
312
|
+
if len(messages) <= keep_recent + 2:
|
|
313
|
+
# Even if short, condense older bulky assistant tool calls while keeping latest intact
|
|
314
|
+
result = []
|
|
315
|
+
last_asst_idx = -1
|
|
316
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
317
|
+
if messages[i].get("role") == "assistant":
|
|
318
|
+
last_asst_idx = i
|
|
319
|
+
break
|
|
320
|
+
for i, m in enumerate(messages):
|
|
321
|
+
if i != last_asst_idx and m.get("role") == "assistant":
|
|
322
|
+
cm = dict(m)
|
|
323
|
+
if cm.get("tool_calls"):
|
|
324
|
+
cm["tool_calls"] = compact_tool_calls(m["tool_calls"])
|
|
325
|
+
if cm.get("content"):
|
|
326
|
+
cm["content"] = compact_text_tool_calls(cm["content"])
|
|
327
|
+
result.append(cm)
|
|
328
|
+
else:
|
|
329
|
+
result.append(m)
|
|
330
|
+
return result
|
|
237
331
|
|
|
238
332
|
# Always keep system prompt
|
|
239
333
|
system = messages[0] if messages and messages[0].get("role") == "system" else None
|
|
240
334
|
|
|
241
335
|
# Split into old and recent
|
|
242
|
-
cutoff = len(messages) -
|
|
336
|
+
cutoff = len(messages) - keep_recent
|
|
243
337
|
|
|
244
338
|
# ── Tool-Call Pairing Guard ──
|
|
245
339
|
# If cutoff lands on a 'tool' message, move it backwards to include
|
|
@@ -263,11 +357,11 @@ def compact_messages(messages: List[dict]) -> List[dict]:
|
|
|
263
357
|
compacted_old.append(msg)
|
|
264
358
|
|
|
265
359
|
elif role == "assistant":
|
|
266
|
-
# Compact assistant messages: keep tool calls but remove reasoning
|
|
360
|
+
# Compact assistant messages: keep tool calls (condensed) but remove reasoning
|
|
267
361
|
compact_msg = {"role": "assistant"}
|
|
268
362
|
if msg.get("content"):
|
|
269
363
|
# Truncate long assistant content
|
|
270
|
-
content = msg["content"]
|
|
364
|
+
content = compact_text_tool_calls(msg["content"])
|
|
271
365
|
if len(content) > 200:
|
|
272
366
|
compact_msg["content"] = content[:200] + "..."
|
|
273
367
|
else:
|
|
@@ -276,7 +370,7 @@ def compact_messages(messages: List[dict]) -> List[dict]:
|
|
|
276
370
|
compact_msg["content"] = None
|
|
277
371
|
|
|
278
372
|
if msg.get("tool_calls"):
|
|
279
|
-
compact_msg["tool_calls"] = msg["tool_calls"]
|
|
373
|
+
compact_msg["tool_calls"] = compact_tool_calls(msg["tool_calls"])
|
|
280
374
|
compacted_old.append(compact_msg)
|
|
281
375
|
|
|
282
376
|
elif role == "tool":
|
|
@@ -301,11 +395,29 @@ def compact_messages(messages: List[dict]) -> List[dict]:
|
|
|
301
395
|
"content": _summarize_tool_result(tool_name, content),
|
|
302
396
|
})
|
|
303
397
|
|
|
398
|
+
# In recent messages, also condense older assistant tool calls (except the immediate last one)
|
|
399
|
+
last_asst_idx = -1
|
|
400
|
+
for i in range(len(recent_messages) - 1, -1, -1):
|
|
401
|
+
if recent_messages[i].get("role") == "assistant":
|
|
402
|
+
last_asst_idx = i
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
cleaned_recent = []
|
|
406
|
+
for i, m in enumerate(recent_messages):
|
|
407
|
+
if i != last_asst_idx and m.get("role") == "assistant":
|
|
408
|
+
cm = dict(m)
|
|
409
|
+
if cm.get("tool_calls"):
|
|
410
|
+
cm["tool_calls"] = compact_tool_calls(m["tool_calls"])
|
|
411
|
+
if cm.get("content"):
|
|
412
|
+
cm["content"] = compact_text_tool_calls(cm["content"])
|
|
413
|
+
cleaned_recent.append(cm)
|
|
414
|
+
else:
|
|
415
|
+
cleaned_recent.append(m)
|
|
416
|
+
|
|
304
417
|
# Rebuild message list
|
|
305
418
|
result = []
|
|
306
419
|
if system:
|
|
307
420
|
result.append(system)
|
|
308
421
|
result.extend(compacted_old)
|
|
309
|
-
result.extend(
|
|
310
|
-
|
|
422
|
+
result.extend(cleaned_recent)
|
|
311
423
|
return result
|
|
@@ -158,8 +158,8 @@ def load_config() -> Config:
|
|
|
158
158
|
# For offline mode, set high-performance 16k context (16384 tokens) for big multi-file projects
|
|
159
159
|
# Fits completely in 6GB VRAM on RTX 3050 with zero paging
|
|
160
160
|
if data.get("base_url") == "offline" and not any(os.environ.get(v) for v in ("ALPIECODE_N_CTX", "ALPIECODE_CTX_SIZE")):
|
|
161
|
-
if data.get("n_ctx") in (32768, 4096):
|
|
162
|
-
data["n_ctx"] =
|
|
161
|
+
if data.get("n_ctx") in (32768, 4096, 16384):
|
|
162
|
+
data["n_ctx"] = 8192
|
|
163
163
|
if data.get("max_tokens", 8192) > 4096:
|
|
164
164
|
data["max_tokens"] = 4096
|
|
165
165
|
|
|
@@ -14,6 +14,8 @@ from typing import Any, Dict, List, Optional
|
|
|
14
14
|
|
|
15
15
|
from .backends.base import ChatResponse, ToolCall
|
|
16
16
|
from .compaction import (
|
|
17
|
+
compact_tool_calls,
|
|
18
|
+
compact_text_tool_calls,
|
|
17
19
|
CHARS_PER_TOKEN,
|
|
18
20
|
compact_messages,
|
|
19
21
|
estimate_tokens,
|
|
@@ -350,25 +352,42 @@ class ContextManager:
|
|
|
350
352
|
context_blocks.append(dict(m))
|
|
351
353
|
break
|
|
352
354
|
|
|
355
|
+
# ── Budget Verification & Headroom Limits ──
|
|
356
|
+
effective_limit = max_tokens or self.max_tokens
|
|
357
|
+
|
|
353
358
|
# ── Layer 4 & 5: Recent Messages and Current Request ──
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
359
|
+
# Condense bulky file write payloads in older assistant messages
|
|
360
|
+
last_asst_idx = -1
|
|
361
|
+
for i in range(len(recent_messages) - 1, -1, -1):
|
|
362
|
+
if recent_messages[i].get("role") == "assistant":
|
|
363
|
+
last_asst_idx = i
|
|
364
|
+
break
|
|
365
|
+
|
|
366
|
+
for i, m in enumerate(recent_messages):
|
|
367
|
+
if i != last_asst_idx and m.get("role") == "assistant":
|
|
368
|
+
if m.get("tool_calls"):
|
|
369
|
+
m["tool_calls"] = compact_tool_calls(m["tool_calls"])
|
|
370
|
+
if m.get("content"):
|
|
371
|
+
m["content"] = compact_text_tool_calls(m["content"])
|
|
372
|
+
elif m.get("role") == "tool":
|
|
373
|
+
c = str(m.get("content") or "")
|
|
374
|
+
if len(c) > 300 and "[Plan updated]" not in c and "Plan Status:" not in c:
|
|
375
|
+
lines = c.splitlines()
|
|
376
|
+
if len(lines) > 8:
|
|
377
|
+
top = "\n".join(lines[:3])
|
|
378
|
+
bot = "\n".join(lines[-2:])
|
|
379
|
+
m["content"] = f"[Output: {len(lines)} lines condensed]\n{top}\n...\n{bot}"
|
|
380
|
+
else:
|
|
381
|
+
m["content"] = c[:180] + "... [condensed]"
|
|
365
382
|
|
|
366
383
|
context_blocks.extend(recent_messages)
|
|
367
384
|
|
|
368
385
|
# ── Budget Verification ──
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
386
|
+
reserved_headroom = 2048 if effective_limit <= 8192 else 4096
|
|
387
|
+
prompt_budget = max(1024, effective_limit - reserved_headroom)
|
|
388
|
+
|
|
389
|
+
if estimate_tokens(context_blocks) > prompt_budget:
|
|
390
|
+
context_blocks = compact_messages(context_blocks, keep_recent=4 if effective_limit <= 8192 else 8)
|
|
372
391
|
|
|
373
392
|
if is_default_params:
|
|
374
393
|
self._cached_context = [dict(m) for m in context_blocks]
|
|
@@ -403,6 +422,16 @@ class ContextManager:
|
|
|
403
422
|
"""Returns parallel metadata list for all messages."""
|
|
404
423
|
return list(self._metadata)
|
|
405
424
|
|
|
425
|
+
def compact_for_headroom(self, target_headroom: int = 1536, n_ctx: int = 8192, tools_overhead: int = 800) -> int:
|
|
426
|
+
"""
|
|
427
|
+
Actively compact context to ensure at least target_headroom tokens are available for generation.
|
|
428
|
+
Returns the new estimated token count.
|
|
429
|
+
"""
|
|
430
|
+
self.invalidate_cache(reset_summary=True)
|
|
431
|
+
self._cached_summary = extract_conversation_summary(self._messages, self._metadata)
|
|
432
|
+
self._messages = compact_messages(self._messages, keep_recent=4)
|
|
433
|
+
return self.estimate_tokens()
|
|
434
|
+
|
|
406
435
|
def estimate_tokens(self) -> int:
|
|
407
436
|
"""Estimate tokens of the active built context."""
|
|
408
437
|
return estimate_tokens(self.build_context())
|
|
@@ -565,6 +565,11 @@ def gather_relevant_context(
|
|
|
565
565
|
return None
|
|
566
566
|
|
|
567
567
|
task_lower = task.lower().strip()
|
|
568
|
+
|
|
569
|
+
# Skip pre-reading repo files for standalone algorithms or scripts (e.g. palindrome, calculator)
|
|
570
|
+
standalone_kws = ["palindrome", "fibonacci", "quicksort", "bubblesort", "factorial", "hello world", "calculator", "reverse string", "reverse_string"]
|
|
571
|
+
if any(kw in task_lower for kw in standalone_kws):
|
|
572
|
+
return None
|
|
568
573
|
sections: List[str] = []
|
|
569
574
|
chars_used = 0
|
|
570
575
|
|