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.
- auc/__init__.py +125 -0
- auc/agent.py +237 -0
- auc/chat_agent.py +177 -0
- auc/checkpoint.py +219 -0
- auc/cli.py +734 -0
- auc/cli_ui.py +560 -0
- auc/config.py +571 -0
- auc/context/__init__.py +3 -0
- auc/context/compactor.py +217 -0
- auc/context/window.py +55 -0
- auc/diagrams.py +191 -0
- auc/events/__init__.py +3 -0
- auc/events/bus.py +63 -0
- auc/extras.py +32 -0
- auc/integration/__init__.py +39 -0
- auc/integration/aum.py +62 -0
- auc/integration/dispatcher.py +105 -0
- auc/integration/evolution.py +297 -0
- auc/integration/im_base.py +50 -0
- auc/integration/im_card.py +20 -0
- auc/integration/nuggets.py +123 -0
- auc/integration/qq.py +177 -0
- auc/integration/slicer.py +150 -0
- auc/integration/telegram.py +174 -0
- auc/loop/__init__.py +17 -0
- auc/loop/base.py +215 -0
- auc/loop/react.py +197 -0
- auc/messages.py +62 -0
- auc/model/__init__.py +29 -0
- auc/model/anthropic.py +369 -0
- auc/model/client.py +73 -0
- auc/model/deepseek_anthropic.py +23 -0
- auc/model/factory.py +48 -0
- auc/model/json_util.py +112 -0
- auc/model/openai.py +220 -0
- auc/model/streaming.py +89 -0
- auc/multimodal.py +189 -0
- auc/plan.py +67 -0
- auc/policy/__init__.py +3 -0
- auc/policy/autonomy.py +53 -0
- auc/policy/escalation.py +83 -0
- auc/policy/privilege.py +264 -0
- auc/ports/__init__.py +35 -0
- auc/ports/approval.py +75 -0
- auc/ports/memory.py +145 -0
- auc/ports/package.py +28 -0
- auc/ports/rules.py +84 -0
- auc/prompt_input.py +256 -0
- auc/py.typed +0 -0
- auc/sandbox.py +34 -0
- auc/stream_display.py +6 -0
- auc/terminal.py +144 -0
- auc/tools/__init__.py +19 -0
- auc/tools/base.py +125 -0
- auc/tools/builtin.py +17 -0
- auc/tools/decorator.py +44 -0
- auc/tools/fetch.py +122 -0
- auc/tools/files.py +97 -0
- auc/tools/registry.py +64 -0
- auc/tools/search.py +233 -0
- auc/tools/shell.py +177 -0
- auc/types.py +38 -0
- auc/web/__init__.py +1 -0
- auc/web/approval.py +89 -0
- auc/web/conversations.py +308 -0
- auc/web/editor_context.py +90 -0
- auc/web/preview.py +69 -0
- auc/web/projects.py +155 -0
- auc/web/runner.py +264 -0
- auc/web/server.py +960 -0
- auc/web/session.py +122 -0
- auc/web/static/app.js +1350 -0
- auc/web/static/index.html +172 -0
- auc/web/static/message_render.js +461 -0
- auc/web/static/styles.css +1007 -0
- auc/web/workspace.py +118 -0
- auc/work_mode.py +304 -0
- ufy_auc-0.2.4.dist-info/METADATA +254 -0
- ufy_auc-0.2.4.dist-info/RECORD +82 -0
- ufy_auc-0.2.4.dist-info/WHEEL +4 -0
- ufy_auc-0.2.4.dist-info/entry_points.txt +3 -0
- 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
|
+
)
|