- Replace Python test plugins (echo, ai_test, claudecode) with Rust implementations driven by the plugin SDK - Convert fs/network/process/timer syscall modules to async/await - Change Network.OpenConn to return stream ConnEvent (conn_id handed to client in first "open" frame for Send/Close routing) - Bind timer callbacks to plugin identity; stream_call subscriptions are identity-checked - Update docs and regenerate Python SDK protobuf stubs Verified: cargo build/test/clippy clean; echo + ai_test full syscall suites pass; hermes & claudecode register and heartbeat; cross-plugin bridge.call routing and auth denial verified end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
202 lines
6.1 KiB
Markdown
202 lines
6.1 KiB
Markdown
# 02 - 插件模型
|
|
|
|
## 三种插件(对标 Linux 三层)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ 标准插件 ← 应用服务(nginx、docker) │
|
|
│ 可依赖基础插件 + 其他标准插件 │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ 基础插件 ← 基础 daemon(udevd、resolved) │
|
|
│ 只依赖 9 个 syscall,无插件依赖 │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ 系统插件 ← 内核模块 / 设备驱动(.ko) │
|
|
│ 注册为 syscall 的实现 │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ agentsd ← Linux 内核 + systemd(PID 1) │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### 系统插件(= 内核模块 / 驱动)
|
|
|
|
为 syscall 提供具体实现。类比 `insmod alsa_driver.ko`。
|
|
|
|
```yaml
|
|
id: alsa-audio
|
|
type: system
|
|
provides: audio
|
|
entry: ./libalsa_audio.so
|
|
```
|
|
|
|
- FFI 加载(.so/.dylib/.dll),零开销
|
|
- 只限 Rust / C ABI
|
|
- 同一 syscall 可有多个驱动,同时只激活一个
|
|
- 无依赖
|
|
|
|
---
|
|
|
|
### 基础插件(= systemd 基础 unit,无依赖)
|
|
|
|
只调 syscall,不调其他插件。类比 `systemd-resolved.service`(只用内核网络栈)。
|
|
|
|
```yaml
|
|
id: tts-edge
|
|
type: basic
|
|
syscalls:
|
|
- audio.play
|
|
- network.request
|
|
exports:
|
|
- name: speak
|
|
```
|
|
|
|
- 独立进程,gRPC/TCP 通信(后续切 UDS/Named Pipe)
|
|
- 零插件依赖,加载顺序无所谓(= 无 After/Requires)
|
|
- 任意语言
|
|
|
|
---
|
|
|
|
### 标准插件(= systemd 应用 unit,有依赖)
|
|
|
|
组合其他插件。类比 `nginx.service`(After=network.target, Requires=...)。
|
|
|
|
```yaml
|
|
id: voice-agent
|
|
type: standard
|
|
syscalls:
|
|
- input.text
|
|
- display.text
|
|
depends:
|
|
- tts-edge
|
|
- stt-whisper
|
|
- chat-agent
|
|
exports:
|
|
- name: voice_chat
|
|
```
|
|
|
|
- 可依赖基础插件 + 其他标准插件
|
|
- 按依赖拓扑排序加载(= systemd ordering)
|
|
- 任意语言
|
|
|
|
---
|
|
|
|
## 通信:gRPC(= D-Bus)
|
|
|
|
类比 Linux 的 D-Bus 进程间通信总线,agentsd 用 gRPC。
|
|
|
|
### 为什么 gRPC
|
|
|
|
- protobuf 二进制,快
|
|
- 流式 RPC(音频流、文件 watch)
|
|
- 所有语言都有库
|
|
- `.proto` = 接口契约 + 代码生成
|
|
|
|
### 传输分层
|
|
|
|
| 插件类型 | 传输 | 延迟 | Linux 类比 |
|
|
|---|---|---|---|
|
|
| 系统插件 | FFI | ~纳秒 | 内核态函数调用 |
|
|
| 基础/标准(本机,当前) | gRPC/TCP | ~毫秒 | D-Bus over TCP |
|
|
| 基础/标准(目标) | gRPC/Unix socket / Windows Named Pipe | ~微秒 | D-Bus over UDS |
|
|
| 远程(可选) | gRPC/TCP | ~毫秒 | D-Bus over TCP |
|
|
|
|
### proto 示例
|
|
|
|
```protobuf
|
|
syntax = "proto3";
|
|
|
|
service FS {
|
|
rpc Read(ReadRequest) returns (ReadResponse);
|
|
rpc Write(WriteRequest) returns (WriteResponse);
|
|
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
|
rpc List(ListRequest) returns (ListResponse);
|
|
rpc Watch(WatchRequest) returns (stream WatchEvent);
|
|
}
|
|
|
|
service Audio {
|
|
rpc Play(stream AudioChunk) returns (PlayResponse);
|
|
rpc Record(RecordRequest) returns (stream AudioChunk);
|
|
}
|
|
```
|
|
|
|
### 插件间调用(经内核路由)
|
|
|
|
```protobuf
|
|
service PluginBridge {
|
|
rpc Call(CallRequest) returns (CallResponse);
|
|
rpc StreamCall(CallRequest) returns (stream CallResponse);
|
|
}
|
|
|
|
message CallRequest {
|
|
string target = 1;
|
|
string fn = 2;
|
|
bytes args = 3;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 生命周期管理(= systemctl)
|
|
|
|
| agentsd 命令 | systemd 对标 | 作用 |
|
|
|---|---|---|
|
|
| `agentsd start <id>` | `systemctl start` | 启动插件 |
|
|
| `agentsd stop <id>` | `systemctl stop` | 停止插件 |
|
|
| `agentsd restart <id>` | `systemctl restart` | 重启 |
|
|
| `agentsd reload <id>` | `systemctl reload` | 热更新 |
|
|
| `agentsd status` | `systemctl status` | 状态查询 |
|
|
| `agentsd list` | `systemctl list-units` | 列出所有插件 |
|
|
| `agentsd logs <id>` | `journalctl -u` | 查日志 |
|
|
| `agentsd enable <id>` | `systemctl enable` | 开机自启 |
|
|
| `agentsd disable <id>` | `systemctl disable` | 取消自启 |
|
|
|
|
---
|
|
|
|
## 加载顺序(= systemd boot)
|
|
|
|
```
|
|
1. 系统插件(FFI,注册 syscall 实现) ← 类比内核模块加载
|
|
2. 基础插件(gRPC,任意顺序) ← 类比 basic.target
|
|
3. 标准插件(gRPC,拓扑排序) ← 类比 multi-user.target
|
|
```
|
|
|
|
---
|
|
|
|
## 看门狗(= Restart=always)
|
|
|
|
插件崩溃 → agentsd 自动重启(可配置策略):
|
|
- `always`:总是重启
|
|
- `on-failure`:异常退出才重启
|
|
- `never`:不重启
|
|
|
|
---
|
|
|
|
## 权限(= seccomp + cgroup)
|
|
|
|
- 系统插件:内核信任,无限制
|
|
- 基础插件:按 `syscalls` 白名单精确到方法(如 `fs.list`)校验,也支持 `fs` / `fs.*` / `*` 授权(= seccomp filter)
|
|
- 标准插件:syscalls + depends 白名单,未声明的一律拒绝
|
|
|
|
### 身份认证
|
|
|
|
所有 syscall 调用**必须**携带 `x-plugin-id` gRPC metadata,否则返回 `UNAUTHENTICATED`。
|
|
这是第一道安全边界——即使 server 监听 TCP,未注册的进程也无法调用任何 syscall。
|
|
|
|
认证流程:
|
|
1. 插件通过 `PluginBridge.Register` 注册,获得 `session_id`
|
|
2. 后续所有 syscall 请求须在 gRPC metadata 中带 `x-plugin-id: <plugin_id>`
|
|
3. 内核校验:plugin_id 存在 → status=Running → syscall 在该插件的白名单内 → 放行
|
|
|
|
### stream_call 身份绑定
|
|
|
|
`PluginBridge.StreamCall` 用于订阅 Timer 等回调事件。安全策略:
|
|
- 必须携带 `x-plugin-id`(否则 UNAUTHENTICATED)
|
|
- 只能订阅**自己**的事件通道(target 为空或等于自身 plugin_id)
|
|
- 尝试订阅其他插件的事件返回 PERMISSION_DENIED
|
|
|
|
### 死插件路由保护
|
|
|
|
`PluginBridge.Call` 在路由到目标插件前,校验其 status 为 Running。
|
|
Stopped/Failed 的插件会立即返回错误,而不是等 5 秒连接超时。
|