Files
claw/docs/L2-核心模块与接口契约层.md
2026-03-06 03:36:12 +08:00

2010 lines
68 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 是叶节点**:所有模块依赖 ConfigConfig 不依赖任何业务模块
- **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 — 外部工具扩展
**职责**:通过 rmcpRust 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 层模块设计与接口契约参考,各组应基于此文档中的接口定义
进行并行开发。接口变更需经三方组长会审后更新本文档。*