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

发布时间:2026/9/21 18:57:28
Paseo 插件 Header 按钮与 Composer Pill 实战:从按钮描述符到动态更新的完整实现 【免费下载链接】paseoOrchestrate 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 并刷新标签与 tooltippending 状态与错误反馈由 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/buttonsbuttons/ 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.0Paseo 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 标题icon是Lucide 图标名或ComponentTypePluginButtonIconProps自定义图标组件label否非空展示文本省略则使用所在位置的默认值visible否默认为true为false时移除触发器及其占位空间disabled否默认为false按钮保持可见但不可交互behavior是三种行为形状之一见下行为behavior是一个判别联合类型type PluginButtonBehavior | { kind: action; onPress(): void | Promisevoid } | { kind: menu; items: readonly PluginButtonMenuEntry[] } | { kind: popover; Content: React.ComponentTypePluginButtonContentProps };action在客户端执行。Paseo 会在 promise 结算前把按钮标记为 busy、阻止重复点击并用 toast 展示失败信息失败后可重试。menu弹出菜单。宽布局下为锚定浮层紧凑布局下为底部 sheet。popover渲染自定义 React Native 内容。Paseo 负责锚定、滚动、内边距与 sheet 呈现插件只提供内容主体。两种注册方式都返回{ update, remove }句柄这是 Header 按钮与 Composer Pill 区别于其他客户端注册的特例——其余注册返回幂等移除函数。示例中header与pill分别通过client.addHeaderButton与client.addComposerPill注册examples.tsx L181-L187const 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 模式的核心是refreshWorkspaceexamples.tsx L80-L88。它返回真实操作的 Promise让 Paseo 接管 pending 状态、防重复点击与错误反馈async function refreshWorkspace() { // 返回真实操作的 promisePaseo 负责 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: PartialPluginButton)原地变更描述符并保留身份与顺序。button(action)分支返回的描述符如下L90-L97if (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完整覆盖菜单条目的三种用法普通 action 项refresh图标RefreshCw复用同一个refreshWorkspace分隔线{ kind: separator, id: details-divider }只含kind与id两个字段popover 项details使用自定义图标组件WorkspaceStatusIcon行为为 popover嵌套子菜单display图标Eye其items内包含「Hide example buttons」与「Disable example buttons」两个 action 项——分别调用setVisible(false)与setDisabled(true)禁用项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提供theme、host、layout、size、color及目标上下文组件必须在给定的size内渲染指针交互全部由 Paseo 接管且图标组件可以使用插件 hooks。WorkspaceDetails是PluginButtonContentProps型内容组件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 呈现插件只负责内容主体并可在此使用usePaseo、useRpc、useWorkspace、useAgent以及安装实例的 React Query 缓存。所有样式均取自theme.colorsforeground、foregroundMuted、surface2、statusDanger等并遵循 跨平台规则只用View/Text/Pressable不使用任何 HTML 元素或 DOM 全局对象以保证在 iOS、Android 与浏览器React Native Web中行为一致。动态更新可见性与禁用状态的切换setVisible与setDisabledexamples.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); }, }); }入口函数最后返回清理函数L60return () current?.buttons.remove();这符合插件参考文档的入口约定客户端入口默认导出一个接收PluginClientContext的contribute函数并返回清理逻辑入口清理会在 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.iconbutton.labeltitle作为独立字段移入button.titleonPress作为独立回调移入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 --workspacegetpaseo/plugin它把示例代码与getpaseo/pluginSDK 类型对齐既有插件项目必须先升级依赖再做自己的npm run typecheck才能发现旧的Component/onPresspill 形状与可调用的清理句柄。浏览器回归测试把该目录原样安装进隔离的 daemon在桌面与手机两种宽度下分别验证两种布局、动态更新与清理逻辑plugin-button-example.spec.tsnpm run test:e2e --workspacegetpaseo/app -- e2e/browser/plugin-button-example.spec.ts测试依次执行三个断言组refreshFromHeaderAndComposer从头部与 Composer 触发刷新、useMenusAndToggleButtons使用菜单并切换可见性/禁用、inspectLiveWorkspaceDetails检查实时 workspace 详情并在开头验证「没有任何竞争的溢出按钮」这正是示例刻意不引入三点控件与插件溢出行为的原因。需要说明的边界是截图基于 Chromium 的桌面与手机宽度生成原生 iOS 与 Android 并未被该测试覆盖README 明确声明了这一点。小结把「按钮」当作可编程的注册而非静态 UIplugin-examples/buttons示例的精髓在于Header 按钮与 Composer Pill 只是同一份按钮描述符在两种表面上的呈现而update/remove句柄赋予插件完整的动态控制力——实时刷新标签、切换行为模式、隐藏恢复、禁用启用、随 Agent 迁移。配合paseo-plugin.json的版本要求、客户端入口的清理约定、跨平台组件规则与浏览器回归测试这个最小示例构成了从「写一个按钮」到「在生产插件中管理复杂按钮生命周期」的完整参考路径。想继续深入可对照 插件参考文档 中的按钮描述符、菜单条目与生命周期契约或在 迁移指南 中查看从旧版 Pill 形状升级的完整核对表。赞分享【免费下载链接】paseoOrchestrate multiple coding agents from desktop and mobile项目地址https://gitcode.com/gh_mirrors/pa/paseo点击查看免费下载相关推荐如何打造专属MacBook Pro触控栏从静态按钮到动态脚本按钮的完整指南如何打造专属MacBook Pro触控栏从静态按钮到动态脚本按钮的完整指南 MTMRMy TouchBar My Rules是一款强大的MacBook P桌面应用Epic Stack按钮组件交互按钮实现Epic Stack按钮组件交互按钮实现 概述 Epic Stack的按钮组件系统基于现代React技术栈构建提供了高度可定制化的交互按钮解决方案。该系统包后端前端开发工具认证鉴权如何快速实现React Native Navigation悬浮按钮FAB按钮的完整指南如何快速实现React Native Navigation悬浮按钮FAB按钮的完整指南 React Native Navigation是一个功能强大的原生导航移动开发上一篇FIFA 23实时编辑器完整指南5分钟掌握游戏修改技巧下一篇Django REST framework SimpleJWT 黑名单功能详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考