6.2 KiB
02 架构与目录
源码落地:D:\ideaProject\gcode(本次只定结构,不建库)。
分层
┌─────────────────────────────────────────────┐
│ ui/detect + tui / repl │
│ 探测终端、画界面、收集确认(y/N、diff) │
├─────────────────────────────────────────────┤
│ graph + session + prompt │
│ ReAct、checkpoint、静态/动态提示词 │
├─────────────────────────────────────────────┤
│ models(注册表 + openai_compatible) │
├─────────────────────────────────────────────┤
│ tools + safety + mcp │
│ 文件/命令沙箱、HITL、Playwright MCP │
└─────────────────────────────────────────────┘
依赖只能从上到下。工具不得 import 图模块去改全局 llm / agent。TUI 与 REPL 都只消费 graph.astream(...)。
flowchart LR
detect[终端探测] -->|WT 等| tui[自研TUI]
detect -->|conhost| repl[REPL回退]
tui --> graph[LangGraph ReAct]
repl --> graph
graph --> llmCall[llm_call]
llmCall -->|tool_calls| toolNode[tool_node]
toolNode --> safety[HITL]
safety --> llmCall
llmCall -->|纯文本| uiOut[TUI或REPL渲染]
目录骨架
D:\ideaProject\gcode\
pyproject.toml
README.md
src/gcode/
__init__.py
cli.py # 入口:解析参数、探测、选 UI
config.py # 单一 home:%APPDATA%\gcode 与 <cwd>/.gcode
windows.py # 代码页、默认 PowerShell、Job Object / taskkill
prompt.py
graph.py # StateGraph:llm_call / tool_node
session.py # sqlite checkpointer + thread_id
safety.py # 写文件 diff、shell 确认;回调由 UI 注入
ui/
detect.py
repl.py
tui/ # 自研 Textual,不拷 Qoze CSS/logo
models/
registry.py # 读 models.yaml
openai_compatible.py
tools/
fs.py
shell.py
mcp/
client.py # 薄客户端,默认只起 @playwright/mcp
tests/
test_path_sandbox.py
test_graph_smoke.py
test_detect_terminal.py
项目级数据:<cwd>/.gcode/(checkpoints、rules、debug log)。
用户级:%APPDATA%\gcode\(models.yaml、mcp 配置、token 统计)。禁止再混用 ~\.gcode 与 %APPDATA% 两套根。
Prompt 怎么写
不要一上来写 400 行。对照 Qoze 的 system_prompt.py 学 静态/动态拆分,不要整段粘贴。G-CODE 工具清单不同,抄过来模型会去调不存在的 dispatch_subagent。
每次加一段之前问:
- 模型此刻有哪些工具?没实现的一句不写
- 最容易做错的 3 件事?只为这些写硬规则
- 静态还是动态?每次一样 → System;会变(cwd、git、rules)→ 动态段
- 能用代码拦住的不要只靠文字(路径沙箱在
read_file里拒绝) - 用失败任务加规则,不用想象加规则
静态段先控制在 80~150 行:身份、工具怎么选、5~8 条硬规则、ReAct 与「连续 3 次无进展就问人」、中文推理、TUI 内避免 emoji。
动态段保持短:cwd、目录树摘要、git 摘要、.gcode/rules。
多模型
不复制 Qoze 的枚举树。models.yaml 示例:
providers:
deepseek:
api: openai_compatible
base_url: https://api.deepseek.com/v1
api_key_env: DEEPSEEK_API_KEY
models:
- id: deepseek-v4-flash
vision: false
reasoning: false
- id: deepseek-v4-pro
vision: false
reasoning: true
agicto:
api: openai_compatible
base_url: https://api.agicto.cn/v1
api_key_env: GCODE_API_KEY
models:
- id: deepseek-v4-flash
vision: false
reasoning: false
一期只实现 openai_compatible。加一家模型改 yaml,不改 Python。reasoning_content 尽量显式解析,避免猴子补丁 LangChain。Vertex / Anthropic / Bedrock 有账号再加 adapter。
启动:gcode --model deepseek-v4-flash 或 TUI 内选择。
人在回路
safety.py 定义确认点,UI 注入回调(TUI 用对话框,REPL 用 y/N):
| 动作 | 展示 | 拒绝时 |
|---|---|---|
| 写文件 / 替换 | unified diff | ToolMessage:「用户拒绝本次写入」 |
execute_command |
完整命令 + 工作目录 | ToolMessage:「用户拒绝执行该命令」 |
读文件、列目录、只读 git 不必确认。安装依赖、构建、测试、git commit / push 必须走确认(prompt 硬规则 + HITL 双保险)。
工具
一期内置:
read_file/list_dir/replace_in_file:仅 cwd 或用户级 gcode 目录;拒绝..execute_command:默认 PowerShell;输出 UTF-8 失败则 GBK;超时用 Job Object 或taskkill /T,不要只terminate()留孤儿
浏览器不进 tools/ 进程内实现,见下一节。
Playwright MCP(仅途径 A)
官方:npx -y @playwright/mcp@latest。
G-CODE 薄 MCP 客户端:stdio 拉起 → 把 tools 动态交给 bind_tools。MCP 只当 Playwright 插座,一期不接 GitHub / Postgres。不复活 Qoze 已注释的 browser_tool.py,不默认 chrome-devtools-mcp(那是 --remote-debugging-port=9222 附着已开 Chrome)。
硬依赖 Node.js / npx。检测不到则 TUI/REPL 提示「浏览器工具不可用,请安装 Node.js」,没有 Python Playwright 备选。
相对 Qoze 必须避开的结构
- 模块级全局
llm、agent、tools_by_name被 MCP/TUI 到处改 mcp_tools/subagent_tool反向 importqoze_code_agentqoze_tui.py里命令路由、拼消息、token、流式全混在一个 App 类- 配置路径分裂:
%APPDATA%\qozevs~\.qoze - shell 一律按 UTF-8 解码(cmd 的
dir常为 GBK)