夜雨聆风学习资料网

ARTICLE · 1030205

把插件接入工作台:Preset、UI 入口与用户配置

把插件接入工作台:Preset、UI 入口与用户配置

工具已经注册,生命周期测试也全绿,打开 Web 工作台却找不到任何入口。这通常不是前端“少写了一个按钮”,而是能力可见性、用户设置和会话投影还没有形成同一条链。把一个工具硬塞进全局菜单看起来很快,代价是所有 Agent 都突然获得它,连本来不该读取仓库的会话也跟着沾光。

仓库问答要成为工作台里的完整能力,需要同时回答四件事:哪些 Agent 能看到工具,用户如何配置预算,执行状态怎样进入持久 Session,浏览器如何从事件重建同一个节点。Preset、Settings、Host/Client 和 Conversation Node 分别负责其中一段。

Preset 决定能力在哪个 Agent 中可见

Agent Preset 是一个包含 agent.cordis.yml 的目录。Roster 在进程内把每个 preset 常驻挂载一次,再通过 scope 父链让会话的 Agent 加入该组装。工具、提示词和投影贡献只创建一份,但解析顺序遵循 agent → preset → global:更近的 scope 可以遮蔽更远的注册,兄弟 preset 之间彼此不可见。

仓库问答工具因此应该放进需要它的 coding preset,而不是注册到全局根上下文。一个没有加入任何 preset 的子 Agent 到达模型请求时,既看不到父方工具,也看不到父方提示词;进程内子 Agent 使用 composeFrom() 继承父 Agent 已运行的常驻组装,而不是按 id 重新读取磁盘。

Preset 选择也是模型可见事实。初始 preset 写入 Session Header;空白会话切换后,agent-preset/selected 事件记录实际选择。恢复、fork 和冷读必须解析这个事件,不能只看创建时 header,否则新进程会用旧工具集重建一段由新工具集产生的历史。

会话一旦有任何产出就不能 recompose。原因不是 UI 偷懒,而是换走工具 schema 后,历史里已有的工具调用可能再也找不到实现。切换只对空白会话开放,并且先确保新组装挂载成功,再移动 scope 链;失败时保留原 preset。

Settings 不是另一份插件配置

用户需要调整最大匹配数、上下文预算和是否显示引用,但这些值不应直接写回 preset 文件。Settings 服务按 namespace 注册 schema,并把三层数据解析成一个值:

schema 默认值 → 组装 base → 用户文档 section

base 是部署或 preset 提供的工程默认,user 只覆盖用户明确设置的字段;清除某个用户字段就重新继承 base。若部署没有挂载 Settings provider,消费插件仍可只靠 entry 配置工作,不必为“没有设置页”的环境维护第二套逻辑。

每个 namespace descriptor 都带单调递增的 revision。设置页读到 revision 7 后提交更新,若别的窗口已经推进到 8,服务抛出 SETTINGS_CONFLICT,而不是让旧页面覆盖新值。同一进程内的写入按 namespace 串行,但跨进程一致性仍由 provider 决定。

涉及 API Key 等秘密字段时,所有 wire 接口都必须使用 describe({ redactSecrets: true })。返回值只暴露 secret slot 是否已设置,不返回原值。更稳的更新方式是 path-addressed mutation:它允许页面只修改一个已知字段,不需要用脱敏后的半份对象做 wholesale replace,从而误删看不见的秘密。

仓库问答可使用一个小而明确的设置契约:

字段
作用
建议边界
maxMatches
搜索阶段最多接收多少命中
正整数、有上限
maxContextChars
进入模型的片段总预算
正整数、有上限
showCitations
UI 是否展开引用
布尔值

Workspace 根目录属于会话身份和权限边界,不适合被任意全局设置覆盖;若允许选择仓库,应由工作区服务提供受控候选并验证归属。

UI 状态必须来自可回放事件

浏览器若直接监听一个内存回调,实时运行时看起来正常,刷新页面后节点就消失了。Conversation Node 的输入必须是持久 Session 事件族,例如:

事件
稳定事实
repo-qa/startanswerId
、Turn/Step、问题
repo-qa/progress
同一 answerId、命中数量
repo-qa/end
同一 answerId、回答与引用
repo-qa/error
同一 answerId、稳定错误码

answerId 不能用“最近一个未完成任务”猜出来。分页、断线重连和并行回答都会让“最近”失去确定性。每条 update 自带稳定 id,Assembler 才能用 (kind, id) 找到 Context,并按 seq 折叠成同一份 State。

只有 update 的尾部窗口可以暂时保持 pending;当更早分页补回 start 后,再从 start 按序重放。若产品要求 start 不在窗口时也立即展示,terminal 或 checkpoint 事件就必须携带足够的完整 fallback 状态,不能扫描所有无关事件拼凑。

Conversation Node 是投影,不是第二个后端

一个 Node Definition 主要完成四个动作:match() 从当前事件提取 id 和角色;start() 创建状态;update() 纯折叠后续事件;buildViewNode() 生成 renderer 直接消费的数据。

在当前基线的正常 append 热路径中,每个 Definition 只检查当前事件,命中后可近似按常数时间定位 Context。不要在 update() 中遍历完整 Session、所有 Context 或已渲染 Node。累计数量保存在 State,同一 Turn/Step 的共享值放进 Location data,需要前序业务状态时用受限的 reader.previous()。

视图节点的 key 必须稳定。进度从 20% 变到 80% 时,更新同一个 keyed node;暂时隐藏时设置 visibility: hidden,不要返回一个新 key 或先删除再创建。publication: animation-frame 可以合并高频可见进度,但事件仍按日志顺序逐条折叠。

Renderer 只读取 node.data 和受限 Location hook,不执行文件 I/O、模型调用或 Session 搜索。UI 是后端事实的投影,不是另一个偷偷拥有业务状态的后端。

Host 与 Client 之间只传协议事实

Host 负责 Workspace、工具执行、Settings 持久化和 Session 事件;Client Runtime 通过 Remote namespace 获取列表、描述和更新接口,再把事件窗口交给 Conversation Assembler。断线重连时可以 replace 当前窗口,向前翻页时 prepend 更早事件,实时阶段 append 新事件。

三条摄入路径必须得到相同最终状态:完整窗口一次 replace;先加载尾部再 prepend start;历史加载后继续实时 append。若三者结果不同,通常说明 fold 依赖了内存到达顺序,而不是事件 seq。

协议上只传可序列化、已脱敏的数据。客户端不需要拿到绝对 Workspace 路径、原始 API Key 或服务实例;引用可以使用受控的相对路径和行号,由 Host 决定点击后能否读取详情。

一个可验证的设置与节点快照

配套 TypeScript 快照包含两个纯函数:一项按 defaults、base、user 顺序解析设置;另一项按 answerId 和 seq 折叠仓库问答事件。它不依赖 React,也不访问文件系统,因此同一组事件在测试、Host 诊断和 Client 回放中得到相同状态。

测试至少覆盖:用户层只覆盖指定字段;陈旧 revision 被拒绝;秘密不出现在 wire descriptor;仅 update 的窗口保持 pending;prepend start 后结果等于完整 replace;实时 append 与完整回放一致;多个 answerId 不串状态;renderer 的 key 在进度更新中保持不变。

总结

把仓库问答接入工作台,不是补一个前端按钮,而是连接四个边界:Preset 用 scope 控制模型可见能力,Settings 用 schema 和 revision 管理用户覆盖,Session 事件保存可回放事实,Conversation Node 把这些事实折叠成稳定 UI。

能力属于 Host,视图属于 Client,历史属于 Session,用户覆盖属于 Settings。四者各自守住边界,刷新、重连、换 preset 和配置冲突才不会让同一个回答长出四种版本。

参考资料

  1. DeepSeek AI,Agent Presets
  2. DeepSeek AI,Settings Service
  3. DeepSeek AI,添加 Conversation Node
  4. DeepSeek AI,[Web App Bundle

相关学习资料