# 02 - 插件模型 ## 三种插件(对标 Linux 三层) ``` ┌─────────────────────────────────────────────────────┐ │ 标准插件 ← 应用服务(nginx、docker) │ │ 可依赖基础插件 + 其他标准插件 │ ├─────────────────────────────────────────────────────┤ │ 基础插件 ← 基础 daemon(udevd、resolved) │ │ 只依赖 9 个 syscall,无插件依赖 │ ├─────────────────────────────────────────────────────┤ │ 系统插件 ← 内核模块 / 设备驱动(.ko) │ │ 注册为 syscall 的实现 │ ├─────────────────────────────────────────────────────┤ │ agentsd ← Linux 内核 + systemd(PID 1) │ └─────────────────────────────────────────────────────┘ ``` --- ### 系统插件(= 内核模块 / 驱动)⏳ 设计目标,未实现 为 syscall 提供具体实现。类比 `insmod alsa_driver.ko`。 ```yaml # 设计示意(provides 字段与 FFI 加载尚未实现) id: alsa-audio type: system provides: audio entry: ./libalsa_audio.so ``` - FFI 加载(.so/.dylib/.dll),零开销 —— **未实现**,当前无动态库加载代码 - 只限 Rust / C ABI - 同一 syscall 可有多个驱动,同时只激活一个 - 无依赖 - 当前内核仅拒绝外部进程注册 `type: system`(防止权限提升),加载机制本身待实现 --- ### 基础插件(= systemd 基础 unit,无依赖) 只调 syscall,不调其他插件。类比 `systemd-resolved.service`(只用内核网络栈)。 ```yaml id: tts-edge name: Edge TTS version: 0.1.0 type: basic syscalls: - audio.play - network.request exports: - name: speak description: Text to speech entry: python tts_plugin.py ``` - 独立进程,gRPC/TCP 通信(后续切 UDS/Named Pipe) - 零插件依赖,加载顺序无所谓(= 无 After/Requires) - 任意语言 --- ### 标准插件(= systemd 应用 unit,有依赖) 组合其他插件。类比 `nginx.service`(After=network.target, Requires=...)。 ```yaml id: voice-agent name: Voice Agent version: 0.1.0 type: standard syscalls: - input.text - display.text depends: - tts-edge - stt-whisper - chat-agent exports: - name: voice_chat description: Voice conversation loop entry: cargo run -p voice-agent ``` - 可依赖基础插件 + 其他标准插件 - 按依赖拓扑排序加载(= systemd ordering)—— **未实现**:`depends` 当前仅随注册存储,不做解析/排序/校验(阶段 4) - 任意语言 > **plugin.yaml 的当前角色**:内核**不会**扫描或解析 plugin.yaml——它目前是给开发者看的清单(entry/port 供手动启动用)。 > 实际注册机制是插件进程启动后主动调用 `PluginBridge.Register`(gRPC),传 protobuf `PluginInfo{id, name, version, plugin_type, syscalls, depends, exports, endpoint}`。 > 基于 YAML 的自动发现/自动加载属于阶段 4 的插件管理目标。 --- ## 通信: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)⏳ 阶段 4 目标,未实现 以下命令为规划中的 CLI,当前内核仅提供 gRPC server,无任何子命令: | agentsd 命令 | systemd 对标 | 作用 | |---|---|---| | `agentsd start ` | `systemctl start` | 启动插件 | | `agentsd stop ` | `systemctl stop` | 停止插件 | | `agentsd restart ` | `systemctl restart` | 重启 | | `agentsd reload ` | `systemctl reload` | 热更新 | | `agentsd status` | `systemctl status` | 状态查询 | | `agentsd list` | `systemctl list-units` | 列出所有插件 | | `agentsd logs ` | `journalctl -u` | 查日志 | | `agentsd enable ` | `systemctl enable` | 开机自启 | | `agentsd disable ` | `systemctl disable` | 取消自启 | --- ## 加载顺序(= systemd boot)⏳ 设计目标,未实现 当前插件由开发者手动启动,启动后自行注册。规划中的加载顺序: ``` 1. 系统插件(FFI,注册 syscall 实现) ← 类比内核模块加载 2. 基础插件(gRPC,任意顺序) ← 类比 basic.target 3. 标准插件(gRPC,拓扑排序) ← 类比 multi-user.target ``` --- ## 看门狗(= Restart=always)⏳ 阶段 4 目标,未实现 插件崩溃 → agentsd 自动重启(可配置策略): - `always`:总是重启 - `on-failure`:异常退出才重启 - `never`:不重启 --- ## 权限(= seccomp + cgroup) - 系统插件:内核信任,无限制 - 基础/标准插件:按 `syscalls` 白名单精确到方法(如 `fs.list`)校验,也支持 `fs` / `fs.*` / `*` 授权(= seccomp filter) - `depends` 字段当前仅声明、随注册存储,不参与权限校验(依赖校验属阶段 4) ### 身份认证 所有 syscall 调用**必须**携带 `x-plugin-id` gRPC metadata,否则返回 `UNAUTHENTICATED`。 这是第一道安全边界——即使 server 监听 TCP,未注册的进程也无法调用任何 syscall。 认证流程: 1. 插件通过 `PluginBridge.Register` 注册(返回的 `session_id` 目前仅作回执,不参与后续校验) 2. 后续所有 syscall 请求须在 gRPC metadata 中带 `x-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 秒连接超时。