yycode 0.3.2__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.
- yycode-0.3.2/PKG-INFO +12 -0
- yycode-0.3.2/README.md +450 -0
- yycode-0.3.2/agent/__init__.py +33 -0
- yycode-0.3.2/agent/acp/__init__.py +2 -0
- yycode-0.3.2/agent/acp/approval_adapter.py +134 -0
- yycode-0.3.2/agent/acp/content_adapter.py +45 -0
- yycode-0.3.2/agent/acp/jsonrpc.py +92 -0
- yycode-0.3.2/agent/acp/server.py +197 -0
- yycode-0.3.2/agent/acp/session_manager.py +193 -0
- yycode-0.3.2/agent/acp/update_adapter.py +192 -0
- yycode-0.3.2/agent/app_paths.py +25 -0
- yycode-0.3.2/agent/approval.py +169 -0
- yycode-0.3.2/agent/cancellation.py +52 -0
- yycode-0.3.2/agent/change_snapshot.py +186 -0
- yycode-0.3.2/agent/context_compressor.py +116 -0
- yycode-0.3.2/agent/graph.py +137 -0
- yycode-0.3.2/agent/llm_retry.py +434 -0
- yycode-0.3.2/agent/logger.py +97 -0
- yycode-0.3.2/agent/lsp/__init__.py +13 -0
- yycode-0.3.2/agent/lsp/client.py +151 -0
- yycode-0.3.2/agent/lsp/manager.py +234 -0
- yycode-0.3.2/agent/lsp/types.py +119 -0
- yycode-0.3.2/agent/message_context_manager.py +322 -0
- yycode-0.3.2/agent/message_format.py +105 -0
- yycode-0.3.2/agent/nodes/llm_node.py +58 -0
- yycode-0.3.2/agent/nodes/state.py +12 -0
- yycode-0.3.2/agent/nodes/task_guard_node.py +50 -0
- yycode-0.3.2/agent/nodes/tools_node.py +70 -0
- yycode-0.3.2/agent/plan_snapshot.py +70 -0
- yycode-0.3.2/agent/providers/__init__.py +13 -0
- yycode-0.3.2/agent/providers/anthropic_provider.py +268 -0
- yycode-0.3.2/agent/providers/base.py +52 -0
- yycode-0.3.2/agent/providers/openai_provider.py +279 -0
- yycode-0.3.2/agent/providers/text_tool_calls.py +118 -0
- yycode-0.3.2/agent/runtime/approval_service.py +184 -0
- yycode-0.3.2/agent/runtime/context.py +43 -0
- yycode-0.3.2/agent/runtime/tool_events.py +368 -0
- yycode-0.3.2/agent/runtime/tool_executor.py +208 -0
- yycode-0.3.2/agent/runtime/tool_output.py +261 -0
- yycode-0.3.2/agent/runtime/tool_registry.py +91 -0
- yycode-0.3.2/agent/runtime/tool_scheduler.py +35 -0
- yycode-0.3.2/agent/runtime/workflow_guard.py +217 -0
- yycode-0.3.2/agent/runtime/workspace.py +5 -0
- yycode-0.3.2/agent/runtime/workspace_tools.py +22 -0
- yycode-0.3.2/agent/session.py +787 -0
- yycode-0.3.2/agent/session_replay.py +95 -0
- yycode-0.3.2/agent/session_store.py +186 -0
- yycode-0.3.2/agent/skills.py +254 -0
- yycode-0.3.2/agent/streaming.py +248 -0
- yycode-0.3.2/agent/subagent.py +634 -0
- yycode-0.3.2/agent/task_memory.py +340 -0
- yycode-0.3.2/agent/todo_manager.py +304 -0
- yycode-0.3.2/agent/tool_retry.py +106 -0
- yycode-0.3.2/agent/tui/__init__.py +14 -0
- yycode-0.3.2/agent/tui/app.py +1325 -0
- yycode-0.3.2/agent/tui/approval.py +53 -0
- yycode-0.3.2/agent/tui/commands/__init__.py +6 -0
- yycode-0.3.2/agent/tui/commands/base.py +48 -0
- yycode-0.3.2/agent/tui/commands/clear.py +37 -0
- yycode-0.3.2/agent/tui/commands/help.py +27 -0
- yycode-0.3.2/agent/tui/commands/registry.py +94 -0
- yycode-0.3.2/agent/tui/help_content.py +108 -0
- yycode-0.3.2/agent/tui/renderers.py +1961 -0
- yycode-0.3.2/agent/tui/runner.py +439 -0
- yycode-0.3.2/agent/tui/state.py +653 -0
- yycode-0.3.2/main.py +465 -0
- yycode-0.3.2/pyproject.toml +38 -0
- yycode-0.3.2/setup.cfg +4 -0
- yycode-0.3.2/skills/code_review.md +61 -0
- yycode-0.3.2/skills/code_workflow.md +404 -0
- yycode-0.3.2/skills/drawio/SKILL.md +636 -0
- yycode-0.3.2/skills/drawio/agents/openai.yaml +19 -0
- yycode-0.3.2/skills/drawio/assets/demo-erd.drawio +84 -0
- yycode-0.3.2/skills/drawio/assets/demo-layered-cn.drawio +91 -0
- yycode-0.3.2/skills/drawio/assets/demo-layered-cn.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-layered.drawio +112 -0
- yycode-0.3.2/skills/drawio/assets/demo-layered.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-ml.drawio +90 -0
- yycode-0.3.2/skills/drawio/assets/demo-ring-cn.drawio +68 -0
- yycode-0.3.2/skills/drawio/assets/demo-ring-cn.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-ring.drawio +86 -0
- yycode-0.3.2/skills/drawio/assets/demo-ring.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-sequence.drawio +116 -0
- yycode-0.3.2/skills/drawio/assets/demo-star-cn.drawio +66 -0
- yycode-0.3.2/skills/drawio/assets/demo-star-cn.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-star.drawio +79 -0
- yycode-0.3.2/skills/drawio/assets/demo-star.png +0 -0
- yycode-0.3.2/skills/drawio/assets/demo-uml-class.drawio +64 -0
- yycode-0.3.2/skills/drawio/assets/microservices-example.drawio +173 -0
- yycode-0.3.2/skills/drawio/assets/microservices-example.png +0 -0
- yycode-0.3.2/skills/drawio/assets/workflow-cn.drawio +120 -0
- yycode-0.3.2/skills/drawio/assets/workflow-cn.png +0 -0
- yycode-0.3.2/skills/drawio/assets/workflow.drawio +120 -0
- yycode-0.3.2/skills/drawio/assets/workflow.png +0 -0
- yycode-0.3.2/skills/drawio/docs/index.html +469 -0
- yycode-0.3.2/skills/drawio/docs/zh.html +456 -0
- yycode-0.3.2/skills/drawio/references/style-extraction.md +254 -0
- yycode-0.3.2/skills/drawio/styles/schema.json +112 -0
- yycode-0.3.2/skills/plan.md +115 -0
- yycode-0.3.2/skills/ppt/SKILL.md +254 -0
- yycode-0.3.2/tests/test_acp.py +278 -0
- yycode-0.3.2/tests/test_anthropic_provider.py +27 -0
- yycode-0.3.2/tests/test_apply_patch_tool.py +271 -0
- yycode-0.3.2/tests/test_code_navigation_tools.py +88 -0
- yycode-0.3.2/tests/test_compatibility_primitives.py +151 -0
- yycode-0.3.2/tests/test_grep_tool.py +79 -0
- yycode-0.3.2/tests/test_llm_retry.py +119 -0
- yycode-0.3.2/tests/test_lsp_tools.py +190 -0
- yycode-0.3.2/tests/test_main_input.py +1080 -0
- yycode-0.3.2/tests/test_message_context_manager.py +96 -0
- yycode-0.3.2/tests/test_message_format.py +189 -0
- yycode-0.3.2/tests/test_openai_provider_reasoning.py +190 -0
- yycode-0.3.2/tests/test_safety.py +153 -0
- yycode-0.3.2/tests/test_session_store.py +130 -0
- yycode-0.3.2/tests/test_skills.py +319 -0
- yycode-0.3.2/tests/test_streaming.py +34 -0
- yycode-0.3.2/tests/test_streaming_events.py +120 -0
- yycode-0.3.2/tests/test_subagent.py +754 -0
- yycode-0.3.2/tests/test_task_guard.py +171 -0
- yycode-0.3.2/tests/test_task_memory.py +113 -0
- yycode-0.3.2/tests/test_task_state.py +138 -0
- yycode-0.3.2/tests/test_text_tool_calls.py +63 -0
- yycode-0.3.2/tests/test_token_counting.py +112 -0
- yycode-0.3.2/tests/test_tool_concurrency.py +1206 -0
- yycode-0.3.2/tests/test_tool_metadata.py +26 -0
- yycode-0.3.2/tests/test_tui_approval.py +39 -0
- yycode-0.3.2/tests/test_tui_commands.py +202 -0
- yycode-0.3.2/tests/test_tui_runner.py +571 -0
- yycode-0.3.2/tests/test_tui_state.py +1509 -0
- yycode-0.3.2/tests/test_verify_tool.py +37 -0
- yycode-0.3.2/tests/test_web_search_tool.py +153 -0
- yycode-0.3.2/tests/test_workspace_tools.py +126 -0
- yycode-0.3.2/tests/test_write_tools_diff.py +71 -0
- yycode-0.3.2/tools/__init__.py +50 -0
- yycode-0.3.2/tools/apply_patch.py +305 -0
- yycode-0.3.2/tools/bash.py +76 -0
- yycode-0.3.2/tools/diff_utils.py +139 -0
- yycode-0.3.2/tools/edit_file.py +40 -0
- yycode-0.3.2/tools/git_diff.py +72 -0
- yycode-0.3.2/tools/git_show.py +65 -0
- yycode-0.3.2/tools/grep.py +149 -0
- yycode-0.3.2/tools/list_files.py +90 -0
- yycode-0.3.2/tools/list_skills.py +24 -0
- yycode-0.3.2/tools/load_skill.py +30 -0
- yycode-0.3.2/tools/lsp_definition.py +27 -0
- yycode-0.3.2/tools/lsp_diagnostics.py +32 -0
- yycode-0.3.2/tools/lsp_document_symbols.py +23 -0
- yycode-0.3.2/tools/lsp_hover.py +29 -0
- yycode-0.3.2/tools/lsp_references.py +37 -0
- yycode-0.3.2/tools/lsp_utils.py +38 -0
- yycode-0.3.2/tools/lsp_workspace_symbols.py +23 -0
- yycode-0.3.2/tools/read_file.py +61 -0
- yycode-0.3.2/tools/read_many_files.py +50 -0
- yycode-0.3.2/tools/safety.py +50 -0
- yycode-0.3.2/tools/subagent.py +57 -0
- yycode-0.3.2/tools/todo.py +89 -0
- yycode-0.3.2/tools/verify.py +107 -0
- yycode-0.3.2/tools/web_search.py +250 -0
- yycode-0.3.2/tools/workspace.py +36 -0
- yycode-0.3.2/tools/workspace_state.py +60 -0
- yycode-0.3.2/tools/write_file.py +88 -0
- yycode-0.3.2/utils/__init__.py +5 -0
- yycode-0.3.2/utils/retry.py +13 -0
- yycode-0.3.2/yycode.egg-info/PKG-INFO +12 -0
- yycode-0.3.2/yycode.egg-info/SOURCES.txt +167 -0
- yycode-0.3.2/yycode.egg-info/dependency_links.txt +1 -0
- yycode-0.3.2/yycode.egg-info/entry_points.txt +2 -0
- yycode-0.3.2/yycode.egg-info/requires.txt +7 -0
- yycode-0.3.2/yycode.egg-info/top_level.txt +4 -0
yycode-0.3.2/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yycode
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Terminal coding agent with TUI, plain input mode, tools, skills, and ACP support
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: anthropic>=0.40.0
|
|
7
|
+
Requires-Dist: openai>=1.0.0
|
|
8
|
+
Requires-Dist: tiktoken>=0.12.0
|
|
9
|
+
Requires-Dist: langgraph>=0.2.0
|
|
10
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
11
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
12
|
+
Requires-Dist: textual>=0.80.0
|
yycode-0.3.2/README.md
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# Yoyo Agent
|
|
2
|
+
|
|
3
|
+
作者: yoyofx-g
|
|
4
|
+
|
|
5
|
+
一个基于 LangGraph 的智能编程助手,支持多 LLM 提供商。
|
|
6
|
+
|
|
7
|
+
## 快速开始
|
|
8
|
+
|
|
9
|
+
### 1. 准备环境
|
|
10
|
+
|
|
11
|
+
Yoyo Agent 需要 Python 3.10 或更高版本:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
推荐使用 `uv` 管理 Python 环境和依赖。如果本机还没有 `uv`,可以按官方安装方式安装:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# macOS / Linux
|
|
21
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
22
|
+
|
|
23
|
+
# Windows PowerShell
|
|
24
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
安装后重新打开终端,确认命令可用:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv --version
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
如果你不想使用 `uv`,也可以使用 Python 自带的 `venv` + `pip`,见下一步的备选命令。
|
|
34
|
+
|
|
35
|
+
### 2. 安装 yycode 命令
|
|
36
|
+
|
|
37
|
+
如果只是使用 yycode,推荐直接用 `uv tool install` 安装命令:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install git+https://github.com/on-my-yycode/yycode.git
|
|
41
|
+
yycode --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
安装后可以在任意项目目录运行:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
yycode
|
|
48
|
+
yycode ~/project
|
|
49
|
+
yycode --plain
|
|
50
|
+
yycode --acp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
以后升级到远程最新版本:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv tool upgrade yycode
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
如果是从 GitHub 分支安装且需要强制刷新:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install --force git+https://github.com/on-my-yycode/yycode.git
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. 开发环境安装
|
|
66
|
+
|
|
67
|
+
如果要修改 yycode 源码,再 clone 仓库并安装开发依赖:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git clone <your-repo-url>
|
|
71
|
+
cd yoyoagent
|
|
72
|
+
|
|
73
|
+
# 推荐:使用 uv 创建虚拟环境并安装依赖
|
|
74
|
+
uv sync
|
|
75
|
+
|
|
76
|
+
# 备选:使用 venv + pip
|
|
77
|
+
python -m venv .venv
|
|
78
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
79
|
+
pip install -e .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> 依赖声明在 `pyproject.toml` 中,包含 TUI、LLM Provider、LangGraph 和 dotenv 支持。
|
|
83
|
+
|
|
84
|
+
### 4. 配置模型
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cp .env.example .env
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
编辑 `.env`,填入你的 LLM Provider、API Key 和模型名称:
|
|
91
|
+
|
|
92
|
+
```dotenv
|
|
93
|
+
PROVIDER=openai
|
|
94
|
+
API_KEY=your-api-key
|
|
95
|
+
API_BASE=https://api.openai.com/v1
|
|
96
|
+
AI_MODEL=gpt-4o
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
常用配置:
|
|
100
|
+
|
|
101
|
+
| 变量 | 说明 | 示例 |
|
|
102
|
+
|------|------|------|
|
|
103
|
+
| `PROVIDER` | LLM 提供商,支持 `anthropic` 或 `openai` | `openai` |
|
|
104
|
+
| `API_KEY` | 对应提供商的 API 密钥 | `your-api-key` |
|
|
105
|
+
| `API_BASE` | 可选,自定义 API Base/Base URL | `https://api.openai.com/v1` |
|
|
106
|
+
| `AI_MODEL` | 模型名称 | `gpt-4o` |
|
|
107
|
+
|
|
108
|
+
不要把真实 API Key 提交到仓库;本地私密配置只放在 `.env`。
|
|
109
|
+
|
|
110
|
+
### 5. 启动 TUI
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# 在当前目录启动,默认把当前目录作为被操作工作区
|
|
114
|
+
yycode
|
|
115
|
+
|
|
116
|
+
# 指定要让 agent 操作的项目目录
|
|
117
|
+
yycode ~/project
|
|
118
|
+
|
|
119
|
+
# 开发环境中也可以使用 uv 运行项目入口
|
|
120
|
+
uv run yycode ~/project
|
|
121
|
+
|
|
122
|
+
# 等价的源码入口
|
|
123
|
+
uv run python main.py ~/project
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
启动后会进入终端 TUI。你可以直接输入需求,例如:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
阅读这个项目并总结结构
|
|
130
|
+
修复测试失败的问题
|
|
131
|
+
给 README 增加安装说明
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 6. 常用运行方式
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv run python main.py -a # 自动批准高风险操作
|
|
138
|
+
uv run python main.py --debug # 输出调试日志
|
|
139
|
+
uv run python main.py --log-file # 写入 agent_debug.log
|
|
140
|
+
uv run python main.py --plain # 使用普通终端输入模式,作为 TUI/输入法兼容兜底
|
|
141
|
+
uv run python main.py -s # 查看当前工作区可恢复的 sessions
|
|
142
|
+
uv run python main.py -r <session-id> # 恢复指定 session
|
|
143
|
+
uv run python main.py -x <session-id> # 删除指定 session
|
|
144
|
+
uv run python main.py -t # 临时会话,不保存 messages
|
|
145
|
+
uv run python main.py --acp # 启动 ACP stdio server
|
|
146
|
+
uv run python main.py acp # 同上,便于作为子命令使用
|
|
147
|
+
yycode --plain # 安装发行包后的普通终端输入模式
|
|
148
|
+
yycode --acp # 安装发行包后的 ACP stdio server
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
更多 TUI 快捷键、内置工具和会话说明见 [使用说明](docs/usage.md)。
|
|
152
|
+
|
|
153
|
+
### 7. 版本更新与发布
|
|
154
|
+
|
|
155
|
+
项目版本号定义在 `pyproject.toml` 的 `version` 字段。准备发布给用户使用时,建议同步更新版本号:
|
|
156
|
+
|
|
157
|
+
- 文档或小修复:例如 `0.3.2` -> `0.3.3`
|
|
158
|
+
- 新增用户可见能力或安装体验:例如 `0.3.2` -> `0.4.0`
|
|
159
|
+
- 破坏兼容的 CLI 或配置改动:升级 minor 或后续 major 版本
|
|
160
|
+
|
|
161
|
+
发布新版本的一般流程:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# 修改 pyproject.toml 中的 version
|
|
165
|
+
uv lock
|
|
166
|
+
git add pyproject.toml uv.lock
|
|
167
|
+
git commit -m "Bump version to 0.4.0"
|
|
168
|
+
git tag v0.4.0
|
|
169
|
+
git push origin dev master --tags
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
用户升级:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv tool upgrade yycode
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
如果用户是直接从 GitHub 分支安装,也可以强制重新安装最新代码:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv tool install --force git+https://github.com/on-my-yycode/yycode.git
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 功能特性
|
|
185
|
+
|
|
186
|
+
- 🖥️ **TUI 终端界面** - 基于 Textual 的现代终端 UI,支持紧凑 Transcript 风格时间线、工具活动摘要、审批弹窗和历史浏览
|
|
187
|
+
- 🤖 **多提供商支持** - Anthropic Claude、OpenAI GPT,兼容自定义 API Base
|
|
188
|
+
- 🛠️ **丰富的内置工具** - 代码导航 (grep/list_files/read_file/git_diff)、文件编辑 (apply_patch/write_file)、命令执行 (bash)、验证 (verify) 等 16 个自动注册工具
|
|
189
|
+
- 📚 **技能系统** - 可扩展的专业知识模块 (code_review、code_workflow、drawio 图表生成)
|
|
190
|
+
- 📋 **任务管理** - 自动跟踪 Todo 列表和任务状态,支持任务完成保护
|
|
191
|
+
- 🔄 **子代理系统** - 分解复杂任务,支持 explorer/architect/worker/tester/security 五种角色
|
|
192
|
+
- 💬 **流式输出** - 结构化事件流,支持实时交互和思考过程展示
|
|
193
|
+
- 🔒 **运行时安全审批** - 高风险操作 (文件编辑、命令执行) 需用户确认,静默模式可自动批准
|
|
194
|
+
- 🗜️ **上下文压缩** - 长会话自动压缩旧工具输出,避免超出上下文窗口
|
|
195
|
+
- 🔁 **智能重试** - LLM 调用和工具执行均支持自动重试
|
|
196
|
+
|
|
197
|
+
### 最新功能更新
|
|
198
|
+
|
|
199
|
+
- **更紧凑的 TUI 时间线**:连续工具调用会聚合为活动摘要,例如 `explored 1 file`、`Edited 1 file`,同时保留每个工具调用的关键目标和耗时。
|
|
200
|
+
- **更清晰的模型输出**:主时间线中的模型文本采用对话式 Transcript 风格展示,不再重复显示固定助手名称,阅读更接近常见代码代理体验。
|
|
201
|
+
- **独立使用说明**:常用启动命令、TUI 快捷键和内置工具清单已整理到 [使用说明](docs/usage.md),README 保留概览与入口信息。
|
|
202
|
+
|
|
203
|
+
## 配置参考
|
|
204
|
+
|
|
205
|
+
### 依赖说明
|
|
206
|
+
|
|
207
|
+
| 包名 | 最低版本 | 用途 |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| **anthropic** | 0.40.0 | Anthropic Claude API 调用 |
|
|
210
|
+
| **openai** | 1.0.0 | OpenAI API 调用 |
|
|
211
|
+
| **tiktoken** | 0.12.0 | Token 计数与估算 |
|
|
212
|
+
| **langgraph** | 0.2.0 | LangGraph 状态图编排 |
|
|
213
|
+
| **langchain-core** | 0.3.0 | LangChain 消息类型 (AIMessage, HumanMessage, ToolMessage) |
|
|
214
|
+
| **python-dotenv** | 1.0.0 | 加载 `.env` 环境变量 |
|
|
215
|
+
| **textual** | 0.80.0 | TUI 终端界面框架 |
|
|
216
|
+
|
|
217
|
+
> 以上依赖均声明在 `pyproject.toml` 中,`pip install -e .` 会一次性安装所有依赖。
|
|
218
|
+
|
|
219
|
+
### 环境变量
|
|
220
|
+
|
|
221
|
+
| 变量 | 说明 | 默认值/示例 |
|
|
222
|
+
|------|------|-------------|
|
|
223
|
+
| `PROVIDER` | LLM 提供商,支持 `anthropic` 或 `openai` | `anthropic` |
|
|
224
|
+
| `API_KEY` | 对应提供商的 API 密钥 | `sk-...` |
|
|
225
|
+
| `API_BASE` | 可选,自定义 API Base/Base URL | `https://api.openai.com/v1` |
|
|
226
|
+
| `AI_MODEL` | 模型名称 | Anthropic 默认 `claude-3-5-sonnet-20241022`,OpenAI 默认 `gpt-4o` |
|
|
227
|
+
| `YOYO_CONTEXT_WINDOW_TOKENS` | 可选,覆盖上下文窗口大小,用于 TUI/CLI 提示符统计;未设置时会按模型推断 | Claude `200000`,Doubao Code `224000`,GPT-4o/4.1/5 `128000` |
|
|
228
|
+
| `YOYO_APP_ROOT` | 可选,覆盖 yoyoagent 应用根目录;默认是源码/发行目录 | `/path/to/yoyoagent` |
|
|
229
|
+
| `YOYO_RUNTIME_DATA_DIR` | 可选,覆盖运行数据目录;默认等于 `app_root` | `/path/to/yoyoagent` |
|
|
230
|
+
| `YOYO_SESSION_DIR` | 可选,覆盖 session messages 保存目录 | `~/.yoyoagent/sessions` |
|
|
231
|
+
| `YOYO_SKILL_DIRS` | 可选,额外技能目录,多个目录用逗号分隔;默认技能目录是 `{app_root}/skills` | `../shared-skills` |
|
|
232
|
+
| `YOYO_SILENT` / `YOYO_AUTO_APPROVE` | 可选,启用后自动批准高风险操作 | `true` |
|
|
233
|
+
|
|
234
|
+
高级重试配置:
|
|
235
|
+
|
|
236
|
+
| 变量 | 说明 | 默认值/示例 |
|
|
237
|
+
|------|------|-------------|
|
|
238
|
+
| `YOYO_LLM_TIMEOUT_SECONDS` | LLM 单次调用超时时间 | `120` |
|
|
239
|
+
| `YOYO_LLM_HEARTBEAT_SECONDS` | LLM 等待期间的心跳提示间隔 | `5` |
|
|
240
|
+
| `YOYO_LLM_MAX_RETRIES` | LLM 调用失败后的最大重试次数 | `2` |
|
|
241
|
+
|
|
242
|
+
> 请勿把真实 API 密钥提交到仓库;建议在 `.env` 中只保存本地私密配置。`.env.example` 也应使用占位符(如 `API_KEY=your-api-key`),避免提交真实或专属密钥。
|
|
243
|
+
|
|
244
|
+
### 完整命令参考
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python main.py # 默认以当前目录作为工作区启动 TUI
|
|
248
|
+
python main.py ~/project # 指定工作区目录启动
|
|
249
|
+
python main.py -a # 自动批准高风险操作
|
|
250
|
+
python main.py --debug # 调试模式,输出详细日志
|
|
251
|
+
python main.py --log-file # 将日志写入 agent_debug.log
|
|
252
|
+
python main.py --plain # 使用普通终端输入模式,作为 TUI/输入法兼容兜底
|
|
253
|
+
python main.py --acp # 启动 ACP stdio server
|
|
254
|
+
python main.py acp # 同上,便于作为子命令使用
|
|
255
|
+
python main.py -s # 列出当前工作区可恢复的 sessions
|
|
256
|
+
python main.py -r abc # 恢复指定 session 的历史 messages
|
|
257
|
+
python main.py -x abc # 删除指定 session
|
|
258
|
+
python main.py -t # 临时会话,不保存 session messages
|
|
259
|
+
yycode # 安装发行包后启动 TUI
|
|
260
|
+
yycode --plain # 安装发行包后使用普通终端输入模式
|
|
261
|
+
yycode --acp # 安装发行包后启动 ACP stdio server
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
当前默认入口会启动 TUI 界面。工作区使用位置参数指定;如果不传,则使用启动命令时所在目录。上述 `/p` / `/paste` 多行粘贴辅助函数保留在控制台输入实现中,但默认 TUI 路径不直接使用。
|
|
265
|
+
|
|
266
|
+
会话 messages 默认保存到 yoyoagent 应用目录下的 `sessions/{workspace_hash}/{session_id}.json`,不会写入被操作项目。默认会保存但不会自动恢复;需要继续旧上下文时先用 `-s` / `--sessions` 查看,再用 `-r <id>` / `--resume <id>` 恢复;不再需要的历史可用 `-x <id>` / `--delete <id>` 删除。
|
|
267
|
+
|
|
268
|
+
## 项目结构
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
yoyoagent/
|
|
272
|
+
├── agent/ # 核心代理模块
|
|
273
|
+
│ ├── graph.py # LangGraph 状态机编排
|
|
274
|
+
│ ├── session.py # 会话管理 (消息历史、token 统计、上下文压缩)
|
|
275
|
+
│ ├── skills.py # 技能发现与加载
|
|
276
|
+
│ ├── subagent.py # 子代理执行器
|
|
277
|
+
│ ├── todo_manager.py # 任务状态管理器
|
|
278
|
+
│ ├── context_compressor.py # 上下文压缩器
|
|
279
|
+
│ ├── streaming.py # 结构化流式事件
|
|
280
|
+
│ ├── approval.py # 运行时审批模型
|
|
281
|
+
│ ├── llm_retry.py # LLM 超时/心跳/重试
|
|
282
|
+
│ ├── tool_retry.py # 工具执行重试
|
|
283
|
+
│ ├── message_format.py # 消息格式转换
|
|
284
|
+
│ ├── nodes/ # LangGraph 节点实现
|
|
285
|
+
│ │ ├── state.py # AgentState 定义
|
|
286
|
+
│ │ ├── llm_node.py # LLM 调用节点
|
|
287
|
+
│ │ ├── tools_node.py # 工具执行节点
|
|
288
|
+
│ │ └── task_guard_node.py # Task State 完成保护
|
|
289
|
+
│ ├── runtime/ # 运行时服务层
|
|
290
|
+
│ │ ├── context.py # AgentRuntimeContext
|
|
291
|
+
│ │ ├── tool_registry.py # 工具注册与绑定
|
|
292
|
+
│ │ ├── tool_scheduler.py # 工具并发/串行调度
|
|
293
|
+
│ │ ├── tool_executor.py # 单工具执行流水线
|
|
294
|
+
│ │ ├── workflow_guard.py # workspace/git diff 检查
|
|
295
|
+
│ │ ├── approval_service.py # 运行时审批服务
|
|
296
|
+
│ │ └── tool_events.py # 工具事件格式化
|
|
297
|
+
│ ├── tui/ # Textual TUI 界面
|
|
298
|
+
│ │ ├── app.py # TUI 应用主入口
|
|
299
|
+
│ │ ├── runner.py # Agent 运行器
|
|
300
|
+
│ │ ├── state.py # TUI 状态管理
|
|
301
|
+
│ │ ├── renderers.py # 时间线渲染
|
|
302
|
+
│ │ ├── approval.py # 审批 UI
|
|
303
|
+
│ │ └── styles.tcss # Textual CSS 样式
|
|
304
|
+
│ └── providers/ # LLM 提供商抽象
|
|
305
|
+
│ ├── base.py # LLMProvider 基类
|
|
306
|
+
│ ├── anthropic_provider.py
|
|
307
|
+
│ └── openai_provider.py
|
|
308
|
+
├── tools/ # 内置工具实现 (16 个工具)
|
|
309
|
+
│ ├── apply_patch.py # 精确文件补丁
|
|
310
|
+
│ ├── bash.py # Shell 命令执行
|
|
311
|
+
│ ├── read_file.py / read_many_files.py # 文件读取
|
|
312
|
+
│ ├── write_file.py / edit_file.py # 文件写入/编辑
|
|
313
|
+
│ ├── grep.py # 正则搜索
|
|
314
|
+
│ ├── list_files.py # 文件列表
|
|
315
|
+
│ ├── git_diff.py / git_show.py # Git 操作
|
|
316
|
+
│ ├── workspace_state.py # 工作区状态
|
|
317
|
+
│ ├── verify.py # 代码验证
|
|
318
|
+
│ ├── todo.py # 任务管理工具
|
|
319
|
+
│ ├── subagent.py # 子代理工具
|
|
320
|
+
│ ├── list_skills.py / load_skill.py # 技能工具
|
|
321
|
+
│ └── safety.py # 安全工具
|
|
322
|
+
├── skills/ # 技能文件目录
|
|
323
|
+
│ ├── code_review.md # 代码审查技能
|
|
324
|
+
│ ├── code_workflow.md # 通用开发工作流
|
|
325
|
+
│ ├── plan.md # 规划/需求澄清技能
|
|
326
|
+
│ └── drawio/SKILL.md # draw.io 图表生成
|
|
327
|
+
├── tests/ # 测试文件 (100+ 测试用例)
|
|
328
|
+
├── examples/ # 示例项目 (贪吃蛇、塔防、数学游戏)
|
|
329
|
+
├── docs/ # 设计文档
|
|
330
|
+
├── changes/ # 变更日志
|
|
331
|
+
├── main.py # 入口文件 (默认启动 TUI)
|
|
332
|
+
└── pyproject.toml # 项目配置
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 使用指南
|
|
336
|
+
|
|
337
|
+
### 启动
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
python main.py # 默认启动 TUI 界面
|
|
341
|
+
python main.py -a # 自动批准高风险操作
|
|
342
|
+
python main.py --debug # 调试模式,输出详细日志
|
|
343
|
+
python main.py --log-file # 将日志写入 agent_debug.log
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### TUI 快捷键
|
|
347
|
+
|
|
348
|
+
| 快捷键 | 功能 |
|
|
349
|
+
|--------|------|
|
|
350
|
+
| `Ctrl+Enter` / `Ctrl+J` | 提交输入 |
|
|
351
|
+
| `Ctrl+C` | 取消当前任务 |
|
|
352
|
+
| `Ctrl+H` | 打开历史记录浏览器 |
|
|
353
|
+
| `Ctrl+Shift+C` | 复制时间线内容 |
|
|
354
|
+
| `Ctrl+Q` | 退出 |
|
|
355
|
+
| `PageUp` / `PageDown` | 滚动时间线 |
|
|
356
|
+
| `Home` / `End` | 跳转到时间线顶部/底部 |
|
|
357
|
+
| `Esc` | 聚焦输入框 |
|
|
358
|
+
|
|
359
|
+
### 可用工具
|
|
360
|
+
|
|
361
|
+
| 工具 | 功能 |
|
|
362
|
+
|------|------|
|
|
363
|
+
| `read_file` / `read_many_files` | 读取文件内容 |
|
|
364
|
+
| `write_file` | 创建新文件 |
|
|
365
|
+
| `apply_patch` | 精确编辑已有文件 (推荐) |
|
|
366
|
+
| `edit_file` | 文本替换编辑;已有文件编辑优先使用 `apply_patch` |
|
|
367
|
+
| `bash` | 执行 Shell 命令 |
|
|
368
|
+
| `grep` | 正则搜索文件 |
|
|
369
|
+
| `list_files` | 列出工作区文件 |
|
|
370
|
+
| `git_diff` / `git_show` | 查看 Git 变更 |
|
|
371
|
+
| `workspace_state` | 查看工作区状态 |
|
|
372
|
+
| `verify` | 运行测试/检查 |
|
|
373
|
+
| `todo` | 任务状态管理 |
|
|
374
|
+
| `subagent` | 委派子代理 |
|
|
375
|
+
| `list_skills` / `load_skill` | 技能管理 |
|
|
376
|
+
|
|
377
|
+
## 开发
|
|
378
|
+
|
|
379
|
+
### 运行测试
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
pytest tests/
|
|
383
|
+
# 或
|
|
384
|
+
uv run pytest tests/
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 代码规范
|
|
388
|
+
|
|
389
|
+
项目使用 ruff 进行代码检查:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
ruff check .
|
|
393
|
+
# 或
|
|
394
|
+
uv run ruff check .
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## 文档
|
|
398
|
+
|
|
399
|
+
详细文档请查看 `docs/` 目录。顶层文档和图表作用如下:
|
|
400
|
+
|
|
401
|
+
| 文档 | 作用和说明 |
|
|
402
|
+
|------|------------|
|
|
403
|
+
| [使用说明](docs/usage.md) | 日常启动参数、会话恢复/删除、TUI 快捷键、审批交互、技能与工具清单。 |
|
|
404
|
+
| [项目结构](docs/project_structure.md) | 项目目录、核心模块、LangGraph 工作流、runtime 层和当前架构扩展建议。 |
|
|
405
|
+
| [代码代理路线图](docs/code_agent_roadmap.md) | 代码代理能力演进路线、已完成项、后续计划和里程碑记录。 |
|
|
406
|
+
| [核心工作流](docs/core_workflow.md) | 当前核心执行链路说明,覆盖 Session、graph、nodes、runtime、Task State 和测试覆盖。 |
|
|
407
|
+
| [核心工作流重构设计](docs/core_workflow_refactor_design.md) | 将 `graph.py` 拆分为 nodes/runtime 服务的设计背景、目标结构、迁移计划和风险控制。 |
|
|
408
|
+
| [完整 TUI 设计](docs/full_tui_design.md) | Textual TUI 的界面布局、交互设计、组件职责和实现方案。 |
|
|
409
|
+
| [TUI 流程分析](docs/tui_flow_analysis.md) | TUI 从输入到 Agent 执行、流式事件、渲染和审批的流程梳理。 |
|
|
410
|
+
| [上下文压缩设计](docs/context_compression_design.md) | 长会话上下文压缩策略、触发条件、消息裁剪和摘要保留方案。 |
|
|
411
|
+
| [会话持久化设计](docs/session_persistence_design.md) | Session messages 本地保存、恢复、列表、删除、临时会话和后续增强设计。 |
|
|
412
|
+
| [结构化事件时间线设计](docs/structured_event_timeline_design.md) | 结构化事件时间线的数据模型、事件类型、渲染行为和 UI 演进方案。 |
|
|
413
|
+
| [Task Graph DAG 设计](docs/task_graph_dag_design.md) | 面向复杂任务的 DAG 调度、依赖关系、并发执行和状态管理设计。 |
|
|
414
|
+
| [LSP 集成设计](docs/lsp_integration_design.md) | Language Server Protocol 集成目标、模块划分、诊断/符号/补全能力和演进计划。 |
|
|
415
|
+
| [安全审查报告](docs/security_review_report.md) | 项目安全风险清单、严重程度分级、已有防护和优先行动建议。 |
|
|
416
|
+
| [变更日志更新确认计划](docs/confirmed_plan_changelog_update.md) | 更新 changelog 前确认过的执行计划和范围说明。 |
|
|
417
|
+
| [工作流 Mermaid 图](docs/workflow_diagram.mmd) | 核心工作流的 Mermaid 源文件,可用于生成流程图。 |
|
|
418
|
+
| [工作流 ASCII 图](docs/workflow_diagram_art.txt) | 核心工作流的纯文本图示,便于在终端或 Markdown 中快速查看。 |
|
|
419
|
+
| [架构图源文件](docs/yoyoagent_architecture.drawio) / [PNG](docs/yoyoagent_architecture.drawio.png) | Yoyo Agent 架构图的 draw.io 源文件和导出图片。 |
|
|
420
|
+
| [核心工作流程图源文件](docs/yoyoagent_core_workflow.drawio) / [PNG](docs/yoyoagent_core_workflow.drawio.png) | 核心工作流 draw.io 源文件和导出图片。 |
|
|
421
|
+
| [结构化事件时间线图源文件](docs/structured_event_timeline_design.drawio) / [PNG](docs/structured_event_timeline_design.png) | 结构化事件时间线设计图源文件和导出图片。 |
|
|
422
|
+
| [结构化事件时间线流程图源文件](docs/structured_event_timeline_flowchart.drawio) / [PNG](docs/structured_event_timeline_flowchart.drawio.png) | 结构化事件时间线流程图源文件和导出图片。 |
|
|
423
|
+
|
|
424
|
+
变更日志请查看 `changes/` 目录。
|
|
425
|
+
|
|
426
|
+
## 许可证
|
|
427
|
+
|
|
428
|
+
MIT License
|
|
429
|
+
|
|
430
|
+
Copyright (c) 2025 Yoyo Agent
|
|
431
|
+
|
|
432
|
+
作者: 张磊, zlhxd, yoyofx, zl.hxd@hotmail.com, vvvv
|
|
433
|
+
|
|
434
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
435
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
436
|
+
in the Software without restriction, including without limitation the rights
|
|
437
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
438
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
439
|
+
furnished to do so, subject to the following conditions:
|
|
440
|
+
|
|
441
|
+
The above copyright notice and this permission notice shall be included in all
|
|
442
|
+
copies or substantial portions of the Software.
|
|
443
|
+
|
|
444
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
445
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
446
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
447
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
448
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
449
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
450
|
+
SOFTWARE.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Agent package."""
|
|
2
|
+
|
|
3
|
+
from .graph import build_graph
|
|
4
|
+
from .session import Session
|
|
5
|
+
from .skills import LoadedSkill, SkillRegistry, discover_skills, load_skills, parse_skill_paths
|
|
6
|
+
from .streaming import ConsoleStreamRenderer, StreamEvent, StreamPrinter
|
|
7
|
+
from .todo_manager import TodoManager
|
|
8
|
+
from .providers import (
|
|
9
|
+
LLMProvider,
|
|
10
|
+
ChatResponse,
|
|
11
|
+
ToolCall,
|
|
12
|
+
AnthropicProvider,
|
|
13
|
+
OpenAIProvider,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"build_graph",
|
|
18
|
+
"Session",
|
|
19
|
+
"LoadedSkill",
|
|
20
|
+
"SkillRegistry",
|
|
21
|
+
"discover_skills",
|
|
22
|
+
"load_skills",
|
|
23
|
+
"parse_skill_paths",
|
|
24
|
+
"ConsoleStreamRenderer",
|
|
25
|
+
"StreamEvent",
|
|
26
|
+
"StreamPrinter",
|
|
27
|
+
"TodoManager",
|
|
28
|
+
"LLMProvider",
|
|
29
|
+
"ChatResponse",
|
|
30
|
+
"ToolCall",
|
|
31
|
+
"AnthropicProvider",
|
|
32
|
+
"OpenAIProvider",
|
|
33
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""ACP permission request adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Awaitable, Callable
|
|
8
|
+
|
|
9
|
+
from agent.approval import ApprovalDecision, ApprovalRequest
|
|
10
|
+
from agent.acp.update_adapter import _tool_kind
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
PermissionRequester = Callable[[str, dict[str, Any]], Awaitable[dict[str, Any]]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AcpApprovalAdapter:
|
|
17
|
+
"""Convert runtime approval callbacks into ACP permission requests."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
session_id: str,
|
|
22
|
+
requester: PermissionRequester,
|
|
23
|
+
*,
|
|
24
|
+
workdir: Path | None = None,
|
|
25
|
+
timeout_seconds: float | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.session_id = session_id
|
|
28
|
+
self.requester = requester
|
|
29
|
+
self.workdir = workdir
|
|
30
|
+
self.timeout_seconds = timeout_seconds
|
|
31
|
+
self._pending: set[asyncio.Task] = set()
|
|
32
|
+
|
|
33
|
+
async def callback(self, request: ApprovalRequest) -> bool:
|
|
34
|
+
"""Return True when the ACP client approves the requested action."""
|
|
35
|
+
return (await self.decide(request)).approved
|
|
36
|
+
|
|
37
|
+
async def decide(self, request: ApprovalRequest) -> ApprovalDecision:
|
|
38
|
+
"""Request a permission decision from the ACP client."""
|
|
39
|
+
task = asyncio.create_task(self.requester("session/request_permission", self.payload(request)))
|
|
40
|
+
self._pending.add(task)
|
|
41
|
+
try:
|
|
42
|
+
if self.timeout_seconds is None:
|
|
43
|
+
response = await task
|
|
44
|
+
else:
|
|
45
|
+
response = await asyncio.wait_for(task, timeout=self.timeout_seconds)
|
|
46
|
+
except asyncio.CancelledError:
|
|
47
|
+
return ApprovalDecision("cancelled")
|
|
48
|
+
except TimeoutError:
|
|
49
|
+
return ApprovalDecision("denied")
|
|
50
|
+
finally:
|
|
51
|
+
self._pending.discard(task)
|
|
52
|
+
option_id = _response_option_id(response)
|
|
53
|
+
if option_id in {"approve", "allow", "approved"}:
|
|
54
|
+
return ApprovalDecision("approved")
|
|
55
|
+
if option_id in {"cancel", "cancelled"}:
|
|
56
|
+
return ApprovalDecision("cancelled")
|
|
57
|
+
return ApprovalDecision("denied")
|
|
58
|
+
|
|
59
|
+
def cancel_pending(self) -> int:
|
|
60
|
+
"""Cancel pending permission requests."""
|
|
61
|
+
count = 0
|
|
62
|
+
for task in list(self._pending):
|
|
63
|
+
if not task.done():
|
|
64
|
+
task.cancel()
|
|
65
|
+
count += 1
|
|
66
|
+
self._pending.clear()
|
|
67
|
+
return count
|
|
68
|
+
|
|
69
|
+
def payload(self, request: ApprovalRequest) -> dict[str, Any]:
|
|
70
|
+
"""Build the ACP permission request payload."""
|
|
71
|
+
locations = []
|
|
72
|
+
for path in _split_paths(request.path):
|
|
73
|
+
location_path = str(path)
|
|
74
|
+
if self.workdir is not None and path and not path.startswith("/"):
|
|
75
|
+
location_path = str((self.workdir / path).resolve())
|
|
76
|
+
locations.append({"path": location_path})
|
|
77
|
+
return {
|
|
78
|
+
"sessionId": self.session_id,
|
|
79
|
+
"toolCall": {
|
|
80
|
+
"title": _permission_title(request),
|
|
81
|
+
"kind": _tool_kind(request.tool_name),
|
|
82
|
+
"status": "waiting_for_user",
|
|
83
|
+
"locations": locations,
|
|
84
|
+
"rawInput": {
|
|
85
|
+
"action": request.action,
|
|
86
|
+
"toolName": request.tool_name,
|
|
87
|
+
"path": request.path,
|
|
88
|
+
"command": request.command,
|
|
89
|
+
"reason": request.reason,
|
|
90
|
+
"risk": request.risk,
|
|
91
|
+
"diffPreview": request.diff_preview,
|
|
92
|
+
},
|
|
93
|
+
"content": [
|
|
94
|
+
{
|
|
95
|
+
"type": "text",
|
|
96
|
+
"text": request.format(include_diff=bool(request.diff_preview)),
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
"options": [
|
|
101
|
+
{"optionId": "approve", "name": "Approve", "kind": "allow"},
|
|
102
|
+
{"optionId": "deny", "name": "Deny", "kind": "reject"},
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _permission_title(request: ApprovalRequest) -> str:
|
|
108
|
+
if request.action == "edit_file":
|
|
109
|
+
return "Approve file edit"
|
|
110
|
+
if request.action == "create_file":
|
|
111
|
+
return "Approve file creation"
|
|
112
|
+
if request.action == "run_command":
|
|
113
|
+
return "Approve command"
|
|
114
|
+
return "Approve action"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _split_paths(path: str) -> list[str]:
|
|
118
|
+
return [item.strip() for item in path.split(",") if item.strip()]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _response_option_id(response: Any) -> str:
|
|
122
|
+
if isinstance(response, str):
|
|
123
|
+
return response
|
|
124
|
+
if not isinstance(response, dict):
|
|
125
|
+
return ""
|
|
126
|
+
for key in ("optionId", "option_id", "decision", "status"):
|
|
127
|
+
if response.get(key):
|
|
128
|
+
return str(response[key])
|
|
129
|
+
if response.get("approved") is True:
|
|
130
|
+
return "approve"
|
|
131
|
+
if response.get("approved") is False:
|
|
132
|
+
return "deny"
|
|
133
|
+
return ""
|
|
134
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""ACP prompt content conversion helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
MAX_EMBEDDED_TEXT_CHARS = 20_000
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def content_blocks_to_text(content: Any) -> str:
|
|
12
|
+
"""Convert ACP content blocks or plain text into a yoyoagent prompt string."""
|
|
13
|
+
if isinstance(content, str):
|
|
14
|
+
return content
|
|
15
|
+
if isinstance(content, dict):
|
|
16
|
+
return _block_to_text(content)
|
|
17
|
+
if not isinstance(content, list):
|
|
18
|
+
return str(content or "")
|
|
19
|
+
parts = [_block_to_text(block) for block in content]
|
|
20
|
+
return "\n\n".join(part for part in parts if part).strip()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _block_to_text(block: Any) -> str:
|
|
24
|
+
if isinstance(block, str):
|
|
25
|
+
return block
|
|
26
|
+
if not isinstance(block, dict):
|
|
27
|
+
return str(block)
|
|
28
|
+
block_type = str(block.get("type") or block.get("kind") or "text")
|
|
29
|
+
if block_type in {"text", "markdown"}:
|
|
30
|
+
return str(block.get("text") or block.get("content") or "")
|
|
31
|
+
if block_type in {"resource_link", "resource", "uri"}:
|
|
32
|
+
uri = block.get("uri") or block.get("url") or block.get("path") or ""
|
|
33
|
+
name = block.get("name") or block.get("title") or "resource"
|
|
34
|
+
return f"Context resource: {name}\n{uri}".strip()
|
|
35
|
+
if block_type in {"embedded_resource", "embedded"}:
|
|
36
|
+
uri = block.get("uri") or block.get("url") or block.get("path") or ""
|
|
37
|
+
text = str(block.get("text") or block.get("content") or block.get("data") or "")
|
|
38
|
+
if len(text) > MAX_EMBEDDED_TEXT_CHARS:
|
|
39
|
+
text = text[:MAX_EMBEDDED_TEXT_CHARS] + "\n... embedded resource truncated"
|
|
40
|
+
header = f"Embedded context resource: {uri}" if uri else "Embedded context resource:"
|
|
41
|
+
return f"{header}\n```\n{text}\n```"
|
|
42
|
+
if block_type in {"image", "audio"}:
|
|
43
|
+
return f"[Unsupported ACP {block_type} content omitted]"
|
|
44
|
+
return str(block.get("text") or block.get("content") or block)
|
|
45
|
+
|