yycode 0.3.2__tar.gz → 0.3.4__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.
Files changed (175) hide show
  1. {yycode-0.3.2 → yycode-0.3.4}/PKG-INFO +2 -2
  2. {yycode-0.3.2 → yycode-0.3.4}/README.md +40 -6
  3. yycode-0.3.4/agent/app_paths.py +113 -0
  4. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/workflow_guard.py +0 -11
  5. {yycode-0.3.2 → yycode-0.3.4}/agent/session.py +9 -2
  6. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/renderers.py +8 -14
  7. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/state.py +1 -1
  8. yycode-0.3.4/agent/tui/styles.tcss +280 -0
  9. {yycode-0.3.2 → yycode-0.3.4}/main.py +38 -5
  10. {yycode-0.3.2 → yycode-0.3.4}/pyproject.toml +7 -3
  11. yycode-0.3.4/skills/drawio/styles/built-in/corporate.json +49 -0
  12. yycode-0.3.4/skills/drawio/styles/built-in/default.json +49 -0
  13. yycode-0.3.4/skills/drawio/styles/built-in/handdrawn.json +49 -0
  14. {yycode-0.3.2 → yycode-0.3.4}/tests/test_main_input.py +40 -4
  15. yycode-0.3.4/tests/test_packaging.py +40 -0
  16. {yycode-0.3.2 → yycode-0.3.4}/tests/test_skills.py +103 -10
  17. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tool_concurrency.py +41 -0
  18. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tui_state.py +9 -8
  19. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/PKG-INFO +2 -2
  20. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/SOURCES.txt +5 -0
  21. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/requires.txt +1 -1
  22. yycode-0.3.2/agent/app_paths.py +0 -25
  23. {yycode-0.3.2 → yycode-0.3.4}/agent/__init__.py +0 -0
  24. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/__init__.py +0 -0
  25. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/approval_adapter.py +0 -0
  26. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/content_adapter.py +0 -0
  27. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/jsonrpc.py +0 -0
  28. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/server.py +0 -0
  29. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/session_manager.py +0 -0
  30. {yycode-0.3.2 → yycode-0.3.4}/agent/acp/update_adapter.py +0 -0
  31. {yycode-0.3.2 → yycode-0.3.4}/agent/approval.py +0 -0
  32. {yycode-0.3.2 → yycode-0.3.4}/agent/cancellation.py +0 -0
  33. {yycode-0.3.2 → yycode-0.3.4}/agent/change_snapshot.py +0 -0
  34. {yycode-0.3.2 → yycode-0.3.4}/agent/context_compressor.py +0 -0
  35. {yycode-0.3.2 → yycode-0.3.4}/agent/graph.py +0 -0
  36. {yycode-0.3.2 → yycode-0.3.4}/agent/llm_retry.py +0 -0
  37. {yycode-0.3.2 → yycode-0.3.4}/agent/logger.py +0 -0
  38. {yycode-0.3.2 → yycode-0.3.4}/agent/lsp/__init__.py +0 -0
  39. {yycode-0.3.2 → yycode-0.3.4}/agent/lsp/client.py +0 -0
  40. {yycode-0.3.2 → yycode-0.3.4}/agent/lsp/manager.py +0 -0
  41. {yycode-0.3.2 → yycode-0.3.4}/agent/lsp/types.py +0 -0
  42. {yycode-0.3.2 → yycode-0.3.4}/agent/message_context_manager.py +0 -0
  43. {yycode-0.3.2 → yycode-0.3.4}/agent/message_format.py +0 -0
  44. {yycode-0.3.2 → yycode-0.3.4}/agent/nodes/llm_node.py +0 -0
  45. {yycode-0.3.2 → yycode-0.3.4}/agent/nodes/state.py +0 -0
  46. {yycode-0.3.2 → yycode-0.3.4}/agent/nodes/task_guard_node.py +0 -0
  47. {yycode-0.3.2 → yycode-0.3.4}/agent/nodes/tools_node.py +0 -0
  48. {yycode-0.3.2 → yycode-0.3.4}/agent/plan_snapshot.py +0 -0
  49. {yycode-0.3.2 → yycode-0.3.4}/agent/providers/__init__.py +0 -0
  50. {yycode-0.3.2 → yycode-0.3.4}/agent/providers/anthropic_provider.py +0 -0
  51. {yycode-0.3.2 → yycode-0.3.4}/agent/providers/base.py +0 -0
  52. {yycode-0.3.2 → yycode-0.3.4}/agent/providers/openai_provider.py +0 -0
  53. {yycode-0.3.2 → yycode-0.3.4}/agent/providers/text_tool_calls.py +0 -0
  54. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/approval_service.py +0 -0
  55. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/context.py +0 -0
  56. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/tool_events.py +0 -0
  57. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/tool_executor.py +0 -0
  58. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/tool_output.py +0 -0
  59. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/tool_registry.py +0 -0
  60. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/tool_scheduler.py +0 -0
  61. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/workspace.py +0 -0
  62. {yycode-0.3.2 → yycode-0.3.4}/agent/runtime/workspace_tools.py +0 -0
  63. {yycode-0.3.2 → yycode-0.3.4}/agent/session_replay.py +0 -0
  64. {yycode-0.3.2 → yycode-0.3.4}/agent/session_store.py +0 -0
  65. {yycode-0.3.2 → yycode-0.3.4}/agent/skills.py +0 -0
  66. {yycode-0.3.2 → yycode-0.3.4}/agent/streaming.py +0 -0
  67. {yycode-0.3.2 → yycode-0.3.4}/agent/subagent.py +0 -0
  68. {yycode-0.3.2 → yycode-0.3.4}/agent/task_memory.py +0 -0
  69. {yycode-0.3.2 → yycode-0.3.4}/agent/todo_manager.py +0 -0
  70. {yycode-0.3.2 → yycode-0.3.4}/agent/tool_retry.py +0 -0
  71. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/__init__.py +0 -0
  72. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/app.py +0 -0
  73. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/approval.py +0 -0
  74. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/commands/__init__.py +0 -0
  75. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/commands/base.py +0 -0
  76. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/commands/clear.py +0 -0
  77. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/commands/help.py +0 -0
  78. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/commands/registry.py +0 -0
  79. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/help_content.py +0 -0
  80. {yycode-0.3.2 → yycode-0.3.4}/agent/tui/runner.py +0 -0
  81. {yycode-0.3.2 → yycode-0.3.4}/setup.cfg +0 -0
  82. {yycode-0.3.2 → yycode-0.3.4}/skills/code_review.md +0 -0
  83. {yycode-0.3.2 → yycode-0.3.4}/skills/code_workflow.md +0 -0
  84. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/SKILL.md +0 -0
  85. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/agents/openai.yaml +0 -0
  86. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-erd.drawio +0 -0
  87. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-layered-cn.drawio +0 -0
  88. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-layered-cn.png +0 -0
  89. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-layered.drawio +0 -0
  90. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-layered.png +0 -0
  91. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-ml.drawio +0 -0
  92. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-ring-cn.drawio +0 -0
  93. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-ring-cn.png +0 -0
  94. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-ring.drawio +0 -0
  95. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-ring.png +0 -0
  96. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-sequence.drawio +0 -0
  97. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-star-cn.drawio +0 -0
  98. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-star-cn.png +0 -0
  99. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-star.drawio +0 -0
  100. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-star.png +0 -0
  101. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/demo-uml-class.drawio +0 -0
  102. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/microservices-example.drawio +0 -0
  103. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/microservices-example.png +0 -0
  104. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/workflow-cn.drawio +0 -0
  105. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/workflow-cn.png +0 -0
  106. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/workflow.drawio +0 -0
  107. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/assets/workflow.png +0 -0
  108. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/docs/index.html +0 -0
  109. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/docs/zh.html +0 -0
  110. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/references/style-extraction.md +0 -0
  111. {yycode-0.3.2 → yycode-0.3.4}/skills/drawio/styles/schema.json +0 -0
  112. {yycode-0.3.2 → yycode-0.3.4}/skills/plan.md +0 -0
  113. {yycode-0.3.2 → yycode-0.3.4}/skills/ppt/SKILL.md +0 -0
  114. {yycode-0.3.2 → yycode-0.3.4}/tests/test_acp.py +0 -0
  115. {yycode-0.3.2 → yycode-0.3.4}/tests/test_anthropic_provider.py +0 -0
  116. {yycode-0.3.2 → yycode-0.3.4}/tests/test_apply_patch_tool.py +0 -0
  117. {yycode-0.3.2 → yycode-0.3.4}/tests/test_code_navigation_tools.py +0 -0
  118. {yycode-0.3.2 → yycode-0.3.4}/tests/test_compatibility_primitives.py +0 -0
  119. {yycode-0.3.2 → yycode-0.3.4}/tests/test_grep_tool.py +0 -0
  120. {yycode-0.3.2 → yycode-0.3.4}/tests/test_llm_retry.py +0 -0
  121. {yycode-0.3.2 → yycode-0.3.4}/tests/test_lsp_tools.py +0 -0
  122. {yycode-0.3.2 → yycode-0.3.4}/tests/test_message_context_manager.py +0 -0
  123. {yycode-0.3.2 → yycode-0.3.4}/tests/test_message_format.py +0 -0
  124. {yycode-0.3.2 → yycode-0.3.4}/tests/test_openai_provider_reasoning.py +0 -0
  125. {yycode-0.3.2 → yycode-0.3.4}/tests/test_safety.py +0 -0
  126. {yycode-0.3.2 → yycode-0.3.4}/tests/test_session_store.py +0 -0
  127. {yycode-0.3.2 → yycode-0.3.4}/tests/test_streaming.py +0 -0
  128. {yycode-0.3.2 → yycode-0.3.4}/tests/test_streaming_events.py +0 -0
  129. {yycode-0.3.2 → yycode-0.3.4}/tests/test_subagent.py +0 -0
  130. {yycode-0.3.2 → yycode-0.3.4}/tests/test_task_guard.py +0 -0
  131. {yycode-0.3.2 → yycode-0.3.4}/tests/test_task_memory.py +0 -0
  132. {yycode-0.3.2 → yycode-0.3.4}/tests/test_task_state.py +0 -0
  133. {yycode-0.3.2 → yycode-0.3.4}/tests/test_text_tool_calls.py +0 -0
  134. {yycode-0.3.2 → yycode-0.3.4}/tests/test_token_counting.py +0 -0
  135. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tool_metadata.py +0 -0
  136. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tui_approval.py +0 -0
  137. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tui_commands.py +0 -0
  138. {yycode-0.3.2 → yycode-0.3.4}/tests/test_tui_runner.py +0 -0
  139. {yycode-0.3.2 → yycode-0.3.4}/tests/test_verify_tool.py +0 -0
  140. {yycode-0.3.2 → yycode-0.3.4}/tests/test_web_search_tool.py +0 -0
  141. {yycode-0.3.2 → yycode-0.3.4}/tests/test_workspace_tools.py +0 -0
  142. {yycode-0.3.2 → yycode-0.3.4}/tests/test_write_tools_diff.py +0 -0
  143. {yycode-0.3.2 → yycode-0.3.4}/tools/__init__.py +0 -0
  144. {yycode-0.3.2 → yycode-0.3.4}/tools/apply_patch.py +0 -0
  145. {yycode-0.3.2 → yycode-0.3.4}/tools/bash.py +0 -0
  146. {yycode-0.3.2 → yycode-0.3.4}/tools/diff_utils.py +0 -0
  147. {yycode-0.3.2 → yycode-0.3.4}/tools/edit_file.py +0 -0
  148. {yycode-0.3.2 → yycode-0.3.4}/tools/git_diff.py +0 -0
  149. {yycode-0.3.2 → yycode-0.3.4}/tools/git_show.py +0 -0
  150. {yycode-0.3.2 → yycode-0.3.4}/tools/grep.py +0 -0
  151. {yycode-0.3.2 → yycode-0.3.4}/tools/list_files.py +0 -0
  152. {yycode-0.3.2 → yycode-0.3.4}/tools/list_skills.py +0 -0
  153. {yycode-0.3.2 → yycode-0.3.4}/tools/load_skill.py +0 -0
  154. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_definition.py +0 -0
  155. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_diagnostics.py +0 -0
  156. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_document_symbols.py +0 -0
  157. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_hover.py +0 -0
  158. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_references.py +0 -0
  159. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_utils.py +0 -0
  160. {yycode-0.3.2 → yycode-0.3.4}/tools/lsp_workspace_symbols.py +0 -0
  161. {yycode-0.3.2 → yycode-0.3.4}/tools/read_file.py +0 -0
  162. {yycode-0.3.2 → yycode-0.3.4}/tools/read_many_files.py +0 -0
  163. {yycode-0.3.2 → yycode-0.3.4}/tools/safety.py +0 -0
  164. {yycode-0.3.2 → yycode-0.3.4}/tools/subagent.py +0 -0
  165. {yycode-0.3.2 → yycode-0.3.4}/tools/todo.py +0 -0
  166. {yycode-0.3.2 → yycode-0.3.4}/tools/verify.py +0 -0
  167. {yycode-0.3.2 → yycode-0.3.4}/tools/web_search.py +0 -0
  168. {yycode-0.3.2 → yycode-0.3.4}/tools/workspace.py +0 -0
  169. {yycode-0.3.2 → yycode-0.3.4}/tools/workspace_state.py +0 -0
  170. {yycode-0.3.2 → yycode-0.3.4}/tools/write_file.py +0 -0
  171. {yycode-0.3.2 → yycode-0.3.4}/utils/__init__.py +0 -0
  172. {yycode-0.3.2 → yycode-0.3.4}/utils/retry.py +0 -0
  173. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/dependency_links.txt +0 -0
  174. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/entry_points.txt +0 -0
  175. {yycode-0.3.2 → yycode-0.3.4}/yycode.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yycode
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Terminal coding agent with TUI, plain input mode, tools, skills, and ACP support
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: anthropic>=0.40.0
@@ -9,4 +9,4 @@ Requires-Dist: tiktoken>=0.12.0
9
9
  Requires-Dist: langgraph>=0.2.0
10
10
  Requires-Dist: langchain-core>=0.3.0
11
11
  Requires-Dist: python-dotenv>=1.0.0
12
- Requires-Dist: textual>=0.80.0
12
+ Requires-Dist: textual==8.2.4
@@ -34,10 +34,23 @@ uv --version
34
34
 
35
35
  ### 2. 安装 yycode 命令
36
36
 
37
- 如果只是使用 yycode,推荐直接用 `uv tool install` 安装命令:
37
+ yycode 已发布到 PyPI:
38
+
39
+ ```text
40
+ https://pypi.org/project/yycode/
41
+ ```
42
+
43
+ 如果只是使用 yycode,推荐直接从 PyPI 安装命令:
44
+
45
+ ```bash
46
+ uv tool install yycode
47
+ yycode --help
48
+ ```
49
+
50
+ 也可以使用 pip 安装:
38
51
 
39
52
  ```bash
40
- uv tool install git+https://github.com/on-my-yycode/yycode.git
53
+ pip install yycode
41
54
  yycode --help
42
55
  ```
43
56
 
@@ -56,7 +69,7 @@ yycode --acp
56
69
  uv tool upgrade yycode
57
70
  ```
58
71
 
59
- 如果是从 GitHub 分支安装且需要强制刷新:
72
+ 如果要安装 GitHub 分支上的预览版本,或者需要强制刷新本地工具环境:
60
73
 
61
74
  ```bash
62
75
  uv tool install --force git+https://github.com/on-my-yycode/yycode.git
@@ -142,9 +155,11 @@ uv run python main.py -s # 查看当前工作区可恢复的 sessio
142
155
  uv run python main.py -r <session-id> # 恢复指定 session
143
156
  uv run python main.py -x <session-id> # 删除指定 session
144
157
  uv run python main.py -t # 临时会话,不保存 messages
158
+ uv run python main.py --update-skills # 用内置 skills 覆盖同步用户数据目录中的同路径文件
145
159
  uv run python main.py --acp # 启动 ACP stdio server
146
160
  uv run python main.py acp # 同上,便于作为子命令使用
147
161
  yycode --plain # 安装发行包后的普通终端输入模式
162
+ yycode --update-skills # 更新用户数据目录中的默认 skills,保留用户自定义文件
148
163
  yycode --acp # 安装发行包后的 ACP stdio server
149
164
  ```
150
165
 
@@ -167,6 +182,23 @@ git add pyproject.toml uv.lock
167
182
  git commit -m "Bump version to 0.4.0"
168
183
  git tag v0.4.0
169
184
  git push origin dev master --tags
185
+
186
+ # 构建并发布到 PyPI
187
+ uv build
188
+ uv publish --token <your-pypi-token>
189
+ ```
190
+
191
+ 仓库也提供 GitHub Actions 自动发布流程:`.github/workflows/publish-pypi.yml`。在 GitHub 仓库设置中添加 secret:
192
+
193
+ ```text
194
+ PYPI_API_TOKEN=<your-pypi-token>
195
+ ```
196
+
197
+ 之后每次提交到 `master` 都会自动执行:
198
+
199
+ ```bash
200
+ uv build
201
+ uv publish
170
202
  ```
171
203
 
172
204
  用户升级:
@@ -181,6 +213,8 @@ uv tool upgrade yycode
181
213
  uv tool install --force git+https://github.com/on-my-yycode/yycode.git
182
214
  ```
183
215
 
216
+ PyPI token 只应通过本地命令、CI secret 或凭据管理器使用,不要提交到仓库、文档或聊天记录中。如果 token 泄露,应立即在 PyPI 后台撤销并重新生成。
217
+
184
218
  ## 功能特性
185
219
 
186
220
  - 🖥️ **TUI 终端界面** - 基于 Textual 的现代终端 UI,支持紧凑 Transcript 风格时间线、工具活动摘要、审批弹窗和历史浏览
@@ -225,10 +259,10 @@ uv tool install --force git+https://github.com/on-my-yycode/yycode.git
225
259
  | `API_BASE` | 可选,自定义 API Base/Base URL | `https://api.openai.com/v1` |
226
260
  | `AI_MODEL` | 模型名称 | Anthropic 默认 `claude-3-5-sonnet-20241022`,OpenAI 默认 `gpt-4o` |
227
261
  | `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` |
262
+ | `YOYO_APP_ROOT` | 可选,覆盖 yycode 内置资源来源目录;通常不需要设置 | `/path/to/yycode-install` |
263
+ | `YOYO_RUNTIME_DATA_DIR` | 可选,覆盖用户数据目录;默认使用系统用户数据目录 | macOS: `~/Library/Application Support/yycode` |
230
264
  | `YOYO_SESSION_DIR` | 可选,覆盖 session messages 保存目录 | `~/.yoyoagent/sessions` |
231
- | `YOYO_SKILL_DIRS` | 可选,额外技能目录,多个目录用逗号分隔;默认技能目录是 `{app_root}/skills` | `../shared-skills` |
265
+ | `YOYO_SKILL_DIRS` | 可选,额外技能目录,多个目录用逗号分隔;默认技能目录是用户数据目录下的 `skills` | `../shared-skills` |
232
266
  | `YOYO_SILENT` / `YOYO_AUTO_APPROVE` | 可选,启用后自动批准高风险操作 | `true` |
233
267
 
234
268
  高级重试配置:
@@ -0,0 +1,113 @@
1
+ """Application-level path helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import NamedTuple
10
+
11
+
12
+ def resolve_app_root(raw_app_root: str | Path | None = None) -> Path:
13
+ """Resolve the yoyoagent application root directory."""
14
+ raw = raw_app_root or os.environ.get("YOYO_APP_ROOT")
15
+ if raw:
16
+ return Path(raw).expanduser().resolve()
17
+ return Path(__file__).resolve().parents[1]
18
+
19
+
20
+ def resolve_resource_root(raw_app_root: str | Path | None = None) -> Path:
21
+ """Resolve the bundled yycode resource root for default files."""
22
+ raw = raw_app_root or os.environ.get("YOYO_APP_ROOT")
23
+ if raw:
24
+ return Path(raw).expanduser().resolve()
25
+
26
+ source_root = Path(__file__).resolve().parents[1]
27
+ if (source_root / "skills").is_dir():
28
+ return source_root
29
+
30
+ prefix_root = Path(sys.prefix).resolve()
31
+ if (prefix_root / "skills").is_dir():
32
+ return prefix_root
33
+
34
+ return source_root
35
+
36
+
37
+ def resolve_runtime_data_dir(
38
+ app_root: Path,
39
+ raw_runtime_data_dir: str | Path | None = None,
40
+ ) -> Path:
41
+ """Resolve the user-editable runtime data directory for yycode-owned data."""
42
+ raw = raw_runtime_data_dir or os.environ.get("YOYO_RUNTIME_DATA_DIR")
43
+ if raw:
44
+ return Path(raw).expanduser().resolve()
45
+ return default_user_data_dir()
46
+
47
+
48
+ def default_user_data_dir() -> Path:
49
+ """Return the default per-user yycode data directory."""
50
+ if sys.platform == "darwin":
51
+ return (Path.home() / "Library" / "Application Support" / "yycode").resolve()
52
+ if os.name == "nt":
53
+ base = os.environ.get("APPDATA")
54
+ if base:
55
+ return (Path(base).expanduser() / "yycode").resolve()
56
+ return (Path.home() / "AppData" / "Roaming" / "yycode").resolve()
57
+ base = os.environ.get("XDG_DATA_HOME")
58
+ if base:
59
+ return (Path(base).expanduser() / "yycode").resolve()
60
+ return (Path.home() / ".local" / "share" / "yycode").resolve()
61
+
62
+
63
+ def ensure_default_skills_dir(runtime_data_dir: Path, resource_root: Path) -> Path:
64
+ """Initialize the user-editable skills directory from bundled defaults if needed."""
65
+ skills_dir = runtime_data_dir / "skills"
66
+ if skills_dir.exists():
67
+ return skills_dir
68
+
69
+ bundled_skills = resource_root / "skills"
70
+ if bundled_skills.is_dir():
71
+ skills_dir.parent.mkdir(parents=True, exist_ok=True)
72
+ shutil.copytree(bundled_skills, skills_dir)
73
+ else:
74
+ skills_dir.mkdir(parents=True, exist_ok=True)
75
+ return skills_dir
76
+
77
+
78
+ class SkillSyncResult(NamedTuple):
79
+ """Summary of a bundled skill sync into the user data directory."""
80
+
81
+ source_dir: Path
82
+ target_dir: Path
83
+ copied: list[Path]
84
+ updated: list[Path]
85
+ skipped: list[Path]
86
+
87
+
88
+ def sync_default_skills_dir(runtime_data_dir: Path, resource_root: Path) -> SkillSyncResult:
89
+ """Copy bundled skills into user data, overwriting matching bundled files only."""
90
+ source_dir = resource_root / "skills"
91
+ target_dir = runtime_data_dir / "skills"
92
+ copied: list[Path] = []
93
+ updated: list[Path] = []
94
+ skipped: list[Path] = []
95
+
96
+ if not source_dir.is_dir():
97
+ target_dir.mkdir(parents=True, exist_ok=True)
98
+ return SkillSyncResult(source_dir, target_dir, copied, updated, skipped)
99
+
100
+ for source_path in sorted(path for path in source_dir.rglob("*") if path.is_file()):
101
+ relative_path = source_path.relative_to(source_dir)
102
+ target_path = target_dir / relative_path
103
+ target_path.parent.mkdir(parents=True, exist_ok=True)
104
+ if target_path.exists():
105
+ if target_path.read_bytes() == source_path.read_bytes():
106
+ skipped.append(relative_path)
107
+ continue
108
+ updated.append(relative_path)
109
+ else:
110
+ copied.append(relative_path)
111
+ shutil.copy2(source_path, target_path)
112
+
113
+ return SkillSyncResult(source_dir, target_dir, copied, updated, skipped)
@@ -171,17 +171,6 @@ class WorkflowGuard:
171
171
  def after_batch_messages(self, tool_calls_data: list) -> list[HumanMessage]:
172
172
  """Return extra HumanMessages to append after a tools batch."""
173
173
  additional_messages = []
174
- todo_manager = self.runtime.todo_manager
175
- if todo_manager.needs_reminder():
176
- additional_messages.append(
177
- HumanMessage(
178
- content=todo_manager.consume_reminder_message(),
179
- additional_kwargs={
180
- "context_ephemeral": True,
181
- "ephemeral_kind": "task_reminder",
182
- },
183
- )
184
- )
185
174
  if self.state.needs_verify and not any(tc.name == "verify" for tc in tool_calls_data):
186
175
  additional_messages.append(
187
176
  HumanMessage(
@@ -14,7 +14,12 @@ from langchain_core.messages import (
14
14
  )
15
15
 
16
16
  from agent.approval import ApprovalCallback, ApprovalDenied
17
- from agent.app_paths import resolve_app_root, resolve_runtime_data_dir
17
+ from agent.app_paths import (
18
+ ensure_default_skills_dir,
19
+ resolve_app_root,
20
+ resolve_resource_root,
21
+ resolve_runtime_data_dir,
22
+ )
18
23
  from agent.session_replay import ReplayEvent, build_session_replay
19
24
  from agent.graph import build_graph
20
25
  from agent.llm_retry import LLMCallError
@@ -65,7 +70,9 @@ class Session:
65
70
  self.provider = provider
66
71
  self.workdir = (workdir or Path.cwd()).expanduser().resolve()
67
72
  self.app_root = resolve_app_root(app_root)
73
+ self.resource_root = resolve_resource_root(app_root)
68
74
  self.runtime_data_dir = resolve_runtime_data_dir(self.app_root, runtime_data_dir)
75
+ self.default_skill_dir = ensure_default_skills_dir(self.runtime_data_dir, self.resource_root)
69
76
  self.skill_dirs = self._resolve_skill_dirs(skill_dirs)
70
77
  self.skill_registry = SkillRegistry(self.workdir, self.skill_dirs)
71
78
  self.skill_catalog_prompt = self.skill_registry.format_skill_catalog_prompt()
@@ -114,7 +121,7 @@ class Session:
114
121
  self.task_summary_memory_builder = TaskSummaryMemoryBuilder()
115
122
 
116
123
  def _resolve_skill_dirs(self, skill_dirs: Optional[Iterable[str]]) -> list[str]:
117
- default_dir = str(self.app_root / "skills")
124
+ default_dir = str(self.default_skill_dir)
118
125
  if skill_dirs is None:
119
126
  return [default_dir]
120
127
  return [default_dir, *[str(path) for path in skill_dirs]]
@@ -423,9 +423,9 @@ def render_brand_text(state: TuiState | None = None, width: int = 100) -> str:
423
423
  """Render the compact app brand block."""
424
424
  W = max(72, min(width, 180))
425
425
  brand_line = (
426
- "[bold #c9a6ff]YOYOAGENT[/] "
426
+ "[bold #c9a6ff]YYCode[/] "
427
427
  "[#7f8794]code assistant[/] "
428
- "[#3f4652]" + ("─" * max(4, W - 29)) + "[/]"
428
+ "[#3f4652]" + ("─" * max(4, W - 26)) + "[/]"
429
429
  )
430
430
  if state is None:
431
431
  return brand_line
@@ -1275,7 +1275,7 @@ def _render_timeline_item(item: TimelineItem, state: TuiState | None = None, *,
1275
1275
  if item.event_type == "context_compressed":
1276
1276
  return f"{role_prefix}[#7f8794][context] {_safe_text(item.content)}[/]"
1277
1277
  if item.event_type == "context_summarized":
1278
- return f"{role_prefix}[#8fd6a3][context] {_safe_text(item.content)}[/]"
1278
+ return f"{role_prefix}[#7f8794][context] {_safe_text(item.content)}[/]"
1279
1279
  if item.event_type == "llm_waiting":
1280
1280
  return _render_llm_waiting_item(item, role_prefix, state)
1281
1281
  if item.event_type == "llm_timeout":
@@ -1598,19 +1598,13 @@ def _is_task_state_result(item: TimelineItem) -> bool:
1598
1598
 
1599
1599
  def _render_task_state_summary(role_prefix: str, item: TimelineItem) -> str:
1600
1600
  counts = _task_state_counts(item.content)
1601
- summary = "Task plan"
1601
+ parts = [f"{role_prefix}[bold #8fd6a3]● Plan Progress[/]"]
1602
1602
  if counts["total"]:
1603
- summary = f"{counts['completed']}/{counts['total']} done"
1604
- if counts["active"]:
1605
- summary += " · current"
1606
- lines = [
1607
- f"{role_prefix}[bold #8fd6a3]● Task Plan[/]",
1608
- f" [#7f8794]{_safe_text(summary)}[/]",
1609
- ]
1603
+ parts.append(f"[#7f8794]{counts['completed']}/{counts['total']} done[/]")
1610
1604
  if counts["active"]:
1611
- lines.append(f" [#d7dae0]{_safe_text(counts['active'])}[/]")
1612
- lines.append(" [#7f8794]Ctrl+T full plan[/]")
1613
- return "\n".join(lines)
1605
+ parts.append(f"[#d7dae0]{_safe_text(counts['active'])}[/]")
1606
+ parts.append("[#7f8794][Ctrl+T][/]")
1607
+ return " [#7f8794]·[/] ".join(parts)
1614
1608
 
1615
1609
 
1616
1610
  def _task_state_counts(content: str) -> dict[str, int | str]:
@@ -385,7 +385,7 @@ class TuiState:
385
385
  continue
386
386
  if item.event_type == "llm_waiting" and item.status in {"running", "retrying", "timeout", None}:
387
387
  item.status = "completed"
388
- item.title = "Model response started"
388
+ item.title = "Thinking"
389
389
  item.invalidate_render_cache()
390
390
  return
391
391
  if item.event_type in {"text_delta", "tool_start", "tool_result", "tool_end", "user_message"}:
@@ -0,0 +1,280 @@
1
+ Screen {
2
+ layout: vertical;
3
+ background: #101216;
4
+ color: #d7dae0;
5
+ }
6
+
7
+ #root-layout {
8
+ height: 1fr;
9
+ width: 1fr;
10
+ margin: 0 0;
11
+ padding: 1 2;
12
+ background: #101216;
13
+ }
14
+
15
+ #top-panel {
16
+ height: 3;
17
+ padding: 0 0;
18
+ color: #e6e8ee;
19
+ background: #101216;
20
+ margin-bottom: 1;
21
+ }
22
+
23
+ #timeline-panel {
24
+ height: 1fr;
25
+ padding: 0 0;
26
+ background: #101216;
27
+ color: #d7dae0;
28
+ margin-bottom: 1;
29
+ border: none;
30
+ }
31
+
32
+ #timeline-panel.selectable {
33
+ background: #101216;
34
+ }
35
+
36
+ #timeline-panel.selectable:focus {
37
+ background: #121419;
38
+ }
39
+
40
+ #skill-completion {
41
+ display: none;
42
+ height: auto;
43
+ max-height: 10;
44
+ margin-bottom: 1;
45
+ padding: 1 2;
46
+ border: round #3f4652;
47
+ background: #14171d;
48
+ color: #d7dae0;
49
+ }
50
+
51
+ #input-shell {
52
+ height: 8;
53
+ background: #101216;
54
+ margin-top: 1;
55
+ }
56
+
57
+ #input-shell.approving {
58
+ height: 6;
59
+ background: #101216;
60
+ }
61
+
62
+ #input-top-rule,
63
+ #input-bottom-rule {
64
+ height: 1;
65
+ color: #3f4652;
66
+ background: #101216;
67
+ }
68
+
69
+ #input-shell.approving #input-top-rule,
70
+ #input-shell.approving #input-bottom-rule {
71
+ color: #d7ba7d;
72
+ }
73
+
74
+ #approval-inline {
75
+ display: none;
76
+ height: 5;
77
+ padding: 0 0;
78
+ background: transparent;
79
+ }
80
+
81
+ #input-status-bar {
82
+ height: 1;
83
+ color: #d7dae0;
84
+ background: #101216;
85
+ padding: 0 0;
86
+ }
87
+
88
+ #input-row {
89
+ height: 5;
90
+ background: #101216;
91
+ }
92
+
93
+ #input-prompt {
94
+ height: 5;
95
+ color: #c9a6ff;
96
+ padding: 0 0;
97
+ width: 3;
98
+ background: #101216;
99
+ content-align: left top;
100
+ }
101
+
102
+ #prompt-input {
103
+ height: 5;
104
+ border: none;
105
+ background: #101216;
106
+ color: #e6e8ee;
107
+ padding: 0 0;
108
+ width: 1fr;
109
+ }
110
+
111
+ #help-dialog {
112
+ width: 1fr;
113
+ height: 1fr;
114
+ margin: 1 2;
115
+ padding: 1 2;
116
+ border: round #3f4652;
117
+ background: #101216;
118
+ }
119
+
120
+ #help-body {
121
+ width: 1fr;
122
+ height: 1fr;
123
+ color: #d7dae0;
124
+ background: #101216;
125
+ }
126
+
127
+ #task-plan-dialog {
128
+ width: 1fr;
129
+ height: 1fr;
130
+ margin: 1 2;
131
+ padding: 1 2;
132
+ border: round #3f4652;
133
+ background: #101216;
134
+ }
135
+
136
+ #task-plan-body {
137
+ width: 1fr;
138
+ height: 1fr;
139
+ color: #d7dae0;
140
+ background: #101216;
141
+ }
142
+
143
+ #timeline-text-dialog {
144
+ width: 1fr;
145
+ height: 1fr;
146
+ margin: 1 2;
147
+ padding: 1 2;
148
+ border: round #3f4652;
149
+ background: #101216;
150
+ }
151
+
152
+ #timeline-text-header {
153
+ width: 1fr;
154
+ height: 1;
155
+ color: #7f8794;
156
+ background: #101216;
157
+ margin-bottom: 1;
158
+ }
159
+
160
+ #timeline-text-body {
161
+ width: 1fr;
162
+ height: 1fr;
163
+ color: #d7dae0;
164
+ background: #101216;
165
+ scrollbar-size: 1 1;
166
+ }
167
+
168
+ #changed-files-dialog {
169
+ width: 1fr;
170
+ height: 1fr;
171
+ margin: 1 2;
172
+ padding: 1 2;
173
+ border: round #3f4652;
174
+ background: #101216;
175
+ }
176
+
177
+ #changed-files-header {
178
+ width: 1fr;
179
+ height: 1;
180
+ color: #d7dae0;
181
+ background: #101216;
182
+ }
183
+
184
+ #changed-files-split {
185
+ width: 1fr;
186
+ height: 1fr;
187
+ background: #101216;
188
+ }
189
+
190
+ #changed-files-list {
191
+ width: 36;
192
+ height: 1fr;
193
+ background: #101216;
194
+ color: #d7dae0;
195
+ scrollbar-size: 1 1;
196
+ margin-right: 1;
197
+ }
198
+
199
+ #changed-files-diff {
200
+ width: 1fr;
201
+ height: 1fr;
202
+ color: #d7dae0;
203
+ background: #101216;
204
+ scrollbar-size: 1 1;
205
+ }
206
+
207
+ #message-token-dialog {
208
+ width: 1fr;
209
+ height: 1fr;
210
+ margin: 1 2;
211
+ padding: 1 2;
212
+ border: round #3f4652;
213
+ background: #101216;
214
+ }
215
+
216
+ #message-token-header {
217
+ width: 1fr;
218
+ height: 3;
219
+ padding: 0 1;
220
+ color: #d7dae0;
221
+ background: #14171d;
222
+ border: round #2f3540;
223
+ margin-bottom: 1;
224
+ }
225
+
226
+ #message-token-split {
227
+ width: 1fr;
228
+ height: 1fr;
229
+ background: #101216;
230
+ }
231
+
232
+ #message-token-list {
233
+ width: 52;
234
+ height: 1fr;
235
+ background: #101216;
236
+ color: #d7dae0;
237
+ scrollbar-size: 1 1;
238
+ margin-right: 1;
239
+ border: round #2f3540;
240
+ }
241
+
242
+ #message-token-list:focus {
243
+ border: round #c9a6ff;
244
+ }
245
+
246
+ #message-token-detail {
247
+ width: 1fr;
248
+ height: 1fr;
249
+ color: #d7dae0;
250
+ background: #101216;
251
+ scrollbar-size: 1 1;
252
+ border: round #2f3540;
253
+ padding: 1 2;
254
+ }
255
+
256
+ #message-token-footer {
257
+ width: 1fr;
258
+ height: 1;
259
+ color: #7f8794;
260
+ background: #101216;
261
+ margin-top: 1;
262
+ }
263
+
264
+ #approval-title {
265
+ width: 1fr;
266
+ height: 1;
267
+ color: #e6e8ee;
268
+ }
269
+
270
+ #approval-detail {
271
+ width: 1fr;
272
+ height: 2;
273
+ color: #d7dae0;
274
+ }
275
+
276
+ #approval-actions {
277
+ width: 1fr;
278
+ height: 1;
279
+ color: #7f8794;
280
+ }
@@ -14,7 +14,12 @@ from dotenv import load_dotenv
14
14
 
15
15
  from agent import Session
16
16
  from agent.approval import ApprovalRequest
17
- from agent.app_paths import resolve_app_root, resolve_runtime_data_dir
17
+ from agent.app_paths import (
18
+ resolve_app_root,
19
+ resolve_resource_root,
20
+ resolve_runtime_data_dir,
21
+ sync_default_skills_dir,
22
+ )
18
23
  from agent.logger import setup_logging
19
24
  from agent.session_store import FileSessionStore
20
25
  from agent.streaming import colorize_diff
@@ -221,11 +226,12 @@ Examples:
221
226
  yycode -r bugfix-123
222
227
  yycode -x bugfix-123
223
228
  yycode ~/project -t
229
+ yycode --update-skills
224
230
  yycode -a
225
231
  yycode --plain
226
232
 
227
233
  Session data:
228
- Messages are saved by default under {app_root}/sessions/{workspace_hash}/{session_id}.json.
234
+ Messages are saved by default under {data_dir}/sessions/{workspace_hash}/{session_id}.json.
229
235
  Use -s/--sessions to inspect saved sessions for WORKDIR.
230
236
  Use -r/--resume ID to continue a previous conversation in the same workspace.
231
237
  Use -x/--delete ID to delete a saved session for WORKDIR.
@@ -235,10 +241,10 @@ Environment:
235
241
  API_KEY API key for the selected provider.
236
242
  API_BASE Optional custom API base URL.
237
243
  AI_MODEL Model name override.
238
- YOYO_APP_ROOT Yoyo Agent app/release directory.
239
- YOYO_RUNTIME_DATA_DIR Runtime data directory; defaults to app_root.
244
+ YOYO_APP_ROOT Bundled yycode resource root override.
245
+ YOYO_RUNTIME_DATA_DIR User data directory; skills, sessions, and logs live here.
240
246
  YOYO_SESSION_DIR Session messages directory override.
241
- YOYO_SKILL_DIRS Extra skill directories, separated by comma/pathsep.
247
+ YOYO_SKILL_DIRS Extra skill directories appended after {data_dir}/skills.
242
248
  YOYO_CONTEXT_WINDOW_TOKENS Context window size override for token pressure.
243
249
  YOYO_SILENT Auto-approve risky actions when truthy.
244
250
  YOYO_AUTO_APPROVE Alias for YOYO_SILENT.
@@ -321,6 +327,11 @@ Environment:
321
327
  metavar="ID",
322
328
  help="Delete a persisted session id for WORKDIR and exit.",
323
329
  )
330
+ parser.add_argument(
331
+ "--update-skills",
332
+ action="store_true",
333
+ help="Copy bundled skills into the user data skills directory, overwriting matching files.",
334
+ )
324
335
  parser.add_argument(
325
336
  "--no-persist",
326
337
  dest="temp",
@@ -389,6 +400,25 @@ def delete_session_for_workdir(workdir: Path, session_id: str) -> str:
389
400
  return f"Deleted session for workspace {workdir}: {session_id}"
390
401
 
391
402
 
403
+ def update_default_skills() -> str:
404
+ """Overwrite user data skills with bundled defaults where bundled files exist."""
405
+ app_root = resolve_app_root()
406
+ resource_root = resolve_resource_root(app_root)
407
+ runtime_data_dir = resolve_runtime_data_dir(app_root)
408
+ result = sync_default_skills_dir(runtime_data_dir, resource_root)
409
+ lines = [
410
+ "Updated yycode skills.",
411
+ f"Source: {result.source_dir}",
412
+ f"Target: {result.target_dir}",
413
+ f"Copied: {len(result.copied)}",
414
+ f"Updated: {len(result.updated)}",
415
+ f"Unchanged: {len(result.skipped)}",
416
+ ]
417
+ preserved_note = "User-created files without matching bundled paths were preserved."
418
+ lines.append(preserved_note)
419
+ return "\n".join(lines)
420
+
421
+
392
422
  def create_session_store_for_workdir(workdir: Path) -> FileSessionStore:
393
423
  """Create the default file session store for a workspace."""
394
424
  app_root = resolve_app_root()
@@ -420,6 +450,9 @@ def main() -> None:
420
450
  parser = build_arg_parser()
421
451
  args = parser.parse_args()
422
452
  log_file_path = resolve_log_file_path()
453
+ if args.update_skills:
454
+ print(update_default_skills())
455
+ return
423
456
  if args.acp or args.workdir == "acp":
424
457
  setup_logging(debug=args.debug, log_to_file=args.log_file, log_file=log_file_path)
425
458
  load_dotenv(override=True)