编写插件
一个 Wuu 插件包可以组合 agent 运行时、桌面 UI、技能、hook、MCP 服务器、命令、主题和设置。完整示例见 agent 快速上手或桌面快速上手。本页说明这些示例使用的包和运行时契约。
包结构和 manifest
Section titled “包结构和 manifest”包根目录包含 plugin.json,只需声明实际使用的入口和贡献:
{ "schema_version": 1, "id": "example-plugin", "name": "Example Plugin", "version": "0.1.0", "runtime": { "protocol": "wuu-plugin-v1", "command": "node", "args": ["dist/runtime.js"] }, "desktop": { "entry": "dist/renderer.js" }}这个例子需要两个已声明的文件。纯桌面或声明式插件可省略 runtime;不需要 renderer 代码时省略 desktop。桌面入口必须是包内相对路径指向的、自包含的 ESM 模块。打包会排除 node_modules,因此运行时依赖也必须在打包后可用;适用时应将 JavaScript 依赖一起打入运行时入口。
| 字段 | 契约 |
|---|---|
schema_version | 包 schema,目前为 1 |
id | 稳定的包身份,更新时应保持不变 |
version | 插件自身版本,与 Wuu 产品版本分开 |
minimum_wuu_version | 最低 Wuu CalVer,例如 2026.1108.0,不能代替服务或 API 兼容性检查 |
platforms | 可选的宿主平台限制 |
requires | 兼容旧格式的必需包 ID,不含版本约束 |
dependencies | { id, version?, optional? } 形式的包关系;必需依赖必须可用且满足版本约束 |
breaks | 硬不兼容关系;不兼容的包同时启用会拒绝激活计划 |
conflicts | 软冲突,只报告,不自动禁用任何一方 |
skills、hooks、mcpServers / mcp_servers | 分别使用技能、hook、MCP 契约的贡献 |
contributes | 命令、主题、设置、UI 声明和视图入口 |
作为同一产品交付的多个包可以声明相同的 interface.family:
{ "interface": { "family": { "id": "example.automation", "name": "Automations", "description": "Schedule prompts and manage recurring work." } }}使用稳定、带发布者命名空间的 id,各成员填写相同的 name 和可选的 description。插件目录和已启用侧栏按族显示一个入口,即使只安装了其中一个成员也一样。搜索同时匹配成员名称和描述。打开插件族后可以查看组件的启用状态、故障、权限和设置;直接打开单个包的链接仍然有效。族信息不完整(ID 或名称为空)时,包保持独立显示。
内置插件和社区插件使用相同的展示信息。插件族不会安装缺少的成员、建立依赖、授予信任,也不会统一启停或更新包。各组件保留自己的来源、生命周期和设置。旧宿主忽略这项可选信息,不需要数据迁移。
dependencies 在不改变包 schema 的前提下增加版本检查。例如:
{ "dependencies": [ { "id": "text-analysis-example", "version": "^1.0.0" }, { "id": "draft-export", "version": ">=1.2.0 <2.0.0", "optional": true } ]}省略 version 表示接受任意包版本。约束使用 SemVer 语法,包括精确版本、比较、^ 和 ~;预发布版本必须由约束明确允许。这些是插件包版本,不是 Wuu 的产品 CalVer。requires 仍受支持,并与必需的 dependencies 合并。不要将同一依赖同时声明为可选和必需。
必需依赖缺失、被禁用、未获信任或版本不兼容时,消费者及其下游必需消费者保持未激活。可选依赖不可用或不兼容时,不阻止激活。可用且版本匹配的依赖建立 provider 先于消费者的顺序;必需依赖环会拒绝激活计划。若可选排序边会构成环,则跳过该边并记录诊断。声明关系不会下载、安装、启用其他包,也不会授予信任。breaks 和 conflicts 仍为 ID 列表。requested_permissions 描述申请的权限,不会为插件进程建立沙箱。
包依赖与运行时服务要求用途不同。包依赖选择已安装的包并确定生命周期顺序;provided_services / required_services 协商命名 API 和服务主版本,不按包 ID 选择,也不继承包版本。功能需要特定包及其 API 时,应同时声明两者。仅依赖服务的消费者则可接受该服务的任意合格 provider。
不替换 provider 也能协作
Section titled “不替换 provider 也能协作”新增插件可以消费已有服务,并向服务所属插件公开的 UI 插槽贡献内容, 无需修改该插件或选择替代 provider。例如,计数器与控制插件 组合了外部进程状态/操作 API、公开的类型化 helper、可取消订阅和嵌套桌面贡献。 这只是可复验的一个样本,不限制插件组合服务、hook、capability 和 UI 的方式。
使用 api.invokeService 前先声明 desktop.required_services;运行时消费者在
初始化时声明 required_services。宿主依据消费者身份和已捕获的 generation
路由调用。所属插件通过 registerUISlot 发布插槽,其他包通过
registerUIContribution 添加内容。使用公开 UI kit 和契约 context,
不要访问私有 DOM 或 controller。注册和调用取消由 generation 管理;
视图卸载时也应停止领域订阅。
交互安装缺少必需依赖的新插件时,候选会被拒绝。更新若会破坏已活跃消费者的 必需依赖,也会在发布前拒绝并保留原有可用图。可选依赖不保证服务可用:应处理 调用失败,只禁用受影响的功能。包版本约束与服务主版本是独立检查。 安装附加插件并不隐含替换现有 provider;存在 provider 冲突时,仍需明确选择组合。
MCP 服务
Section titled “MCP 服务”插件可以在 mcp_servers 中声明服务,字段与用户配置相同。仅含声明的插件不需要 runtime 或 desktop 入口。规范名称为 plugin.<plugin-id>.<server>,OAuth 凭据也使用这个名称。插件自带的定义由包管理:用户可以连接、登录和断开,但不能作为独立定义编辑或移除。禁用插件会停止其服务,不会替换为其他实现。
插件自带的服务只在 Coding 中使用。共享的 Personal 宿主只获得用户的全局独立服务。
请使用真实的服务商端点。有 URL 不等于支持动态客户端注册;需要固定 OAuth 客户端的服务须配置 oauth.client_id。
默认输入框通过 listMCPServers 发现 MCP 连接,通过目录的 conversation_tools 发现提供运行时工具的插件。主题和仅提供后台服务的包不需要额外声明退出。构造运行时插件引用时,请使用规范的 provenance.plugin_id,而不是目录记录的 subject ID。
plugin_reference 消息片段携带可读的 text 和 plugin 对象(ApplicationPluginReferenceV1)。运行时引用使用 plugin_id;MCP 引用还保留 mcp_server、resource_url和不透明的 authorization_id。草稿恢复、排队、编辑和提交时应完整保留片段。这些标识表示用户选择,不是凭据或执行授权。宿主解析工具名称,并在接收消息和开始轮次时重新验证服务和授权。OAuth 刷新保留授权标识,新登录则替换它。MCP 客户端绑定已接受的授权;新登录取代它后,旧客户端会失败。
引擎声明 conversation-plugins,即承诺本轮使用宿主选中的插件工具和说明,并保留原有权限检查。外部引擎通过现有的轮次级 MCP 桥接获得明确选中的工具。不支持的适配器应省略该能力;宿主会拒绝带引用的提交,避免静默丢失用户意图。运行中的轮次收到新引用时应排队,因为已有的外部桥接不能在执行中途取得新连接。
主题、外壳皮肤和设置不需要桌面模块。例如,将下面的片段合并到 manifest:
{ "contributes": { "themes": [{ "id": "calm-night", "name": "Calm Night", "base": "dark", "tokens": { "--wuu-color-canvas": "#151820", "--wuu-color-accent": "#8fa7ff" } }], "settings": { "enabled": { "type": "boolean", "title": "Enable feature", "default": true, "scope": "workspace", "apply": "live" } } }}主题 token 来自生成的主题契约,不是任意宿主 CSS 变量。公共语义颜色为独立 UI 外壳提供可读取的默认值。在主题的 tokens 中声明颜色即可覆盖内置界面;未声明的颜色保留各组件的默认外观。清除或停用主题会恢复这些默认外观。设置支持 boolean、string、number、enum,作用域为 user|workspace,应用方式为 live|next-operation|restart。运行时通过 host.settings.get/list 服务读取,桌面 View 通过 host.getSetting 读取。禁用或删除包默认保留设置和存储。
contributes.commands 中的命令包含本地 ID 和 kind。prompt_template 引用包内有大小限制的 UTF-8 文件,提供宿主管理的输入框动作;runtime_action 指向桌面 generation 注册的命令,只有声明不会执行代码。side_conversation 引用包内提示词,打开宿主管理的附属会话界面;消息继续使用现有侧聊执行与历史机制,宿主保留当前权限边界。发送请求使用 plugin.command 公共 ID 标识命令;停用或移除所属插件会取消正在生成的回答,但不会删除历史。同 ID 更新从下一次回答生效,正在生成的回答保留其原有 generation。此类命令不需要桌面入口或辅助进程。
首次设置在完成前中断时,会保留已经应用的插件选择。清单区分实际 enabled 状态与可选的 enabled_override 用户设置;没有 override 时使用插件默认值。
manifest UI 声明使用 contributes.presenters、navigation、workspaceTools 或 settingsPages,描述放置位置和入口,实际渲染由桌面模块注册。对于已声明的 UI 区域,调用 registerUIContribution 并指定带所有者前缀的目标,无需 manifest 条目。旧 contributes.slots 和 surfaces 字段会被拒绝。组合规则见桌面 UI 插件。
配置快照与生效时机
Section titled “配置快照与生效时机”修改配置不会重启插件或重建其资源。为每个非 secret 设置选择生效方式:
apply | 运行时读取行为 |
|---|---|
live | 每次读取或刷新采用最新的、完整验证通过的候选配置。 |
next-operation | 经过宿主认证的工具、能力或服务执行首次读取快照或非 secret 设置时,固定全部操作级设置;同次执行后续读取保留这些值,新执行采用新值。 |
restart | 在 generation 准备阶段之前固定;该 generation 及其保留的在途执行都使用旧值,成功重载或宿主重启后采用已保存的新值。 |
连接端点、池结构等资源配置使用 restart,操作参数使用 next-operation。
执行级读取要使用该回调收到的 host;后台 host 没有所属操作。对插件自定义的
多步骤操作,即使设置声明为 live,也可以只捕获一次,再把不可变快照传给每一步:
import { createPluginSettings, requireKernelService } from "@wuu/plugin-sdk";
// initialize(): required_services: [requireKernelService("host.settings.list")]// 在 executeTool、invokeService 或 invokeCapability 中:const settings = createPluginSettings(host, { signal: execution.signal });const config = await settings.capture();// 整次操作使用 config.values;后续刷新不会修改已有快照。host.settings.list 继续接受 {} 并返回 entries,同时增加不透明的 revision、
已保存配置的 scope_revisions、以及待重启生效的本地键 restart_required。
可选参数 {scope: "user" | "workspace"} 过滤返回值及其元数据。修订标识是内容
令牌,只能比较相等,不能排序,也不是事件日志。每个 scope 只读取一次提供方文档,
所有已声明值通过验证后才发布。各 scope 独立提交;组合快照不是跨 scope 事务,
相互依赖的值应放在同一 scope。secret 条目始终为空字符串,修订标识不包含秘密,
也不随秘密是否已配置而改变。
读取器的 refresh() 只有在读取与可选的 validate(values) 跨字段校验成功后才
原子替换 current。存储值非法、后端失败或取消会拒绝刷新,已有快照不变。
subscribe(callback) 只通知新发布且修订不同的快照,返回取消订阅函数。
可选的 watch(intervalMs) 默认每秒刷新,允许 100–60,000 毫秒,并合并并发刷新。
必须等待其 Promise 或处理拒绝。订阅不会自动启动观察;轮询可能合并中间变化,
不是持久事件流。后台观察传入 generation 生命周期 signal;中止后清除监听器,
晚到结果不得发布。监听器异常会在发布后使刷新拒绝,不会回滚已发布配置。
现有 get/getAll 接口和 host.settings.get/list 调用形态保持可用。新读取器要求
快照元数据,面对旧宿主会明确拒绝,不把多次独立读取伪装成一致快照。
桌面 host.getSetting 用于配置编辑器,返回已保存值,不代表运行时 restart
设置的当前生效值。secret 读取仍不返回令牌。
带 fingerprint 校验的 plugin/setting/set 编辑入口在现有 key、value 外,
接受可选的 patch,原子修改主键所在 scope 内的其他非 secret 键。完整声明候选
验证通过后,通过所选提供方的一次 compare-and-exchange 提交。重复键、外来键、
跨 scope 修改以及 patch 中的 secret 都会在写入前拒绝;这也允许一次修复 schema
变更后多个非法存储值。已移除的声明仍保留存储以兼容降级,但不暴露给当前插件。
结果不确定的提供方写入失败不会自动重试;先重新读取,再决定是否重试。
执行 node scripts/plugin-settings-e2e.mjs,可用打包后的 SDK 子进程验收在途读取、
重载、资源稳定性、候选拒绝及退休。测试只用合成状态,可复查证据保存在
.tmp/e2e/plugin-settings/,也可通过 WUU_SETTINGS_EVIDENCE 指定目录。
使用独立于 contributes.themes 的 contributes.skins 声明皮肤。每套皮肤包含插件内唯一的 id、name,可选的 placement 和 userImage,以及必填的 light 和 dark 两种方案。placement 可取 "frame"(默认)或 "canvas":frame 仅装饰窗口外框和 ribbon,canvas 还会把图片铺在主内容区画布之下——对话在任何状态下都盖在半透遮罩下,不透明目的地将其完全遮住,只有声明 "transparent" 的插件视图会直接露出图片。每种方案提供不透明的 #RRGGBB 格式 background 和 foreground,可选包内相对路径 image,以及可选的 overlay、hover、active 颜色(#RRGGBB 或 #RRGGBBAA)。position 可选 center(默认)、top、bottom、left 或 right。另有两个可选的方案字段控制画布渲染,二者都要求 "placement": "canvas":veil 为 0 到 1 的数值(默认 0.86),决定半透明目的地上主题画布遮盖图片的强度;dots 为 { "pitch": p, "size": s, "shape": "round" | "square" } 对象,把图片渲染成格点网格。宿主按整设备像素为每格采样一种颜色;pitch 是以 CSS 像素计的格距(2–32),size 是圆点直径或方块边长(0.5 起、不超过 pitch),shape 默认 "round"。设置 "userImage": true 后,用户的背景图片会替换方案中的图片,宿主按皮肤的摆放方式处理这张图片;未保存图片时仍使用方案的 image。placement、userImage、veil 和 dots 要求宿主不低于你声明的 minimumWuuVersion;更旧的宿主会在安装时拒绝未知的皮肤字段。
{ "contributes": { "skins": [{ "id": "ocean", "name": "Ocean", "placement": "canvas", "userImage": true, "light": { "background": "#234b60", "foreground": "#f3fafc", "image": "ocean.svg", "veil": 0.86, "dots": { "pitch": 4, "size": 2.4 } }, "dark": { "background": "#112e41", "foreground": "#e3f1f6", "image": "ocean.svg", "overlay": "#06172555", "veil": 0.82, "dots": { "pitch": 4, "size": 3, "shape": "square" } } }] }}图片支持 PNG、JPEG、WebP 和自包含 SVG,每张最多 8 MiB;渐变和纹理也通过图片提供。路径必须位于插件包内,SVG 不允许外部引用和脚本。桌面只提供所请求包指纹中声明的图片。frame 摆放时图片以整个窗口为基准连续覆盖 ribbon 和外框;canvas 摆放时,外框和 ribbon 保持方案的纯色底色,与窗口对齐的图片只铺在主内容区之下。应用负责布局、裁切、交互和内容区边界,皮肤颜色不会覆盖内容主题变量——主题的画布和表面色请保持不透明,因为文字层级由宿主从中推导,半透明效果由宿主通过遮罩和玻璃材质自行施加。请搭配图片设计前景、悬停和选中颜色,保证控件清晰。
皮肤选择保存在桌面配置中,跟随浅色/深色模式,并在插件更新后保留。禁用、移除插件或进入安全模式会停止装饰外框;重新启用同一插件会恢复选择。图片加载失败时显示方案底色,并在预览中报告图片不可用。同一插件可以同时提供皮肤和内容主题,用户分别选择。可安装的完整示例见外壳皮肤示例(examples/plugins/shell-skins)。
秘密设置与认证请求
Section titled “秘密设置与认证请求”将凭据声明为 secret 设置,要求 default: ""、scope: "user"、
apply: "live" 和 credential_origins: ["https://api.example.com"]。
每项必须是完整 HTTPS 来源,不含路径、查询、片段或用户信息。宿主设置界面使用
掩码输入和明确的保存、移除按钮。凭据保存在 macOS 钥匙串或 Linux Secret Service,
不会回退到明文文件;存储不受支持或被锁定时会明确报错。秘密设置读取始终返回空字符串,
只有宿主设置界面收到 configured 存在标记;凭据不会写入普通设置或工具历史。
运行时声明 requireKernelService("host.credentials.request"),再调用
host.call("host.credentials.request", { credential: "token", url, method, headers, body }, { signal })。凭据键只能指向本插件声明的秘密设置。宿主仅向声明的
来源添加 Bearer 认证,拒绝重定向以及调用方提供的认证、Cookie、路由和连接控制头,
返回 { status, headers, body }。响应头使用小写,仅暴露内容类型、重试间隔和请求 ID。
请求超时为 20 秒,请求与响应正文各限 4 MiB。响应正文、头及传输错误会隐藏令牌。
不要把凭据作为工具参数,也不要复制到普通存储、日志或生成的插件包。
凭据绑定 Wuu 数据目录、插件来源身份、本地键和来源集合;项目插件还绑定项目根目录。 更改这些信息需要重新输入凭据。停用插件会阻止新调用,但保留凭据,直到用户移除。 凭据已保存不代表 API 权限已验证。令牌刷新和服务专用授权仍由插件负责。 写入失败或超时可能已经在远端生效,不要静默重试。
运行时进程和生命周期
Section titled “运行时进程和生命周期”宿主启动 manifest 中的命令,通过 stdin/stdout 交换每行一个 JSON 对象。stdout 专用于协议帧,诊断信息写入 stderr。manifest 的传输名称 wuu-plugin-v1 与协商的能力协议编号是不同字段;使用服务的新运行时应协商 protocol_version: 3。
官方插件编写 SDK 是 TypeScript 的 @wuu/plugin-sdk。实现 RuntimePlugin 并传给 runJSONLRuntime,SDK 负责消息分帧、请求 ID、取消信号和 lifecycle-v2 握手。其他语言的进程也可实现相同的 JSONL 契约,但 Wuu 不提供另一套编写 SDK。
添加能力(包括观察能力)时,应从 initialize 返回 protocol_version: 3。
这是返回结果中的字段:宿主的 params.protocol_version: 1 标识传输协议,
不能照搬为能力协议版本;params.capability_protocol_version 表示宿主支持的
能力协议。没有服务声明时,省略返回结果的版本意味着仅支持工具的旧版 v1,
不是最新版。SDK 会在返回声明前,以 invalid_capability_protocol 拒绝 v1 的
能力或模型适配器声明。显式 v2 能力仍受支持;声明服务时,SDK 仍选择 v3。
| 回调 | 职责 |
|---|---|
initialize(params, host, lifecycle) | 返回工具、能力、提供的服务和需要的服务 |
prepare(host, lifecycle) | 服务绑定完成后读取并验证状态,不写入或启动后台工作 |
activate(host, lifecycle) | 运行时变更提交后,启动定时器、订阅或其他行为 |
executeTool(params, host, execution) | 执行已注册工具,返回结果 |
invokeCapability(params, host, execution) | 处理已声明能力 |
invokeService(params, host, execution) | 响应一次宿主路由的服务调用 |
serviceChanged(params, host) | 响应服务解析变化 |
shutdown() | 停止插件拥有的工作并释放资源 |
initialize 只声明契约。Lifecycle v2 在全部声明绑定后增加独立的 prepare 请求。请将持久状态的读取和验证从 initialize 移到 prepare,使用配套 SDK 重新构建;手写适配器需返回 lifecycle_version: 2。旧生命周期适配器会在提交前被拒绝。provider 先于消费者准备;准备阶段只能调用已声明且标记 preparation: true 的方法,嵌套调用也遵守此约束。该标记承诺不进行持久化写入或产生外部效果;省略时仅可在激活后调用。写入和后台任务应推迟到 activate,shutdown 也应能安全处理部分初始化或部分激活。准备和持久化失败时保留旧运行时,不调用候选的 activate。变更提交后,激活失败会留下带失败诊断的降级新 generation,不恢复旧代,也不撤销外部效果。明确重试激活时,应避免重复产生效果。可信初始化代码和 MCP 准备仍可能在宿主服务边界之外产生外部效果。提交边界见运行时 generation。
准备阶段可以验证持久状态,但旧代在激活前仍可能修改它。不要直接以准备时的快照作为激活写入的依据:应重新读取并验证当前状态,或按提供者实际的交接契约验证版本。丢弃候选不得写入持久状态或取消运行中旧代的工作。这不要求所有后端都使用 compare-and-swap,单纯重读也不能隔离并发写入者。
请求关闭或连接结束时,lifecycle.signal 会中止。激活后启动的后台工作应
使用此信号,并在 shutdown 中停止定时器。清理仍可使用不绑定执行作用域的
生命周期 host 保存状态或释放资源,不应依赖已经中止的信号完成清理。
进程关闭只在有限时间内等待确认。超时后,宿主会终止进程,并报告清理未获确认, 而不是清理成功。进程已终止不代表待完成的外部写入成功,也不代表其副作用被撤销; 重试前应先核实状态。运行时正常退出但未回复关闭请求,仍可视为成功关闭。
模型工具注册在初始化结果的 tools 数组中,每个工具需要 id、description 和对象类型的 input_schema,宿主会生成带命名空间的公开名称。execution_scopes 可限制工具在 root 或 child 中可用;activity 描述只读性、并发安全、风险和是否编排子工具。应如实声明副作用,不要把写入工具标成只读来绕过调度或权限检查。这些声明属于可信扩展契约,不是沙箱。可选的 guidance 字符串是面向 agent 的工具使用契约:当工具通过宿主能力面暴露给外部引擎时,宿主会把收集到的 guidance 连同自身绑定说明一并展示给引擎。
TypeScript 运行时适配器按 activity.concurrency_safe: true 调度工具。相邻的显式安全工具可以重叠运行,每个运行时最多同时执行八个工具处理函数。省略或设为 false 的工具、未知工具 ID,以及普通能力和服务回调,仍按 FIFO 顺序独占执行;仅声明 read_only 不会启用并发。这些屏障同时适用于共享或不同的 execution ID。initialize、prepare、activate 和 shutdown 还会等待此前已接收的可重入回调结束。
启用前应保护共享可变状态。每次调用的 execution context 与带执行归属的 host,都应随该次调用传递,不要保存为共享的“当前调用”变量。取消在读取帧时注册,并在真正进入处理函数前再次检查,包括等待工具空位之后。取消某个 execution 会中止其所有仍活跃的回调,不影响独立 execution;已运行的代码仍需协作响应取消。显式可重入的能力和服务保留独立通道,使工具空位用尽时嵌套宿主回调仍能完成。该上限不限制这些回调或排队帧数量;TypeScript 并发也不会将同步 CPU 工作移至工作线程。
会话启用 PTC 时,普通工具默认作为程序内绑定。用户交互、显式交付或必须保留
为模型直调的生命周期控制可声明 direct_only: true。此类工具留在顶层工具
列表,并拒绝嵌套调用。这个可选标记不授予权限,也不改变执行作用域;未声明
时仍使用普通路由。不要将这项宿主元数据放入参数 schema。
executeTool 接收参数以及 cwd、调用 ID、可用的会话和轮次标识等上下文。即使有 schema,也应验证参数。结果格式为 { result: { content: [...] } },工具失败时设置 is_error: true。内容可包含文本和支持的富结果部分。需要成为会话产物的文件应使用 importArtifact,不要只返回可能消失的临时路径。
能力单独声明 ID、kind 和版本。当前支持的能力 ID 为:
| 能力 | 类型和用途 |
|---|---|
agent.system_prompt.section | transform:添加提示词区块 |
agent.request.transform | transform:在模型请求前添加系统消息 |
agent.pre_step | transform:在步骤前追加带标识的消息 |
agent.step.policy | decision:继续或停止默认循环的模型步骤,见 Harness 实验 |
agent.turn.completed、agent.turn.lifecycle、agent.turn.interrupted | observe:响应支持的轮次事件 |
plugin.client.request | decision:分派插件桌面 UI 发来的请求 |
agent.compaction | decision:实验性的压缩契约 |
agent.request.transform 接收与 provider 无关的请求视图,公开输出是 prepend_system_messages,不是任意修改 provider 的传输请求。SDK 将压缩标为实验性,不应把它当作稳定的、可替换整个 agent 循环的通用接口。
能力可声明 priority、depends_on 和 conflicts。优先级高的先执行,同优先级按插件 ID 排序。error_policy 为 propagate、isolate 或 ignore;观察能力不能传播错误,只有观察能力能忽略错误。内置轮次观察能力默认隔离,其他能力默认传播。预期内的失败应在插件内部处理,不要只依赖统一错误策略。
调用宿主服务
Section titled “调用宿主服务”当前宿主只通过 host.service.call 运行时入口路由服务。每项消费的服务都要在 required_services 中声明名称和主版本。旧的 host.storage.get 直接消息不是传输契约。TypeScript SDK 将 host.call("host.storage.get", params) 等带类型的简写转换为统一入口。服务要求只通过 required_services 声明;requireKernelService 等辅助函数用于构造这些带版本的条目。宿主不会维护第二套路由。
例如,下面的初始化结果申请读取存储:
{ "protocol_version": 3, "required_services": [ { "name": "host.storage.get", "major_version": 1, "required": true } ]}激活后,通过统一入口调用:
import type { RuntimeHost } from "@wuu/plugin-sdk";
async function readCounter(host: RuntimeHost): Promise<string | null> { const result = await host.call("host.service.call", { service: "host.storage.get", method: "call", params: { scope: "workspace", key: "counter" }, }) as { value: string | null }; return result.value;}requireKernelService 可构造对应的版本 1 服务要求。host.supports("host.service.call") 只检查统一调用入口,不代表某个服务 provider 一定存在。可选服务也需要声明,并处理不可用时的错误。
| 服务族 | 用途 |
|---|---|
host.storage.get/set/delete/keys | 插件用户或工作区命名空间中的字符串值 |
host.storage.compare-exchange | 用 { scope, key, expected, value } 条件替换,返回 { swapped, value } |
host.file-lock | 在宿主提供的数据根目录中获取和释放跨进程文件锁 |
| host.model.request | 使用宿主配置完成一次有界的纯文本模型请求 |
| host.model.invoke | 独立的完整逻辑模型调用与流式响应 |
| host.model.emit | 流式适配器或 middleware 发出的事件 |
| host.background | 调用插件自己服务的 generation 所属后台工作 |
| host.client.invalidate | 提示客户端重新读取插件状态,不携带业务内容 |
| host.process | 绑定会话的 shell 进程、输出读取和事件投递 |
| host.settings.get/list | 声明的设置值 |
| host.credentials.request | 使用宿主持有的 Bearer 凭据向限定 HTTPS 来源发送请求 |
| host.session.create/send/list/inspect/cancel/control | 按宿主生命周期规则创建和管理会话 |
| host.session.model | 通过有效的所有者界面读取或修改插件拥有的用户空间会话模型 |
| host.session.history.read/search | 使用序列快照和续读游标访问有限历史 |
| host.workspace.status/apply/discard | 检查或处理所拥有会话工作区中的改动 |
| execution.update | 报告活动执行的进展 |
| execution.invoke-tool | 在编排执行中调用子工具 |
| host.artifact.import | 将文件或字节导入为会话产物 |
| host.user-question.ask | 执行期间提出结构化问题 |
大多数核心服务使用 call 方法。存储不提供跨 key 事务;并发更新单个值时应使用 compare-exchange,检查 swapped,并限制重试次数。先读取再无条件写入会丢失并发更新。不存在的值为 null;结构化数据可编码为 JSON 等字符串存储。 返回结果描述最终完成的比较:swapped: false 返回当前值,swapped: true 返回替换后的值(删除后为 null)。
创建会话时提供稳定的 request_id、visibility=user|plugin 和 context_source=fresh|fork|seed。发送也需要稳定的请求 ID 和 input.prompt。请求被接纳或排队不代表完成,应检查轮次或处理生命周期事件后再消费结果。模型提示、业务状态和重试策略由插件负责,执行、历史、工作区改动和恢复交给宿主。
host.session.model 读取调用插件所拥有会话的当前 provider 和模型。修改时须同时提供 provider 和 model。调用需要同一用户空间内有效的所有者界面,并检查会话归属;会话忙碌时拒绝修改。成功的选择保存在该会话中,不修改其他会话或全局默认值。已获准的委派任务保留自身的授权和模型选择。此服务不配置凭据,也不选择外部引擎。
host.session.list 的 owned 结果,以及调用插件所拥有会话的 host.session.inspect 结果,在可用时包含 creation_request_id。该字段是原始 host.session.create 请求 ID,在首个轮次开始前也可返回,因此插件能发现已被接纳但响应丢失的创建请求。shared 列表和对其他所有者会话的检查不返回此字段。它仅用于请求关联,不授予会话控制权限。
host.session.send.input.attachment_handoff 提供附加的 v1 Agent 字节信封:{version:1, session_id, request_id, source:{session_id,seq}, attachments:[{sha256,filename,media_type,data}]}。data 为标准 base64,最多 16 个非空文件、解码后合计 20 MiB。目标和请求 ID 必须与发送一致,不能与真人输入证明、回执恢复、普通附件引用或 steering 混用。原有发送授权、私有会话归属及接收端策略继续生效。来源是已认证发送方提供的出处信息,不是真人署名证明或读取其他会话的凭据;发送方必须通过已授权的源读取获取文件。接收端校验摘要和安全文件名后写入目标工作副本,并保留插件来源、只读消息标记。发送宿主通过 session-attachment-handoff.v1 特性声明支持;旧接收端拒绝新字段。路由回执摘要覆盖整个信封,因此同一操作 ID 不能重放改变后的字节。
host.session.attachment.snapshot 接受与附件读取相同的源引用(不含 offset 或 snapshot_id),返回 {snapshot_id, attachment}。它通过已授权的可见历史读取并冻结字节,使用选定的 state.documents 提供者,以 512 KiB base64 分块持久化,全部分块提交后才发布清单。源消息、插件和用户空间相同的重试复用已有分块,可恢复部分提交或响应丢失。向附件读取传入 snapshot_id 和原源引用即可按 128 KiB 范围恢复,不再重复读取原消息;每次读取重新校验当前会话可见性、完整绑定和分块摘要。快照创建是写操作,准备阶段不可调用;调用前检查 session-attachment-snapshot.v1。
分块不占用插件设置的 1 MiB 单值预算。字节存储约为原始数据的 4/3 加少量清单,任务修订和路由发件箱不重复保存字节。快照及未完成分块随工作区状态保留;源访问权限撤销后不能继续读取。最终发送仍是有上限的内存信封:20 MiB 原始数据约为 26.7 MiB base64,低于 64 MiB RPC 帧上限。它不是无限制的网络流,序列化和目标规范化的内存开销也随文件大小增加。超限请求明确失败;旧接收端在执行前拒绝不支持的字段或超过旧版上限的请求。
对于 host.session.send,session_send_unavailable 表示宿主没有发送处理器,
本次调用未被分派。服务恢复后,显式重试可以释放这条未被接受的请求身份。
该错误码仅保留给此边界:已调用的处理器返回同码(包括嵌套调用返回)时,
外层发送会改报 host_error。通用的 service_unavailable、其他错误和超时
均不能证明未被接纳;应保留请求身份并核对回执,再决定是否替换。
host.session.send 支持可选的 disable_tools: true,仅对本轮禁用所有工具。宿主移除模型可见的工具并拒绝工具执行,包括 code-mode 分发;不会影响后续轮次,也不会授予权限。省略或设为 false 时保持普通行为。禁用工具的轮次不能使用 if_running: "steer" 或外部代理引擎,不支持的请求会在接纳前被拒绝。tool_policy: { allow?: string[], deny?: string[] } 可为一个独立轮次添加仅收窄权限的工具策略。省略 allow 时保留原有工具范围并扣除 deny;显式 allow: [] 禁止全部工具。宿主取该策略与会话既有限制的交集,同时检查实际执行及动态发现的工具,并在成功、失败或中断后恢复原有工具范围;不会修改持久会话设置。依赖此契约前应检查 session-turn-tool-policy.v1 宿主能力。
带有 tool_policy 的轮次是独立执行边界:不能插入其他轮次,传入的插件消息或完成通知会单独排队。对于尚未接纳的人类 turn/steer 输入,宿主返回 isolated_turn_requires_queue;桌面客户端仅对此明确拒绝,在同一已捕获连接上使用原始输入和相同 client ID 排队。其他错误或结果不确定时不得按此方式重试。已保留的输入必须使用原有恢复流程。用户仍可中断轮次。允许 run_code 时,其内嵌目录及搜索/描述文本仍可能列出被轮次策略排除的工具,但实际嵌套调用仍会受校验。若审查轮次不应展示无关绑定,应仅允许直接工具。工具名称只限制工具范围,不授予任务权限:接收插件仍须校验确切任务、执行请求、版本和工具参数。
声明依赖 agent.tools@1 的插件,可调用 toolService(host).resolveOwn({ tool_id: "local_tool_id" }) 查询自身工具当前的公开名称,无须猜测前缀或冲突哈希。这是可从后台回调调用的只读查询,不会初始化工具后端。原生提供方使用注册表认证的调用插件身份及所捕获 generation 的声明;调用方不能指定其他插件。结果为 {found:false} 或 {found:true,name,direct_only?}。每次使用前都应重新查询。结果只证明声明身份,不授予权限,也不保证目标会话暴露该工具:须另行检查确切已接纳模型请求的工具范围。选中提供方缺失或不支持查询时应保持只读,不能回退到原生提供方。执行方法仍保留原有更严格的作用域要求。
host.session.history.read 只读返回物理审计记录,包括隐藏的模型上下文和工具记录。记录带有可选的 hidden、read_only 和 origin 元数据;省略的布尔标志视为 false,旧记录省略的来源视为空字符串。仅凭 user 角色不能认定内容由用户输入。构建对话视图时应保留并使用这些字段:排除隐藏记录,只有非只读且来源为 user 或空字符串的用户角色记录才作为真实用户输入。不要按文字特征判断来源,也不要删改真实输入中的相似文字。对话筛选不删除审计记录,也不改变提供方上下文。
历史记录可包含 display_id,其值为原始流式气泡的宿主 turn_id:item_id。构建历史视图时应保留此字段,让客户端在最初没有提供方 ID 或历史序号时也能恢复同一气泡的显示位置。它标识显示对象,不表示历史游标或执行顺序;旧记录和非流式记录可以省略此字段。
引用仍在流式输出的气泡时,在 relation.reply_to 中传入其 session_id、seq: 0 和 display_id。宿主确认该可见助手消息属于目标会话;引用在入库和重启后继续有效。普通历史引用使用正数 seq。会话所有者可通过 host.session.inspect({ session_id, message_display_id }) 获取当前内容,返回的可选 message 使用历史记录结构,入库前序号为零。未知显示标识不返回消息。查询不会结束或中断正在执行的轮次。
host.session.history.search 使用 view: "conversation" 时,在分页前对用户和助理记录应用这些可见性与来源规则,搜索显示的对话文字,不搜索推理或工具输出。省略 view 时保留默认审计/执行轨迹搜索。序号仍指向物理记录,客户端可以恢复原消息,不应将筛选结果重新编号。
host.workspace.status 和 apply 以创建工作区时冻结的提交为基线,比较已跟踪文件,包含已提交、已暂存和未暂存的改动。apply 将累计补丁写入父工作目录,不创建提交,随后移除独立工作区并重新绑定会话。存在冲突、未跟踪文件或基线不可用时,调用失败并保留工作区及其绑定。自动清理也会保留已提交成果和无法验证基线的工作区。
已完成的轮次可以没有 final_output:提供商正常结束不要求对外回复或调用确认工具。这只表示本轮执行结束,不证明用户目标已经达成;接收方仍须检查结果和证据,连接失败和异常终止仍会报错。
Peers 插件通过 if_running: "steer" 尽量把终态回执注入当前工作;来不及在本轮处理的回执保留到后续轮次,空闲会话仍会被自动唤醒。返回 running 和 steered: true 时,输入可能尚未被消费,不代表已持久送达。Peers 会保留待送达状态,直到持久记录确认送达;重启丢失的输入使用同一请求 ID 重试。
Subagent 和 Peers 在信息可用时,用宿主提供的会话、工具、调用 ID、回合 ID 与步骤索引共同标识工具调用;传输执行 ID 不决定重试身份。缺少回合和步骤元数据的调用保留原有映射。已持久化的请求继续使用其保存的 ID 恢复,不推断过去未保存的回合或步骤身份。Peers 原有的内容去重与不确定发送匹配仍是独立策略。
自有会话技能
Section titled “自有会话技能”host.session.inspect 可对调用插件拥有的会话传入 include_skills: true。返回的 skills 包含当前可由用户调用的技能目录,以及名称、来源、路径、描述和调用元数据。该选项不授予其他插件会话或当前 Coding 工作区的访问权。字段缺失或空数组表示没有可用技能;新请求会刷新目录。
经过身份校验的 host.session.send 用户输入可携带与 Coding 输入框相同的显式 /skill 身份。宿主在接纳前校验并展开技能,为排队和恢复保存内容,同时保留原草稿作为显示文本。插件来源的消息不会触发斜杠命令。
会话实时进度
Section titled “会话实时进度”状态卡或子代理面板可以声明
required_host_features: [SESSION_PROGRESS_HOST_FEATURE],同时声明普通
host.session.inspect 服务依赖。检查时显式提供 request_id 和
include_progress: true;默认检查不返回含正文的进度。progress 是版本 1
的快照,不是事件流:每次读取后替换显示内容,不要当作新消息追加。
调用插件必须拥有该会话,且该请求必须是轮次唯一的输入身份。排队请求尚无
可投影的轮次;steer 请求不能借用原请求的进度。不同输入一旦进入同一轮次,
任何一方都不能把输出归为自己的独占结果。此时 available: false 会返回
session_not_owned、request_not_exclusive 或 turn_unavailable,不附带内容。
共享会话仍可使用不带进度的普通检查。
可用快照包含会话、请求、轮次及条目 ID,最多返回最近 32 个助手/工具展示条目,
JSON 总量不超过 32 KiB。每条助手正文最多 4096 UTF-8 字节;truncated
明确表示正文被缩短或条目被省略。不返回输入提示、推理、工具参数/结果、附件及
提供方私有状态。尚未输出时 items 可以为空。读取不建立订阅,也不改变被观察
任务。已结束轮次可以继续检查,但快照不证明持久成功、输入消费或消息送达;
结算仍须使用既有终态检查和生命周期回执。本接口不提供事件重放或断线补偿。
参见仅依赖公开 SDK 的会话进度示例。
文件的读改写需要跨插件进程协调时,声明
{ name: "host.file-lock", major_version: 1, required: true }。服务提供
acquire 和 release,SDK 对应帮助函数为 acquireFileLock 和
releaseFileLock。获取时指定相对锁文件路径,以及 workspace_state、
wuu_home 或 project 根目录。成功后返回不透明的 lock_id,释放返回
{ released: boolean }。所有写入者必须使用相同锁路径。宿主管理操作系统锁,
在插件 generation 关闭时释放其句柄。
为这项资源握手保留初始化时传入的不绑定执行作用域的 host。获取时传入当前
执行 ID,等待宿主明确回复,然后检查取消,并在 finally 中释放:
const { lock_id } = await acquireFileLock(lifecycleHost, { root: "workspace_state", path: "example/state.json.lock",}, execution.executionId);try { execution.signal.throwIfAborted(); await updateStateFile();} finally { await releaseFileLock(lifecycleHost, lock_id);}不要在本地放弃等待获取响应:取消可能与成功授予同时发生,使调用者拿不到 必须释放的句柄。执行 ID 让宿主能取消等待中的获取;进程丢失时由 generation 清理兜底。文件锁不将插件的任意文件访问变成沙箱,也不提供跨文件事务。
单次模型请求
Section titled “单次模型请求”在初始化结果的 required_services 中声明 requireModelRequestService(),再从宿主管理的活动工具、capability(包括 plugin.client.request)或服务回调中调用 requestModel(host, input)。使用该处理函数作用域内的 RuntimeHost,TypeScript SDK 会补充 execution ID。generation 所有的后台任务也会提供活动的服务回调。需要在 observer 或其他处理函数返回后继续工作时,应启动这样的后台任务,并在其回调中发起请求。激活阶段及脱离执行的定时器或任务,没有活动执行作用域时不能直接调用模型服务。
import { requestModel, type RuntimeHost } from "@wuu/plugin-sdk";
async function requestText(host: RuntimeHost, content: string) { return await requestModel(host, { messages: [{ role: "user", content }], max_output_tokens: 256, timeout_ms: 10_000, });}host.model.request 的版本为 1.0.0,使用 call 方法,schema 标识为 host.model.request.input.v1 和 host.model.request.output.v1。服务协商检查声明的主版本;SDK 中存在 helper 或 host.supports("host.service.call") 返回真,都不能单独证明旧宿主支持此服务。
| 输入 | 契约 |
|---|---|
messages | 1–32 条消息,角色为 system、user 或 assistant;内容为非空白 UTF-8 文本,总计最多 256 KiB |
model_alias | 可选的现有 agent.model_aliases 名称;省略或为空时使用已保存的有效配置中的主模型 |
max_output_tokens | 整数 1–8192,默认 1024 |
timeout_ms | 整数 1–45000,默认 45000 |
max_output_tokens 是请求的输出 token 上限,不是费用上限。已配置的 token 限制选项不能提高本次调用的上限。如果提供商适配器在准备请求时移除限制,或将其提高到请求值以上,调用会在提交推理前返回 unsupported_limit。这项宿主侧检查不保证远程服务器如何执行 token 限制或计费。
超时同时受 timeout_ms 和所属调用的剩余生命周期限制。运行时默认 capability 超时为 30 秒,因此请求可能在 45 秒之前结束。应明确配置包的运行时超时,不要认为模型服务会延长它。取消、所属调用结束或 generation 撤销都会取消尚未完成的提供商工作。
远程 Web 客户端对 api.invokeRuntime 另有独立的 30 秒 RPC 超时,增加包的超时时间不会延长它。该时限到达后,客户端会请求取消原来的宿主调用。
结果包含 text、provider、model、finish_reason 和 truncated。可选的 usage 包含输入、输出、缓存创建、缓存读取 token 数及 cache_creation_unknown;缺少用量不代表零费用。空或非文本响应,以及请求工具调用的响应都会报错,不会启动工具循环。
宿主复用已配置的凭据、提供商请求准备、容量协调、重试和推理用量记录。不自动加入对话历史、代理专用请求 hook 或工具,也不创建持久会话。上下文由插件自行选择和限制。此服务不接受凭据、端点 URL 或任意提供商覆盖。应说明哪些草稿或历史文本会发送给已配置的提供商,限制请求频率和输出;取消只能尽量避免后续工作,已提交的请求仍可能产生费用。
刷新插件拥有的客户端状态
Section titled “刷新插件拥有的客户端状态”声明 requireClientInvalidationService() 以使用 host.client.invalidate 版本 1。持久化变更后,调用 invalidateClient(host, "reactions"),或使用服务方法 call,参数为 { topic }。此机制也适用于日历和其他插件记录的更新。topic 必须匹配 [a-z][a-z0-9.-]{0,63};任意业务内容及调用方提供的身份字段都会被拒绝。
宿主发出 plugin/client/invalidated,包含 plugin_id(包的 subject ID)、fingerprint、user_space_id、product_domain 和 topic。身份来自活动 generation;已禁用、已关闭或已被替换的运行时不能发布。桌面消费者通过 api.onHostEvent 接收通知,核对包和上下文后,通过带作用域的运行时 API 重新读取状态。该事件仅提示状态失效,不发送消息、不创建轮次、不写入历史,也不请求用户注意。保留轮询或其他恢复读取路径,以应对通知丢失;提示失败不应撤销此前的持久化写入。
可组合的模型扩展
Section titled “可组合的模型扩展”完整逻辑请求使用 host.model.invoke 版本 1;原有的 host.model.request 纯文本契约保持不变。在 required_services 中声明 requireModelInvokeService() 以获得服务调用权限,由活动的工具、capability、服务或后台执行提供生命周期。独立推理不会创建对话轮次,也不会执行模型返回的工具调用。
LogicalModelRequestV1 包含 version: 1、提供商和模型身份、有序消息与工具、缓存提示、模型选项、工具选择、媒体准入和原生回放状态。消息保留 reasoning block、提供商原生 item 及其回退历史、工具调用和结果、发现的工具 schema,以及已解析的图片和文件字节。富工具结果保留 artifact 和延迟媒体引用,以及经过预算限制的 model_text。不导出本地临时文件路径、凭据、宿主重试对象或执行权限。复制快照时应保留未知的提供商选项和不透明原生数据。
工具调用可包含可选字段 thought_signature,其来源由 provider_item_provider 和 provider_item_model 标记。在模型、driver、工具批次和会话之间传递时,应将此不透明值与原调用及来源一起原样保留,也应保留并行调用的顺序。宿主在为其他提供商或模型准备请求时会移除外来签名。这是版本 1 的新增可选字段;重新构造工具调用的外部提供方需要透传该字段,才能支持带签名的 Gemini 续接。
这是逻辑快照,不保证最终 HTTP 字节完全相同或缓存一定命中。上下文、路由、模型、适配器、原生状态兼容性或媒体变化时,插件需要使旧快照失效。已配置凭据、提供商请求准备、取消和推理用量记录仍由宿主负责。原生转发不能配置任意端点或凭据。
| 扩展点 | 契约 |
|---|---|
model.request.transform | 版本 1 的 transform capability,接收 { request, origin },在提供商调用前返回 { request? }。 |
model.request.observe | 版本 1 的 observe capability,观察请求及终态响应或错误,并接收完整逻辑请求和宿主确认的来源。 |
model.request.metrics | 版本 1 的仅元数据 observe capability。以有界、非阻塞方式投递终态耗时和适配器报告的用量,不含请求或响应内容。 |
model.middleware | 版本 1 的 decision capability,错误策略为 propagate;可包装调用、返回替代响应,或执行一次性 continuation。 |
model.adapter | 版本 1 的 decision capability,错误策略为 propagate;提供显式选中的适配器实现。 |
插件发起的独立请求绕过请求 hook 和 around-call middleware,因此观察普通请求后重放不会递归触发同一策略。origin.kind、plugin_id、operation ID 和可用的会话上下文由宿主填写;复制这些字段不能冒充普通代理请求。
仅元数据的模型性能观察
Section titled “仅元数据的模型性能观察”使用协议 2 或 3,声明 MODEL_REQUEST_METRICS_CAPABILITY
(model.request.metrics),并设置 kind: "observe", version: 1,即可接收
ModelRequestMetricsInput。它与包含内容的 model.request.observe 独立:不发送
提示词、输出内容、工具参数、provider options 或原始错误,回调不会延迟或改变模型
完成。性能展示和延迟诊断插件无需保留会话快照。
每条终态记录对应一次选定适配器调用,在外层 middleware 改写响应之前采集。
request_id 在每次调用中唯一;origin.operation_id 关联重试,attempt_id
在可用时标识已准入的尝试。会话元数据和 turn_id 仅来自宿主确认的执行上下文;
缺失时不推测。独立插件调用也会被观察,但从指标回调派生的推理除外,以防递归。
仅由 middleware 缓存返回、或在进入适配器前被拒绝的请求不产生记录。
这不是工具耗时或外部原生引擎的事件流。
duration_ms使用宿主单调时钟,从适配器准入计时至终态,包含容量等待。started_at是用于展示的墙上时钟时间,不应用来计算耗时。first_output_ms是宿主观察到流中首个非空文本、思考、工具输出或图片的延迟, 只是首输出时间的近似指标,不是 provider 的首 token 时间戳。 非流式调用和空响应不包含此字段。text_delta_events和text_utf8_bytes统计非空文本增量,不是 token 数。 思考和替换事件不计入;流重置不会抹除已经观察到的传输量。- 可选的
usage是适配器最后报告的用量,标注为usage_source: "adapter_reported"。原生 provider 适配器转发 provider 用量, 自定义适配器也可提供自己的值;这不是独立验证的物理计费凭据。 缺失表示未知,不能当作零或用分块数估算;取消时可能只有部分用量。 输出 token 数除以总耗时只能得到请求平均速率,不能称为瞬时生成速度。 status为completed、error、cancelled(含超时),或流未发送终态就 关闭时的incomplete。每次调用最多采集一条终态记录,也覆盖提前退出迭代器的取消。
投递是尽力而为且不持久化。每个精确的插件注册拥有一个串行回调和最多 64 条记录的
队列。队列满时丢弃新记录,dropped_before 报告自上次投递回调以来的溢出数量。
回调期限为五秒,超时会取消回调。超时或未确认完成的执行取消都会丢弃队列,
并暂停该注册的指标投递直至重新加载:
取消信号不能证明远端处理函数已经停止。未确认停止的回调之后不会再启动新的指标回调,
插件的其他 capability 仍可使用。移除插件或关闭代际时,宿主取消投递、丢弃排队记录,
等待投递结束后进入插件的正常进程清理;旧记录不会转发到替代注册。
投递失败被隔离,只留下固定的、
不含内容的插件诊断(error_policy: "ignore" 除外);溢出也留下诊断。
失败不会变成会话提示或模型错误。不要将此事件流视为恰好一次的计费账本。
回调应保持简短;如观察时还需处理本插件其他回调,可显式声明
concurrentCapabilities。
独立调用与流式响应
Section titled “独立调用与流式响应”invokeModel(host, { request, model_alias?, max_output_tokens?, timeout_ms? }) 返回 { response, origin }。比较第二个模型时,选择已配置的模型别名。显式别名提供其已配置的选项和 effort 默认值;请求中的显式 effort 选择该模型对应的 variant,显式 provider_options 字段覆盖这些默认值,request.adapter 优先于合并后的 provider_options.adapter。不指定别名时保留完整请求快照,不用当前配置刷新它。输出 token 和超时受宿主及所属执行约束;缺少用量不代表没有费用,取消也不能撤销已经提交的请求。完整请求保留自己的输出限制,除非显式通过 max_output_tokens 覆盖;独立调用默认超时 120 秒,最多 600 秒。输入和保留的流数据分别最多 16 MiB,每条流最多保留 4096 个事件。
startModelInvocation 返回 invocation_id。readModelInvocation 接收该 ID、cursor 和可选 wait_ms,返回有序事件、下一游标、done 和终态结果或错误。句柄绑定当前插件 generation 和执行。cancelModelInvocation 取消活动流;streamModel 将这些方法包装成异步迭代器,在消费者提前退出或抛错时取消未完成工作。应在处理函数的生命周期内消费流;需要跨越原调用生命周期时,使用后台任务。转发事件时保留不透明的 provider_state 和 lifecycle 数据;reset_partial 为真时,在接收重试的新事件前丢弃已累计的部分输出。 assistant_item_done 携带完整的 assistant message 检查点,供中断恢复使用;转发时须保留,不应额外追加可见消息。
middleware 接收 ModelMiddlewareInput,包含 request、origin、stream 和 continuation_id。通过 invokeModel 或 startModelInvocation 转发时,携带该 continuation ID 和未经修改的请求。continuation 只能由当前活动回调使用一次,不接受模型别名或输出限制覆盖;修改输入应使用 request-transform 扩展点。直接返回响应可以跳过后续调用链;先转发再修改响应则包装下游行为,不另建一次推理 operation。
可替换的适配器
Section titled “可替换的适配器”初始化时注册 model_adapters: [{ id: "example.adapter", version: 1 }] 并实现 model.adapter。ID 是全局唯一的点分名称,最多 160 个字符,各段可含小写字母、数字、_ 和 -;候选 generation 内任何重复注册都会失败。通过 request.adapter 显式选择实现,或在模型配置中设置 options.adapter 以用于普通对话;请求中的显式值优先,"native" 选择宿主协议。保留选项在原生传输前会被移除。选中的适配器缺失或被撤销时会报错,不会回退到原生实现。适配器可以生成合成响应,也可以通过 host.model.invoke 发起另一条有界请求,并用 adapter: "native" 转发给原生提供商。
回调接收 { adapter_id, request, stream }。非流式调用返回 { response }。流式调用可以直接返回该响应,也可以通过 emitModelEvent 发出有序事件,以恰好一个 done 或 error 结束,然后返回 {}。发事件前需声明 requireModelEmitService()。混合返回响应与事件、缺少终态事件,或在对应流式回调之外发事件都会失败。宿主不会向插件发送凭据。
适配器在注册时明确声明用量记录方式:
mode: "direct"(默认值):宿主在调用回调前记录一条可能计费的提交,并检查工作流预算。返回的用量是插件报告的本适配器用量;省略用量表示未知,不代表零。额外发起的host.model.invoke调用分别记录其提交和费用。mode: "delegate":用于包装器,例如{ id: "example.forward", version: 1, mode: "delegate" }。宿主保留逻辑操作记录,但不记录一条可计费的父提交。响应中的用量仅用于汇总;每个宿主模型子调用分别记录实际提供商提交和用量,因此转发响应不会重复计费。
这些模式是受信任扩展明确声明的用量契约。原生转发使用宿主配置的凭据和提供商传输。Extension API 不提供自动附加凭据的任意 HTTP 传输;对于插件代码在宿主服务之外自行发起的任意网络请求,Wuu 无法强制执行推理预算。
TypeScript 运行时适配器中的 model.adapter 和 model.middleware 处理函数可重入,能与等待它们的工具或回调并发运行,因此应保护共享可变状态。TypeScript 的其他 concurrentCapabilities 和 concurrentServices 仍须显式启用;生命周期屏障继续有效。
属于 generation 的后台工作
Section titled “属于 generation 的后台工作”声明 requireBackgroundService(),提供有明确结束点的版本化服务处理函数,在激活后调用 startBackground(host, { key, service, method, params?, timeout_ms? })。目标必须是同一插件 generation 自己提供的服务。启动返回 { job_id, execution_id },回调获得普通的作用域 host 和取消信号,因此可以在原对话或观察回调返回后调用模型服务。
inspectBackground(host, { job_id }) 返回 running、completed、failed 或 cancelled,以及结果或错误;cancelBackground 用于取消。活动 key 在每个插件内唯一,最多同时运行 64 个任务并保留 64 个终态结果;参数和结果分别最多 1 MiB。它不是持久调度器:重载、禁用、generation 退役或进程丢失会取消所属工作,旧 generation 不能再启动任务。计时器、调度策略、预算、快照失效和持久业务状态由插件负责,关闭时应清理计时器。
公开 SDK 的有界重放 fixture和影子比较 fixture使用合成数据验证这些扩展点。它们用于验收,不是默认安装的缓存或比较功能。对源码构建的 core 运行 node scripts/plugin-model-pipeline-e2e.mjs,驱动会使用本地模拟提供商并保存可重复检查的结果,不产生付费推理。
绑定会话的 shell 进程
Section titled “绑定会话的 shell 进程”声明 requireProcessService() 以使用 host.process 版本 1。在活动的会话工具回调中调用
startProcess(host, { key, command, cwd?, timeout_ms?, follow_parent? }),沿用调用方的
权限及已绑定的本地或远程执行环境,包括该环境的工作区和进程管理器。远程路径不会被当作
插件或宿主的本地路径。所选远程环境缺失、不可用或协议不兼容时直接失败,不会改在本地
执行。激活阶段或无关 capability 不能启动进程。
getProcessEnvironment(host) 在该活动回调内检查已选位置,不连接或创建执行环境。
返回值为 { backend, root, identity? }:backend: "local" 表示本地执行;root 是所选
后端内的工作区根目录;可选 identity 是其已绑定环境的不透明标识。这些信息不是选择器,
也不授予权限。此方法和进程快照的可选 environment 字段从 host.process 1.1 起提供,
既有版本 1 的 start/read/stop/send 调用保持兼容。旧宿主会明确拒绝新方法;快照没有元数据
不代表本地执行。快照元数据在进程获接纳时捕获,修改默认环境或将事件接收方移交父会话
都不会迁移运行中的进程。
在同一会话与插件 generation 内,同一 key 和相同参数返回已保留的记录,参数变化会冲突;
每代最多运行 64 个进程,宿主最多保留 256 条终态句柄记录。启动请求断线时,命令可能已产生
效果而回复丢失。key 保护宿主已保留的记录,不保证跨重启或记录淘汰的恰好一次执行。
已保留的启动失败记录会让同一 key 返回原错误,而非再次启动。应使用报告中的 operation_id
对照所选后端的进程记录核实不确定结果,再启动另一条命令。辅助函数不会自动重试。
所有进程辅助函数均接受可选的最后一个参数 { signal },且不能解除回调本身的取消信号。
在接纳之前,取消会阻止启动,或在结果已知时清理已启动的进程。接纳之后,进程归该插件
generation 所有,可以活过工具回调。取消读取或中止原始调用的信号不会停止已接纳的进程;
显式取消应调用 stopProcess。
readProcess 返回输出、字节游标、截断信息和进程状态。不指定 stream 时仍返回合并输出。
host.process 1.2 支持 stream: "stdout" 或 stream: "stderr",分别使用独立游标,结果回传 stream。
旧记录、PTY 或不支持分流的后端会明确报错;SDK 会拒绝缺少流确认的结果。原始输出本身不唤醒会话。单次读取范围为 4 字节到
1 MiB,默认 64 KiB,最多等待 30 秒。应使用返回的 next_offset,因为 UTF-8 分块及日志
保留策略可能影响实际长度。stopProcess 停止整个进程树。超时为零表示无截止时间,
但会话归档或删除、generation 关闭、宿主退出仍会停止进程。follow_parent: true 让已完成
的私有子会话将接收方交给父会话,快照中的 session_id 返回当前接收方。
sendProcessEvent(host, processID, message) 沿用 host.session.send 的请求身份、显示和
if_running 契约,由宿主选择该进程绑定的接收会话。这样可以向启动它的私有子会话投递
事件,而不会获得任意私有会话的访问权。输出及进程退出本身不会唤醒会话。插件负责分行、
过滤、事件策略和持久历史;运行时重启后句柄失效。内置 Monitor 插件
演示了这个接口。
观察类输入可以使用 presentation.kind: "background_event":事件仍进入模型上下文和
持久历史,但不显示输入气泡。助手回答、工具活动和轮次失败仍正常显示。
任何插件都可通过 host.session.send 或 sendProcessEvent 使用此展示类型,
接纳、插入当前轮次和请求去重规则保持一致。真实用户输入仍使用普通展示。
旧宿主会拒绝这个新类型,使用前应一起更新宿主和客户端。
初始化时返回 provided_services,并实现 invokeService。描述包含小写点分名称、严格的 MAJOR.MINOR.PATCH 版本,以及带 input_schema、output_schema 标识的方法。这些标识命名契约,不是内嵌 JSON Schema 定义。
消费者声明服务名和主版本,再通过统一入口调用。宿主提供调用者身份并路由到活动 provider,不应直接寻址另一个插件进程或依赖其私有文件。适配插件可以同时提供和消费同名服务,通过显式的 consumer 绑定委托给另一个实现;已解析的依赖图拒绝自调用和循环。参见局部扩展 provider。缺少必需服务会阻止消费者激活;同名同主版本的多个 provider 会产生诊断,不会合并实现。
服务组合示例(英文)包含两个可构建的包:无状态的文本分析 provider,以及消费其版本 1 API 的写作报告工具。示例展示两种声明和真实的 gateway 调用。
组合验收不能只检查成功的服务调用:激活 provider 和带包内技能的消费者,再终止 provider,并让其下一次初始化失败。恢复检查后,验证已有和新建的消费者工具集都不能加载该技能或调用已撤销贡献,而独立包和普通工作区技能仍可用;同时验证已经创建的提示词保持不变。这个场景不需要模型请求:可用 JSONL 运行时测试进程和直接 load_skill 调用检查生命周期边界。
可重入服务处理函数
Section titled “可重入服务处理函数”TypeScript 运行时适配器默认按顺序处理普通请求。对于能够安全地与其他处理函数重叠执行的服务,可将其名称加入 RuntimePlugin.concurrentServices:
const plugin: RuntimePlugin = { concurrentServices: ["example.text.analysis"], // initialize、invokeService 等回调……};只对可重入的处理函数启用:保护共享可变状态,并传递取消信号。在 A → B → A 服务调用链中,A 等待 B 时,其回调服务必须能够执行。此选项仅影响所列名称的入站 service.invoke 请求,不会让所有工具或能力并发,不会授予服务权限,也不会解决包依赖环。RuntimePlugin.concurrentCapabilities 为指定能力 ID 提供相同选项,例如在前台任务等待期间处理只读状态请求。仍须保护并发处理函数读取或修改的状态。初始化和激活仍是执行屏障;shutdown 停止接收新工作,等待已接收的工作结束后再清理。这是同一个 JavaScript 进程中的异步重叠执行,不是工作线程。
工具和能力调用具有宿主管理的执行 ID。SDK 提供 execution.signal,应传给可取消操作,在信号中止时停止工作。工具、服务和能力处理函数收到的 host 自动将执行 ID 和中止信号绑定到
服务调用。前台工作应使用这个 host,不要改用初始化时保存的不绑定作用域
host。host.call(method, params, { signal }) 可附加取消信号,但不能移除执行
本身的信号。取消会拒绝尚未完成的调用,迟到响应会被忽略。调用返回后,执行作用域关闭;迟到的进度不能重新打开已完成执行,取消也不会等待插件确认。
TypeScript 运行时适配器会在进入处理函数前拒绝已取消的工具、能力和服务请求,包括等待其他工作或生命周期屏障的请求。取消某次执行不会跳过清理所需的生命周期回调,并会通知共享该执行 ID 的所有活跃回调。处理函数一旦开始,取消仍是协作式的:应将 abort signal 传给支持取消的操作。取消不能撤销已完成的副作用,也不能强制中断任意插件代码。
App-server 的只读 plugin/executions/list 返回 { executions: [...] },
包含当前连接运行时的当前 generation,以及仍被活动工作持有的旧 generation。
每行在宿主局部 id、所属 plugin_id、可用执行作用域和最新进度之外,增加
generation_id 与 retained。用 (generation_id, id) 作为稳定的诊断标识:
不同 generation 都可能存在 exec-1。原有 id 和取消路由保持不变。
结果按 generation ID、执行 ID 排序;调用结束后移除,读取列表不会延长
任何 generation 的寿命。这是即时快照,不是执行历史或跨工作区清单。
禁用插件也会取消其旧 generation 中的工作。
reportExecutionUpdate、invokeTool、askUserQuestions 和 importArtifact 都调用版本化服务,需要对应声明。编排工具使用 activity.orchestrator=true 和 execution.invoke-tool,通过正常工具链委托副作用。重试相同工具和参数时,保持子调用 call_id 不变;不要用同一个 ID 表示不同工作。
security.authorize 服务可以进一步限制操作,但不能放宽宿主的强制边界。sandbox.process provider 必须提供真实的文件系统约束,不是命令审批弹窗。实现前请阅读权限。
Agent 管理的开发
Section titled “Agent 管理的开发”内置延迟发现工具 plugin_manager 支持 status、apply、enable、disable 和 reload。apply 接受工作区内的 TypeScript 文件或开发包目录,通过宿主选定的 CLI 桥接执行一次发布。不接受任意命令,也不允许调用者指定 Wuu 主目录。验证或导入源码前必须获得宿主代码执行授权,这与写入源文件的权限不同。完整流程见 Agent 操作步骤。
状态结果包含 plugins 列表。比较 published_fingerprint 与 current_conversation_fingerprint,并检查 adoption_pending、desired_enabled、trusted 和 current_conversation_runtime_state;存在运行时诊断时由 error 返回。这些字段说明发布、策略和当前会话的绑定,不证明某个工具执行成功或桌面视图已渲染。
独立的 hosts 列表查询各个活跃 app-server,包括独立的用户、产品宿主。每项包含宿主绑定、generation、执行身份、存储身份、指纹、运行状态和错误。wuu plugin hosts [id] 提供同样的观测。空列表表示没有兼容宿主报告状态,不能据此判断激活成功。wuu plugin reload <id>(或 plugin_manager 的 reload 操作)重新构建宿主 generation,用于恢复已修复但仍处于失败状态的运行时。重试客户端请求不会重新加载运行时。
普通更新保留当前轮次及其未完成工作者或受管进程的原工具与提示词 generation。这些工作结束后,在下一次空闲轮次边界发现并调用更新后的工具。禁用或移除提交成功后,则无需等待空闲轮次,直接撤销各个仍被引用 generation 中的插件。发布不等于实际调用工具或检查界面,也不会自行安排后续轮次。
本机界面控制不沿用普通的 generation 保留规则。更新所有者时,宿主在替换版本激活前撤销其浏览器与电脑操作权限;禁用、移除或关闭它也会停止相应活动。绑定活动的 MCP 连接断开或替换后,不能继续控制本机界面。同一插件 generation 内重新连接后,可服务明确的新用户任务,但不会恢复旧调用的权限。权限撤销后,不要让旧任务改用其他本机控制后端重试。详见运行时 generation。
wuu plugin dev ./extension.ts 直接运行默认导出实现 RuntimePlugin 的单文件。Wuu 提供匹配的 SDK 并在内部生成进程适配器;此开发路径不要求 manifest、安装、打包或构建。需要支持 --experimental-transform-types 的 Node.js 22.13 或更高版本,仅支持 Node 内置模块和 @wuu/plugin-sdk 导入。每次发布前保存候选快照,检查语法、导入和默认导出;加载失败则保留上次发布的源码。导入验证会执行模块顶层代码,不是无副作用的检查。initialize 在实际宿主中声明契约;随后由 prepare 通过所选服务验证状态。准备失败保留旧运行时;激活发生在提交之后,失败时可能留下新运行时的失败状态,契约见上文。完整示例见 Agent 插件快速上手。
wuu plugin create 生成 agent、desktop 或 full 包骨架。对于目录,wuu plugin dev 使用其中的 plugin.json 和 package.json 构建脚本,再发布构建后的开发 generation。两条路径在构建或加载失败后都会继续监听,修复并保存即可恢复,不需要重启。文件事件经过防抖,并定期核对以补偿遗漏;--poll 必须为正数。
获得授权的开发覆盖保留已安装的主目录级插件的存储身份,同时仍使用 plugin:dev:<id> 执行身份和开发权限。签名 generation 回执固定这一绑定,后续发布沿用。旧回执缺少绑定时,在下次发布时建立;独立开发插件保留自身存储身份。覆盖代码不会重放或改写数据所有权迁移。
替换已发布包前,dev 要求所有已登记的活跃宿主使用各自的所选提供者与配置准备候选版本。预检拒绝时保留已发布包和运行中的 generation。发布后,结果包含各宿主实际观测到的激活状态;激活失败会明确说明发布已经提交。修复后重新发布或显式重新加载,无需删除所有权元数据。旧核心宿主需使用兼容核心重启后才能参与此流程。
用 validate 检查包结构,test 检查可执行初始化和协商描述,再通过真实会话或界面验证行为。pack 生成本地 zip,不会上传到 registry。分发前应检查压缩包内容:准备阶段排除 .git 和 node_modules,不会排除所有可能包含私人数据的本地文件。
Monorepo 插件可在 package.json 声明额外构建输入:"wuu": { "dev": { "watch": ["../shared/src", "../assets/prompt.md"] } }。路径相对插件目录,支持相邻文件或目录;修改后会重新构建并发布,无需重启。绝对路径、插件祖先目录、经过符号链接的路径,以及超过 64 个额外输入会被拒绝。不要列入生成文件;输入目录中的 .git、node_modules、dist 仍被忽略。这只控制开发监听,不改变运行时权限。
install CLI 会确认信任并启用包。同一已记录来源的更新继承信任与启停状态;来源改变或尚未记录来源时需要确认。开发目录授权不会成为分发包的信任。命令见插件管理,信任边界见安全模型。
完整插件编写类型位于 packages/plugin-sdk/src/index.ts。Go 宿主及其内部应用嵌入接口属于宿主实现,不是另一套插件编写 SDK。请与目标 Wuu 版本匹配;产品 CalVer、包 schema、能力协议、服务版本和桌面快照版本是不同的兼容性检查。
私有存储所有权迁移
Section titled “私有存储所有权迁移”插件可通过对应的 storageTransfers.exports 和 storageTransfers.imports
声明,将用户级存储前缀交给另一个插件。每条声明包含 id(稳定的迁移标识)、
plugin(对方插件 ID)、scope(仅支持 user)、domain(宿主选择的产品域)
和 keyPrefix(非空的合法存储键前缀)。导出方指定导入方,导入方指定导出方;
其余字段必须完全一致。
宿主解析实际发现的精确插件身份,并分别验证双方信任。已禁用的导出插件无需运行; 导入插件必须已被选中激活。找不到导出方时,不搜索旧身份或其他用户、产品域的数据。 声明参与包指纹计算,不提供跨插件存储 RPC 权限。
导入插件应在正式激活回调中读取迁入前缀,不应在初始化阶段缓存其内容。 预检不修改存储。所选设置提供者在准备阶段检查所有权、目标冲突和配额,检查失败会在提交前拒绝候选版本。启动或更新被接受后,宿主再次验证整个目标文档,再持久化源前缀隔离, 然后原子保存原始值和导入回执。键冲突或存储配额超限会阻止迁移,不覆盖目标数据。 迁移失败会阻止导入插件运行;未完成迁移在下次接受激活时恢复。完成回执可防止目标 删除数据后再次导入。合并后的目标仍遵守原有的单插件条目数和字节配额。
源字节保留为非活动备份。持久化隔离后,普通读写(包括保留的旧运行代)无法访问该 前缀。源插件的其他前缀以及其他用户、产品域不受影响。宿主不解析插件业务记录。 回退插件版本不会撤销所有权迁移,也不会重新开放备份。
迁移文档使用存储 schema 2,使旧宿主明确拒绝读取,而不是忽略所有权元数据。 未迁移文档保持 schema 1。降级后的旧宿主不能使用已迁移状态;恢复需要支持迁移的 宿主和保留的原始备份,不会自动恢复旧所有者。
除本地目录和 ZIP 外,inspect、install、update 还接受
npm:<name>@<exact-version>。支持带 scope 和不带 scope 的包名;不接受版本范围、
tag、Git URL 或隐含的 latest。默认 registry 为 https://registry.npmjs.org;
--registry 可明确选择另一个 HTTPS npm registry。选项写在位置参数之前:
# 将示例包名替换为你信任的实际包。wuu plugin inspect --json npm:@your-scope/wuu-plugin@1.2.3wuu plugin install --integrity 'sha512-BASE64_FROM_INSPECTION' npm:@your-scope/wuu-plugin@1.2.3wuu plugin update my-plugin npm:@your-scope/wuu-plugin@1.2.4压缩包必须以 package/ 为根目录,包含 package.json、受支持的 Wuu manifest
及全部运行时资源。npm 包名与版本必须匹配 registry 元数据,插件 manifest 版本
必须等于 npm 版本。插件 ID 与 npm 包名独立;update 使用已安装的插件 ID。
发布前应将运行时依赖打入独立 JavaScript bundle,构建工具及 SDK 放在开发依赖中。
非空的 dependencies、optionalDependencies 和 peerDependencies 会被拒绝,
而不是自动安装。检查或安装时,Wuu 不调用 npm,不运行生命周期脚本或构建脚本。
单文件 TypeScript 开发路径保持不变,不需要上述打包步骤。
远程包必须提供 SHA-512 完整性值,并在解压前验证。检查结果中的 distribution
对象与安装后 <WUU_HOME>/plugins/<id>/.wuu-package-lock.json 使用同一 lock
schema;lock 本身也纳入包指纹。更新前可另存一份:
cp "${WUU_HOME:-$HOME/.wuu}/plugins/my-plugin/.wuu-package-lock.json" ./my-plugin.lock.jsonwuu plugin install lock:./my-plugin.lock.json# 或明确为已安装的 ID 恢复该固定包:wuu plugin update my-plugin lock:./my-plugin.lock.jsonlock 重放只下载记录中的 tarball,不读取可变 registry 元数据;仍然需要网络。 lock 记录 registry、npm 包名/版本、插件 ID、tarball URL 和完整性值。这是制品来源 记录,不是 npm provenance/签名验证、作者认证或安全审计。安装前应信任发布者。 恢复旧包不会撤销 provider 数据迁移或已完成的外部副作用。
下载只使用不带凭据、Cookie、查询参数或 fragment 的 HTTPS URL。Wuu 不读取
.npmrc 或 token 环境变量中的 npm 凭据。tarball 与重定向必须保持在所选 registry
同一 origin;此路径不支持需认证的 registry 或跨 origin CDN。元数据上限 4 MiB,
压缩包上限 128 MiB,解压后上限 512 MiB / 10,000 个条目。链接、路径穿越、仅大小写
不同的冲突路径,以及发布者自行携带的 lock 文件都会被拒绝。失败不会改用其他
registry 或本地包。
下载和验证完成后才获取 catalog 变更租约;随后复用本地包的准备、信任、原子发布 和 generation 生命周期。来源身份为 registry 加 npm 包名:同来源精确版本更新 继承信任及明确的启停状态;registry 或包名变化需要确认来源。无效制品或准备失败 保留旧 generation;提交后的激活失败会报告已提交状态,不会声称已回滚。已接收任务 继续保留其捕获的 generation。
plugin/package/inspect 和 plugin/package/install RPC 的 path 接受相同来源,
另有可选 registry 与 integrity 字段。lock 来源不允许覆盖 registry 或完整性值。
registry 请求不占用串行 RPC 分派循环,无关发送和读取无需等待下载。CLI 信号可取消
下载;RPC 下载继承请求 context,连接对应的 server 关闭时会取消并等待其退出。
没有独立的包下载取消 RPC。
官方和社区 npm 包都走这些公开契约,安装不会授予内置包特权。
Personal 执行事实
Section titled “Personal 执行事实”替代 personal.agents@1 的 provider 实现 observe-execution,输入为
{session_id},返回 SDK 的 PersonalExecutionFacts。provider 负责解析自己的
agent、任务、需求版本、run、活动/取消状态、所属 routine 和准确的原始用户消息来源。
宿主独立验证 session 所有权、真实历史、选中的 generation 和执行授权。业务事实本身
不授予执行权限。
修订过的任务通过 task.requirement_sources 按顺序返回每版需求的
{revision, session_id, message_seq},不能遗漏版本。引用必须来自宿主绑定的真实
用户输入,不能来自模型参数或 goal 摘要。恢复任务可以重复引用同一条消息,但序号
不能倒退。宿主在所属 agent 的主会话中读取有效用户历史,核对完整来源链并绑定到
动作摘要;后续限制覆盖早先许可。证据缺失时禁用意图自动审批,撤回或其他会话的
来源不能授权执行。旧的单条来源格式仅在第 1 版有效。不能从缓存文本补造来源,
也不能静默丢弃某版需求。
使用现有 Personal review/memory coordinator 时,应将这些持久化事实存放在 provider 自己的用户域 host storage 中;键和值的格式由 provider 决定。执行副作用时, coordinator 保留选中的 provider,并对该不透明存储、session 和 review 状态持有 持久化 inspection。一次事实读取不是事务或执行保护。替代存储后端必须保持 host storage 的 inspection 契约;内存缓存不能成为事实读取的独立权威。
legacy-memory 返回待迁入选中 memory journal 的旧记录 ID 到序列化值的映射;没有
旧数据则返回空对象。coordinator 不再解释其他插件的私有存储键。两个方法都不修改
状态、不恢复任务。provider 被禁用或不可用时,不得偷偷回退到内置实现。
若同一 runtime 还会调用 host.personal(例如 memory 操作),应将
personal.agents 加入 SDK 的 concurrentServices:宿主可能在完成原调用前回读
这些事实,不能把回读排在正在等待宿主的调用之后。通过存储与 inspection 契约同步
状态修改,而不是串行阻塞整个服务。
Personal 协调器与宿主副作用机制
Section titled “Personal 协调器与宿主副作用机制”personal.coordination@1 是可替换的审核、记忆及文件策略服务。
Personal 组合选择 wuu.personal-client、原生协调器
wuu.personal-actions-consumer 和宿主机制 wuu.personal-effects。
替换绑定或禁用协调器不会创建原生回退;Go 宿主仍负责入口身份验证、
会话解析和资源边界检查。
协调器请求为 {operation?, input?, session_id?, call?, scopes?}。
call 承载现有 review.* / memory.* 操作;file-gate 接收工具调用并返回
工具结果;scope / cancel 支持精确会话与任务取消;inspection 处理经所有者
认证的审核策略、设置和会话恢复,并与正在进行的 Personal 副作用串行;initialize / drain 用于生命周期及调度。
替代实现声明 host.personal-effects@1:prepare-file 接收工具调用,返回
{effect_id, file} 冻结动作;execute 接收 {effect_id},返回 {tool_result},
每个句柄至多执行一次;release 接收 {effect_id},可重复调用且声明为清理方法。
只有当前选中的协调器可调用。跨进程回调使用执行 ID 继承宿主来源,不接受序列化
State、工具箱或所有者声明作为权限。
延迟文件句柄可跨越初始回调,保留原始服务图及工具箱;后续所有者确认不会把旧动作
改绑到新一代。句柄不是持久化审批记录:冷启动恢复需要新认证及冻结动作重新验证,
不确定结果不能自动重放。连接器句柄限于同步 broker 回调,回调结束即释放。
兼容原生代际通过引用计数共享待处理状态,旧代清理不能关闭新代已借用的协调器。
禁用或崩溃先封闭入口,再取消并排空副作用;宿主关闭也释放待处理资源。
原生机制使用 ServiceRegistry.RegisterRetirementCleanup 建立物理清理屏障,
仅把可能重新进入关闭流程的最终图引用释放交给 DeferServiceCompletion,不增加策略权限。