2010 lines
68 KiB
Markdown
2010 lines
68 KiB
Markdown
# L2 — 核心模块与接口契约层
|
||
|
||
**文档版本**: 1.0
|
||
**适用项目**: sgClaw (业数融合一平台 AI Agent 底座)
|
||
**编制日期**: 2026-03-03
|
||
|
||
**读者**: 各组组长(Rust 组、浏览器 C++ 组、前端组)—— 需要理解模块边界、接口契约、依赖关系,指导各组并行开发。
|
||
|
||
---
|
||
|
||
## 1. 模块总览与依赖拓扑
|
||
|
||
### 1.1 全局模块图
|
||
|
||
sgClaw 系统由三个独立的代码域组成:Rust 端(sgclaw binary)、C++ 端(Browser Process 新增模块)、前端(Side Panel Vue 组件)。以下拓扑展示了所有模块及其依赖关系。
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ sgClaw Rust Binary │
|
||
│ │
|
||
│ ┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌────────────────┐ │
|
||
│ │ main.rs │──→│ Agent Runtime│──→│ Critic │ │ Config/ │ │
|
||
│ │ (入口) │ │ (ReAct) │ │ (评估器) │ │ Settings │ │
|
||
│ └────┬─────┘ └──────┬───────┘ └─────┬─────┘ └────────────────┘ │
|
||
│ │ │ │ │
|
||
│ │ ┌──────┴───────┐ ┌──────┴──────┐ │
|
||
│ │ │ BrowserPipe │ │ Circuit │ │
|
||
│ │ │ Tool │ │ Breaker │ │
|
||
│ │ │ (Tool trait) │ └─────────────┘ │
|
||
│ │ └──────┬───────┘ │
|
||
│ │ │ │
|
||
│ ┌────┴─────┐ ┌──────┴───────┐ ┌───────────┐ ┌────────────────┐ │
|
||
│ │ Pipe │ │ MAC Policy │ │ Memory │ │ LLM Provider │ │
|
||
│ │ Protocol │ │ (安全策略) │ │ (记忆层) │ │ (模型适配) │ │
|
||
│ └──────────┘ └──────────────┘ └───────────┘ └────────────────┘ │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ SkillLoader │ │ MCP Client │ │
|
||
│ │ (技能加载) │ │ (rmcp) │ │
|
||
│ └──────────────┘ └──────────────┘ │
|
||
│ │
|
||
└───────────────────────────────┬──────────────────────────────────────────┘
|
||
│ STDIO Pipe (JSON Line)
|
||
│
|
||
┌───────────────────────────────┴──────────────────────────────────────────┐
|
||
│ SuperRPA Browser Process (C++ 新增) │
|
||
│ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ SgClawProcessHost │ Singleton, 进程生命周期管理 │
|
||
│ │ ├─ PipeListener │ 异步读取循环, 解析 JSON Line │
|
||
│ │ ├─ MAC Whitelist Check │ action + domain 白名单校验 │
|
||
│ │ └─→ CommandRouter │ [Existing] 40+ actions, 零修改 │
|
||
│ └─────────────────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
│ FunctionsUI IPC
|
||
│
|
||
┌───────────────────────────────┴──────────────────────────────────────────┐
|
||
│ Side Panel (Vue 前端) │
|
||
│ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ AgentControlPanel.vue │ 启停按钮 + 指令输入 + 状态显示 + 执行日志 │
|
||
│ └─────────────────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 1.2 模块依赖矩阵
|
||
|
||
以下矩阵展示 Rust 端各模块之间的依赖关系(行依赖列):
|
||
|
||
```
|
||
Runtime BrowserPipe Protocol MAC Memory LLM Skill Critic MCP Config
|
||
Runtime - ✓ - - ✓ ✓ ✓ ✓ ✓ ✓
|
||
BrowserPipe - - ✓ ✓ - - - - - -
|
||
Protocol - - - - - - - - - ✓
|
||
MAC Policy - - - - - - - - - ✓
|
||
Memory - - - - - - - - - ✓
|
||
LLM Provider - - - - - - - - - ✓
|
||
SkillLoader - - - - - - - - - ✓
|
||
Critic - - - - ✓ - - - - ✓
|
||
MCP Client - - - - - - - - - ✓
|
||
Config - - - - - - - - - -
|
||
```
|
||
|
||
设计原则:
|
||
- **Config 是叶节点**:所有模块依赖 Config,Config 不依赖任何业务模块
|
||
- **Runtime 是根节点**:Agent Runtime 协调所有模块,是唯一的"上帝模块"
|
||
- **BrowserPipeTool 仅依赖 Protocol 和 MAC**:确保 Tool 实现的纯粹性
|
||
- **无循环依赖**:依赖图是 DAG(有向无环图)
|
||
|
||
### 1.3 并行开发分工
|
||
|
||
基于模块依赖关系,三个组可以并行开发:
|
||
|
||
| 组别 | 负责模块 | 依赖条件 | 可并行阶段 |
|
||
|------|---------|---------|-----------|
|
||
| **Rust 组** | Agent Runtime, BrowserPipeTool, Protocol, MAC Policy, Memory, LLM Provider, SkillLoader, Critic, MCP Client, Config | 无外部依赖,可先行 | 第一阶段即可开始 |
|
||
| **C++ 组** | SgClawProcessHost, PipeListener, MAC Whitelist Check | 需与 Rust 组对齐 Pipe 协议格式 | 协议 JSON Schema 确定后开始 |
|
||
| **前端组** | AgentControlPanel.vue | 需 C++ 组提供 FunctionsUI handler | C++ 组 handler 就绪后开始 |
|
||
|
||
---
|
||
|
||
## 2. sgClaw Rust 端模块详细设计
|
||
|
||
### 2.1 main.rs — 进程入口
|
||
|
||
**职责**:初始化 Pipe I/O、配置加载、tokio 异步运行时启动、模块组装、handshake 握手。
|
||
|
||
**启动序列**:
|
||
|
||
```
|
||
main()
|
||
│
|
||
├── 1. 初始化 tracing (日志)
|
||
│ └── 日志输出到 stderr (stdout 保留给 pipe)
|
||
│
|
||
├── 2. 加载 Config
|
||
│ ├── 默认值 (编译时内嵌)
|
||
│ ├── 配置文件 (sgclaw.toml, 可选)
|
||
│ └── 环境变量覆盖 (SGCLAW_*)
|
||
│
|
||
├── 3. 初始化 tokio runtime
|
||
│ └── multi_thread, worker_threads = 2
|
||
│
|
||
├── 4. 创建 Pipe I/O
|
||
│ ├── stdin → BufReader (接收 Browser 消息)
|
||
│ └── stdout → BufWriter (发送消息到 Browser)
|
||
│
|
||
├── 5. Handshake
|
||
│ ├── 等待 Browser 的 init 消息 (超时 5s)
|
||
│ ├── 校验协议版本
|
||
│ ├── 交换 HMAC 密钥种子
|
||
│ └── 发送 init_ack 确认
|
||
│
|
||
├── 6. 组装模块
|
||
│ ├── MAC Policy ← Config (rules 路径)
|
||
│ ├── Protocol ← Config (max_message_size)
|
||
│ ├── BrowserPipeTool ← Protocol + MAC
|
||
│ ├── LLM Provider ← Config (model, api_key)
|
||
│ ├── Memory ← Config (db_path)
|
||
│ ├── SkillLoader ← Config (skills_dir)
|
||
│ ├── MCP Client ← Config (mcp_servers)
|
||
│ ├── Critic ← Config (thresholds) + Memory
|
||
│ └── Agent Runtime ← 以上全部
|
||
│
|
||
├── 7. 启动 Agent 消息循环
|
||
│ └── 监听 pipe 输入,分发到 Runtime
|
||
│
|
||
└── 8. 优雅退出
|
||
├── 收到 shutdown 命令 / EOF / SIGTERM
|
||
├── 停止 Agent loop
|
||
├── flush Memory 到磁盘
|
||
└── 退出进程 (exit code 0)
|
||
```
|
||
|
||
**关键设计决策**:
|
||
|
||
- **stdout 专用于 pipe**:所有日志、调试信息通过 `tracing` 输出到 stderr。stdout 严格保留给 JSON Line 协议,防止日志混入 pipe 流导致解析失败。
|
||
- **tokio worker_threads = 2**:在 8 GB 内存约束下,2 个 worker 线程足以处理 pipe I/O + LLM HTTP 请求。不需要更多并发。
|
||
- **模块组装使用依赖注入**:所有模块通过构造函数接收依赖,便于测试时替换为 mock 实现。
|
||
|
||
### 2.2 Agent Runtime — ReAct 循环
|
||
|
||
**职责**:实现 think → act → observe 循环,协调 LLM、Tool、Memory、Critic 完成用户任务。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// Agent Runtime 核心结构
|
||
pub struct AgentRuntime {
|
||
provider: Box<dyn LlmProvider>,
|
||
tools: Vec<Box<dyn Tool>>, // 包含 BrowserPipeTool + MCP tools
|
||
memory: Arc<dyn Memory>,
|
||
critic: Critic,
|
||
config: RuntimeConfig,
|
||
}
|
||
|
||
/// Runtime 配置
|
||
pub struct RuntimeConfig {
|
||
pub max_steps: u32, // 单次任务最大步数 (默认 50)
|
||
pub max_thinking_tokens: u32, // 单步 thinking 最大 token (默认 4096)
|
||
pub system_prompt_template: String, // 系统提示模板路径
|
||
pub human_confirm_actions: Vec<String>, // 需人工确认的 action 列表
|
||
}
|
||
|
||
impl AgentRuntime {
|
||
/// 创建 Runtime 实例
|
||
pub fn new(
|
||
provider: Box<dyn LlmProvider>,
|
||
tools: Vec<Box<dyn Tool>>,
|
||
memory: Arc<dyn Memory>,
|
||
critic: Critic,
|
||
config: RuntimeConfig,
|
||
) -> Self;
|
||
|
||
/// 执行用户任务 (核心入口)
|
||
/// 返回任务执行结果或错误
|
||
pub async fn execute_task(&mut self, task: &str) -> Result<TaskResult, AgentError>;
|
||
|
||
/// 停止当前任务 (由外部 shutdown 信号触发)
|
||
pub fn abort(&self);
|
||
}
|
||
|
||
/// 任务执行结果
|
||
pub struct TaskResult {
|
||
pub success: bool,
|
||
pub summary: String, // LLM 生成的任务完成摘要
|
||
pub steps: Vec<StepRecord>, // 所有执行步骤记录
|
||
pub token_usage: TokenUsage, // token 消耗统计
|
||
}
|
||
|
||
/// 单步执行记录
|
||
pub struct StepRecord {
|
||
pub step_num: u32,
|
||
pub thinking: String, // LLM 的推理内容
|
||
pub action: Option<Action>, // 选择的操作 (None = 任务完成)
|
||
pub observation: String, // 操作结果观察
|
||
pub duration_ms: u64,
|
||
}
|
||
```
|
||
|
||
**ReAct 循环伪码**:
|
||
|
||
```
|
||
execute_task(user_instruction):
|
||
context = memory.load_context(user_instruction)
|
||
system_prompt = render_template(config.system_prompt_template, {
|
||
available_tools: tools.descriptions(),
|
||
skills: skill_loader.list_skills(),
|
||
context: context,
|
||
})
|
||
|
||
for step in 1..=config.max_steps:
|
||
// === THINK ===
|
||
llm_response = provider.chat(system_prompt, messages, tools)
|
||
|
||
if llm_response.is_final_answer():
|
||
return TaskResult { success: true, summary: llm_response.text }
|
||
|
||
// === ACT ===
|
||
action = llm_response.tool_call
|
||
if action.name in config.human_confirm_actions:
|
||
confirmation = request_human_confirm(action) // 通过 pipe 发送确认请求
|
||
if not confirmation:
|
||
messages.push(observation: "用户拒绝了此操作")
|
||
continue
|
||
|
||
result = tools.execute(action)
|
||
|
||
// === OBSERVE ===
|
||
observation = format_observation(result)
|
||
messages.push(observation)
|
||
|
||
// === CRITIC ===
|
||
critic_verdict = critic.evaluate(step, action, result)
|
||
if critic_verdict.should_abort:
|
||
return TaskResult { success: false, summary: critic_verdict.reason }
|
||
|
||
memory.record_step(step, action, result)
|
||
|
||
return TaskResult { success: false, summary: "达到最大步数限制" }
|
||
```
|
||
|
||
### 2.3 BrowserPipeTool — 自定义 Tool trait 实现
|
||
|
||
**职责**:将 Agent 的 action 请求序列化为 Pipe JSON 命令,发送到 Browser 并等待响应。这是 sgClaw 与 ZeroClaw 框架的核心对接点。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// ZeroClaw 的 Tool trait (框架定义)
|
||
#[async_trait]
|
||
pub trait Tool: Send + Sync {
|
||
fn name(&self) -> &str;
|
||
fn description(&self) -> &str;
|
||
fn parameters_schema(&self) -> serde_json::Value;
|
||
async fn execute(&self, params: serde_json::Value) -> Result<ToolOutput, ToolError>;
|
||
}
|
||
|
||
/// sgClaw 的浏览器操作工具实现
|
||
pub struct BrowserPipeTool {
|
||
pipe_writer: Arc<Mutex<PipeWriter>>,
|
||
pipe_reader: Arc<PipeResponseReceiver>,
|
||
mac_policy: Arc<MacPolicy>,
|
||
seq_counter: AtomicU64,
|
||
hmac_key: [u8; 32],
|
||
}
|
||
|
||
impl BrowserPipeTool {
|
||
pub fn new(
|
||
pipe_writer: Arc<Mutex<PipeWriter>>,
|
||
pipe_reader: Arc<PipeResponseReceiver>,
|
||
mac_policy: Arc<MacPolicy>,
|
||
hmac_key: [u8; 32],
|
||
) -> Self;
|
||
}
|
||
|
||
#[async_trait]
|
||
impl Tool for BrowserPipeTool {
|
||
fn name(&self) -> &str { "browser_action" }
|
||
|
||
fn description(&self) -> &str {
|
||
"在浏览器中执行操作。支持的操作包括:click, type, navigate, \
|
||
getText, getHtml, waitForSelector, pageScreenshot, select, \
|
||
scrollTo, getAomSnapshot, storageSet, storageGet, \
|
||
zombieSpawn, zombieKill"
|
||
}
|
||
|
||
fn parameters_schema(&self) -> serde_json::Value {
|
||
// 返回所有 action 的 JSON Schema (oneOf 结构)
|
||
// 详见 §5 接口契约
|
||
}
|
||
|
||
async fn execute(&self, params: serde_json::Value) -> Result<ToolOutput, ToolError> {
|
||
// 1. 解析 action + params
|
||
let action: Action = serde_json::from_value(params)?;
|
||
|
||
// 2. MAC 策略校验
|
||
self.mac_policy.check(&action)?;
|
||
|
||
// 3. 构造 pipe message
|
||
let seq = self.seq_counter.fetch_add(1, Ordering::SeqCst);
|
||
let msg = PipeMessage::command(seq, &action, &self.hmac_key);
|
||
|
||
// 4. 发送到 Browser
|
||
self.pipe_writer.lock().await.send(&msg)?;
|
||
|
||
// 5. 等待对应 seq 的 response (超时 30s)
|
||
let response = self.pipe_reader.recv(seq, Duration::from_secs(30)).await?;
|
||
|
||
// 6. 转换为 ToolOutput
|
||
Ok(ToolOutput::from_pipe_response(response))
|
||
}
|
||
}
|
||
```
|
||
|
||
**Action 枚举(白名单)**:
|
||
|
||
```rust
|
||
/// 允许通过 pipe 的操作类型 (封闭枚举)
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
#[serde(tag = "action", content = "params")]
|
||
pub enum Action {
|
||
#[serde(rename = "click")]
|
||
Click { selector: String, wait_after: Option<u64> },
|
||
|
||
#[serde(rename = "type")]
|
||
Type { selector: String, text: String, clear_first: Option<bool> },
|
||
|
||
#[serde(rename = "navigate")]
|
||
Navigate { url: String },
|
||
|
||
#[serde(rename = "getText")]
|
||
GetText { selector: String },
|
||
|
||
#[serde(rename = "getHtml")]
|
||
GetHtml { selector: String, outer: Option<bool> },
|
||
|
||
#[serde(rename = "waitForSelector")]
|
||
WaitForSelector { selector: String, timeout_ms: Option<u64> },
|
||
|
||
#[serde(rename = "pageScreenshot")]
|
||
PageScreenshot { full_page: Option<bool> },
|
||
|
||
#[serde(rename = "select")]
|
||
Select { selector: String, value: String },
|
||
|
||
#[serde(rename = "scrollTo")]
|
||
ScrollTo { selector: Option<String>, x: Option<i32>, y: Option<i32> },
|
||
|
||
#[serde(rename = "getAomSnapshot")]
|
||
GetAomSnapshot { root_selector: Option<String> },
|
||
|
||
#[serde(rename = "storageSet")]
|
||
StorageSet { key: String, value: String },
|
||
|
||
#[serde(rename = "storageGet")]
|
||
StorageGet { key: String },
|
||
|
||
#[serde(rename = "zombieSpawn")]
|
||
ZombieSpawn { url: String },
|
||
|
||
#[serde(rename = "zombieKill")]
|
||
ZombieKill { page_id: String },
|
||
}
|
||
```
|
||
|
||
### 2.4 SkillLoader — 技能加载器
|
||
|
||
**职责**:发现、校验、加载 JavaScript 业务技能脚本,将其注册到 Agent 的工具集中。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// 技能加载器
|
||
pub struct SkillLoader {
|
||
skills_dir: PathBuf,
|
||
registry: HashMap<String, Skill>,
|
||
signature_verifier: SignatureVerifier,
|
||
}
|
||
|
||
/// 技能定义
|
||
pub struct Skill {
|
||
pub name: String, // 技能名称 (如 "oa-approval")
|
||
pub version: String, // 语义版本 (如 "1.2.0")
|
||
pub description: String, // 自然语言描述 (供 LLM 选择)
|
||
pub target_domains: Vec<String>,// 适用域名列表
|
||
pub parameters: JsonSchema, // 输入参数 schema
|
||
pub script: String, // JS 脚本内容
|
||
pub signature: String, // 数字签名 (Ed25519)
|
||
}
|
||
|
||
/// 技能清单文件 (registry.json)
|
||
pub struct SkillRegistry {
|
||
pub version: String,
|
||
pub skills: Vec<SkillManifestEntry>,
|
||
}
|
||
|
||
pub struct SkillManifestEntry {
|
||
pub name: String,
|
||
pub file: String, // 相对路径 (如 "builtin/oa-approval.js")
|
||
pub hash: String, // SHA-256 文件哈希
|
||
pub signature: String, // Ed25519 签名
|
||
}
|
||
|
||
impl SkillLoader {
|
||
/// 创建加载器并扫描技能目录
|
||
pub fn new(skills_dir: PathBuf, public_key: &[u8]) -> Result<Self, SkillError>;
|
||
|
||
/// 获取所有已加载技能的描述 (供 LLM system prompt)
|
||
pub fn list_skills(&self) -> Vec<SkillDescription>;
|
||
|
||
/// 获取指定技能的 JS 脚本
|
||
pub fn get_script(&self, name: &str) -> Option<&str>;
|
||
|
||
/// 运行时重新加载技能 (热更新)
|
||
pub fn reload(&mut self) -> Result<(), SkillError>;
|
||
}
|
||
```
|
||
|
||
**技能加载流程**:
|
||
|
||
```
|
||
SkillLoader::new(skills_dir)
|
||
│
|
||
├── 1. 读取 skills/registry.json
|
||
│ └── 解析技能清单
|
||
│
|
||
├── 2. 遍历每个 SkillManifestEntry
|
||
│ │
|
||
│ ├── 2a. 读取技能文件
|
||
│ │
|
||
│ ├── 2b. 计算文件 SHA-256
|
||
│ │ └── 与 registry.json 中的 hash 比对
|
||
│ │ └── 不匹配 → 跳过 + 警告日志
|
||
│ │
|
||
│ ├── 2c. 验证 Ed25519 签名
|
||
│ │ └── 使用编译时内嵌的公钥
|
||
│ │ └── 签名无效 → 跳过 + 警告日志
|
||
│ │
|
||
│ └── 2d. 解析技能元数据 (JS 文件头部注释)
|
||
│ └── 注册到 registry HashMap
|
||
│
|
||
└── 3. 日志: "Loaded N skills, skipped M (signature failed)"
|
||
```
|
||
|
||
**技能文件格式** (JS 文件头部):
|
||
|
||
```javascript
|
||
/**
|
||
* @skill oa-approval
|
||
* @version 1.2.0
|
||
* @description OA系统审批单自动处理:查询待审批列表、批量审批、添加审批意见
|
||
* @domains oa.example.com, oa-test.example.com
|
||
* @params {
|
||
* "type": "object",
|
||
* "properties": {
|
||
* "action": { "enum": ["list", "approve", "reject"] },
|
||
* "opinion": { "type": "string" }
|
||
* }
|
||
* }
|
||
*/
|
||
async function execute(params, browserAction) {
|
||
// browserAction 是 BrowserAction 函数的引用
|
||
// 技能代码使用 browserAction 调用浏览器操作
|
||
|
||
if (params.action === 'list') {
|
||
await browserAction('navigate', 'https://oa.example.com/approval/pending');
|
||
await browserAction('waitForSelector', '.approval-list');
|
||
const items = await browserAction('getText', '.approval-list');
|
||
return { success: true, data: items };
|
||
}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 2.5 MAC Policy — 强制访问控制策略
|
||
|
||
**职责**:在 Rust 端实施域名白名单和 Action 白名单校验,作为安全架构 Layer 2 的核心组件。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// MAC 策略引擎
|
||
pub struct MacPolicy {
|
||
allowed_domains: HashSet<String>, // 允许操作的域名集合
|
||
allowed_actions: HashSet<String>, // 允许的 action 名称集合
|
||
blocked_actions: HashSet<String>, // 硬性禁止的 action 集合
|
||
confirm_actions: HashSet<String>, // 需人工确认的 action 集合
|
||
storage_key_prefix: String, // 存储 key 前缀限制 (默认 "sgclaw.")
|
||
rate_limits: HashMap<String, RateLimit>, // 每域速率限制
|
||
}
|
||
|
||
/// 速率限制配置
|
||
pub struct RateLimit {
|
||
pub max_per_second: u32, // 每秒最大操作数
|
||
pub cooldown_seconds: u32, // 超限后冷却时间
|
||
}
|
||
|
||
/// MAC 校验结果
|
||
pub enum MacVerdict {
|
||
Allow, // 允许执行
|
||
NeedConfirm(String), // 需要用户确认 (附确认提示)
|
||
Deny(MacDenyReason), // 拒绝执行
|
||
}
|
||
|
||
/// 拒绝原因
|
||
pub enum MacDenyReason {
|
||
ActionNotAllowed(String), // action 不在白名单
|
||
ActionBlocked(String), // action 在黑名单 (eval 等)
|
||
DomainNotAllowed(String), // 域名不在白名单
|
||
StorageKeyViolation(String), // storage key 前缀不合规
|
||
RateLimitExceeded(String, u32), // 超过速率限制 (域名, 当前速率)
|
||
}
|
||
|
||
impl MacPolicy {
|
||
/// 从 rules.json 加载策略
|
||
pub fn from_rules_file(path: &Path) -> Result<Self, PolicyError>;
|
||
|
||
/// 校验 action 是否允许
|
||
pub fn check(&self, action: &Action) -> MacVerdict;
|
||
|
||
/// 校验域名是否在白名单
|
||
pub fn check_domain(&self, domain: &str) -> bool;
|
||
|
||
/// 记录一次操作 (用于速率统计)
|
||
pub fn record_access(&self, domain: &str);
|
||
}
|
||
```
|
||
|
||
**rules.json 策略文件格式**:
|
||
|
||
```json
|
||
{
|
||
"version": "1.0",
|
||
"domains": {
|
||
"allowed": [
|
||
"oa.example.com",
|
||
"erp.example.com",
|
||
"hr.example.com",
|
||
"finance.example.com"
|
||
]
|
||
},
|
||
"pipe_actions": {
|
||
"allowed": [
|
||
"click", "type", "navigate", "getText", "getHtml",
|
||
"waitForSelector", "pageScreenshot", "select", "scrollTo",
|
||
"getAomSnapshot", "storageSet", "storageGet",
|
||
"zombieSpawn", "zombieKill"
|
||
],
|
||
"blocked": [
|
||
"eval", "executeJsInPage", "registerJsFunction",
|
||
"setRequestInterceptor", "exportCookies"
|
||
],
|
||
"need_confirm": [
|
||
"sessionLogin", "sessionLogout", "clearStorage"
|
||
]
|
||
},
|
||
"storage": {
|
||
"key_prefix": "sgclaw."
|
||
},
|
||
"rate_limits": {
|
||
"default": { "max_per_second": 10, "cooldown_seconds": 30 },
|
||
"overrides": {
|
||
"finance.example.com": { "max_per_second": 5, "cooldown_seconds": 60 }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.6 Critic — 输出质量评估器
|
||
|
||
**职责**:在 Agent 每步 observe 后评估操作结果质量,检测异常模式,触发熔断保护。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// 输出质量评估器
|
||
pub struct Critic {
|
||
circuit_breaker: CircuitBreaker,
|
||
memory: Arc<dyn Memory>,
|
||
config: CriticConfig,
|
||
}
|
||
|
||
pub struct CriticConfig {
|
||
pub consecutive_failure_threshold: u32, // 连续失败熔断阈值 (默认 10)
|
||
pub same_action_repeat_limit: u32, // 同一 action 重复上限 (默认 5)
|
||
pub max_task_duration_secs: u64, // 单任务最大时长 (默认 600)
|
||
}
|
||
|
||
/// 评估结果
|
||
pub struct CriticVerdict {
|
||
pub should_abort: bool, // 是否应终止任务
|
||
pub reason: Option<String>, // 终止原因
|
||
pub warning: Option<String>, // 警告 (不终止但需记录)
|
||
}
|
||
|
||
/// 熔断器
|
||
pub struct CircuitBreaker {
|
||
state: CircuitState,
|
||
consecutive_failures: AtomicU32,
|
||
last_failure_time: Mutex<Option<Instant>>,
|
||
config: CircuitBreakerConfig,
|
||
}
|
||
|
||
pub enum CircuitState {
|
||
Closed, // 正常 — 允许操作
|
||
Open, // 断开 — 拒绝所有操作
|
||
HalfOpen, // 半开 — 允许一次试探
|
||
}
|
||
|
||
pub struct CircuitBreakerConfig {
|
||
pub failure_threshold: u32, // 失败次数阈值 (默认 10)
|
||
pub cooldown_base_secs: u64, // 基础冷却时间 (默认 1)
|
||
pub cooldown_max_secs: u64, // 最大冷却时间 (默认 30)
|
||
pub reset_on_success: bool, // 成功后是否重置计数 (默认 true)
|
||
}
|
||
|
||
impl Critic {
|
||
pub fn new(memory: Arc<dyn Memory>, config: CriticConfig) -> Self;
|
||
|
||
/// 评估单步结果
|
||
pub fn evaluate(
|
||
&self,
|
||
step: u32,
|
||
action: &Action,
|
||
result: &ToolOutput,
|
||
elapsed: Duration,
|
||
) -> CriticVerdict;
|
||
|
||
/// 重置熔断器 (用户手动恢复时调用)
|
||
pub fn reset(&self);
|
||
}
|
||
```
|
||
|
||
**评估逻辑**:
|
||
|
||
```
|
||
evaluate(step, action, result, elapsed):
|
||
// 1. 检查操作是否失败
|
||
if result.is_error():
|
||
circuit_breaker.record_failure()
|
||
if circuit_breaker.state == Open:
|
||
return Verdict { should_abort: true, reason: "连续失败次数达到熔断阈值" }
|
||
|
||
// 2. 检查是否在重复同一操作
|
||
recent_actions = memory.get_recent_actions(config.same_action_repeat_limit)
|
||
if all_same(recent_actions, action):
|
||
return Verdict { should_abort: true, reason: "检测到死循环:连续重复相同操作" }
|
||
|
||
// 3. 检查任务时长
|
||
if elapsed > config.max_task_duration_secs:
|
||
return Verdict { should_abort: true, reason: "任务执行超时" }
|
||
|
||
// 4. 成功时重置计数
|
||
if result.is_success() && config.reset_on_success:
|
||
circuit_breaker.reset_failures()
|
||
|
||
return Verdict { should_abort: false }
|
||
```
|
||
|
||
### 2.7 Memory — 记忆系统
|
||
|
||
**职责**:管理 Agent 的短期对话记忆和长期任务知识库,支持上下文检索和经验沉淀。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// Memory trait (ZeroClaw 框架定义)
|
||
#[async_trait]
|
||
pub trait Memory: Send + Sync {
|
||
/// 存储对话消息
|
||
async fn store_message(&self, msg: &Message) -> Result<(), MemoryError>;
|
||
|
||
/// 检索相关上下文 (语义搜索)
|
||
async fn retrieve(&self, query: &str, limit: usize) -> Result<Vec<MemoryEntry>, MemoryError>;
|
||
|
||
/// 清除当前会话记忆
|
||
async fn clear_session(&self) -> Result<(), MemoryError>;
|
||
}
|
||
|
||
/// sgClaw 的复合记忆实现
|
||
pub struct CompositeMemory {
|
||
short_term: ShortTermMemory, // Ring Buffer 短期记忆
|
||
long_term: LongTermMemory, // SQLite 长期记忆
|
||
}
|
||
|
||
/// 短期记忆 (Ring Buffer)
|
||
pub struct ShortTermMemory {
|
||
buffer: VecDeque<Message>,
|
||
max_size: usize, // 默认 50 条消息
|
||
max_tokens: usize, // 默认 8000 tokens
|
||
}
|
||
|
||
/// 长期记忆 (SQLite + 简单向量)
|
||
pub struct LongTermMemory {
|
||
db: SqlitePool, // SQLite 连接池
|
||
embedding_dim: usize, // 向量维度 (默认 384)
|
||
}
|
||
|
||
/// 记忆条目
|
||
pub struct MemoryEntry {
|
||
pub id: String,
|
||
pub content: String,
|
||
pub entry_type: MemoryType,
|
||
pub relevance_score: f32, // 与查询的相关度 (0.0 ~ 1.0)
|
||
pub created_at: DateTime<Utc>,
|
||
}
|
||
|
||
pub enum MemoryType {
|
||
Conversation, // 对话历史
|
||
TaskResult, // 任务执行结果
|
||
SkillExperience, // 技能执行经验
|
||
UserPreference, // 用户偏好
|
||
}
|
||
|
||
impl CompositeMemory {
|
||
pub fn new(config: MemoryConfig) -> Result<Self, MemoryError>;
|
||
|
||
/// 获取最近 N 步的 action 记录 (供 Critic 查重)
|
||
pub fn get_recent_actions(&self, n: usize) -> Vec<Action>;
|
||
|
||
/// 保存任务执行经验 (供自进化学习)
|
||
pub async fn save_task_experience(
|
||
&self,
|
||
task: &str,
|
||
result: &TaskResult,
|
||
) -> Result<(), MemoryError>;
|
||
}
|
||
```
|
||
|
||
**存储 Schema** (SQLite):
|
||
|
||
```sql
|
||
-- 长期记忆表
|
||
CREATE TABLE memory_entries (
|
||
id TEXT PRIMARY KEY,
|
||
content TEXT NOT NULL,
|
||
entry_type TEXT NOT NULL, -- 'conversation' | 'task_result' | 'skill_exp' | 'user_pref'
|
||
embedding BLOB, -- f32 向量 (384 维 = 1536 bytes)
|
||
metadata TEXT, -- JSON 附加信息
|
||
created_at TEXT NOT NULL,
|
||
session_id TEXT -- 关联的 trace_id
|
||
);
|
||
|
||
-- 向量索引 (简单线性扫描,数据量小时足够)
|
||
CREATE INDEX idx_memory_type ON memory_entries(entry_type);
|
||
CREATE INDEX idx_memory_session ON memory_entries(session_id);
|
||
|
||
-- 技能执行记录表
|
||
CREATE TABLE skill_executions (
|
||
id TEXT PRIMARY KEY,
|
||
skill_name TEXT NOT NULL,
|
||
input_hash TEXT NOT NULL, -- 输入参数 hash (用于精确匹配)
|
||
success INTEGER NOT NULL,
|
||
duration_ms INTEGER,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### 2.8 LLM Provider — 模型适配层
|
||
|
||
**职责**:统一封装不同 LLM 服务的调用接口,支持 streaming、tool-use、token 计量。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// LLM Provider trait (ZeroClaw 框架定义)
|
||
#[async_trait]
|
||
pub trait LlmProvider: Send + Sync {
|
||
/// 发送聊天请求
|
||
async fn chat(
|
||
&self,
|
||
messages: &[Message],
|
||
tools: &[ToolDefinition],
|
||
config: &ChatConfig,
|
||
) -> Result<LlmResponse, LlmError>;
|
||
|
||
/// 发送 streaming 请求
|
||
async fn chat_stream(
|
||
&self,
|
||
messages: &[Message],
|
||
tools: &[ToolDefinition],
|
||
config: &ChatConfig,
|
||
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk, LlmError>>>>, LlmError>;
|
||
|
||
/// 获取 provider 信息
|
||
fn info(&self) -> ProviderInfo;
|
||
}
|
||
|
||
/// 聊天配置
|
||
pub struct ChatConfig {
|
||
pub max_tokens: u32, // 最大生成 token 数
|
||
pub temperature: f32, // 温度 (默认 0.1, Agent 场景偏低)
|
||
pub stop_sequences: Vec<String>, // 停止序列
|
||
}
|
||
|
||
/// LLM 响应
|
||
pub struct LlmResponse {
|
||
pub content: Option<String>, // 文本回复
|
||
pub tool_calls: Vec<ToolCall>, // 工具调用请求
|
||
pub usage: TokenUsage, // token 使用量
|
||
pub finish_reason: FinishReason,
|
||
}
|
||
|
||
/// Token 使用统计
|
||
pub struct TokenUsage {
|
||
pub prompt_tokens: u32,
|
||
pub completion_tokens: u32,
|
||
pub total_tokens: u32,
|
||
}
|
||
|
||
/// Provider 信息
|
||
pub struct ProviderInfo {
|
||
pub name: String, // "claude" | "openai" | "ollama"
|
||
pub model: String, // 如 "claude-sonnet-4-20250514"
|
||
pub max_context_tokens: u32, // 最大上下文窗口
|
||
pub supports_tools: bool,
|
||
pub supports_streaming: bool,
|
||
}
|
||
```
|
||
|
||
**已实现的 Provider**:
|
||
|
||
| Provider | 模块 | 支持功能 | 适用场景 |
|
||
|----------|------|---------|---------|
|
||
| **Claude** | `llm/claude.rs` | streaming + tool-use + vision | 默认推荐,综合能力最强 |
|
||
| **OpenAI** | `llm/openai.rs` | streaming + tool-use | 兼容 GPT-4 及 API 兼容服务 |
|
||
| **Ollama** | `llm/ollama.rs` | streaming + tool-use (部分模型) | 本地部署,离线场景 |
|
||
|
||
**Provider 选择逻辑**:
|
||
|
||
```rust
|
||
/// 根据配置创建 Provider 实例
|
||
pub fn create_provider(config: &LlmConfig) -> Result<Box<dyn LlmProvider>, LlmError> {
|
||
match config.provider.as_str() {
|
||
"claude" => Ok(Box::new(ClaudeProvider::new(
|
||
&config.api_key,
|
||
&config.model, // 如 "claude-sonnet-4-20250514"
|
||
config.base_url.as_deref(),
|
||
)?)),
|
||
"openai" => Ok(Box::new(OpenAiProvider::new(
|
||
&config.api_key,
|
||
&config.model, // 如 "gpt-4o"
|
||
config.base_url.as_deref(),
|
||
)?)),
|
||
"ollama" => Ok(Box::new(OllamaProvider::new(
|
||
&config.model, // 如 "qwen2.5:32b"
|
||
config.base_url.as_deref().unwrap_or("http://localhost:11434"),
|
||
)?)),
|
||
other => Err(LlmError::UnknownProvider(other.to_string())),
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.9 MCP Client — 外部工具扩展
|
||
|
||
**职责**:通过 rmcp(Rust MCP SDK)连接外部 MCP Server,动态获取额外工具供 Agent 使用。
|
||
|
||
**模块接口**:
|
||
|
||
```rust
|
||
/// MCP 客户端管理器
|
||
pub struct McpClientManager {
|
||
clients: HashMap<String, McpClient>, // server_name → client
|
||
config: McpConfig,
|
||
}
|
||
|
||
pub struct McpConfig {
|
||
pub servers: Vec<McpServerConfig>,
|
||
}
|
||
|
||
pub struct McpServerConfig {
|
||
pub name: String, // 服务器标识名
|
||
pub command: String, // 启动命令
|
||
pub args: Vec<String>, // 命令参数
|
||
pub env: HashMap<String, String>, // 环境变量
|
||
}
|
||
|
||
impl McpClientManager {
|
||
/// 创建并连接所有配置的 MCP Server
|
||
pub async fn new(config: McpConfig) -> Result<Self, McpError>;
|
||
|
||
/// 获取所有 MCP Server 提供的工具列表
|
||
pub fn list_tools(&self) -> Vec<ToolDefinition>;
|
||
|
||
/// 调用 MCP 工具
|
||
pub async fn call_tool(
|
||
&self,
|
||
server: &str,
|
||
tool: &str,
|
||
params: serde_json::Value,
|
||
) -> Result<serde_json::Value, McpError>;
|
||
|
||
/// 关闭所有连接
|
||
pub async fn shutdown(&mut self);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 浏览器 C++ 端模块详细设计
|
||
|
||
### 3.1 SgClawProcessHost — 进程宿主
|
||
|
||
**职责**:Singleton,管理 sgclaw 子进程的完整生命周期(Start / Stop / OnCrash),创建和持有 STDIO Pipe。
|
||
|
||
**接口声明**:
|
||
|
||
```cpp
|
||
// sg_claw_process_host.h
|
||
|
||
#ifndef CHROME_BROWSER_SUPERRPA_SGCLAW_SG_CLAW_PROCESS_HOST_H_
|
||
#define CHROME_BROWSER_SUPERRPA_SGCLAW_SG_CLAW_PROCESS_HOST_H_
|
||
|
||
#include <memory>
|
||
#include <string>
|
||
#include "base/process/launch.h"
|
||
#include "base/process/process.h"
|
||
|
||
namespace superrpa {
|
||
namespace sgclaw {
|
||
|
||
class PipeListener;
|
||
|
||
// sgClaw 进程状态
|
||
enum class ProcessState {
|
||
kStopped, // 初始状态 / 已停止
|
||
kStarting, // 正在启动 (等待 handshake)
|
||
kRunning, // 正常运行
|
||
kStopping, // 正在停止
|
||
kCrashed, // 异常退出
|
||
};
|
||
|
||
// 进程事件观察者
|
||
class ProcessObserver {
|
||
public:
|
||
virtual ~ProcessObserver() = default;
|
||
virtual void OnStateChanged(ProcessState new_state) = 0;
|
||
virtual void OnPipeMessage(const std::string& json_line) = 0;
|
||
virtual void OnProcessCrash(int exit_code, const std::string& stderr_output) = 0;
|
||
};
|
||
|
||
// sgClaw 进程宿主 (Singleton)
|
||
class SgClawProcessHost {
|
||
public:
|
||
static SgClawProcessHost* GetInstance();
|
||
|
||
// 启动 sgClaw 子进程
|
||
// 成功返回 true,失败返回 false 并通过 observer 通知
|
||
bool Start();
|
||
|
||
// 优雅停止 sgClaw
|
||
void Stop();
|
||
|
||
// 发送消息到 sgClaw
|
||
bool SendMessage(const std::string& json_line);
|
||
|
||
// 获取当前状态
|
||
ProcessState GetState() const;
|
||
|
||
// 注册/移除观察者
|
||
void AddObserver(ProcessObserver* observer);
|
||
void RemoveObserver(ProcessObserver* observer);
|
||
|
||
private:
|
||
SgClawProcessHost();
|
||
~SgClawProcessHost();
|
||
|
||
// 禁止拷贝
|
||
SgClawProcessHost(const SgClawProcessHost&) = delete;
|
||
SgClawProcessHost& operator=(const SgClawProcessHost&) = delete;
|
||
|
||
// 内部方法
|
||
base::FilePath GetSgClawBinaryPath() const;
|
||
void OnChildProcessExited(int exit_code);
|
||
void DoHandshake();
|
||
|
||
// 成员
|
||
ProcessState state_ = ProcessState::kStopped;
|
||
base::Process child_process_;
|
||
std::unique_ptr<PipeListener> pipe_listener_;
|
||
|
||
// Pipe 文件描述符 (Linux: fd, Windows: HANDLE)
|
||
base::ScopedFD pipe_write_fd_; // Browser → sgClaw
|
||
base::ScopedFD pipe_read_fd_; // sgClaw → Browser
|
||
|
||
std::vector<ProcessObserver*> observers_;
|
||
};
|
||
|
||
} // namespace sgclaw
|
||
} // namespace superrpa
|
||
|
||
#endif
|
||
```
|
||
|
||
**关键实现说明**:
|
||
|
||
- **Singleton**:使用 `base::NoDestructor<SgClawProcessHost>` 实现进程全局唯一实例
|
||
- **Start() 流程**:检查二进制存在 → 创建 pipe pair → `base::LaunchProcess` → 启动 PipeListener → 发送 handshake → 等待 ack (5s 超时) → 状态切换到 Running
|
||
- **Stop() 流程**:发送 `{"type":"shutdown"}` → 等待进程退出 (2s 超时) → SIGTERM → 再等 2s → SIGKILL
|
||
- **OnChildProcessExited()**:检查退出码,非零且非主动 Stop → 视为 Crash → 通知 observers → 不自动重启
|
||
- **线程安全**:所有方法在 Browser UI 线程调用,Pipe 读取在 IO 线程
|
||
|
||
### 3.2 PipeListener — 异步读取循环
|
||
|
||
**职责**:在 IO 线程上异步读取 sgClaw 的 stdout 输出,按行解析 JSON,分发到 MAC 校验和 CommandRouter。
|
||
|
||
**接口声明**:
|
||
|
||
```cpp
|
||
// pipe_listener.h
|
||
|
||
#ifndef CHROME_BROWSER_SUPERRPA_SGCLAW_PIPE_LISTENER_H_
|
||
#define CHROME_BROWSER_SUPERRPA_SGCLAW_PIPE_LISTENER_H_
|
||
|
||
#include <string>
|
||
#include <functional>
|
||
#include "base/files/file_descriptor_watcher_posix.h" // Linux
|
||
// Windows: base/win/object_watcher.h
|
||
|
||
namespace superrpa {
|
||
namespace sgclaw {
|
||
|
||
class PipeListener {
|
||
public:
|
||
using MessageCallback = std::function<void(const std::string& json_line)>;
|
||
using ErrorCallback = std::function<void(const std::string& error)>;
|
||
|
||
PipeListener(base::ScopedFD read_fd,
|
||
MessageCallback on_message,
|
||
ErrorCallback on_error);
|
||
~PipeListener();
|
||
|
||
// 开始异步监听
|
||
void StartListening();
|
||
|
||
// 停止监听
|
||
void StopListening();
|
||
|
||
private:
|
||
void OnReadable(); // fd 可读回调
|
||
void ProcessBuffer(); // 从 buffer 中提取完整行
|
||
bool ValidateMessageSize(const std::string& line); // 检查消息大小 (<=1MB)
|
||
|
||
base::ScopedFD read_fd_;
|
||
std::string read_buffer_; // 累积读取缓冲
|
||
MessageCallback on_message_;
|
||
ErrorCallback on_error_;
|
||
|
||
// Linux: FileDescriptorWatcher
|
||
std::unique_ptr<base::FileDescriptorWatcher::Controller> fd_watcher_;
|
||
};
|
||
|
||
} // namespace sgclaw
|
||
} // namespace superrpa
|
||
|
||
#endif
|
||
```
|
||
|
||
**读取流程**:
|
||
|
||
```
|
||
StartListening()
|
||
│
|
||
└── 注册 fd_watcher_ 监听 read_fd_ 可读事件
|
||
│
|
||
▼
|
||
OnReadable() [IO 线程回调]
|
||
│
|
||
├── read(read_fd_, temp_buffer, 4096)
|
||
│ → 返回 0: EOF → 通知 process exited
|
||
│ → 返回 -1: EAGAIN → 忽略 (非阻塞 I/O)
|
||
│ → 返回 N: 追加到 read_buffer_
|
||
│
|
||
└── ProcessBuffer()
|
||
│
|
||
├── 在 read_buffer_ 中查找 '\n'
|
||
│ → 找到: 提取该行作为 json_line
|
||
│ → 未找到: 等待下次 OnReadable()
|
||
│
|
||
├── ValidateMessageSize(json_line)
|
||
│ → > 1MB: 丢弃 + 日志警告
|
||
│
|
||
└── on_message_(json_line)
|
||
│
|
||
└── [UI 线程] MAC 校验 → CommandRouter
|
||
```
|
||
|
||
### 3.3 MAC Whitelist Check — Pipe 来源 MAC 校验
|
||
|
||
**职责**:对通过 Pipe 收到的命令进行强制访问控制校验,作为安全架构 Layer 3 的实现。
|
||
|
||
**接口声明**:
|
||
|
||
```cpp
|
||
// mac_whitelist_check.h
|
||
|
||
#ifndef CHROME_BROWSER_SUPERRPA_SGCLAW_MAC_WHITELIST_CHECK_H_
|
||
#define CHROME_BROWSER_SUPERRPA_SGCLAW_MAC_WHITELIST_CHECK_H_
|
||
|
||
#include <string>
|
||
#include <set>
|
||
#include "base/values.h"
|
||
|
||
namespace superrpa {
|
||
namespace sgclaw {
|
||
|
||
// MAC 校验结果
|
||
enum class MacResult {
|
||
kAllow, // 允许
|
||
kDeny, // 拒绝
|
||
kNeedConfirm, // 需用户确认
|
||
};
|
||
|
||
struct MacCheckResult {
|
||
MacResult result;
|
||
std::string reason; // 拒绝/确认原因
|
||
};
|
||
|
||
class MacWhitelistCheck {
|
||
public:
|
||
// 从 rules.json 加载策略
|
||
static std::unique_ptr<MacWhitelistCheck> CreateFromRules(
|
||
const base::FilePath& rules_path);
|
||
|
||
// 校验 pipe 命令
|
||
MacCheckResult Check(const std::string& action,
|
||
const std::string& expected_domain,
|
||
const std::string& current_page_domain) const;
|
||
|
||
private:
|
||
MacWhitelistCheck();
|
||
|
||
std::set<std::string> allowed_actions_;
|
||
std::set<std::string> blocked_actions_;
|
||
std::set<std::string> confirm_actions_;
|
||
std::set<std::string> allowed_domains_;
|
||
};
|
||
|
||
} // namespace sgclaw
|
||
} // namespace superrpa
|
||
|
||
#endif
|
||
```
|
||
|
||
**校验流程**:
|
||
|
||
```
|
||
Check(action, expected_domain, current_page_domain):
|
||
│
|
||
├── 1. action 在 blocked_actions_ 中?
|
||
│ → 是: return Deny("Action is blocked: " + action)
|
||
│
|
||
├── 2. action 不在 allowed_actions_ 中?
|
||
│ → 是: return Deny("Action not in pipe whitelist: " + action)
|
||
│
|
||
├── 3. expected_domain 不在 allowed_domains_ 中?
|
||
│ → 是: return Deny("Domain not in whitelist: " + expected_domain)
|
||
│
|
||
├── 4. expected_domain != current_page_domain?
|
||
│ → 是: return Deny("Domain mismatch: expected " + expected_domain
|
||
│ + " but current is " + current_page_domain)
|
||
│
|
||
├── 5. action 在 confirm_actions_ 中?
|
||
│ → 是: return NeedConfirm("操作需要用户确认: " + action)
|
||
│
|
||
└── 6. return Allow
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 前端 Side Panel 新增 UI
|
||
|
||
### 4.1 AgentControlPanel.vue
|
||
|
||
**职责**:在 Side Panel 中提供 Agent 控制面板,包括启停按钮、指令输入、状态显示、执行日志流。
|
||
|
||
**组件接口**:
|
||
|
||
```
|
||
AgentControlPanel.vue
|
||
│
|
||
├── Props: 无 (独立组件)
|
||
│
|
||
├── State:
|
||
│ ├── agentState: 'stopped' | 'starting' | 'running' | 'error'
|
||
│ ├── taskInput: string // 用户输入的自然语言指令
|
||
│ ├── logs: LogEntry[] // 执行日志条目
|
||
│ ├── currentTask: string // 当前执行的任务描述
|
||
│ └── errorMessage: string // 错误信息
|
||
│
|
||
├── Methods:
|
||
│ ├── startAgent() // 调用 FunctionsUI → SgClawProcessHost::Start()
|
||
│ ├── stopAgent() // 调用 FunctionsUI → SgClawProcessHost::Stop()
|
||
│ ├── submitTask(instruction) // 发送任务到 Agent
|
||
│ ├── confirmAction(actionId) // 用户确认 human-in-the-loop 操作
|
||
│ └── rejectAction(actionId) // 用户拒绝操作
|
||
│
|
||
└── Events (from C++ via FunctionsUI):
|
||
├── onStateChanged(state) // Agent 状态变更
|
||
├── onLogEntry(entry) // 新日志条目
|
||
├── onConfirmRequired(action) // 需要用户确认的操作
|
||
└── onTaskCompleted(result) // 任务完成
|
||
```
|
||
|
||
**UI 布局**:
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ sgClaw Agent │
|
||
├─────────────────────────────────────┤
|
||
│ │
|
||
│ 状态: ● 运行中 │
|
||
│ │
|
||
│ ┌─────────────────────────────┐ │
|
||
│ │ 请输入业务指令... │ │
|
||
│ │ │ │
|
||
│ └─────────────────────────────┘ │
|
||
│ [ 发送 ] │
|
||
│ │
|
||
│ ─── 执行日志 ─────────────────── │
|
||
│ │
|
||
│ ▸ [10:23:01] 正在分析指令... │
|
||
│ ▸ [10:23:03] 导航至 ERP 系统 │
|
||
│ ▸ [10:23:05] 点击"财务报表"菜单 │
|
||
│ ▸ [10:23:08] 设置时间范围: 本月 │
|
||
│ ▸ [10:23:10] 正在导出数据... │
|
||
│ │
|
||
│ ─── 确认请求 ─────────────────── │
|
||
│ │
|
||
│ ⚠ Agent 请求执行登录操作 │
|
||
│ 目标系统: erp.example.com │
|
||
│ [ 允许 ] [ 拒绝 ] │
|
||
│ │
|
||
├─────────────────────────────────────┤
|
||
│ [ 启动 Agent ] [ 停止 Agent ] │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
**与 C++ 层的通信**:
|
||
|
||
前端通过 FunctionsUI 机制(SuperRPA 已有的 JS → C++ IPC)与 SgClawProcessHost 交互。
|
||
|
||
```javascript
|
||
// 启动 Agent
|
||
window.sgFunctionsUI('sgclaw_start', {}, (response) => {
|
||
// response: { success: true } 或 { success: false, error: "..." }
|
||
});
|
||
|
||
// 停止 Agent
|
||
window.sgFunctionsUI('sgclaw_stop', {}, (response) => { ... });
|
||
|
||
// 发送任务
|
||
window.sgFunctionsUI('sgclaw_submit_task', {
|
||
instruction: "导出本月合规报表"
|
||
}, (response) => { ... });
|
||
|
||
// 确认/拒绝操作
|
||
window.sgFunctionsUI('sgclaw_confirm', {
|
||
action_id: "abc123",
|
||
approved: true
|
||
}, (response) => { ... });
|
||
```
|
||
|
||
**事件接收**(C++ → JS 推送):
|
||
|
||
```javascript
|
||
// C++ 通过 ExecuteJavaScript 推送事件到 Side Panel
|
||
// 前端注册全局事件处理器
|
||
window.sgClawEvents = {
|
||
onStateChanged: function(state) {
|
||
// state: 'stopped' | 'starting' | 'running' | 'error'
|
||
vm.agentState = state;
|
||
},
|
||
onLogEntry: function(entry) {
|
||
// entry: { time: "10:23:01", message: "正在分析指令...", level: "info" }
|
||
vm.logs.push(entry);
|
||
},
|
||
onConfirmRequired: function(action) {
|
||
// action: { id: "abc123", type: "sessionLogin", domain: "erp.example.com" }
|
||
vm.pendingConfirm = action;
|
||
},
|
||
onTaskCompleted: function(result) {
|
||
// result: { success: true, summary: "已成功导出3份报表" }
|
||
vm.currentTask = null;
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 接口契约
|
||
|
||
### 5.1 Pipe JSON 协议完整 Schema
|
||
|
||
以下定义了 sgClaw 与 Browser 之间所有 Pipe 消息的完整 JSON Schema。
|
||
|
||
**Handshake — Browser → sgClaw**:
|
||
|
||
```json
|
||
{
|
||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||
"title": "PipeInitMessage",
|
||
"type": "object",
|
||
"required": ["type", "version", "hmac_seed"],
|
||
"properties": {
|
||
"type": { "const": "init" },
|
||
"version": {
|
||
"type": "string",
|
||
"pattern": "^\\d+\\.\\d+$",
|
||
"description": "协议版本号 (如 1.0)"
|
||
},
|
||
"hmac_seed": {
|
||
"type": "string",
|
||
"minLength": 32,
|
||
"maxLength": 64,
|
||
"description": "HMAC 密钥种子 (hex 编码)"
|
||
},
|
||
"capabilities": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "Browser 支持的扩展能力列表"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Handshake — sgClaw → Browser**:
|
||
|
||
```json
|
||
{
|
||
"title": "PipeInitAckMessage",
|
||
"type": "object",
|
||
"required": ["type", "version", "agent_id"],
|
||
"properties": {
|
||
"type": { "const": "init_ack" },
|
||
"version": {
|
||
"type": "string",
|
||
"description": "sgClaw 协议版本 (必须与 init.version 一致)"
|
||
},
|
||
"agent_id": {
|
||
"type": "string",
|
||
"description": "Agent 实例唯一标识 (UUID v4)"
|
||
},
|
||
"supported_actions": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "sgClaw 可使用的 action 列表"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Command — sgClaw → Browser**:
|
||
|
||
```json
|
||
{
|
||
"title": "PipeCommandMessage",
|
||
"type": "object",
|
||
"required": ["seq", "type", "action", "params", "security"],
|
||
"properties": {
|
||
"seq": {
|
||
"type": "integer",
|
||
"minimum": 1,
|
||
"description": "全局递增序列号"
|
||
},
|
||
"type": { "const": "command" },
|
||
"action": {
|
||
"type": "string",
|
||
"enum": [
|
||
"click", "type", "navigate", "getText", "getHtml",
|
||
"waitForSelector", "pageScreenshot", "select", "scrollTo",
|
||
"getAomSnapshot", "storageSet", "storageGet",
|
||
"zombieSpawn", "zombieKill"
|
||
]
|
||
},
|
||
"params": {
|
||
"type": "object",
|
||
"description": "操作参数,格式取决于 action (见 §5.2)"
|
||
},
|
||
"security": {
|
||
"type": "object",
|
||
"required": ["expected_domain", "hmac"],
|
||
"properties": {
|
||
"expected_domain": {
|
||
"type": "string",
|
||
"description": "期望操作的目标域名"
|
||
},
|
||
"hmac": {
|
||
"type": "string",
|
||
"description": "HMAC-SHA256 签名 (hex)"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Response — Browser → sgClaw**:
|
||
|
||
```json
|
||
{
|
||
"title": "PipeResponseMessage",
|
||
"type": "object",
|
||
"required": ["seq", "type", "success"],
|
||
"properties": {
|
||
"seq": {
|
||
"type": "integer",
|
||
"description": "对应 command 的序列号"
|
||
},
|
||
"type": { "const": "response" },
|
||
"success": { "type": "boolean" },
|
||
"data": {
|
||
"type": "object",
|
||
"description": "操作结果数据 (成功时)"
|
||
},
|
||
"error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"code": { "type": "string" },
|
||
"message": { "type": "string" }
|
||
},
|
||
"description": "错误信息 (失败时)"
|
||
},
|
||
"aom_snapshot": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"properties": {
|
||
"role": { "type": "string" },
|
||
"name": { "type": "string" },
|
||
"bounds": {
|
||
"type": "array",
|
||
"items": { "type": "integer" },
|
||
"minItems": 4,
|
||
"maxItems": 4,
|
||
"description": "[x, y, width, height]"
|
||
},
|
||
"value": { "type": "string" },
|
||
"children": { "type": "array" }
|
||
}
|
||
},
|
||
"description": "操作后的 AOM 快照"
|
||
},
|
||
"timing": {
|
||
"type": "object",
|
||
"properties": {
|
||
"queue_ms": { "type": "integer" },
|
||
"exec_ms": { "type": "integer" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 各 Action 参数 Schema
|
||
|
||
**click**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string",
|
||
"description": "CSS 选择器"
|
||
},
|
||
"wait_after": {
|
||
"type": "integer",
|
||
"minimum": 0,
|
||
"maximum": 30000,
|
||
"default": 1000,
|
||
"description": "点击后等待时间 (ms)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**type**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector", "text"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string",
|
||
"description": "CSS 选择器"
|
||
},
|
||
"text": {
|
||
"type": "string",
|
||
"maxLength": 10000,
|
||
"description": "要输入的文本"
|
||
},
|
||
"clear_first": {
|
||
"type": "boolean",
|
||
"default": true,
|
||
"description": "输入前是否清空已有内容"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**navigate**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["url"],
|
||
"properties": {
|
||
"url": {
|
||
"type": "string",
|
||
"format": "uri",
|
||
"description": "目标 URL (必须在域白名单内)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**getText**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string",
|
||
"description": "CSS 选择器"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**getHtml**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string"
|
||
},
|
||
"outer": {
|
||
"type": "boolean",
|
||
"default": false,
|
||
"description": "true 返回 outerHTML, false 返回 innerHTML"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**waitForSelector**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string"
|
||
},
|
||
"timeout_ms": {
|
||
"type": "integer",
|
||
"minimum": 100,
|
||
"maximum": 30000,
|
||
"default": 5000,
|
||
"description": "等待超时 (ms)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**pageScreenshot**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"full_page": {
|
||
"type": "boolean",
|
||
"default": false,
|
||
"description": "是否截取整页 (true) 或仅可视区域 (false)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**select**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["selector", "value"],
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string",
|
||
"description": "select 元素的 CSS 选择器"
|
||
},
|
||
"value": {
|
||
"type": "string",
|
||
"description": "要选择的 option 的 value 值"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**scrollTo**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"selector": {
|
||
"type": "string",
|
||
"description": "滚动到指定元素 (优先于 x/y)"
|
||
},
|
||
"x": {
|
||
"type": "integer",
|
||
"description": "水平滚动位置 (px)"
|
||
},
|
||
"y": {
|
||
"type": "integer",
|
||
"description": "垂直滚动位置 (px)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**getAomSnapshot**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"root_selector": {
|
||
"type": "string",
|
||
"description": "从指定元素开始获取 AOM 子树 (默认整页)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**storageSet**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["key", "value"],
|
||
"properties": {
|
||
"key": {
|
||
"type": "string",
|
||
"pattern": "^sgclaw\\.",
|
||
"description": "存储键名 (必须以 sgclaw. 开头)"
|
||
},
|
||
"value": {
|
||
"type": "string",
|
||
"maxLength": 65536,
|
||
"description": "存储值"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**storageGet**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["key"],
|
||
"properties": {
|
||
"key": {
|
||
"type": "string",
|
||
"pattern": "^sgclaw\\.",
|
||
"description": "存储键名 (必须以 sgclaw. 开头)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**zombieSpawn**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["url"],
|
||
"properties": {
|
||
"url": {
|
||
"type": "string",
|
||
"format": "uri",
|
||
"description": "Zombie page 加载的 URL (必须在域白名单内)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**zombieKill**:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["page_id"],
|
||
"properties": {
|
||
"page_id": {
|
||
"type": "string",
|
||
"description": "要销毁的 zombie page ID"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 错误码体系
|
||
|
||
所有错误响应使用统一的错误码格式:
|
||
|
||
```json
|
||
{
|
||
"seq": 5,
|
||
"type": "response",
|
||
"success": false,
|
||
"error": {
|
||
"code": "MAC_DOMAIN_MISMATCH",
|
||
"message": "Expected domain erp.example.com but current page is oa.example.com"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误码分类**:
|
||
|
||
| 错误码前缀 | 来源 | 描述 |
|
||
|-----------|------|------|
|
||
| `PIPE_*` | PipeListener | Pipe 传输层错误 |
|
||
| `MAC_*` | MAC Whitelist Check | 安全策略拒绝 |
|
||
| `CMD_*` | CommandRouter | 命令执行错误 |
|
||
| `SESSION_*` | SessionManager | 会话相关错误 |
|
||
| `INTERNAL_*` | 内部 | 系统内部错误 |
|
||
|
||
**完整错误码列表**:
|
||
|
||
| 错误码 | 描述 | 建议处理 |
|
||
|--------|------|---------|
|
||
| `PIPE_INVALID_JSON` | JSON 解析失败 | 检查消息格式 |
|
||
| `PIPE_MESSAGE_TOO_LARGE` | 消息超过 1MB 限制 | 减小消息体积 |
|
||
| `PIPE_SEQ_DUPLICATE` | 序列号重复 | 检查 seq 生成逻辑 |
|
||
| `PIPE_SEQ_OUT_OF_ORDER` | 序列号乱序 | 检查消息发送顺序 |
|
||
| `PIPE_HMAC_INVALID` | HMAC 签名校验失败 | 检查密钥和签名算法 |
|
||
| `MAC_ACTION_BLOCKED` | Action 在黑名单中 | 使用允许的 action |
|
||
| `MAC_ACTION_NOT_ALLOWED` | Action 不在白名单中 | 检查 action 名称 |
|
||
| `MAC_DOMAIN_NOT_ALLOWED` | 域名不在白名单中 | 联系管理员添加域名 |
|
||
| `MAC_DOMAIN_MISMATCH` | 期望域名与实际不匹配 | 确认当前页面域名 |
|
||
| `MAC_RATE_LIMIT` | 超过速率限制 | 降低操作频率 |
|
||
| `MAC_NEED_CONFIRM` | 操作需要用户确认 | 等待用户确认 |
|
||
| `CMD_SELECTOR_NOT_FOUND` | 选择器未匹配到元素 | 检查选择器或等待元素 |
|
||
| `CMD_SELECTOR_TIMEOUT` | 等待元素超时 | 增加超时时间 |
|
||
| `CMD_NAVIGATION_FAILED` | 页面导航失败 | 检查 URL 可达性 |
|
||
| `CMD_ZOMBIE_POOL_FULL` | Zombie 池已满 (max 5) | 先销毁不需要的 zombie |
|
||
| `CMD_ZOMBIE_NOT_FOUND` | 指定的 zombie page 不存在 | 检查 page_id |
|
||
| `SESSION_EXPIRED` | 会话已过期 | 触发重新登录 |
|
||
| `SESSION_LOGIN_FAILED` | 登录失败 | 检查凭证或网络 |
|
||
| `INTERNAL_UNKNOWN` | 未知内部错误 | 查看日志 |
|
||
|
||
### 5.4 Handshake 协议流程
|
||
|
||
```
|
||
Browser sgClaw
|
||
│ │
|
||
│ (1) Start child process │
|
||
│ ──────────────────────────────────→ │
|
||
│ │
|
||
│ (2) init message │
|
||
│ {"type":"init", │
|
||
│ "version":"1.0", │
|
||
│ "hmac_seed":"a1b2c3..."} │
|
||
│ ──────────────────────────────────→ │
|
||
│ │ 校验版本号
|
||
│ │ 派生 HMAC 密钥
|
||
│ (3) init_ack │
|
||
│ {"type":"init_ack", │
|
||
│ "version":"1.0", │
|
||
│ "agent_id":"uuid-v4", │
|
||
│ "supported_actions":[...]} │
|
||
│ ◄────────────────────────────────── │
|
||
│ │
|
||
│ 校验版本号 │
|
||
│ 记录 agent_id (审计) │
|
||
│ 状态 → Running │
|
||
│ │
|
||
│ (4) 正常通信开始 │
|
||
│ ◄═══════════════════════════════════►│
|
||
│ │
|
||
```
|
||
|
||
**超时处理**:
|
||
- Browser 发送 init 后等待 5 秒
|
||
- 超时未收到 init_ack → Kill 子进程 → 状态切换到 Crashed → 通知 UI
|
||
|
||
**版本不匹配处理**:
|
||
- sgClaw 收到的 version 与自身版本不一致 → 发送 error ack → 主动退出
|
||
- Browser 收到的 version 与自身不一致 → Kill 子进程 → 报错
|
||
|
||
---
|
||
|
||
## 6. 跨模块交互时序
|
||
|
||
### 6.1 用户启动 Agent 完整时序
|
||
|
||
```
|
||
用户 Side Panel C++ (Browser) Rust (sgClaw)
|
||
│ │ │ │
|
||
│ 点击 [启动] │ │ │
|
||
│ ──────────────→ │ │ │
|
||
│ │ sgclaw_start │ │
|
||
│ │ ────────────────→ │ │
|
||
│ │ │ Start() │
|
||
│ │ │ ├ 检查二进制 │
|
||
│ │ │ ├ 创建 pipe pair │
|
||
│ │ │ ├ LaunchProcess │
|
||
│ │ │ │ ─────────────────→ │ main()
|
||
│ │ │ │ │ ├ 初始化
|
||
│ │ │ │ init message │ ├ 等待 init
|
||
│ │ │ │ ─────────────────→ │
|
||
│ │ │ │ │ ├ 校验版本
|
||
│ │ │ │ init_ack │ ├ 派生 HMAC
|
||
│ │ │ │ ◄───────────────── │
|
||
│ │ │ ├ 状态 → Running │
|
||
│ │ { success: true } │ │
|
||
│ │ ◄──────────────── │ │
|
||
│ 状态: ● 运行中 │ │ │
|
||
│ ◄────────────── │ │ │
|
||
```
|
||
|
||
### 6.2 用户提交任务完整时序
|
||
|
||
```
|
||
用户 Side Panel C++ (Browser) Rust (sgClaw) LLM
|
||
│ │ │ │ │
|
||
│ "导出合规报表" │ │ │ │
|
||
│ ───────────→ │ │ │ │
|
||
│ │ sgclaw_submit_task │ │ │
|
||
│ │ ──────────────────→ │ │ │
|
||
│ │ │ Pipe: submit_task │ │
|
||
│ │ │ ──────────────────→ │ │
|
||
│ │ │ │ execute_task() │
|
||
│ │ │ │ │
|
||
│ │ │ │ ┌─ ReAct Loop ───┤
|
||
│ │ │ │ │ │
|
||
│ │ │ │ │ THINK │
|
||
│ │ │ │ │ ──────────────→│
|
||
│ │ │ │ │ LLM response │
|
||
│ │ │ │ │ ◄──────────────│
|
||
│ │ │ │ │ │
|
||
│ │ │ │ │ ACT │
|
||
│ │ │ Pipe: click │ │ │
|
||
│ │ │ ◄────────────────── │ │ │
|
||
│ │ │ MAC Check → Allow │ │ │
|
||
│ │ │ CommandRouter.exec │ │ │
|
||
│ │ │ Pipe: response │ │ │
|
||
│ │ │ ──────────────────→ │ │ │
|
||
│ │ │ │ │ OBSERVE │
|
||
│ │ │ │ │ CRITIC │
|
||
│ │ │ │ │ │
|
||
│ │ │ │ │ (repeat...) │
|
||
│ │ │ │ └────────────────┤
|
||
│ │ │ │ │
|
||
│ │ │ Pipe: task_complete │ │
|
||
│ │ │ ◄────────────────── │ │
|
||
│ │ onTaskCompleted │ │ │
|
||
│ │ ◄────────────────── │ │ │
|
||
│ "已导出3份报表" │ │ │ │
|
||
│ ◄─────────── │ │ │ │
|
||
```
|
||
|
||
### 6.3 Human-in-the-loop 确认时序
|
||
|
||
```
|
||
Rust (sgClaw) C++ (Browser) Side Panel 用户
|
||
│ │ │ │
|
||
│ Pipe: command │ │ │
|
||
│ action=sessionLogin │ │ │
|
||
│ ──────────────────→ │ │ │
|
||
│ │ MAC: NeedConfirm │ │
|
||
│ │ push onConfirmReq │ │
|
||
│ │ ──────────────────→ │ │
|
||
│ │ │ 显示确认对话框 │
|
||
│ │ │ ───────────────→ │
|
||
│ │ │ │
|
||
│ │ │ 用户点击 [允许] │
|
||
│ │ │ ◄─────────────── │
|
||
│ │ sgclaw_confirm │ │
|
||
│ │ ◄────────────────── │ │
|
||
│ │ │ │
|
||
│ │ CommandRouter.exec │ │
|
||
│ │ (sessionLogin) │ │
|
||
│ Pipe: response │ │ │
|
||
│ ◄────────────────── │ │ │
|
||
│ │ │ │
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 配置体系
|
||
|
||
### 7.1 sgclaw.toml 配置文件
|
||
|
||
```toml
|
||
# sgClaw 配置文件
|
||
# 路径: 与 sgclaw 二进制同目录, 或 $SGCLAW_CONFIG 指定
|
||
|
||
[general]
|
||
# Agent 日志级别: trace | debug | info | warn | error
|
||
log_level = "info"
|
||
|
||
[llm]
|
||
# LLM Provider: claude | openai | ollama
|
||
provider = "claude"
|
||
model = "claude-sonnet-4-20250514"
|
||
# API Key (建议通过环境变量 SGCLAW_LLM_API_KEY 设置)
|
||
# api_key = "sk-..."
|
||
# 自定义 API 端点 (可选)
|
||
# base_url = "https://api.example.com/v1"
|
||
|
||
[llm.config]
|
||
max_tokens = 4096
|
||
temperature = 0.1
|
||
|
||
[agent]
|
||
# 单次任务最大步数
|
||
max_steps = 50
|
||
# 系统提示模板路径 (相对于 sgclaw 二进制)
|
||
system_prompt_template = "prompts/system.txt"
|
||
|
||
[memory]
|
||
# SQLite 数据库路径
|
||
db_path = "data/memory.db"
|
||
# 短期记忆最大条数
|
||
short_term_max_messages = 50
|
||
# 短期记忆最大 token 数
|
||
short_term_max_tokens = 8000
|
||
|
||
[security]
|
||
# rules.json 路径 (域白名单 + action 白名单)
|
||
rules_path = "rules.json"
|
||
# Skill 公钥路径 (Ed25519)
|
||
skill_public_key_path = "keys/skill_verify.pub"
|
||
|
||
[skills]
|
||
# 技能目录路径
|
||
skills_dir = "sgclaw-skills"
|
||
|
||
[circuit_breaker]
|
||
# 连续失败熔断阈值
|
||
failure_threshold = 10
|
||
# 基础冷却时间 (秒)
|
||
cooldown_base_secs = 1
|
||
# 最大冷却时间 (秒)
|
||
cooldown_max_secs = 30
|
||
|
||
[mcp]
|
||
# MCP Server 配置 (可选)
|
||
# [[mcp.servers]]
|
||
# name = "filesystem"
|
||
# command = "npx"
|
||
# args = ["-y", "@anthropic/mcp-server-filesystem", "/tmp"]
|
||
```
|
||
|
||
### 7.2 环境变量覆盖
|
||
|
||
所有配置均可通过 `SGCLAW_` 前缀的环境变量覆盖:
|
||
|
||
| 环境变量 | 覆盖配置项 | 示例 |
|
||
|---------|-----------|------|
|
||
| `SGCLAW_LOG_LEVEL` | `general.log_level` | `debug` |
|
||
| `SGCLAW_LLM_PROVIDER` | `llm.provider` | `ollama` |
|
||
| `SGCLAW_LLM_MODEL` | `llm.model` | `qwen2.5:32b` |
|
||
| `SGCLAW_LLM_API_KEY` | `llm.api_key` | `sk-...` |
|
||
| `SGCLAW_LLM_BASE_URL` | `llm.base_url` | `http://localhost:11434` |
|
||
| `SGCLAW_MAX_STEPS` | `agent.max_steps` | `100` |
|
||
| `SGCLAW_RULES_PATH` | `security.rules_path` | `/etc/sgclaw/rules.json` |
|
||
|
||
优先级:环境变量 > 配置文件 > 编译时默认值。
|
||
|
||
---
|
||
|
||
*文档结束。本文档为 sgClaw L2 层模块设计与接口契约参考,各组应基于此文档中的接口定义
|
||
进行并行开发。接口变更需经三方组长会审后更新本文档。*
|