ufy-auc 0.2.4__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.
Files changed (82) hide show
  1. auc/__init__.py +125 -0
  2. auc/agent.py +237 -0
  3. auc/chat_agent.py +177 -0
  4. auc/checkpoint.py +219 -0
  5. auc/cli.py +734 -0
  6. auc/cli_ui.py +560 -0
  7. auc/config.py +571 -0
  8. auc/context/__init__.py +3 -0
  9. auc/context/compactor.py +217 -0
  10. auc/context/window.py +55 -0
  11. auc/diagrams.py +191 -0
  12. auc/events/__init__.py +3 -0
  13. auc/events/bus.py +63 -0
  14. auc/extras.py +32 -0
  15. auc/integration/__init__.py +39 -0
  16. auc/integration/aum.py +62 -0
  17. auc/integration/dispatcher.py +105 -0
  18. auc/integration/evolution.py +297 -0
  19. auc/integration/im_base.py +50 -0
  20. auc/integration/im_card.py +20 -0
  21. auc/integration/nuggets.py +123 -0
  22. auc/integration/qq.py +177 -0
  23. auc/integration/slicer.py +150 -0
  24. auc/integration/telegram.py +174 -0
  25. auc/loop/__init__.py +17 -0
  26. auc/loop/base.py +215 -0
  27. auc/loop/react.py +197 -0
  28. auc/messages.py +62 -0
  29. auc/model/__init__.py +29 -0
  30. auc/model/anthropic.py +369 -0
  31. auc/model/client.py +73 -0
  32. auc/model/deepseek_anthropic.py +23 -0
  33. auc/model/factory.py +48 -0
  34. auc/model/json_util.py +112 -0
  35. auc/model/openai.py +220 -0
  36. auc/model/streaming.py +89 -0
  37. auc/multimodal.py +189 -0
  38. auc/plan.py +67 -0
  39. auc/policy/__init__.py +3 -0
  40. auc/policy/autonomy.py +53 -0
  41. auc/policy/escalation.py +83 -0
  42. auc/policy/privilege.py +264 -0
  43. auc/ports/__init__.py +35 -0
  44. auc/ports/approval.py +75 -0
  45. auc/ports/memory.py +145 -0
  46. auc/ports/package.py +28 -0
  47. auc/ports/rules.py +84 -0
  48. auc/prompt_input.py +256 -0
  49. auc/py.typed +0 -0
  50. auc/sandbox.py +34 -0
  51. auc/stream_display.py +6 -0
  52. auc/terminal.py +144 -0
  53. auc/tools/__init__.py +19 -0
  54. auc/tools/base.py +125 -0
  55. auc/tools/builtin.py +17 -0
  56. auc/tools/decorator.py +44 -0
  57. auc/tools/fetch.py +122 -0
  58. auc/tools/files.py +97 -0
  59. auc/tools/registry.py +64 -0
  60. auc/tools/search.py +233 -0
  61. auc/tools/shell.py +177 -0
  62. auc/types.py +38 -0
  63. auc/web/__init__.py +1 -0
  64. auc/web/approval.py +89 -0
  65. auc/web/conversations.py +308 -0
  66. auc/web/editor_context.py +90 -0
  67. auc/web/preview.py +69 -0
  68. auc/web/projects.py +155 -0
  69. auc/web/runner.py +264 -0
  70. auc/web/server.py +960 -0
  71. auc/web/session.py +122 -0
  72. auc/web/static/app.js +1350 -0
  73. auc/web/static/index.html +172 -0
  74. auc/web/static/message_render.js +461 -0
  75. auc/web/static/styles.css +1007 -0
  76. auc/web/workspace.py +118 -0
  77. auc/work_mode.py +304 -0
  78. ufy_auc-0.2.4.dist-info/METADATA +254 -0
  79. ufy_auc-0.2.4.dist-info/RECORD +82 -0
  80. ufy_auc-0.2.4.dist-info/WHEEL +4 -0
  81. ufy_auc-0.2.4.dist-info/entry_points.txt +3 -0
  82. ufy_auc-0.2.4.dist-info/licenses/LICENSE +21 -0
auc/__init__.py ADDED
@@ -0,0 +1,125 @@
1
+ """Agents-ufy-Core (AuC): asyncio single-agent framework."""
2
+
3
+ from auc.agent import AgentConfig, DefaultAgent
4
+ from auc.checkpoint import CheckpointStore
5
+ from auc.config import (
6
+ ModelConfig,
7
+ default_config_path,
8
+ discover_config_path,
9
+ load_model_config,
10
+ user_config_dir,
11
+ )
12
+ from auc.context import ListContextWindow
13
+ from auc.events import EventBus, RunEvent
14
+ from auc.loop import AgentLoopRunner, LoopConfig, ReActLoop
15
+ from auc.messages import ChatMessage, RunRequest, RunResult, ToolCall, ToolResult
16
+ from auc.model import AssistantMessage, InMemoryModelClient
17
+ from auc.ports import (
18
+ AutoApprovePort,
19
+ CodeSnippet,
20
+ ContextPackage,
21
+ DefaultComposer,
22
+ DenyApprovalPort,
23
+ FileRulesPort,
24
+ InMemoryMemoryPort,
25
+ NoOpMemoryPort,
26
+ SlicerPolicy,
27
+ )
28
+ from auc.policy import ToolPrivilegeGate
29
+ from auc.policy.autonomy import AutonomyPolicy
30
+ from auc.sandbox import SandboxViolationError, resolve_under_sandbox
31
+ from auc.tools import DefaultToolRegistry, make_echo_tool, register_function_tools, tool
32
+
33
+ __version__ = "0.2.4"
34
+
35
+ __all__ = [
36
+ "__version__",
37
+ "AgentConfig",
38
+ "AssistantMessage",
39
+ "AutoApprovePort",
40
+ "AutonomyPolicy",
41
+ "CheckpointStore",
42
+ "ChatMessage",
43
+ "CodeSnippet",
44
+ "ContextPackage",
45
+ "DefaultAgent",
46
+ "DefaultComposer",
47
+ "DefaultToolRegistry",
48
+ "DenyApprovalPort",
49
+ "EventBus",
50
+ "FileRulesPort",
51
+ "InMemoryMemoryPort",
52
+ "InMemoryModelClient",
53
+ "ListContextWindow",
54
+ "LoopConfig",
55
+ "NoOpMemoryPort",
56
+ "ReActLoop",
57
+ "RunEvent",
58
+ "RunRequest",
59
+ "RunResult",
60
+ "SandboxViolationError",
61
+ "SlicerPolicy",
62
+ "ToolCall",
63
+ "ToolPrivilegeGate",
64
+ "ToolResult",
65
+ "make_echo_tool",
66
+ "register_function_tools",
67
+ "resolve_under_sandbox",
68
+ "tool",
69
+ ]
70
+
71
+ try:
72
+ from auc.model.anthropic import AnthropicClient
73
+ from auc.model.factory import create_model_client, aclose_model_client
74
+ from auc.model.openai import OpenAICompatibleClient
75
+
76
+ __all__.extend(
77
+ [
78
+ "AnthropicClient",
79
+ "OpenAICompatibleClient",
80
+ "create_model_client",
81
+ "aclose_model_client",
82
+ "ModelConfig",
83
+ "load_model_config",
84
+ "discover_config_path",
85
+ "user_config_dir",
86
+ "default_config_path",
87
+ ]
88
+ )
89
+ except ImportError: # pragma: no cover
90
+ __all__.extend(
91
+ [
92
+ "ModelConfig",
93
+ "load_model_config",
94
+ "discover_config_path",
95
+ "user_config_dir",
96
+ "default_config_path",
97
+ ]
98
+ )
99
+
100
+ try:
101
+ from auc.integration import (
102
+ AuMStack,
103
+ MetaDispatcher,
104
+ NuggetsStore,
105
+ SemanticSlicer,
106
+ SpecialistRegistry,
107
+ SpecialistSpec,
108
+ ConsoleApprovalPort,
109
+ TelegramApprovalPort,
110
+ )
111
+
112
+ __all__.extend(
113
+ [
114
+ "AuMStack",
115
+ "MetaDispatcher",
116
+ "NuggetsStore",
117
+ "SemanticSlicer",
118
+ "SpecialistRegistry",
119
+ "SpecialistSpec",
120
+ "ConsoleApprovalPort",
121
+ "TelegramApprovalPort",
122
+ ]
123
+ )
124
+ except ImportError: # pragma: no cover
125
+ pass
auc/agent.py ADDED
@@ -0,0 +1,237 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import uuid
5
+ from collections.abc import AsyncIterator
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from auc.checkpoint import CheckpointStore
11
+ from auc.context.compactor import CompactionConfig, SummarizingCompactor
12
+ from auc.context.window import ListContextWindow
13
+ from auc.events.bus import EventBus, RunEvent
14
+ from auc.loop.base import AgentLoopRunner, LoopConfig, LoopContext
15
+ from auc.loop.react import ReActLoop
16
+ from auc.messages import ChatMessage, RunRequest, RunResult
17
+ from auc.model.client import ModelClient
18
+ from auc.plan import READONLY_TOOL_NAMES, render_plan_context
19
+ from auc.policy.autonomy import AutonomyPolicy, normalize_autonomy
20
+ from auc.ports.approval import ApprovalPort
21
+ from auc.ports.memory import ContextComposer, MemoryPort
22
+ from auc.ports.package import ContextPackage, SlicerPolicy
23
+ from auc.ports.rules import ProjectRules, ProjectRulesPort
24
+ from auc.policy.privilege import ToolPrivilegeGate
25
+ from auc.tools.registry import DefaultToolRegistry
26
+ from auc.types import AgentId, AutonomyLevel, RunId
27
+
28
+
29
+ @dataclass
30
+ class AgentConfig:
31
+ agent_id: AgentId
32
+ model: ModelClient
33
+ tools: DefaultToolRegistry
34
+ loop: ReActLoop | None = None
35
+ memory: MemoryPort | None = None
36
+ composer: ContextComposer | None = None
37
+ rules: ProjectRulesPort | None = None
38
+ approval: ApprovalPort | None = None
39
+ privilege_gate: ToolPrivilegeGate | None = None
40
+ slicer_policy: SlicerPolicy | None = None
41
+ loop_config: LoopConfig = field(default_factory=LoopConfig)
42
+ system_prompt: str | None = None
43
+ sandbox_root: str | None = None
44
+ autonomy: AutonomyLevel = "auto-edit" # R6 默认级别,可被 metadata.autonomy 覆盖
45
+ enable_checkpoints: bool = True # R4
46
+ compaction: CompactionConfig | None = None # R3,None 时按 loop_config 构造
47
+
48
+
49
+ class DefaultAgent:
50
+ def __init__(self, config: AgentConfig) -> None:
51
+ self._config = config
52
+ self._loop = config.loop or ReActLoop()
53
+ self._runner = AgentLoopRunner()
54
+ self._cancelled_runs: set[RunId] = set()
55
+ self._active_events: dict[RunId, EventBus] = {}
56
+ self._active_ctx: dict[RunId, LoopContext] = {}
57
+ self._last_run_result: RunResult | None = None
58
+
59
+ @property
60
+ def agent_id(self) -> AgentId:
61
+ return self._config.agent_id
62
+
63
+ def cancel(self, run_id: RunId) -> None:
64
+ self._cancelled_runs.add(run_id)
65
+ ctx = self._active_ctx.get(run_id)
66
+ if ctx is not None:
67
+ ctx.cancelled = True
68
+
69
+ async def run(self, request: RunRequest | str | list[ChatMessage]) -> RunResult:
70
+ ctx, bus = await self._prepare_context(self._normalize_request(request))
71
+ self._active_events[ctx.run_id] = bus
72
+ self._active_ctx[ctx.run_id] = ctx
73
+ try:
74
+ return await self._runner.run_until_done(self._loop, ctx)
75
+ finally:
76
+ self._active_events.pop(ctx.run_id, None)
77
+ self._active_ctx.pop(ctx.run_id, None)
78
+ self._cancelled_runs.discard(ctx.run_id)
79
+
80
+ async def run_stream(
81
+ self, request: RunRequest | str | list[ChatMessage]
82
+ ) -> AsyncIterator[RunEvent]:
83
+ ctx, bus = await self._prepare_context(self._normalize_request(request))
84
+ queue = bus.create_stream_queue()
85
+ self._active_events[ctx.run_id] = bus
86
+ self._active_ctx[ctx.run_id] = ctx
87
+
88
+ async def _run() -> RunResult:
89
+ return await self._runner.run_until_done(self._loop, ctx)
90
+
91
+ task = asyncio.create_task(_run())
92
+ try:
93
+ while True:
94
+ event = await queue.get()
95
+ if event is None:
96
+ break
97
+ yield event
98
+ if event.type == "run_end":
99
+ break
100
+ finally:
101
+ bus.close_stream_queue(queue)
102
+ if not task.done():
103
+ task.cancel()
104
+ try:
105
+ await task
106
+ except asyncio.CancelledError:
107
+ pass
108
+ try:
109
+ self._last_run_result = await task
110
+ except (asyncio.CancelledError, Exception): # noqa: BLE001
111
+ self._last_run_result = RunResult(
112
+ output="",
113
+ messages=ctx.window.view(),
114
+ status="error",
115
+ run_id=ctx.run_id,
116
+ error=str(ctx.error or "cancelled"),
117
+ )
118
+ self._active_events.pop(ctx.run_id, None)
119
+ self._active_ctx.pop(ctx.run_id, None)
120
+ self._cancelled_runs.discard(ctx.run_id)
121
+
122
+ @property
123
+ def last_run_result(self) -> RunResult | None:
124
+ return self._last_run_result
125
+
126
+ @staticmethod
127
+ def _normalize_request(
128
+ request: RunRequest | str | list[ChatMessage],
129
+ ) -> RunRequest:
130
+ if isinstance(request, RunRequest):
131
+ return request
132
+ return RunRequest(input=request)
133
+
134
+ async def _prepare_context(
135
+ self, request: RunRequest
136
+ ) -> tuple[LoopContext, EventBus]:
137
+ run_id = request.run_id or str(uuid.uuid4())
138
+ if self._config.slicer_policy and self._config.slicer_policy.require_package:
139
+ if request.context_package is None:
140
+ raise ValueError(
141
+ "context_package required by SlicerPolicy.require_package"
142
+ )
143
+
144
+ window = ListContextWindow()
145
+ approved_plan = request.metadata.get("approved_plan")
146
+ if isinstance(approved_plan, dict):
147
+ window.append(
148
+ ChatMessage(role="system", content=render_plan_context(approved_plan))
149
+ )
150
+ if isinstance(request.input, str):
151
+ window.append(ChatMessage(role="user", content=request.input))
152
+ else:
153
+ for msg in request.input:
154
+ window.append(msg)
155
+
156
+ tools = self._config.tools
157
+ project_rules: ProjectRules | None = None
158
+ repo_root = request.metadata.get("repo_root")
159
+ if self._config.rules and repo_root:
160
+ project_rules = await self._config.rules.load_rules(str(repo_root))
161
+ if project_rules.tool_policy:
162
+ tools = DefaultToolRegistry()
163
+ for t in self._config.tools._tools.values():
164
+ tools.register(t, self._config.tools._policies.get(t.name))
165
+ tools.merge_tool_policy(project_rules.tool_policy)
166
+
167
+ sandbox = self._config.sandbox_root or (
168
+ str(Path(repo_root).resolve()) if repo_root else None
169
+ )
170
+ if sandbox:
171
+ if project_rules is None:
172
+ project_rules = ProjectRules(sandbox_root=sandbox)
173
+ elif not project_rules.sandbox_root:
174
+ project_rules.sandbox_root = sandbox
175
+
176
+ package: ContextPackage | None = request.context_package
177
+ if package is None and request.metadata.get("context_package"):
178
+ package = request.metadata["context_package"]
179
+
180
+ bus = EventBus()
181
+ gate = self._config.privilege_gate or ToolPrivilegeGate(
182
+ approval=self._config.approval
183
+ )
184
+
185
+ # R5:计划模式收窄为只读工具集(按白名单过滤,动态注册工具同样受限)
186
+ if (
187
+ request.metadata.get("work_mode") == "plan"
188
+ or request.metadata.get("readonly_tools") is True
189
+ ):
190
+ tools = tools.filtered_view(READONLY_TOOL_NAMES)
191
+
192
+ # R6:会话级自治级别(metadata 覆盖配置默认值)
193
+ autonomy = AutonomyPolicy(
194
+ level=normalize_autonomy(
195
+ str(request.metadata.get("autonomy") or self._config.autonomy)
196
+ )
197
+ )
198
+
199
+ # R4:写前检查点(沙盒内 .auc/checkpoints/,框架特权 IO)
200
+ checkpoints: CheckpointStore | None = None
201
+ if sandbox and self._config.enable_checkpoints:
202
+ checkpoints = CheckpointStore(sandbox)
203
+
204
+ # R3:上下文自动压缩
205
+ compactor: SummarizingCompactor | None = None
206
+ token_limit = self._config.loop_config.context_token_limit
207
+ if self._config.compaction is not None:
208
+ compactor = SummarizingCompactor(
209
+ self._config.model, self._config.compaction
210
+ )
211
+ elif token_limit and token_limit > 0:
212
+ compactor = SummarizingCompactor(
213
+ self._config.model, CompactionConfig(token_limit=token_limit)
214
+ )
215
+
216
+ ctx = LoopContext(
217
+ agent_id=self._config.agent_id,
218
+ run_id=run_id,
219
+ window=window,
220
+ tools=tools,
221
+ model=self._config.model,
222
+ events=bus,
223
+ config=self._config.loop_config,
224
+ memory=self._config.memory,
225
+ composer=self._config.composer,
226
+ context_package=package,
227
+ project_rules=project_rules,
228
+ privilege_gate=gate,
229
+ approval=self._config.approval,
230
+ system_prompt=self._config.system_prompt,
231
+ cancelled=run_id in self._cancelled_runs,
232
+ autonomy_policy=autonomy,
233
+ checkpoints=checkpoints,
234
+ compactor=compactor,
235
+ parent_run_id=request.metadata.get("parent_run_id"),
236
+ )
237
+ return ctx, bus
auc/chat_agent.py ADDED
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from auc import AgentConfig, DefaultAgent, DefaultToolRegistry
9
+ from auc.config import ModelConfig, load_merged_settings
10
+ from auc.integration.evolution import EvolutionMemoryPort, make_evolution_tools
11
+ from auc.loop.base import LoopConfig
12
+ from auc.model.factory import create_model_client
13
+ from auc.policy.autonomy import normalize_autonomy
14
+ from auc.policy.escalation import merge_escalation_settings
15
+ from auc.policy.privilege import ToolPrivilegeGate
16
+ from auc.ports import FileRulesPort, SlicerPolicy
17
+ from auc.ports.memory import DefaultComposer
18
+ from auc.tools.fetch import make_fetch_tool
19
+ from auc.tools.files import make_file_tools
20
+ from auc.tools.search import make_search_tools
21
+ from auc.tools.shell import make_shell_tool
22
+
23
+ if TYPE_CHECKING:
24
+ from auc.ports.approval import ApprovalPort
25
+ from auc.work_mode import WORK_MODE_OVERVIEW, build_full_system_prompt
26
+
27
+ DEFAULT_CHAT_BASE = """\
28
+ 你是编程助手。工作区根目录(沙盒)为:{sandbox}
29
+ 在沙盒内你拥有完整文件系统权限(不可访问沙盒外路径)。
30
+
31
+ 可用工具:
32
+ - read_file(path): 读取 UTF-8 文本
33
+ - write_file(path, content): 写入或创建文件
34
+ - list_dir(path): 列出目录(path 默认 .)
35
+ - delete_path(path): 删除文件或整个目录
36
+ - run_command(command, cwd?, timeout?): 沙盒内执行 shell 命令(跑测试/构建/git 等);危险命令需用户授权
37
+ - grep_search(pattern, glob?): 按正则搜索文件内容,返回 path:line: text
38
+ - glob_files(pattern): 按名称模式找文件(按修改时间排序)
39
+ - save_lesson(tags, lesson): 固化可复用经验到进化库(跨会话)
40
+ - promote_nugget(nugget_id, tags, content): 将成功经验提升为金块技能
41
+
42
+ 定位代码请优先 grep_search / glob_files,不要逐层 list_dir 遍历。
43
+ 改完代码后用 run_command 跑测试验证(如 pytest),失败则修复后复跑。
44
+
45
+ 进化能力(默认开启):
46
+ - 每轮成功对话会自动写入 .auc/evolution.yaml
47
+ - 启动时会召回 .auc/au-nuggets.yaml 与历史经验
48
+
49
+ 用户要求删除目录/文件时,必须使用 delete_path。
50
+ 当用户需要代码或文件时,必须用 write_file 写入工作区。
51
+
52
+ 外部链接:需要网页/文章正文时,使用 fetch_url(url, save_path?)。
53
+ 该工具为 L3 高危操作,**必须经用户授权后才会发起网络请求**;未授权时不得声称已访问链接。
54
+ 可将结果保存到沙盒文件(save_path)再用 read_file 分析。
55
+ write_file 参数必须是合法 JSON,且同时包含 path 与 content。
56
+ 路径使用相对于沙盒的相对路径。
57
+
58
+ 多模态:用户可通过 @图片路径 附加 png/jpg/gif/webp,请结合图片内容回答。
59
+
60
+ Web 编辑器:用户消息可能附带「当前文件」或「选中代码」。修改需求时:
61
+ 1. 优先用 write_file 直接写入工作区,不要只贴代码不保存
62
+ 2. 小范围改动保持原文件风格;大范围可拆分多文件
63
+ 3. 改完后简要说明改了哪些文件
64
+
65
+ 图表:说明架构、流程、状态时,用 Mermaid(```mermaid ... ```),Web 端会自动渲染。
66
+ 支持 flowchart、sequenceDiagram、classDiagram、stateDiagram、erDiagram、gantt、pie、journey、gitGraph、mindmap、timeline、quadrantChart、C4、kanban、sankey、xychart 等全部类型。
67
+ Mermaid 语法要求:
68
+ - subgraph 标题含中文或标点时必须加双引号:subgraph "第一阶段:基础"
69
+ - 节点标签含 emoji、中文、/、空格时用双引号:A["求职 / 研究"]
70
+ - gantt 的 title/section/任务名含冒号、+、/、→ 或中文时用双引号:"任务名" :id, after x, 3w
71
+ - 渲染失败时系统会自动尝试修复;若仍失败请输出修正后的完整 ```mermaid``` 块
72
+ """
73
+
74
+ # 兼容测试与外部引用:含 {sandbox} 占位符的完整模板
75
+ DEFAULT_CHAT_SYSTEM = (
76
+ DEFAULT_CHAT_BASE + "\n\n" + WORK_MODE_OVERVIEW
77
+ )
78
+
79
+
80
+ def build_chat_system_prompt(
81
+ sandbox: str,
82
+ *,
83
+ extra: str | None = None,
84
+ include_work_mode: bool = True,
85
+ ) -> str:
86
+ return build_full_system_prompt(
87
+ sandbox,
88
+ base=DEFAULT_CHAT_BASE,
89
+ include_work_mode=include_work_mode,
90
+ extra=extra,
91
+ )
92
+
93
+
94
+ @dataclass
95
+ class ChatAgentOptions:
96
+ sandbox: str
97
+ repo: str | None = None
98
+ system_prompt: str | None = None
99
+ evolve: bool = True
100
+ no_tools: bool = False
101
+ max_steps: int = 40
102
+ include_work_mode: bool = True
103
+
104
+
105
+ def resolve_sandbox_root(*, sandbox: str | None = None, repo: str | None = None) -> str:
106
+ if sandbox:
107
+ return str(Path(sandbox).expanduser().resolve())
108
+ if repo:
109
+ return str(Path(repo).expanduser().resolve())
110
+ return str(Path.cwd().resolve())
111
+
112
+
113
+ def build_chat_agent(
114
+ cfg: ModelConfig,
115
+ opts: ChatAgentOptions,
116
+ *,
117
+ approval: ApprovalPort | None = None,
118
+ ) -> DefaultAgent:
119
+ sandbox = resolve_sandbox_root(sandbox=opts.sandbox, repo=opts.repo)
120
+ try:
121
+ settings, _ = load_merged_settings(
122
+ None, Path(opts.repo) if opts.repo else None
123
+ )
124
+ except Exception: # noqa: BLE001
125
+ settings = {}
126
+ memory = (
127
+ EvolutionMemoryPort(sandbox_root=sandbox) if opts.evolve and not opts.no_tools else None
128
+ )
129
+ shell_settings = settings.get("shell") or {}
130
+ registry = DefaultToolRegistry()
131
+ if not opts.no_tools:
132
+ for tool, pol in make_file_tools(sandbox):
133
+ registry.register(tool, pol)
134
+ shell_tool, shell_pol = make_shell_tool(
135
+ sandbox,
136
+ default_timeout=float(shell_settings.get("default_timeout") or 120),
137
+ max_timeout=float(shell_settings.get("max_timeout") or 600),
138
+ )
139
+ registry.register(shell_tool, shell_pol)
140
+ for tool, pol in make_search_tools(sandbox):
141
+ registry.register(tool, pol)
142
+ if memory is not None:
143
+ for tool, pol in make_evolution_tools(memory):
144
+ registry.register(tool, pol)
145
+ for tool, pol in make_fetch_tool(sandbox):
146
+ registry.register(tool, pol)
147
+
148
+ gate = ToolPrivilegeGate(
149
+ approval=approval,
150
+ escalation_rules=merge_escalation_settings(settings.get("escalations")),
151
+ )
152
+ system = opts.system_prompt or build_chat_system_prompt(
153
+ sandbox, include_work_mode=opts.include_work_mode
154
+ )
155
+ model = create_model_client(cfg)
156
+ compaction = settings.get("compaction") or {}
157
+ loop_config = LoopConfig(
158
+ max_steps=opts.max_steps,
159
+ context_token_limit=int(compaction.get("token_limit") or 96_000),
160
+ )
161
+ return DefaultAgent(
162
+ AgentConfig(
163
+ agent_id="chat",
164
+ model=model,
165
+ tools=registry,
166
+ memory=memory,
167
+ composer=DefaultComposer(),
168
+ rules=FileRulesPort() if opts.repo else None,
169
+ slicer_policy=SlicerPolicy(require_package=False),
170
+ system_prompt=system,
171
+ sandbox_root=sandbox,
172
+ approval=approval,
173
+ privilege_gate=gate,
174
+ loop_config=loop_config,
175
+ autonomy=normalize_autonomy(str(settings.get("autonomy") or "")),
176
+ )
177
+ )