news 2026/9/21 18:57:24

Paseo 插件 Header 按钮与 Composer Pill 实战:从按钮描述符到动态更新的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Paseo 插件 Header 按钮与 Composer Pill 实战:从按钮描述符到动态更新的完整实现

【免费下载链接】paseo

Orchestrate multiple coding agents from desktop and mobile

项目地址:https://gitcode.com/gh_mirrors/pa/paseo
点击查看免费下载

本文以仓库中的可运行示例插件plugin-examples/buttons为骨架,系统讲解 Paseo 插件如何向 Agent 工作区头部(Header)与消息输入区(Composer)贡献交互按钮,涵盖按钮描述符(Button Descriptor)的完整字段、action/menu/popover三种行为模式、update()/remove()动态生命周期、跨平台紧凑布局适配,以及从旧版 Pill 形状迁移与浏览器回归测试的验证方法。读完本文,你将能独立为 Paseo 插件实现可实时刷新、可隐藏、可禁用并随 Agent 切换迁移的头部按钮与 Composer Pill。

示例概览:一个插件,五种命令,两类表面

plugin-examples/buttons是一个最小但功能完整的客户端插件:它不包含index.server.ts服务端入口,所有能力都来自客户端运行时。安装该目录为插件、打开任意 Agent 后,在 Command Center 中依次选择以下命令即可观察同一份注册在不同模式下的表现:

Command Center 命令Header 表现Composer 表现可以尝试什么
Button examples: action仅 Refresh 图标Refresh 图标 + 标签点击 Refresh 会从 daemon 读取 workspace 并刷新标签与 tooltip;pending 状态与错误反馈由 Paseo 负责
Button examples: menuWrench、Tools、chevronWrench 与 Tools菜单内含刷新、分隔线、自定义详情项、Display 子菜单以及一个禁用项
Button examples: popover响应式状态圆点、Details、chevron响应式状态圆点与 Details通过useWorkspace实时接收 workspace 数据;点击 Done 关闭表面
Button examples: hide / show隐藏或恢复按钮隐藏或恢复 pill可见性通过registration.update({ visible })切换
Button examples: disable / enable禁用或启用按钮禁用或启用 pill禁用状态通过registration.update({ disabled })切换

以下三张截图来自示例插件的screenshots目录,分别展示了 action 模式的头部按钮与 Composer Pill、命名 Tools 菜单、以及自定义状态图标与 popover:

示例每次只贡献一个头部按钮:其余附加动作收纳在命名按钮Tools的菜单之下。示例刻意不添加三点(more)控件,也不触发插件溢出菜单,保证回归测试可以精确断言每个能力都直接呈现在界面上。

插件清单与安装前提

插件目录结构如下(来自 plugin-examples/buttons):

buttons/ paseo-plugin.json # 插件清单 index.client.tsx # 客户端运行时入口 client/examples.tsx # 按钮描述符、更新逻辑与 React 内容 screenshots/ # 各模式的界面截图

清单文件 paseo-plugin.json 声明了插件 ID 与最低 Paseo 版本要求:

{ "id": "button-examples", "requirements": { "paseo": ">=0.8.0" } }

根据 插件参考文档 中的说明,requirements.paseo接受 npm semver 范围:>=0.8.0表示兼容 0.8.0 及之后(含预发布与未来破坏性版本)的发行版;缺省该字段则等价于<0.8.0,Paseo 0.8 及之后会拒绝加载并给出指向迁移指南的提示。因此本示例要求 Paseo 0.8 及以上版本。

按钮描述符:一个描述符驱动两种表面

Header 按钮与 Composer Pill 共用同一个按钮描述符(Button Descriptor),类型与字段均从@getpaseo/plugin/client导出。这是本示例的核心设计:createButtonExamples中的button(next)函数只构造一份描述符,composerButton(next)再通过展开运算符叠加 Composer 侧专属的展示信息(见 client/examples.tsx)。

描述符完整字段如下(摘自 按钮描述符章节):

字段必填含义
title非空的无障碍标签、tooltip 与 sheet 标题
iconLucide 图标名,或ComponentType<PluginButtonIconProps>自定义图标组件
label非空展示文本;省略则使用所在位置的默认值
visible默认为true;为false时移除触发器及其占位空间
disabled默认为false;按钮保持可见但不可交互
behavior三种行为形状之一(见下)

行为(behavior)是一个判别联合类型:

type PluginButtonBehavior = | { kind: "action"; onPress(): void | Promise<void> } | { kind: "menu"; items: readonly PluginButtonMenuEntry[] } | { kind: "popover"; Content: React.ComponentType<PluginButtonContentProps> };
  • action:在客户端执行。Paseo 会在 promise 结算前把按钮标记为 busy、阻止重复点击,并用 toast 展示失败信息,失败后可重试。
  • menu:弹出菜单。宽布局下为锚定浮层,紧凑布局下为底部 sheet。
  • popover:渲染自定义 React Native 内容。Paseo 负责锚定、滚动、内边距与 sheet 呈现,插件只提供内容主体。

两种注册方式都返回{ update, remove }句柄(这是 Header 按钮与 Composer Pill 区别于其他客户端注册的特例——其余注册返回幂等移除函数)。示例中headerpill分别通过client.addHeaderButtonclient.addComposerPill注册(examples.tsx L181-L187):

const header = client.addHeaderButton({ id: "example", workspaceId, button: button(mode) }); const pill = client.addComposerPill({ id: "example", workspaceId, agentId, button: composerButton(mode), });

注意这里id在同一个目标(workspace / agent)内保持插件局部唯一:同一个 ID 可以用于不同目标或不同位置,但在同一目标内重复注册会抛错(见 更新与生命周期)。

Header 按钮与 Composer Pill 的差异

两者共享描述符,但呈现规则不同(参考文档):

  • Header 按钮client.addHeaderButton({ id, workspaceId, button })将按钮加在工作区头部右侧的内置动作之前。省略label即为纯图标按钮;菜单与 popover 在宽布局下显示 chevron。紧凑布局下头部按钮使用纯图标、无标签无 chevron,且为无边框样式。
  • Composer Pillclient.addComposerPill({ id, workspaceId, agentId, button })将 pill 放在指定 Agent 的 Composer 轨道上(与 Tasks、Subagents 并列)。Pill始终显示图标与label(省略 label 时回退到title),并且从不显示 chevron——即使行为是菜单或 popover 也一样。紧凑布局下 pill 依旧保留图标与标签。

示例通过composerPresentation表(examples.tsx L13-L17)为三种模式提供 Composer 专属的标题与标签,实现同一行为在两种表面上的差异化呈现:

const composerPresentation = { action: { title: "Refresh context", label: "Refresh" }, menu: { title: "Composer tools", label: "Tools" }, popover: { title: "Context details", label: "Details" }, };

三种行为模式的源码拆解

action:把真实操作交给 Paseo 管理

action 模式的核心是refreshWorkspace(examples.tsx L80-L88)。它返回真实操作的 Promise,让 Paseo 接管 pending 状态、防重复点击与错误反馈:

async function refreshWorkspace() { // 返回真实操作的 promise:Paseo 负责 pending、防重复点击与错误处理。 await client.paseo.workspaces.ref(workspaceId).refresh(); refreshes += 1; if (mode === "action") { header.update({ title: `Refresh workspace (${refreshes})` }); pill.update({ label: `Refreshed · ${refreshes}` }); } }

刷新成功后,update()把刷新计数写回标题与标签——这是「刷新后更新 UI」的标准姿势:不要直接修改原始描述符对象,而要调用update(patch: Partial<PluginButton>)原地变更描述符并保留身份与顺序。button("action")分支返回的描述符如下(L90-L97):

if (next === "action") return { title: "Refresh workspace", icon: "RefreshCw", label: undefined, // 头部为纯图标;Composer 用自己的 label behavior: { kind: "action", onPress: refreshWorkspace }, };

注释点明了label: undefined的意图:Header 侧保持纯图标,而 Composer 侧通过composerPresentation提供label: "Refresh"

menu:分隔线、子菜单与禁用项

button("menu")返回一个包含五类条目的菜单(L105-L171),完整覆盖菜单条目的三种用法:

  1. 普通 action 项refresh,图标RefreshCw,复用同一个refreshWorkspace
  2. 分隔线{ kind: "separator", id: "details-divider" },只含kindid两个字段;
  3. popover 项details,使用自定义图标组件WorkspaceStatusIcon,行为为 popover;
  4. 嵌套子菜单display(图标Eye),其items内包含「Hide example buttons」与「Disable example buttons」两个 action 项——分别调用setVisible(false)setDisabled(true)
  5. 禁用项publish,带disabled: true,标题明确标注 "(unavailable in example)"。

菜单条目的 ID 规则与按钮 ID 一致:小写字母开头,仅含小写字母、数字与连字符,且在菜单内唯一。宽布局下嵌套菜单以 flyout 展开,紧凑布局下在同一 sheet 内以带返回导航的页面展开。选中 action 会关闭菜单;打开另一页面则保持菜单开启。Paseo 会在过滤隐藏项后自动移除前导、尾随与连续的分隔线。

popover:自定义图标 + 实时 workspace 数据

popover 模式展示了两个自定义能力:自定义图标组件自定义内容组件

WorkspaceStatusIcon是一个PluginButtonIconProps型组件(L19-L30),用useWorkspace订阅 workspace 状态,再依据状态映射为颜色圆点:

function WorkspaceStatusIcon({ workspaceId, size, color, theme }: PluginButtonIconProps) { const status = useWorkspace(workspaceId, (workspace) => workspace.status); let fill = color; if (status === "failed") fill = theme.colors.statusDanger; else if (status === "needs_input") fill = theme.colors.statusWarning; else if (status === "done") fill = theme.colors.statusSuccess; const style = useMemo( () => ({ width: size, height: size, borderRadius: size / 2, backgroundColor: fill }), [size, fill], ); return <View style={style} />; }

PluginButtonIconProps提供themehostlayoutsizecolor及目标上下文;组件必须在给定的size内渲染,指针交互全部由 Paseo 接管,且图标组件可以使用插件 hooks。

WorkspaceDetailsPluginButtonContentProps型内容组件(L32-L70),通过useWorkspace一次订阅多个字段,展示 workspace 名称、项目名、状态与 diff 统计:

const workspace = useWorkspace(workspaceId, ({ name, projectDisplayName, status, diffStat }) => ({ name, projectDisplayName, status, diffStat, }));

内容区渲染一个Done按钮,其onPress={close}调用PluginButtonContentProps提供的close()来关闭表面。这正是 README 中「Done closes the surface」的实现:Paseo 拥有锚定、滚动、内边距与 sheet 呈现,插件只负责内容主体,并可在此使用usePaseouseRpcuseWorkspaceuseAgent以及安装实例的 React Query 缓存。

所有样式均取自theme.colorsforegroundforegroundMutedsurface2statusDanger等),并遵循 跨平台规则:只用View/Text/Pressable,不使用任何 HTML 元素或 DOM 全局对象,以保证在 iOS、Android 与浏览器(React Native Web)中行为一致。

动态更新:可见性与禁用状态的切换

setVisiblesetDisabled(examples.tsx L189-L196)通过update同步两个注册:

function setVisible(visible: boolean) { header.update({ visible }); pill.update({ visible }); } function setDisabled(disabled: boolean) { header.update({ disabled }); pill.update({ disabled }); }

生命周期语义(更新与生命周期)值得强调:

  • update(patch)原地变更描述符,保留身份与顺序;变更behavior时必须提供完整的 behavior 对象;非法更新会抛错且不改变现有按钮。
  • 隐藏或禁用会关闭其已打开的表面;变更 behavior 同样关闭表面。隐藏保留注册,因此重新显示时恢复原位置,但不会取消进行中的 action。
  • remove()幂等的;移除后的update不产生任何效果。插件卸载或宿主连接断开时,Paseo 会自动移除未清理的按钮。

切换 Agent / Workspace 与入口清理

client/examples.tsx是描述符与逻辑的载体,而入口文件 index.client.tsx 拥有活动示例的切换策略:它保持「同一时刻只有一个示例按钮组处于活动状态」,包括跨 Agent 与跨 Workspace 切换。

核心是buttonsFor函数(index.client.tsx L14-L24):当目标 workspace 或 agent 变化时,先对旧实例调用current.buttons.remove()完成清理,再为新目标创建新实例;目标未变化则复用现有句柄。三个循环分别注册 3 个模式命令、2 个可见性命令与 2 个禁用命令(L26-L58),共 7 个 Command Center 项,全部使用context: "agent"onSelect回调:

for (const mode of ["action", "menu", "popover"] satisfies ButtonMode[]) { client.addCommandCenterItem({ id: `show-${mode}`, title: `Button examples: ${mode}`, icon: "MousePointerClick", context: "agent", onSelect(context) { buttonsFor(context).setMode(mode); }, }); }

入口函数最后返回清理函数(L60):

return () => current?.buttons.remove();

这符合插件参考文档的入口约定:客户端入口默认导出一个接收PluginClientContextcontribute函数并返回清理逻辑;入口清理会在 Paseo 移除其余注册之前运行,因此插件应在此释放订阅、定时器与 socket 等自有资源。createButtonExamples返回的remove()(L205-L208)同时调用header.remove()pill.remove(),确保两个表面的注册一并卸载。

从旧版迁移:Pill 形状与清理句柄的变化

README 特别提醒既有插件项目:plugin-examples/buttons面向 Paseo 0.8 的运行时入口格式。0.7 及更早版本的插件在迁移时需同步更新@getpaseo/plugin依赖,否则npm run typecheck会立即报出旧 API 的 TypeScript 错误。按 Composer Pill 迁移指南,变化要点如下:

旧形状0.8 形状
Component渲染整个 pillbutton.icon+button.label
title作为独立字段移入button.title
onPress作为独立回调移入button.behavior{ kind: "action", onPress }
注册返回值是函数,直接调用即清理注册返回{ update, remove },调用.remove()清理

迁移后的典型写法:

const pill = client.addComposerPill({ id: "review", workspaceId, agentId, button: { title: "Open review", icon: "Scan", label: "Review", behavior: { kind: "action", onPress: openReview }, }, }); pill.update({ label: "Review · 3", visible: true }); // 客户端入口清理: pill.remove();

PluginComposerPillProps不再导出,旧式按函数调用的注册方式会直接报 TypeScript 错误——这是机械迁移中最容易定位的信号。旧的动态文本需要从「组件内部渲染」改为「模型或 SDK 订阅后调用update推送」;自定义图标组件仍可使用 hooks。

类型检查与浏览器回归测试

README 给出两条验证命令。类型检查针对 SDK 契约:

npm run typecheck --workspace=@getpaseo/plugin

它把示例代码与@getpaseo/pluginSDK 类型对齐;既有插件项目必须先升级依赖再做自己的npm run typecheck,才能发现旧的Component/onPresspill 形状与可调用的清理句柄。

浏览器回归测试把该目录原样安装进隔离的 daemon,在桌面与手机两种宽度下分别验证两种布局、动态更新与清理逻辑(plugin-button-example.spec.ts):

npm run test:e2e --workspace=@getpaseo/app -- e2e/browser/plugin-button-example.spec.ts

测试依次执行三个断言组:refreshFromHeaderAndComposer(从头部与 Composer 触发刷新)、useMenusAndToggleButtons(使用菜单并切换可见性/禁用)、inspectLiveWorkspaceDetails(检查实时 workspace 详情),并在开头验证「没有任何竞争的溢出按钮」,这正是示例刻意不引入三点控件与插件溢出行为的原因。需要说明的边界是:截图基于 Chromium 的桌面与手机宽度生成,原生 iOS 与 Android 并未被该测试覆盖(README 明确声明了这一点)。

小结:把「按钮」当作可编程的注册而非静态 UI

plugin-examples/buttons示例的精髓在于:Header 按钮与 Composer Pill 只是同一份按钮描述符在两种表面上的呈现,而update/remove句柄赋予插件完整的动态控制力——实时刷新标签、切换行为模式、隐藏恢复、禁用启用、随 Agent 迁移。配合paseo-plugin.json的版本要求、客户端入口的清理约定、跨平台组件规则与浏览器回归测试,这个最小示例构成了从「写一个按钮」到「在生产插件中管理复杂按钮生命周期」的完整参考路径。想继续深入,可对照 插件参考文档 中的按钮描述符、菜单条目与生命周期契约,或在 迁移指南 中查看从旧版 Pill 形状升级的完整核对表。

【免费下载链接】paseo

Orchestrate multiple coding agents from desktop and mobile

项目地址:https://gitcode.com/gh_mirrors/pa/paseo
点击查看免费下载
上一篇:FIFA 23实时编辑器完整指南:5分钟掌握游戏修改技巧
下一篇:Django REST framework SimpleJWT 黑名单功能详解

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 18:55:28

Vibe 语音转写工具:离线批量转录的高效实战指南

Vibe 语音转写工具&#xff1a;离线批量转录的高效实战指南 【免费下载链接】vibe Transcribe on your own! 项目地址: https://gitcode.com/GitHub_Trending/vib/vibe Vibe 是一款基于 Whisper 引擎的本地语音转写工具&#xff0c;全程在你的设备上运行。它能离线转录音…

作者头像 李华
网站建设 2026/9/21 18:46:16

改进减法优化器算法GSABO:融合黄金正弦与混沌映射

1. 项目概述在智能优化算法领域&#xff0c;2023年新提出的减法优化器算法(SABO)因其独特的数学基础和优化机制引起了广泛关注。作为一名长期从事算法优化研究的工程师&#xff0c;我在实际应用中发现原始SABO算法在解决高维非线性问题时存在收敛速度不稳定、易陷入局部最优等问…

作者头像 李华
网站建设 2026/9/21 18:44:11

企业级3D模型轻量化:从评估维度到工程实测的选型指南

前阵子有家做工业设备选型平台的客户&#xff0c;把一套装配模型扔给我&#xff1a;原始CAD导出STEP文件1.8GB&#xff0c;转到OBJ后1200多万个三角面&#xff0c;加载到浏览器里直接白屏。他们内部吵了一个星期——设计部门坚持模型一个倒角都不能少&#xff0c;前端要求首页3…

作者头像 李华