alpiecode 5.1.1__tar.gz → 6.0.0__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-5.1.1 → alpiecode-6.0.0}/PKG-INFO +1 -1
- {alpiecode-5.1.1 → alpiecode-6.0.0}/pyproject.toml +1 -1
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/agent.py +15 -7
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/cli.py +2 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/compaction.py +1 -1
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/discovery.py +18 -15
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/guardian.py +9 -4
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/orchestrator.py +23 -6
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/prompt.py +29 -6
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/tools.py +39 -9
- {alpiecode-5.1.1 → alpiecode-6.0.0}/README.md +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/setup.cfg +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode/__init__.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/SOURCES.txt +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/__init__.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/backends/base.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/backends/openai_backend.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/cache.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/client.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/config.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/context.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/doctor.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/executor.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/github.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/local_model.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/media.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/memory.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/server.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/session.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/updater.py +0 -0
- {alpiecode-5.1.1 → alpiecode-6.0.0}/src/codeagent/vscode_installer.py +0 -0
|
@@ -37,6 +37,7 @@ _build_system_prompt = lambda workdir, is_offline=False: PromptBuilder().build_s
|
|
|
37
37
|
try:
|
|
38
38
|
from rich.console import Console
|
|
39
39
|
from rich.markdown import Markdown
|
|
40
|
+
from rich.markup import escape
|
|
40
41
|
from rich.panel import Panel
|
|
41
42
|
from rich.rule import Rule
|
|
42
43
|
from rich.text import Text
|
|
@@ -46,6 +47,9 @@ try:
|
|
|
46
47
|
except ImportError:
|
|
47
48
|
HAS_RICH = False
|
|
48
49
|
|
|
50
|
+
def escape(text: str) -> str:
|
|
51
|
+
return text
|
|
52
|
+
|
|
49
53
|
class _FallbackConsole:
|
|
50
54
|
def print(self, *args, **kwargs):
|
|
51
55
|
kwargs.pop("style", None)
|
|
@@ -78,7 +82,7 @@ def _print_tool_call(turn: int, name: str, args: dict):
|
|
|
78
82
|
if HAS_RICH:
|
|
79
83
|
args_str = json.dumps(display_args, indent=2)
|
|
80
84
|
console.print(f"\n🔧 [bold cyan]Tool:[/bold cyan] [bold]{name}[/bold]", highlight=False)
|
|
81
|
-
console.print(f" {args_str}", style="cyan"
|
|
85
|
+
console.print(Text(f" {args_str}", style="cyan"))
|
|
82
86
|
else:
|
|
83
87
|
console.print(f"\n🔧 Tool: {name}({display_args})")
|
|
84
88
|
|
|
@@ -86,7 +90,7 @@ def _print_tool_call(turn: int, name: str, args: dict):
|
|
|
86
90
|
def _print_tool_result(result: str):
|
|
87
91
|
truncated = result[:1500] + ("..." if len(result) > 1500 else "")
|
|
88
92
|
if HAS_RICH:
|
|
89
|
-
console.print(f" → {truncated}", style="green"
|
|
93
|
+
console.print(Text(f" → {truncated}", style="green"))
|
|
90
94
|
else:
|
|
91
95
|
console.print(f" → {truncated}")
|
|
92
96
|
|
|
@@ -97,7 +101,7 @@ def _print_assistant_message(content: str):
|
|
|
97
101
|
md = Markdown(content)
|
|
98
102
|
console.print(Panel(md, title="🤖 Assistant", border_style="green", padding=(0, 1)))
|
|
99
103
|
except Exception:
|
|
100
|
-
console.print(Panel(content, title="🤖 Assistant", border_style="green", padding=(0, 1)))
|
|
104
|
+
console.print(Panel(Text(content), title="🤖 Assistant", border_style="green", padding=(0, 1)))
|
|
101
105
|
else:
|
|
102
106
|
console.print(f"\n🤖 Assistant: {content}")
|
|
103
107
|
|
|
@@ -225,7 +229,7 @@ def run_agent(
|
|
|
225
229
|
data = event.data
|
|
226
230
|
if HAS_RICH:
|
|
227
231
|
console.rule("[bold blue]Agent Started[/bold blue]")
|
|
228
|
-
console.print(f"📋 Task: {task.splitlines()[0]}", style="bold")
|
|
232
|
+
console.print(Text(f"📋 Task: {task.splitlines()[0]}", style="bold"))
|
|
229
233
|
if github_repo:
|
|
230
234
|
console.print(f"🐙 GitHub Repo: {github_repo}", style="cyan")
|
|
231
235
|
if image_path:
|
|
@@ -288,8 +292,9 @@ def run_agent(
|
|
|
288
292
|
_checkpoint(workdir, "checkpoint: response")
|
|
289
293
|
|
|
290
294
|
elif event.type == "fallback" and verbose:
|
|
295
|
+
err_str = escape(str(event.data.get('error', '')))
|
|
291
296
|
if HAS_RICH:
|
|
292
|
-
console.print(f"\n⚠️ [bold yellow]Online Server Error / Timeout[/bold yellow] ({
|
|
297
|
+
console.print(f"\n⚠️ [bold yellow]Online Server Error / Timeout[/bold yellow] ({err_str})", style="yellow")
|
|
293
298
|
console.print("🔄 [bold cyan]Auto-falling back to local GGUF engine...[/bold cyan]", style="cyan")
|
|
294
299
|
else:
|
|
295
300
|
print(f"\n⚠️ Online Server Error: {event.data['error']}")
|
|
@@ -297,7 +302,7 @@ def run_agent(
|
|
|
297
302
|
|
|
298
303
|
elif event.type == "error" and verbose:
|
|
299
304
|
if HAS_RICH:
|
|
300
|
-
console.print(f"\n❌
|
|
305
|
+
console.print(Text(f"\n❌ Model Error\n Error: {event.data['error']}\n", style="bold red"))
|
|
301
306
|
else:
|
|
302
307
|
print(f"\n❌ Model Error: {event.data['error']}")
|
|
303
308
|
|
|
@@ -385,7 +390,10 @@ def run_chat(workdir: Path, cfg: Config, verbose: bool = True) -> None:
|
|
|
385
390
|
_checkpoint(workdir, "checkpoint: done")
|
|
386
391
|
|
|
387
392
|
elif event.type == "error":
|
|
388
|
-
|
|
393
|
+
if HAS_RICH:
|
|
394
|
+
console.print(Text(f"❌ Model error: {event.data['error']}", style="bold red"))
|
|
395
|
+
else:
|
|
396
|
+
print(f"❌ Model error: {event.data['error']}")
|
|
389
397
|
|
|
390
398
|
elif event.type == "done":
|
|
391
399
|
break
|
|
@@ -162,6 +162,7 @@ def main():
|
|
|
162
162
|
if args.command == "run":
|
|
163
163
|
if args.max_turns:
|
|
164
164
|
cfg.max_turns = args.max_turns
|
|
165
|
+
cfg._explicit_max_turns = True
|
|
165
166
|
_show_banner()
|
|
166
167
|
from .agent import run_agent
|
|
167
168
|
run_agent(
|
|
@@ -177,6 +178,7 @@ def main():
|
|
|
177
178
|
elif args.command == "chat":
|
|
178
179
|
if args.max_turns:
|
|
179
180
|
cfg.max_turns = args.max_turns
|
|
181
|
+
cfg._explicit_max_turns = True
|
|
180
182
|
_show_banner()
|
|
181
183
|
from .agent import run_chat
|
|
182
184
|
run_chat(Path(args.workdir), cfg, verbose=not args.quiet)
|
|
@@ -20,7 +20,7 @@ MAX_CONTEXT_TOKENS = 262_144
|
|
|
20
20
|
# Start compacting when we hit this percentage of the context window
|
|
21
21
|
COMPACT_THRESHOLD = 0.70
|
|
22
22
|
# Number of recent turns to always keep intact
|
|
23
|
-
KEEP_RECENT_TURNS =
|
|
23
|
+
KEEP_RECENT_TURNS = 12
|
|
24
24
|
# Approximate chars per token (rough heuristic)
|
|
25
25
|
CHARS_PER_TOKEN = 4
|
|
26
26
|
|
|
@@ -178,16 +178,18 @@ def detect_environment() -> dict:
|
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def _detect_shell(os_name: str) -> str:
|
|
181
|
-
"""Detect available shell,
|
|
181
|
+
"""Detect available shell, aligned with tools.py execution path."""
|
|
182
182
|
|
|
183
183
|
if os_name == "windows":
|
|
184
|
-
#
|
|
184
|
+
# Check if bash binary exists (Git Bash / MSYS)
|
|
185
|
+
if shutil.which("bash"):
|
|
186
|
+
return "bash"
|
|
185
187
|
if _is_wsl_available():
|
|
186
188
|
return "wsl"
|
|
187
189
|
# Fallback to PowerShell
|
|
188
190
|
if shutil.which("powershell") or shutil.which("pwsh"):
|
|
189
191
|
return "powershell"
|
|
190
|
-
return "cmd"
|
|
192
|
+
return "cmd"
|
|
191
193
|
|
|
192
194
|
if os_name in ("linux", "wsl"):
|
|
193
195
|
return "bash" # Standard on Linux/WSL
|
|
@@ -466,13 +468,14 @@ def compute_complexity(intent: str, repo_info: dict, task: str) -> str:
|
|
|
466
468
|
|
|
467
469
|
# -- MEDIUM: multi-step tasks --
|
|
468
470
|
medium_keywords = [
|
|
469
|
-
"api", "rest api", "graphql", "server",
|
|
470
|
-
"game", "snake", "tetris", "chess", "pong", "sudoku",
|
|
471
|
-
"website", "web page", "web app", "webapp",
|
|
472
|
-
"test suite", "unit tests", "integration test",
|
|
473
|
-
"dashboard", "portfolio",
|
|
474
|
-
"react", "vue", "angular", "next.js",
|
|
475
|
-
"django", "flask app", "fastapi app",
|
|
471
|
+
"api", "rest api", "graphql", "server", "backend", "frontend",
|
|
472
|
+
"game", "snake", "tetris", "chess", "pong", "sudoku", "flappy", "arcade",
|
|
473
|
+
"website", "web page", "web app", "webapp", "html", "css", "javascript",
|
|
474
|
+
"test suite", "unit tests", "integration test", "e2e test",
|
|
475
|
+
"dashboard", "portfolio", "application", "app with", "notes app", "todo app",
|
|
476
|
+
"react", "vue", "angular", "next.js", "tailwind",
|
|
477
|
+
"django", "flask app", "fastapi app", "express",
|
|
478
|
+
"refactor", "migrate", "redesign", "restructure", "investigate",
|
|
476
479
|
]
|
|
477
480
|
if any(kw in task_lower for kw in medium_keywords):
|
|
478
481
|
return "medium"
|
|
@@ -503,19 +506,19 @@ COMPLEXITY_CONFIG = {
|
|
|
503
506
|
"enable_thinking": False,
|
|
504
507
|
},
|
|
505
508
|
"low": {
|
|
506
|
-
"max_turns":
|
|
509
|
+
"max_turns": 15,
|
|
507
510
|
"max_tokens": 8192,
|
|
508
511
|
"tool_set": "core",
|
|
509
512
|
"enable_thinking": False,
|
|
510
513
|
},
|
|
511
514
|
"medium": {
|
|
512
|
-
"max_turns":
|
|
513
|
-
"max_tokens":
|
|
515
|
+
"max_turns": 40,
|
|
516
|
+
"max_tokens": 16384,
|
|
514
517
|
"tool_set": "full",
|
|
515
|
-
"enable_thinking":
|
|
518
|
+
"enable_thinking": True,
|
|
516
519
|
},
|
|
517
520
|
"high": {
|
|
518
|
-
"max_turns":
|
|
521
|
+
"max_turns": 60,
|
|
519
522
|
"max_tokens": 16384,
|
|
520
523
|
"tool_set": "full",
|
|
521
524
|
"enable_thinking": True,
|
|
@@ -16,12 +16,17 @@ from typing import Tuple
|
|
|
16
16
|
try:
|
|
17
17
|
from rich.console import Console
|
|
18
18
|
from rich.panel import Panel
|
|
19
|
+
from rich.text import Text
|
|
20
|
+
from rich.markup import escape
|
|
19
21
|
console = Console()
|
|
20
22
|
HAS_RICH = True
|
|
21
23
|
except ImportError:
|
|
22
24
|
HAS_RICH = False
|
|
23
25
|
console = None
|
|
24
26
|
|
|
27
|
+
def escape(text: str) -> str:
|
|
28
|
+
return text
|
|
29
|
+
|
|
25
30
|
|
|
26
31
|
class RiskLevel(Enum):
|
|
27
32
|
SAFE = "safe"
|
|
@@ -134,19 +139,19 @@ def gate_command(command: str, auto_approve: bool = False) -> bool:
|
|
|
134
139
|
if risk == RiskLevel.WARNING:
|
|
135
140
|
if auto_approve:
|
|
136
141
|
if HAS_RICH:
|
|
137
|
-
console.print(f" ⚠️
|
|
142
|
+
console.print(Text(f" ⚠️ {reason}", style="yellow"))
|
|
138
143
|
return True
|
|
139
144
|
# In interactive mode, show warning but proceed
|
|
140
145
|
if HAS_RICH:
|
|
141
|
-
console.print(f" ⚠️
|
|
146
|
+
console.print(Text(f" ⚠️ {reason}", style="yellow"))
|
|
142
147
|
return True
|
|
143
148
|
|
|
144
149
|
if risk == RiskLevel.DANGEROUS:
|
|
145
150
|
if HAS_RICH:
|
|
146
151
|
console.print(Panel(
|
|
147
152
|
f"[bold red]🛑 BLOCKED — Dangerous Command[/bold red]\n\n"
|
|
148
|
-
f"Command: [cyan]{command}[/cyan]\n"
|
|
149
|
-
f"Reason: {reason}\n\n"
|
|
153
|
+
f"Command: [cyan]{escape(command)}[/cyan]\n"
|
|
154
|
+
f"Reason: {escape(reason)}\n\n"
|
|
150
155
|
f"This command has been blocked for safety.\n"
|
|
151
156
|
f"If you need to run it, do so manually in your terminal.",
|
|
152
157
|
border_style="red",
|
|
@@ -77,7 +77,7 @@ class AgentOrchestrator:
|
|
|
77
77
|
effective_max_tokens = task_context.max_tokens
|
|
78
78
|
|
|
79
79
|
# User override: if they set --max-turns explicitly, respect it
|
|
80
|
-
if cfg
|
|
80
|
+
if getattr(cfg, "_explicit_max_turns", False):
|
|
81
81
|
effective_max_turns = cfg.max_turns
|
|
82
82
|
|
|
83
83
|
# ── Response cache check ──
|
|
@@ -137,6 +137,13 @@ class AgentOrchestrator:
|
|
|
137
137
|
"complexity": complexity,
|
|
138
138
|
})
|
|
139
139
|
|
|
140
|
+
if complexity in ("medium", "high"):
|
|
141
|
+
session.context.add_user_message(
|
|
142
|
+
f"[BUDGET & GOAL] Available turn budget: {effective_max_turns} turns. "
|
|
143
|
+
"Plan the needed components, create the complete files, verify with bash, "
|
|
144
|
+
"and finish with DONE: <summary> as soon as verification succeeds."
|
|
145
|
+
)
|
|
146
|
+
|
|
140
147
|
# ── Adaptive thinking ──
|
|
141
148
|
enable_thinking = cfg.enable_thinking or task_context.enable_thinking
|
|
142
149
|
if enable_thinking and complexity in ("qa", "low"):
|
|
@@ -155,17 +162,27 @@ class AgentOrchestrator:
|
|
|
155
162
|
if session.context.check_and_compact():
|
|
156
163
|
yield AgentEvent("compaction", {"turn": turn + 1})
|
|
157
164
|
|
|
158
|
-
# ──
|
|
159
|
-
|
|
165
|
+
# ── Progressive wrap-up injection ──
|
|
166
|
+
# Step 1: Gentle verification reminder at 70%
|
|
167
|
+
if not wrap_up_injected and turn >= int(effective_max_turns * 0.70):
|
|
160
168
|
wrap_up_injected = True
|
|
161
169
|
remaining = effective_max_turns - turn
|
|
162
170
|
session.context.add_user_message(
|
|
163
|
-
f"[SYSTEM]
|
|
164
|
-
"
|
|
165
|
-
"output DONE: <summary>.
|
|
171
|
+
f"[SYSTEM] Turn budget update: {remaining} turns remaining. "
|
|
172
|
+
"Ensure all necessary files are created and run verification tests now. "
|
|
173
|
+
"As soon as verification succeeds, output DONE: <summary>."
|
|
166
174
|
)
|
|
167
175
|
yield AgentEvent("wrap_up", {"turn": turn + 1, "remaining": remaining})
|
|
168
176
|
|
|
177
|
+
# Step 2: Final wrap-up call at 90%
|
|
178
|
+
if not getattr(self, "_final_wrap_up_injected", False) and turn >= int(effective_max_turns * 0.90):
|
|
179
|
+
self._final_wrap_up_injected = True
|
|
180
|
+
remaining = effective_max_turns - turn
|
|
181
|
+
session.context.add_user_message(
|
|
182
|
+
f"[SYSTEM] FINAL TURNS: Only {remaining} turns remaining. "
|
|
183
|
+
"Do not start new exploration. Fix any remaining errors and output DONE: <summary>."
|
|
184
|
+
)
|
|
185
|
+
|
|
169
186
|
yield AgentEvent("turn_start", {"turn": turn + 1})
|
|
170
187
|
|
|
171
188
|
try:
|
|
@@ -339,19 +339,19 @@ COMPLEXITY_CONFIG = {
|
|
|
339
339
|
"prompt": "default",
|
|
340
340
|
},
|
|
341
341
|
"low": {
|
|
342
|
-
"max_turns":
|
|
342
|
+
"max_turns": 15,
|
|
343
343
|
"max_tokens": 8192,
|
|
344
344
|
"tools": "core", # 5 core tools
|
|
345
345
|
"prompt": "default",
|
|
346
346
|
},
|
|
347
347
|
"medium": {
|
|
348
|
-
"max_turns":
|
|
349
|
-
"max_tokens":
|
|
348
|
+
"max_turns": 40,
|
|
349
|
+
"max_tokens": 16384,
|
|
350
350
|
"tools": "full", # All 15 tools
|
|
351
351
|
"prompt": "default",
|
|
352
352
|
},
|
|
353
353
|
"high": {
|
|
354
|
-
"max_turns":
|
|
354
|
+
"max_turns": 60,
|
|
355
355
|
"max_tokens": 16384,
|
|
356
356
|
"tools": "full", # All 15 tools
|
|
357
357
|
"prompt": "high", # Detailed system prompt
|
|
@@ -408,7 +408,7 @@ REPO_CONTEXT_TEMPLATE = """\
|
|
|
408
408
|
{extra}"""
|
|
409
409
|
|
|
410
410
|
INTENT_CREATE = """\
|
|
411
|
-
## Task Intent: Create New Code
|
|
411
|
+
## Task Intent: Create New Code (Single-file / Simple Tasks)
|
|
412
412
|
- Write the COMPLETE, WORKING code on Turn 1 using write_file
|
|
413
413
|
- Do NOT explore the filesystem first -- start coding immediately
|
|
414
414
|
- Include ALL imports, ALL functions, ALL logic -- no stubs, no TODOs
|
|
@@ -417,6 +417,23 @@ INTENT_CREATE = """\
|
|
|
417
417
|
- When everything works: DONE: <summary>
|
|
418
418
|
"""
|
|
419
419
|
|
|
420
|
+
INTENT_CREATE_COMPLEX = """\
|
|
421
|
+
## Task Intent: Create Multi-Component Code (Medium/High Complexity)
|
|
422
|
+
- Turn 1: Think through and plan the architecture. Identify ALL files needed (e.g. HTML, CSS, JS, tests).
|
|
423
|
+
- Sequential Creation: Write each file completely using write_file in logical dependency order.
|
|
424
|
+
- No Thrashing: Do NOT delete files you just created with `rm`. If adjustments are needed, use edit_file or overwrite directly.
|
|
425
|
+
- Verification: After writing the files, run or verify them using bash (e.g. run test suite, build check, or verify syntax).
|
|
426
|
+
- Early Stopping: Once verified and working, output: DONE: <summary> immediately. Do not run redundant checks.
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
GOAL_DRIVEN_RULES = """\
|
|
430
|
+
## Execution & Goal Convergence Rules
|
|
431
|
+
- Efficiency First: Plan your actions to minimize wasted turns. You can execute multiple tool calls in a single turn.
|
|
432
|
+
- No Thrashing: NEVER delete a file (e.g. `rm <file>`) immediately after creating it to start over. Use `edit_file` to modify what needs fixing.
|
|
433
|
+
- Immediate Completion: As soon as your code is written and verified, output `DONE: <summary>`. Do NOT linger or rerun commands that already passed.
|
|
434
|
+
- Targeted Editing: If `edit_file` fails, use `read_file` to inspect the exact lines and whitespace before attempting another edit.
|
|
435
|
+
"""
|
|
436
|
+
|
|
420
437
|
INTENT_MODIFY = """\
|
|
421
438
|
## Task Intent: Modify Existing Code
|
|
422
439
|
- Turn 1: Use read_file to read the relevant file(s)
|
|
@@ -492,13 +509,19 @@ class PromptBuilder:
|
|
|
492
509
|
# Inject intent-specific workflow
|
|
493
510
|
intent = getattr(task_context, "intent", "create")
|
|
494
511
|
if intent == "create":
|
|
495
|
-
|
|
512
|
+
if complexity in ("medium", "high"):
|
|
513
|
+
prompt += "\n\n" + INTENT_CREATE_COMPLEX
|
|
514
|
+
else:
|
|
515
|
+
prompt += "\n\n" + INTENT_CREATE
|
|
496
516
|
elif intent == "modify":
|
|
497
517
|
prompt += "\n\n" + INTENT_MODIFY
|
|
498
518
|
elif intent == "debug":
|
|
499
519
|
prompt += "\n\n" + INTENT_DEBUG
|
|
500
520
|
elif intent == "explain":
|
|
501
521
|
prompt += "\n\n" + INTENT_EXPLAIN
|
|
522
|
+
|
|
523
|
+
if complexity in ("medium", "high"):
|
|
524
|
+
prompt += "\n\n" + GOAL_DRIVEN_RULES
|
|
502
525
|
# qa intent: no extra prompt needed (model answers directly)
|
|
503
526
|
|
|
504
527
|
memories = format_memories_for_prompt(workdir)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
_RECENTLY_WRITTEN_FILES = set()
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
Tool definitions and implementations for AlpieCode.
|
|
3
5
|
|
|
@@ -437,11 +439,11 @@ def _bash(workdir: Path, command: str) -> str:
|
|
|
437
439
|
shell_cmd,
|
|
438
440
|
cwd=workdir, capture_output=True, text=True,
|
|
439
441
|
stdin=subprocess.DEVNULL,
|
|
440
|
-
timeout=
|
|
442
|
+
timeout=120, env=env,
|
|
441
443
|
)
|
|
442
444
|
|
|
443
|
-
stdout = _smart_truncate(result.stdout,
|
|
444
|
-
stderr = _smart_truncate(result.stderr,
|
|
445
|
+
stdout = _smart_truncate(result.stdout, 3000)
|
|
446
|
+
stderr = _smart_truncate(result.stderr, 2000)
|
|
445
447
|
|
|
446
448
|
output = json.dumps({
|
|
447
449
|
"stdout": stdout,
|
|
@@ -463,6 +465,20 @@ def _bash(workdir: Path, command: str) -> str:
|
|
|
463
465
|
)
|
|
464
466
|
output = hint + output
|
|
465
467
|
|
|
468
|
+
# ── Anti-thrashing guard: warned if deleting file created in this session ──
|
|
469
|
+
cmd_parts = command.strip().split()
|
|
470
|
+
if cmd_parts and cmd_parts[0] in ("rm", "del", "Remove-Item"):
|
|
471
|
+
for part in cmd_parts[1:]:
|
|
472
|
+
base_part = Path(part.strip('"\'')).name.lower()
|
|
473
|
+
if base_part in _RECENTLY_WRITTEN_FILES:
|
|
474
|
+
_RECENTLY_WRITTEN_FILES.discard(base_part)
|
|
475
|
+
output += (
|
|
476
|
+
f"\n\n💡 ADVICE: You deleted '{base_part}' which was created in a recent turn. "
|
|
477
|
+
"Avoid deleting and rewriting entire files from scratch. "
|
|
478
|
+
"For iterative improvements, use edit_file or overwrite directly with write_file."
|
|
479
|
+
)
|
|
480
|
+
break
|
|
481
|
+
|
|
466
482
|
# ── Smart Guardrail 2: Silent success — script ran but no output ──
|
|
467
483
|
elif result.returncode == 0 and not stdout.strip() and not stderr.strip():
|
|
468
484
|
script_file = _extract_script_path(command)
|
|
@@ -505,7 +521,8 @@ def _read_file(workdir: Path, path: str, start_line: int = None, end_line: int =
|
|
|
505
521
|
def _write_file(workdir: Path, path: str, content: str) -> str:
|
|
506
522
|
p = workdir / path
|
|
507
523
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
508
|
-
p.write_text(content)
|
|
524
|
+
p.write_text(content, encoding="utf-8")
|
|
525
|
+
_RECENTLY_WRITTEN_FILES.add(Path(path).name.lower())
|
|
509
526
|
return f"wrote {len(content)} bytes to {path}"
|
|
510
527
|
|
|
511
528
|
|
|
@@ -513,13 +530,25 @@ def _edit_file(workdir: Path, path: str, old_str: str, new_str: str) -> str:
|
|
|
513
530
|
p = workdir / path
|
|
514
531
|
if not p.exists():
|
|
515
532
|
return f"error: file not found: {path}"
|
|
516
|
-
text = p.read_text()
|
|
533
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
517
534
|
count = text.count(old_str)
|
|
518
535
|
if count == 0:
|
|
519
|
-
|
|
536
|
+
import difflib
|
|
537
|
+
lines = text.splitlines()
|
|
538
|
+
old_lines = [ol.strip() for ol in old_str.splitlines() if ol.strip()]
|
|
539
|
+
target_line = old_lines[0] if old_lines else old_str.strip()
|
|
540
|
+
close_matches = difflib.get_close_matches(target_line, [l.strip() for l in lines], n=3, cutoff=0.3)
|
|
541
|
+
nearby = [(i + 1, l) for i, l in enumerate(lines) if l.strip() in close_matches]
|
|
542
|
+
hint = ""
|
|
543
|
+
if nearby:
|
|
544
|
+
hint = "\nClosest matching lines in file:\n" + "\n".join(f" Line {n}: {l.strip()[:80]}" for n, l in nearby[:3])
|
|
545
|
+
return (
|
|
546
|
+
f"error: old_str not found in file '{path}'. It must match existing text EXACTLY (including whitespace/indentation).{hint}\n"
|
|
547
|
+
f"→ Action: Run read_file on '{path}' around these lines to see the exact whitespace, then retry edit_file."
|
|
548
|
+
)
|
|
520
549
|
if count > 1:
|
|
521
|
-
return f"error: old_str matched {count} times
|
|
522
|
-
p.write_text(text.replace(old_str, new_str, 1))
|
|
550
|
+
return f"error: old_str matched {count} times in '{path}'. Must match exactly 1 occurrence. Include more surrounding lines in old_str to make it unique."
|
|
551
|
+
p.write_text(text.replace(old_str, new_str, 1), encoding="utf-8")
|
|
523
552
|
return "edit applied"
|
|
524
553
|
|
|
525
554
|
|
|
@@ -689,9 +718,10 @@ def _request_user_input(question: str) -> str:
|
|
|
689
718
|
try:
|
|
690
719
|
from rich.console import Console
|
|
691
720
|
from rich.panel import Panel
|
|
721
|
+
from rich.text import Text
|
|
692
722
|
console = Console()
|
|
693
723
|
console.print(Panel(
|
|
694
|
-
|
|
724
|
+
Text(question, style="bold"),
|
|
695
725
|
title="❓ Agent needs your input",
|
|
696
726
|
border_style="yellow",
|
|
697
727
|
))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|