aru-code 0.12.0__tar.gz → 0.13.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.
- {aru_code-0.12.0/aru_code.egg-info → aru_code-0.13.0}/PKG-INFO +1 -1
- aru_code-0.13.0/aru/__init__.py +1 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/agent_factory.py +13 -2
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/cli.py +30 -18
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/config.py +5 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/context.py +25 -16
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/runner.py +28 -21
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/session.py +3 -1
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/codebase.py +10 -8
- {aru_code-0.12.0 → aru_code-0.13.0/aru_code.egg-info}/PKG-INFO +1 -1
- {aru_code-0.12.0 → aru_code-0.13.0}/pyproject.toml +1 -1
- aru_code-0.12.0/aru/__init__.py +0 -1
- {aru_code-0.12.0 → aru_code-0.13.0}/LICENSE +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/README.md +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/agents/__init__.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/agents/base.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/agents/executor.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/agents/planner.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/commands.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/completers.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/display.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/permissions.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/providers.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/runtime.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/__init__.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/ast_tools.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/gitignore.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/mcp_client.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/ranker.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru/tools/tasklist.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru_code.egg-info/SOURCES.txt +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru_code.egg-info/dependency_links.txt +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru_code.egg-info/entry_points.txt +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru_code.egg-info/requires.txt +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/aru_code.egg-info/top_level.txt +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/setup.cfg +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_agents_base.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_ast_tools.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_advanced.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_base.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_completers.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_new.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_run_cli.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_session.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_cli_shell.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_codebase.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_config.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_context.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_executor.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_gitignore.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_main.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_mcp_client.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_permissions.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_planner.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_providers.py +0 -0
- {aru_code-0.12.0 → aru_code-0.13.0}/tests/test_ranker.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.13.0"
|
|
@@ -12,8 +12,14 @@ def create_general_agent(
|
|
|
12
12
|
session: Session,
|
|
13
13
|
config: AgentConfig | None = None,
|
|
14
14
|
model_override: str | None = None,
|
|
15
|
+
env_context: str = "",
|
|
15
16
|
):
|
|
16
|
-
"""Create the general-purpose agent.
|
|
17
|
+
"""Create the general-purpose agent.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
env_context: Environment context (cwd, tree, git status) to include
|
|
21
|
+
in the system prompt. Placed in instructions so it's cacheable.
|
|
22
|
+
"""
|
|
17
23
|
from agno.agent import Agent
|
|
18
24
|
from agno.compression.manager import CompressionManager
|
|
19
25
|
|
|
@@ -21,6 +27,8 @@ def create_general_agent(
|
|
|
21
27
|
from aru.runtime import get_ctx
|
|
22
28
|
|
|
23
29
|
extra = config.get_extra_instructions() if config else ""
|
|
30
|
+
if env_context:
|
|
31
|
+
extra = f"{extra}\n\n{env_context}" if extra else env_context
|
|
24
32
|
model_ref = model_override or session.model_ref
|
|
25
33
|
|
|
26
34
|
return Agent(
|
|
@@ -40,7 +48,8 @@ def create_general_agent(
|
|
|
40
48
|
|
|
41
49
|
|
|
42
50
|
def create_custom_agent_instance(agent_def: CustomAgent, session: Session,
|
|
43
|
-
config: AgentConfig | None = None
|
|
51
|
+
config: AgentConfig | None = None,
|
|
52
|
+
env_context: str = ""):
|
|
44
53
|
"""Create an Agno Agent from a CustomAgent definition."""
|
|
45
54
|
from agno.agent import Agent
|
|
46
55
|
from agno.compression.manager import CompressionManager
|
|
@@ -52,6 +61,8 @@ def create_custom_agent_instance(agent_def: CustomAgent, session: Session,
|
|
|
52
61
|
tools = resolve_tools(agent_def.tools)
|
|
53
62
|
|
|
54
63
|
extra = config.get_extra_instructions() if config else ""
|
|
64
|
+
if env_context:
|
|
65
|
+
extra = f"{extra}\n\n{env_context}" if extra else env_context
|
|
55
66
|
parts = [agent_def.system_prompt, BASE_INSTRUCTIONS]
|
|
56
67
|
if extra:
|
|
57
68
|
parts.append(extra)
|
|
@@ -71,6 +71,7 @@ from aru.runner import ( # noqa: F401
|
|
|
71
71
|
_MUTATION_TOOLS,
|
|
72
72
|
_build_file_context,
|
|
73
73
|
_extract_plan_file_paths,
|
|
74
|
+
build_env_context,
|
|
74
75
|
execute_plan_steps,
|
|
75
76
|
run_agent_capture,
|
|
76
77
|
)
|
|
@@ -156,6 +157,11 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
156
157
|
|
|
157
158
|
extra_instructions = config.get_extra_instructions()
|
|
158
159
|
|
|
160
|
+
def _build_env_ctx() -> str:
|
|
161
|
+
"""Build fresh environment context for agent system prompt."""
|
|
162
|
+
from aru.runner import build_env_context
|
|
163
|
+
return build_env_context(session)
|
|
164
|
+
|
|
159
165
|
# Resume or create session
|
|
160
166
|
if resume_id:
|
|
161
167
|
if resume_id == "last":
|
|
@@ -183,6 +189,9 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
183
189
|
_sync_model(session)
|
|
184
190
|
_render_home(session, skip_permissions)
|
|
185
191
|
|
|
192
|
+
# Apply tree_depth from config
|
|
193
|
+
session._tree_max_depth = config.tree_depth
|
|
194
|
+
|
|
186
195
|
# Wire file-mutation callback and atexit cleanup
|
|
187
196
|
ctx.on_file_mutation = session.invalidate_context_cache
|
|
188
197
|
atexit.register(lambda: cleanup_processes(ctx.tracked_processes))
|
|
@@ -192,9 +201,12 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
192
201
|
paste_state = PasteState()
|
|
193
202
|
prompt_session = _create_prompt_session(paste_state, config)
|
|
194
203
|
|
|
195
|
-
# Startup: load MCP tools
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
# Startup: load MCP tools in background (don't block REPL)
|
|
205
|
+
async def _load_mcp_background():
|
|
206
|
+
from aru.tools.codebase import load_mcp_tools
|
|
207
|
+
await load_mcp_tools()
|
|
208
|
+
|
|
209
|
+
asyncio.create_task(_load_mcp_background())
|
|
198
210
|
|
|
199
211
|
while True:
|
|
200
212
|
try:
|
|
@@ -359,15 +371,14 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
359
371
|
continue
|
|
360
372
|
|
|
361
373
|
if user_input.lower() == "/mcp":
|
|
362
|
-
from aru.tools.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if not mcp_tools:
|
|
374
|
+
from aru.tools.mcp_client import get_mcp_manager
|
|
375
|
+
manager = get_mcp_manager()
|
|
376
|
+
if not manager or not manager.catalog:
|
|
366
377
|
console.print("[dim]No MCP tools loaded. Check aru.mcp.json config.[/dim]")
|
|
367
378
|
else:
|
|
368
|
-
console.print(f"[bold]
|
|
369
|
-
for
|
|
370
|
-
console.print(f" [bold cyan]{
|
|
379
|
+
console.print(f"[bold]MCP Tools ({len(manager.catalog)}):[/bold]\n")
|
|
380
|
+
for entry in manager.catalog.values():
|
|
381
|
+
console.print(f" [bold cyan]{entry.name}[/bold cyan] [dim]{entry.description}[/dim]")
|
|
371
382
|
continue
|
|
372
383
|
|
|
373
384
|
if user_input.lower() == "/help":
|
|
@@ -436,16 +447,17 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
436
447
|
prompt = render_command_template(cmd_def.template, cmd_args)
|
|
437
448
|
console.print(f"[bold magenta]Running /{cmd_name}...[/bold magenta]")
|
|
438
449
|
|
|
450
|
+
env_ctx = _build_env_ctx()
|
|
439
451
|
if cmd_def.agent and cmd_def.agent in config.custom_agents:
|
|
440
452
|
agent_def = config.custom_agents[cmd_def.agent]
|
|
441
|
-
agent = create_custom_agent_instance(agent_def, session, config)
|
|
453
|
+
agent = create_custom_agent_instance(agent_def, session, config, env_context=env_ctx)
|
|
442
454
|
elif cmd_def.agent:
|
|
443
455
|
console.print(f"[yellow]Warning: agent '{cmd_def.agent}' not found, using default[/yellow]")
|
|
444
|
-
agent = create_general_agent(session, config, model_override=cmd_def.model)
|
|
456
|
+
agent = create_general_agent(session, config, model_override=cmd_def.model, env_context=env_ctx)
|
|
445
457
|
elif cmd_def.model:
|
|
446
|
-
agent = create_general_agent(session, config, model_override=cmd_def.model)
|
|
458
|
+
agent = create_general_agent(session, config, model_override=cmd_def.model, env_context=env_ctx)
|
|
447
459
|
else:
|
|
448
|
-
agent = create_general_agent(session, config)
|
|
460
|
+
agent = create_general_agent(session, config, env_context=env_ctx)
|
|
449
461
|
session.add_message("user", user_input)
|
|
450
462
|
run_result = await run_agent_capture(agent, prompt, session, images=attached_images or None)
|
|
451
463
|
if run_result.content:
|
|
@@ -458,7 +470,7 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
458
470
|
prompt = render_skill_template(skill.content, cmd_args)
|
|
459
471
|
console.print(f"[bold magenta]Running skill /{cmd_name}...[/bold magenta]")
|
|
460
472
|
|
|
461
|
-
agent = create_general_agent(session, config)
|
|
473
|
+
agent = create_general_agent(session, config, env_context=_build_env_ctx())
|
|
462
474
|
session.add_message("user", user_input)
|
|
463
475
|
run_result = await run_agent_capture(agent, prompt, session, images=attached_images or None)
|
|
464
476
|
if run_result.content:
|
|
@@ -470,7 +482,7 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
470
482
|
else:
|
|
471
483
|
from aru.permissions import permission_scope
|
|
472
484
|
console.print(f"[bold magenta]Running agent /{cmd_name}...[/bold magenta]")
|
|
473
|
-
agent = create_custom_agent_instance(agent_def, session, config)
|
|
485
|
+
agent = create_custom_agent_instance(agent_def, session, config, env_context=_build_env_ctx())
|
|
474
486
|
session.add_message("user", user_input)
|
|
475
487
|
with permission_scope(agent_def.permission):
|
|
476
488
|
run_result = await run_agent_capture(agent, cmd_args or user_input, session, images=attached_images or None)
|
|
@@ -498,14 +510,14 @@ async def run_cli(skip_permissions: bool = False, resume_id: str | None = None):
|
|
|
498
510
|
agent_def = config.custom_agents[agent_name]
|
|
499
511
|
from aru.permissions import permission_scope
|
|
500
512
|
console.print(f"[bold magenta]Routing to @{agent_name}...[/bold magenta]")
|
|
501
|
-
agent = create_custom_agent_instance(agent_def, session, config)
|
|
513
|
+
agent = create_custom_agent_instance(agent_def, session, config, env_context=_build_env_ctx())
|
|
502
514
|
session.add_message("user", user_input)
|
|
503
515
|
with permission_scope(agent_def.permission):
|
|
504
516
|
run_result = await run_agent_capture(agent, message_text, session, images=attached_images or None)
|
|
505
517
|
if run_result.content:
|
|
506
518
|
session.add_message("assistant", run_result.with_tools_summary())
|
|
507
519
|
else:
|
|
508
|
-
agent = create_general_agent(session, config)
|
|
520
|
+
agent = create_general_agent(session, config, env_context=_build_env_ctx())
|
|
509
521
|
session.add_message("user", user_input)
|
|
510
522
|
run_result = await run_agent_capture(agent, user_input, session, images=attached_images or None)
|
|
511
523
|
if run_result.content:
|
|
@@ -160,6 +160,7 @@ class AgentConfig:
|
|
|
160
160
|
model_aliases: dict[str, str] = field(default_factory=dict)
|
|
161
161
|
custom_agents: dict[str, CustomAgent] = field(default_factory=dict)
|
|
162
162
|
plan_reviewer: bool = True
|
|
163
|
+
tree_depth: int = 2 # max depth for directory tree in system prompt
|
|
163
164
|
|
|
164
165
|
@property
|
|
165
166
|
def has_instructions(self) -> bool:
|
|
@@ -515,6 +516,10 @@ def load_config(cwd: str | None = None) -> AgentConfig:
|
|
|
515
516
|
config.model_aliases = data["model_aliases"]
|
|
516
517
|
if "plan_reviewer" in data:
|
|
517
518
|
config.plan_reviewer = bool(data["plan_reviewer"])
|
|
519
|
+
if "tree_depth" in data:
|
|
520
|
+
td = data["tree_depth"]
|
|
521
|
+
if isinstance(td, int) and 0 <= td <= 5:
|
|
522
|
+
config.tree_depth = td
|
|
518
523
|
# Resolve instructions (local files, globs, URLs)
|
|
519
524
|
if "instructions" in data and isinstance(data["instructions"], list):
|
|
520
525
|
entries = [str(e) for e in data["instructions"] if isinstance(e, str)]
|
|
@@ -10,12 +10,16 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
# ── Constants ──────────────────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
# Pruning: protect the most recent N chars of
|
|
13
|
+
# Pruning: protect the most recent N chars of content from eviction
|
|
14
14
|
PRUNE_PROTECT_CHARS = 50_000 # ~14K tokens
|
|
15
15
|
# Pruning: minimum chars that must be freeable to justify a prune pass
|
|
16
16
|
PRUNE_MINIMUM_CHARS = 20_000 # ~5.7K tokens
|
|
17
17
|
# Placeholder that replaces evicted content
|
|
18
18
|
PRUNED_PLACEHOLDER = "[previous output cleared to save context]"
|
|
19
|
+
# User messages larger than this threshold are truncated when outside protection window
|
|
20
|
+
PRUNE_USER_MSG_THRESHOLD = 2_000 # ~570 tokens — catches @file mentions
|
|
21
|
+
# How many chars to keep from the start of a pruned user message
|
|
22
|
+
PRUNE_USER_MSG_KEEP = 500 # ~140 tokens — enough to understand the request
|
|
19
23
|
|
|
20
24
|
# Truncation: universal limits for any tool output
|
|
21
25
|
TRUNCATE_MAX_LINES = 500
|
|
@@ -80,24 +84,24 @@ Be concise but complete. This summary replaces the full conversation history."""
|
|
|
80
84
|
# ── Layer 1: Pruning ──────────────────────────────────────────────
|
|
81
85
|
|
|
82
86
|
def prune_history(history: list[dict[str, str]]) -> list[dict[str, str]]:
|
|
83
|
-
"""Replace old
|
|
87
|
+
"""Replace old messages with a short placeholder to reduce tokens.
|
|
84
88
|
|
|
85
|
-
Walks backward through history, protecting the most recent
|
|
86
|
-
|
|
87
|
-
that budget are
|
|
89
|
+
Walks backward through history, protecting the most recent content
|
|
90
|
+
(up to PRUNE_PROTECT_CHARS total across both roles). Older messages
|
|
91
|
+
beyond that budget are pruned:
|
|
92
|
+
- Assistant messages: replaced entirely with placeholder
|
|
93
|
+
- User messages over PRUNE_USER_MSG_THRESHOLD: truncated to first N chars
|
|
88
94
|
|
|
89
95
|
Returns a new list (does not mutate the input).
|
|
90
96
|
"""
|
|
91
97
|
if len(history) <= 2:
|
|
92
98
|
return list(history)
|
|
93
99
|
|
|
94
|
-
# Calculate total
|
|
95
|
-
|
|
96
|
-
len(msg["content"]) for msg in history if msg["role"] == "assistant"
|
|
97
|
-
)
|
|
100
|
+
# Calculate total prunable chars (both roles)
|
|
101
|
+
total_chars = sum(len(msg["content"]) for msg in history)
|
|
98
102
|
|
|
99
103
|
# Not enough to prune
|
|
100
|
-
if
|
|
104
|
+
if total_chars < PRUNE_PROTECT_CHARS + PRUNE_MINIMUM_CHARS:
|
|
101
105
|
return list(history)
|
|
102
106
|
|
|
103
107
|
# Walk backward, protecting recent content
|
|
@@ -106,17 +110,22 @@ def prune_history(history: list[dict[str, str]]) -> list[dict[str, str]]:
|
|
|
106
110
|
|
|
107
111
|
for i in range(len(result) - 1, -1, -1):
|
|
108
112
|
msg = result[i]
|
|
109
|
-
if msg["role"] != "assistant":
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
113
|
msg_len = len(msg["content"])
|
|
114
|
+
|
|
113
115
|
if protected + msg_len <= PRUNE_PROTECT_CHARS:
|
|
114
116
|
# Still within protection window
|
|
115
117
|
protected += msg_len
|
|
116
118
|
else:
|
|
117
|
-
# Beyond protection window — prune
|
|
118
|
-
if msg["
|
|
119
|
-
|
|
119
|
+
# Beyond protection window — prune
|
|
120
|
+
if msg["role"] == "assistant":
|
|
121
|
+
if msg["content"] != PRUNED_PLACEHOLDER:
|
|
122
|
+
result[i] = {"role": "assistant", "content": PRUNED_PLACEHOLDER}
|
|
123
|
+
elif msg["role"] == "user" and msg_len > PRUNE_USER_MSG_THRESHOLD:
|
|
124
|
+
# Large user messages (e.g. @file mentions with file contents)
|
|
125
|
+
# are truncated to keep a brief summary of the original request
|
|
126
|
+
truncated = msg["content"][:PRUNE_USER_MSG_KEEP] + \
|
|
127
|
+
f"\n\n[... {msg_len - PRUNE_USER_MSG_KEEP:,} chars pruned to save context ...]"
|
|
128
|
+
result[i] = {"role": "user", "content": truncated}
|
|
120
129
|
|
|
121
130
|
return result
|
|
122
131
|
|
|
@@ -26,6 +26,24 @@ from aru.permissions import get_skip_permissions
|
|
|
26
26
|
_MUTATION_TOOLS = {"write_file", "write_files", "edit_file", "edit_files", "bash", "run_command"}
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def build_env_context(session, cwd: str | None = None) -> str:
|
|
30
|
+
"""Build environment context string (cwd, git status) for system prompt.
|
|
31
|
+
|
|
32
|
+
This context goes into agent instructions (system prompt) so it's cached
|
|
33
|
+
by the provider between turns. Tree is omitted — the model uses
|
|
34
|
+
glob_search/list_directory on demand instead of paying upfront tokens.
|
|
35
|
+
"""
|
|
36
|
+
cwd = cwd or os.getcwd()
|
|
37
|
+
parts = [f"The current working directory is: {cwd}"]
|
|
38
|
+
|
|
39
|
+
if session:
|
|
40
|
+
git_status = session.get_cached_git_status(cwd)
|
|
41
|
+
if git_status:
|
|
42
|
+
parts.append(f"Git status:\n{git_status}")
|
|
43
|
+
|
|
44
|
+
return "\n\n".join(parts)
|
|
45
|
+
|
|
46
|
+
|
|
29
47
|
@dataclass
|
|
30
48
|
class AgentRunResult:
|
|
31
49
|
"""Result from run_agent_capture including text output and tool call history."""
|
|
@@ -77,35 +95,24 @@ async def run_agent_capture(agent, message: str, session=None, lightweight: bool
|
|
|
77
95
|
display = StreamingDisplay(status)
|
|
78
96
|
tracker = display.tool_tracker
|
|
79
97
|
|
|
80
|
-
# Build
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
# Build message — environment context (tree/git/cwd) is now in the
|
|
99
|
+
# system prompt (agent instructions) so it's cacheable across turns.
|
|
100
|
+
# Only plan progress and budget warnings are added here.
|
|
101
|
+
msg_parts = []
|
|
84
102
|
|
|
85
103
|
if session and not lightweight:
|
|
86
|
-
env_context_parts = []
|
|
87
|
-
tree_text = session.get_cached_tree(cwd)
|
|
88
|
-
if tree_text:
|
|
89
|
-
env_context_parts.append(f"Directory Tree (max depth 3):\n```text\n{tree_text}\n```")
|
|
90
|
-
|
|
91
|
-
git_status = session.get_cached_git_status(cwd)
|
|
92
|
-
if git_status:
|
|
93
|
-
env_context_parts.append(f"Git status:\n{git_status}")
|
|
94
|
-
|
|
95
|
-
if env_context_parts:
|
|
96
|
-
dynamic_parts.append("## Environment Context\n" + "\n\n".join(env_context_parts))
|
|
97
|
-
|
|
98
|
-
# Include only compact plan progress (not full plan text)
|
|
99
104
|
if session.current_plan:
|
|
100
|
-
|
|
105
|
+
msg_parts.append(f"## Active Plan\nTask: {session.plan_task}\n\n{session.render_plan_progress()}")
|
|
101
106
|
|
|
102
|
-
# Token budget warning
|
|
103
107
|
warning = session.check_budget_warning()
|
|
104
108
|
if warning:
|
|
105
109
|
console.print(warning)
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
if msg_parts:
|
|
112
|
+
prefix = "\n\n".join(msg_parts)
|
|
113
|
+
run_message = f"{prefix}\n\n---\n\n{message}"
|
|
114
|
+
else:
|
|
115
|
+
run_message = message
|
|
109
116
|
|
|
110
117
|
# Build conversation history as real messages for the LLM
|
|
111
118
|
from aru.context import prune_history
|
|
@@ -145,6 +145,8 @@ class Session:
|
|
|
145
145
|
self._cached_tree: str | None = None
|
|
146
146
|
self._cached_git_status: str | None = None
|
|
147
147
|
self._context_dirty: bool = True
|
|
148
|
+
# Tree depth for env context (configurable via aru.json "tree_depth")
|
|
149
|
+
self._tree_max_depth: int = 2
|
|
148
150
|
# Token budget (0 = unlimited)
|
|
149
151
|
self.token_budget: int = 0
|
|
150
152
|
|
|
@@ -229,7 +231,7 @@ class Session:
|
|
|
229
231
|
"""Regenerate tree and git status caches."""
|
|
230
232
|
try:
|
|
231
233
|
from aru.tools.codebase import get_project_tree
|
|
232
|
-
self._cached_tree = get_project_tree(cwd, max_depth=
|
|
234
|
+
self._cached_tree = get_project_tree(cwd, max_depth=self._tree_max_depth) or None
|
|
233
235
|
except Exception:
|
|
234
236
|
self._cached_tree = None
|
|
235
237
|
try:
|
|
@@ -632,8 +632,10 @@ def get_project_tree(root_dir: str, max_depth: int = 3, max_files_per_dir: int =
|
|
|
632
632
|
lines.append(f"{file_indent}{f}")
|
|
633
633
|
|
|
634
634
|
result = "\n".join(lines)
|
|
635
|
-
|
|
636
|
-
|
|
635
|
+
# Cap size — tree goes in system prompt now, keep it compact
|
|
636
|
+
max_chars = 5000
|
|
637
|
+
if len(result) > max_chars:
|
|
638
|
+
return result[:max_chars] + "\n... [Tree truncated due to size]"
|
|
637
639
|
return result
|
|
638
640
|
|
|
639
641
|
|
|
@@ -1098,15 +1100,12 @@ ALL_TOOLS = [
|
|
|
1098
1100
|
web_search,
|
|
1099
1101
|
web_fetch,
|
|
1100
1102
|
delegate_task,
|
|
1101
|
-
code_structure,
|
|
1102
|
-
find_dependencies,
|
|
1103
|
-
rank_files,
|
|
1104
1103
|
]
|
|
1105
1104
|
|
|
1106
1105
|
# Task list tools for executor subtask tracking
|
|
1107
1106
|
from aru.tools.tasklist import create_task_list, update_task
|
|
1108
1107
|
|
|
1109
|
-
# Executor tools — full write/execute capability
|
|
1108
|
+
# Executor tools — full write/execute capability
|
|
1110
1109
|
EXECUTOR_TOOLS = [
|
|
1111
1110
|
create_task_list,
|
|
1112
1111
|
update_task,
|
|
@@ -1123,10 +1122,9 @@ EXECUTOR_TOOLS = [
|
|
|
1123
1122
|
web_search,
|
|
1124
1123
|
web_fetch,
|
|
1125
1124
|
delegate_task,
|
|
1126
|
-
code_structure,
|
|
1127
1125
|
]
|
|
1128
1126
|
|
|
1129
|
-
# General-purpose tools
|
|
1127
|
+
# General-purpose tools
|
|
1130
1128
|
GENERAL_TOOLS = [
|
|
1131
1129
|
read_file,
|
|
1132
1130
|
read_file_smart,
|
|
@@ -1147,6 +1145,10 @@ GENERAL_TOOLS = [
|
|
|
1147
1145
|
TOOL_REGISTRY: dict[str, object] = {f.__name__: f for f in ALL_TOOLS}
|
|
1148
1146
|
TOOL_REGISTRY["create_task_list"] = create_task_list
|
|
1149
1147
|
TOOL_REGISTRY["update_task"] = update_task
|
|
1148
|
+
# Kept in registry for custom agents that may reference them via resolve_tools
|
|
1149
|
+
TOOL_REGISTRY["code_structure"] = code_structure
|
|
1150
|
+
TOOL_REGISTRY["find_dependencies"] = find_dependencies
|
|
1151
|
+
TOOL_REGISTRY["rank_files"] = rank_files
|
|
1150
1152
|
|
|
1151
1153
|
|
|
1152
1154
|
def resolve_tools(tool_spec: list[str] | dict[str, bool]) -> list:
|
aru_code-0.12.0/aru/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.12.0"
|
|
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
|
|
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
|