diff --git a/apps/buddy/extensions/README.md b/apps/buddy/extensions/README.md index ad1390350..4298f3dc0 100644 --- a/apps/buddy/extensions/README.md +++ b/apps/buddy/extensions/README.md @@ -1,20 +1,17 @@ # Lexora 插件开发 -## 术语与范围 +插件是可安装、启用、更新和卸载的功能包,可以提供界面、文件处理、命令、Agent 工具、Skills 或主题。协议中的 `Extension` 指同一套工作台插件;它与 Agent 内部扩展的加载方式和权限边界不同。 -**插件**是用户可以安装、启用、禁用、更新和卸载的功能包。代码和协议中的 `Extension`(工作台扩展)是这套插件机制的技术名称,不代表另一类可安装产品;用户界面统一使用“插件”。`extension.json`(插件清单)和 `.lexora-extension`(安装包后缀)保持不变。 +- [开发文档](https://uselexora.app/plugins/):从第一个插件到文件编辑、Agent 能力和分发。 +- [本地能力索引](../service/resources/skills/plugin-creator/references/capabilities.md):按开发目标找到接口专题。 +- [清单与兼容性](../service/resources/skills/plugin-creator/references/protocol.md)、[公共类型](sdk/index.d.ts):核对字段与调用签名。 +- [Lexora Plugins](https://github.com/useLexora/plugins):插件源码与市场目录。 -**插件视图**是插件提供的界面,可以在资源面板里作为标签页打开,也可以显示为独立页面、工作台控件或装饰。资源面板是展示容器,不是插件的所属模块;一个插件可以提供多个视图,也可以只提供命令、AI 工具或后台动作。关闭标签页不等于禁用插件。 +也可以在 Lexora 对话中输入 `$plugin-creator` 和需求,由伙伴生成源码与安装包,确认安装后继续迭代。 -**Agent 内部扩展**是应用向底层 AI 运行时注入工具、策略和事件处理逻辑的机制,与用户安装的工作台插件不同;插件只能通过宿主开放的接口贡献 AI 能力,不能直接注入进程内扩展。 +## 仓库开发工具 -## 开发与安装 - -插件可以添加命令、上下文视图与独立配置页面。[Lexora Plugins](https://github.com/useLexora/plugins) 保存平铺的插件源码与市场目录;开发工具及创作 Skill 位于独立的 Lexora Plugin SDK。`sdk/index.d.ts` 提供公共类型,无需发布或安装单独的 npm SDK。 - -也可以直接在 Lexora 对话中输入 `$plugin-creator` 和需求,例如 `$plugin-creator 打字时窗口出现礼花`。内置技能生成源码并使用 Lexora 编译器打包,随后显示安装确认;源码保留在任务工作区,便于继续修改。 - -在 `apps/buddy` 中运行(Node.js 24+,已安装 workspace 依赖): +使用当前仓库 `package.json` 要求的 Node.js 与 pnpm 版本,安装 workspace 依赖后,在 `apps/buddy` 中运行: ```sh node extensions/tools.mjs check /path/to/plugin @@ -22,32 +19,12 @@ node extensions/tools.mjs build /path/to/plugin .output/extensions/my-plugin node extensions/tools.mjs pack .output/extensions/my-plugin .output/extensions/my-plugin.lexora-extension ``` -在「插件 → 安装插件包」中安装生成的包。安装按钮右侧箭头可以加载开发目录;「更多操作 → 安装记录」查看进度、编译错误与取消操作。 - -`extension.json` 中的 `id` 是稳定的插件身份,创建后在修改、升级和发布时持续复用;可选的 `author` 独立保存作者署名,修改署名不改变 ID。命令和视图 ID 以插件 ID 加 `.` 开头。清单声明版本、`engines.lexora`、API 版本、贡献项、依赖与权限。`categories` 和 `tags` 用于市场分类与搜索。 - -`icon` 可指定包内 SVG、PNG、JPEG 或 WebP 图标(最多 64 KiB),用于导航、插件卡片与安装弹窗。未提供图标时显示默认线框图标。 - -包预算统一为单文件 64 MiB、压缩安装包 128 MiB、展开合计 256 MiB;最多 4096 个文件,其中非图片文件最多 512 个,归档条目最多 8192 个。对话构建自动按 256 KiB 分块传输,不增加普通插件消息的容量。大型预构建 JS 库请使用 `compiled` 包;源码编译仍有独立的时间与内存限制。 - -API 3 支持一个插件提供多个完整主题、可选欢迎文字与图片,以及主题编辑器接口;见 [主题开发](../service/resources/skills/plugin-creator/references/themes.md)。主题包可仅包含 JSON 和图片。 - -API 3 还支持原生设置模块、分组和单项,以及带权限的 Agent 指令、工具、模型请求和当前任务标题更新。先通过能力目录确认宿主支持,再声明最低应用版本;接口与示例见 [Agent 与设置](../service/resources/skills/plugin-creator/references/agent-settings.md)。 - -`format: "source"` 支持自包含 TS/JS 源码。Lexora 使用固定编译器,不执行 npm 安装、脚本或第三方构建配置。只能导入包内相对路径;含运行时 npm 依赖的插件需要作者预先打包为 `format: "compiled"`。已编译包直接安装,源码包确认权限后编译,失败不会替换已安装版本。`build` 和 `dev` 完整保留 `compiled` 包的目录、脚本与二进制资源,不再次打包其入口;第三方依赖应在交付 compiled 包之前完成构建。 - -宿主入口导出 `activate(context)`,可选 `deactivate()`。通过 `context.commands.register` 注册清单中声明的命令;订阅资源使用 `context.subscriptions` 清理。`permissions.notifications` 开放系统通知,`permissions.schedules` 开放 Lexora 运行期间的定时命令。定时配置跨重启保存,休眠或退出期间错过的提醒不会补发,卸载会删除定时任务。 - -视图入口导出 `render(context, container)`,在隔离页面中运行。`location: "context"` 用于上下文标签;`location: "page"` 配合 `contributes.navigation` 可在自动化下方添加入口。通过 `context.commands.execute` 调用自己的宿主命令;使用 `context.environment` 的语言、主题与颜色适配外观,通过 `context.onEnvironmentChange` 响应变化。事件和定时器随 `context.signal` 取消。 - -源码包在清单中添加 `"styles": { "uno": true }` 后,可直接使用 UnoCSS 和跟随宿主品牌色的语义类,例如 `flex gap-2 bg-accent text-on-accent p-3`。编译器生成并打包 `lexora-uno.css`,视图自动加载;动态类使用完整字符串或 `styles.safelist`。详见[主题与样式](../service/resources/skills/plugin-creator/references/protocol.md#主题与样式)。 - -`location: "window-overlay"` 提供独立挂载的透明窗口效果,需要 `permissions.windowEffects`。通过 `context.onActivity` 接收不包含输入文字或按键的对话输入活动;视图不会拦截鼠标或焦点,禁用时立即移除。 +在「插件 → 安装插件包」安装输出包,或从相邻菜单加载开发目录。`check` 校验清单与包;构建和加载通过后,仍需在真实 Lexora 中验证交互、关闭和重新启用。 -API 1 不开放 Node、shell 或任意 Desktop API。文件只读,仅能访问用户选中的文件;HTTPS GET 仅访问已确认的来源,不共享浏览器登录态。`context.storage` 保存私有 JSON,`dataVersion` 改变时入口提供 `migrate(previous, from, to)`,失败保留旧数据。上下文视图可用 `setState` 保存标签状态,独立配置页通过宿主命令保存设置。 +先构建 Electron,再用 `node extensions/tools.mjs dev /path/to/plugin` 打开独立开发窗口。它使用临时配置,修改后重新构建并加载;不会替代正式安装包验证。 -更新安装后,当前版本继续运行,点击“重启插件”应用更新。禁用停止插件,重新启用保留配置;普通卸载保留插件设置、私有 JSON 与上下文视图位置,但移除代码和定时任务。“卸载并清理”还会删除设置、私有数据和视图状态,不删除任务内容或用户文件。 +## 独立 SDK -需要独立开发窗口时,先构建 Electron,再运行 `node extensions/tools.mjs dev /path/to/plugin`。开发窗口使用单独的临时配置;修改后重新加载构建目录并重启插件。SDK 快照可以通过 `node extensions/tools.mjs export-sdk /path/to/plugin-sdk/sdk` 导出。 +`node extensions/tools.mjs export-sdk /path/to/plugin-sdk/sdk` 导出开发工具和公共类型快照。类型可复制为插件内的 `lexora.d.ts`,仅通过 `import type` 引用,无需安装 npm SDK 包。已取得独立 SDK 的作者使用 [SDK 工作流](../service/resources/skills/plugin-creator/references/sdk-workflow.md)。 -API 2 通过 `contributes.placements` 提供装饰、工作台面板与思考等级控件。API 3 增加可替换的内容区、操作菜单、独立分屏挂载、带插件命名空间的输入命令与可取消交互。创作时用 `lexora_plugin_capabilities` 按需查询当前宿主支持的位置。内容仍在隔离视图运行;清单和调用示例见 [插件协议](../service/resources/skills/plugin-creator/references/protocol.md)。插件自行提供配置交互,内容区及控件可由插件启停并交还宿主默认内容;具体用法见 [内容插槽](../service/resources/skills/plugin-creator/references/slots.md)。旧版插件继续可用。 +自包含 TS/JS 使用 `source` 包;Vue、React 和其他运行时依赖由作者预先构建为 `compiled` 包。身份、权限、生命周期与保存行为见专题说明,开发和升级时保持插件 ID 稳定。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/SKILL.md b/apps/buddy/service/resources/skills/plugin-creator/SKILL.md index 569ed69fe..15e783a0b 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/SKILL.md +++ b/apps/buddy/service/resources/skills/plugin-creator/SKILL.md @@ -1,76 +1,52 @@ --- name: plugin-creator -description: 创建、修改并验证可安装的 Lexora 桌面插件,包括配套 Skills、多主题配色包、工作台装饰、导航与面板、文件阅读工具、控件样式、网络查询、通知和定时命令。也响应 $plugin-creator。普通任务执行、使用现有插件、其他产品的插件开发不属于此技能。 +description: 创建、修改并验证可安装的 Lexora 桌面插件,包括视图与面板、文件编辑、Agent 工具与任务动作、配套 Skills、主题、工作台装饰、网络查询和定时命令。也响应 $plugin-creator。普通任务执行、使用现有插件、其他产品的插件开发不属于此技能。 --- # 创建 Lexora 插件 -将用户的需求变成可安装、可修改的真实插件。完成最小可用版本,再依据编译诊断与用户的实际体验迭代。不要用占位 demo 代替需求。 +将需求变成可安装、可修改的真实插件。先完成用户需要的最小流程,再依据编译诊断和实际体验迭代;不以占位 demo 代替需求。 -## 理解需求 +## 1. 确定行为与能力 -先利用当前对话确定输入、触发时机、呈现位置与生效范围、需要保留的数据和停止方式。描述充分时直接实施;只询问会改变实现的缺失信息。短需求选择轻量默认值并说明,不要求用户先填技术问卷。没有设置需求时优先复用插件管理中的启用、禁用,不额外增加导航和配置页面。 +从当前对话确定输入、触发时机、呈现位置、生效范围、持久数据和停止方式。描述充分时直接实施,只询问会改变实现的缺失信息;没有设置需求时优先复用启用、禁用,不额外增加页面。 -分别确定事件来自哪里、内容画在哪里、何时可见。事件来自某个控件,不意味着内容只能画在该控件内;保留用户指定的窗口、分屏或局部区域,不擅自缩小范围。未指定的视觉细节可以选择默认值;指定范围缺少正式能力时明确说明,不能用相近位置代替交付。 +分别判断事件来自哪里、内容画在哪里和何时可见。保持用户指定范围;指定位置缺少正式能力时说明缺口,不用相近区域或父页面 DOM 模拟交付。外部参考先核对公开资料,再映射到 Lexora 的接口。 -用户提供外部参考时先阅读公开说明或代码,提取需要复现的用户行为,再映射到 Lexora 的正式能力。参考项目的内部接口不能直接移植;读取失败时说明缺少的依据,不把推测写成已经核对的实现。 +读取 [能力选择](references/capabilities.md)。通过 `lexora_tool_search` 查找 `lexora_plugin_capabilities`,先查简短目录,再按 `kind` 或 `target` 查询需要的契约。先说明所选组合能完成的用户行为,不提前加载全部专题。 -用户给出的作者署名或调用名不符合规则时,说明具体原因并给出 1–3 个保留原意的可用候选,通过对话请用户确定后继续。不要静默截断、转拼音、替换为系统账号或生成陌生名称;中文作者署名本身合法,无需改成英文。尚未指定调用名时可以按插件功能提出简短名称。 +## 2. 阅读对应契约 -## 按需求组合能力 +读取 [清单与兼容性](references/protocol.md),按下表补充本次所需专题,并在 [API 类型](references/api.d.ts) 中核对实际使用的接口。继续旧任务时重新读取当前说明和已有源码清单,历史示例不替代当前契约。 -读取 [能力选择与组合](references/capabilities.md),将需求对应到输入来源、呈现方式、运行入口与状态归属,再确定最小清单。一个插件可以组合多种正式扩展点,也可以只提供 Skills、命令或一个文件视图;现有示例不是插件类别或必须复制的模板。 +| 需求 | 资料 | +| --- | --- | +| 后台入口、视图通信、数据与实例状态 | [生命周期](references/lifecycle.md) | +| 页面、面板、剪贴板、挂载和可见性 | [视图](references/views.md)、[插槽](references/slots.md)、[临时交互](references/interactions.md) | +| 命令、菜单、通知和定时 | [命令](references/commands.md) | +| 文件打开、读取、导出 | [资源](references/resources.md) | +| 编辑原件、恢复草稿、关闭保护 | [文档](references/documents.md) | +| 引用到对话、定位原文、工具解析引用 | [引用](references/references.md) | +| Agent 工具、任务动作、模型调用 | [Agent](references/agent.md)、[事件](references/events.md) | +| 原生设置、配置热应用、条件函数 | [设置](references/settings.md)、[条件](references/conditions.md) | +| 配色、图标、窗口样式和装饰 | [主题](references/themes.md)、[图标](references/file-icon-themes.md)、[样式](references/styles.md) | +| Worker/WASM、插件 Skills、解释器候选 | [计算](references/workers.md)、[Skills](references/skills.md)、[Shell](references/shells.md) | +| 权限或访问失败 | [权限](references/permissions.md) | -查询宿主扩展能力时,通过 `lexora_tool_search` 查找 `lexora_plugin_capabilities`。先无参数查询当前宿主的简短目录,再按 `kind` 或 `target` 获取所需能力的契约;不要把完整目录提前塞进提示词。内容区的共同规则见 [内容插槽](references/slots.md)。没有匹配位置时说明能力缺口,不借助 CSS 选择器或相近区域模拟替换。 +纯主题和纯 Skills 包不需要脚本入口。仅声明实际需要的权限,确认能力所属的后台、视图或本次 Agent 调用,不能互换上下文。 -先说明所选组合能完成的用户行为,再创作。文件阅读工具需要按文件打开的独立视图和各自的阅读状态;常驻信息条可能需要底部挂载、宿主查询命令与私有设置。不要因为都“有界面”就统一做导航页,也不要为了演示全部 API 给插件添加无关权限。 +## 3. 创建源码并构建 -## 阅读协议并创作 +读取 [对话创作与交付](references/authoring.md),使用当前工作区中的独立目录保留真实源码。新插件通过 `lexora_plugin_identity` 生成身份;维护、升级和署名修改保留现有 ID。署名不从系统账号或目录推断;无效署名或调用名给出候选并让用户确定,不静默替换。 -继续旧对话创作时,重新读取当前技能与协议。历史消息、旧工具结果和压缩摘要中的命名示例不作为新插件的身份依据;维护已有插件时读取实际源码清单,并保留其中的 ID。 +默认编写自包含 TS/JS 和包内资源,复制 API 类型为 `lexora.d.ts` 并仅作 type import。视图导出 `render(context, container)`;确有后台职责时再提供 `activate(context)`。源码包设为 `format: "source"`,无需用户安装工具链。第三方库需先构建为浏览器可用的 compiled 包,不能运行时导入 npm 或 CDN。 -主题配色、欢迎文字/插画或主题编辑器需求,先读 [主题](references/themes.md)。纯主题包只需清单、JSON 与图片,无需创建脚本入口。 +通过 `lexora_plugin_build` 构建,依据诊断修复。安装走用户可见的确认流程;构建结果中的审核状态不等于已安装。需要核实版本、待应用更新或加载错误时使用 `lexora_plugin_inspect`。确在用户提供的独立 SDK 环境中开发时,改读 [SDK 工作流](references/sdk-workflow.md)。 -文件或目录图标需求,读取 [文件图标主题](references/file-icon-themes.md),通过独立的 `contributes.iconThemes` 提供图片与名称映射。 +## 4. 验证与交付 -需要随插件提供可搜索、按需读取的工作方法或资源时,读取 [插件 Skills](references/skills.md)。使用标准 `SKILL.md` 与 `contributes.skills`,纯 Skills 包不需要脚本入口;工具仍通过 Agent 能力单独声明。 +检查所需行为、状态恢复、关闭与停用,以及相应的取消和失败路径。文件编辑必须区分草稿、原件保存和导出;异步操作固定发起时的资源、视图和调用,不能用后来焦点选择目标。订阅、定时器与 Worker 按各自生命周期释放。 -1. 读取 [协议与清单](references/protocol.md) 和 [完整 API 类型](references/api.d.ts)。这些文件与当前 Lexora 一起分发;不要根据其他产品的插件格式猜测接口。 - 需要实时配置、表单依赖或运行状态判断时,读取 [条件函数](references/conditions.md),声明数据输入与作用域,不将函数序列化进清单。需要 Agent 指令、后台任务动作、模型调用、任务标题或原生设置时,追加读取 [Agent 与设置](references/agent-settings.md),并确认当前宿主能力目录支持。事件驱动动作与订阅按 [事件与触发列表](references/events.md) 选择精确入口和载荷,不把普通事件监听器当作任务能力调用。 -2. 在当前可写工作区创建独立源码目录,例如 `plugins/<插件名>/`。通过文件工具写入真实文件,保留源码以便后续对话修改。不需要用户另外安装 Node、Bun 或 SDK。 -3. 新建独立插件时通过 `lexora_tool_search` 查找 `lexora_plugin_identity`,传入按功能选定的简短英文 `slug`,将返回的 `id`、`author` 和 `engines` 写入 `extension.json`。作者未指定时省略参数,使用已保存的默认署名;明确不署名时传空字符串。作者名支持中文,不要求用户提供英文名、平台账号或唯一名称,也不从系统用户名、Git 配置或目录推断署名。不要自行编造 ID 或继续生成 `local.*`。修改、升级、换电脑维护及分发沿用源码内已有 ID,不重新调用身份生成工具;包括已有的 `local.*`。只有创建独立副本时生成新 ID 并同步内部引用。 -4. 按选定组合声明视图、命令、导航和挂载点,明确每项能力由宿主入口还是视图入口调用;限定页面时使用公开上下文和声明条件。仅声明实际需要的权限;没有对应能力时解释限制,不绕过隔离访问宿主 DOM、内部接口或文件。明确用户第一次如何启动、关闭后如何再次打开;普通挂载需要显式打开;内容插槽和替换控件的启用、回退与配置归属按 [内容插槽](references/slots.md) 实现。现有入口足够时不额外增加设置页。 -5. 使用自包含 TS/JS 和包内资源。复制 `references/api.d.ts` 为源码目录的 `lexora.d.ts`,仅通过 `import type` 引用。源码清单设置 `format: "source"`。视图导出 `render(context, container)`,需要命令、存储或调度时再添加宿主入口 `activate(context)`。 -6. 事件、定时器、动画在关闭时清理,动画结束回收节点、空闲时停止循环。页面使用主题变量;窗口效果保持透明、不阻挡输入,并尊重减少动态效果偏好。 +编译通过和视图 ready 只证明各自阶段。能操作真实插件界面时实际验证;否则明确待体验项,不能用普通浏览器模拟宿主后声称插件验收通过,也不编造截图或效果。等待安装确认时不循环轮询。 -修改作者署名时直接更新清单 `author`,保留已有 `id` 和所有贡献引用,无需调用身份生成工具。ID 的前缀不会自动成为署名;用户说“用户名”或“发布者”且含义不明确时,先区分作者署名与更换插件身份。用户明确要求更换 ID 时,先说明它会被当作另一份插件,既有数据不会自动转移。 - -## 编译与修正 - -通过 `lexora_tool_search` 查找 `lexora_plugin_build`,然后调用它: - -如果当前明确是在独立 Lexora Plugin SDK 仓库中开发、没有 Lexora 内置工具,使用 [SDK 工作流](references/sdk-workflow.md)。普通 Lexora 对话不需要这条分支。 - -```json -{ "source": "plugins/my-plugin", "output": "my-plugin-1.0.0.lexora-extension", "review": true } -``` - -`output` 是已存在父目录中的新文件,放在源码目录之外;现有文件不会被覆盖。工具使用内置隔离编译器,不执行 npm 安装脚本或插件代码。错误结果包含 `code` 与有限的 `diagnostics`:修正对应问题后重新构建,避免重复提交相同错误。需要重新生成包时使用新的输出文件名。 - -`review: true` 请求显示安装确认,用户确认后才会安装。只要求源码或打包时设为 `false`。不要绕过确认或通过内部配置安装。交付前核对构建结果中的 `id`、`name`、`author` 和 `version`。`author` 为空表示安装界面会显示未署名;用户要求署名时,修正清单并重新构建,不能把 ID 前缀当作署名已经生效。构建结果中的 `installation` 只表示是否发起审核,不代表当前安装状态;旧版本返回的 `installed: false` 也不能用于断言用户尚未安装。 - -需要判断安装、待应用更新或加载失败时,通过 `lexora_tool_search` 查找 `lexora_plugin_inspect`,以插件 `id` 读取当前宿主状态。比较当前版本与 `pendingVersion`,检查可见命令、导航入口、视图 ready 和错误。仍待用户审核时交付结果即可,不循环轮询;工具不可用时明确安装状态未核实。ready 只证明入口完成加载,不能代替实际功能验收。 - -排查失败时先用检查结果中的错误码、方法、阶段和字段路径定位,再对照 [API 类型](references/api.d.ts) 与对应协议、插件源码核验请求。独立线索合批查询,定位后只读相关范围;诊断不足时明确缺少什么证据,不反复扫描安装包来猜接口。 - -通过 `lexora_output_present` 交付源码目录与安装包。不要只在回答中粘贴代码,也不要下载 SDK 或建立另一套构建工程。 - -## 验证与迭代 - -按所选能力检查主要动作,以及真正影响它的边界:文件视图验证两份资源的状态互不覆盖;组合面板验证导航切换和设置刷新;控件验证宿主允许值、失败回退与明确重试;后台命令验证关闭页面后仍按约定运行。再验证禁用后的停止行为,避免机械套用某个示例的测试步骤。 - -有持久设置时,检查修改后的实际行为、离开页面再返回及重启后的恢复;开关需要验证关闭与重新开启,不能只确认表单显示成功提示。 - -编译通过只证明包可构建,不证明界面和运行效果通过。Lexora 的浏览器工具只能控制资源浏览器,不能操作插件安装弹窗或窗口效果;不要为此创建另一个 HTML 模拟宿主来代替安装验收。有能操作真实插件界面的工具时执行检查;否则交付编译后的包和待验证项,让用户安装后反馈,不在等待安装期间反复调用无关工具。不要编造截图、通知送达、运行成功或性能数据。 - -交付简短说明:完成了什么、源码与包的位置、如何触发、已检查和仍待体验的行为。用户反馈后读取现有源码,修正实现、重新编译并再次验证相同场景。保持 ID 与数据含义稳定。发布市场或同步远端单独遵循用户指示。 +通过 `lexora_output_present` 交付源码和安装包,简述入口、已检查行为与待验证项。后续修改读取已有源码并保留身份和数据含义;市场发布或远端同步遵循用户指示。构建、诊断和验收的详细步骤统一见 [创作流程](references/authoring.md)。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/agent-settings.md b/apps/buddy/service/resources/skills/plugin-creator/references/agent.md similarity index 65% rename from apps/buddy/service/resources/skills/plugin-creator/references/agent-settings.md rename to apps/buddy/service/resources/skills/plugin-creator/references/agent.md index 7ce488dac..baed6b87f 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/agent-settings.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/agent.md @@ -1,22 +1,6 @@ -# Agent 工具、任务动作与设置 +# Agent 工具与任务动作 -这些是 API 3 的增量能力。先查询当前宿主的 `runtime` 能力目录,确认所需的 `agent.tools`、`agent.actions`、`agent.models`、`agent.task`、`agent.taskMessages`、`agent.references` 或 `settings` 可用,再声明对应的最低 `engines.lexora`。相同 API 版本的旧宿主不一定提供全部增量能力。 - -## 设置 - -`contributes.settings` 包含三个可选数组: - -- `modules: [{id,title,order?}]`:设置侧栏中的独立模块。 -- `groups: [{id,module,title,order?}]`:模块中的分组。module 引用自己的模块或能力目录列出的内置模块。 -- `items: [{id,key,group,title,description?,type,default,order?,enabledWhen?}]`:分组中的单项。group 引用自己的分组或能力目录开放的内置分组。 - -所有 id 以插件 ID 加 `.` 开头且全包唯一;key 在本插件内唯一,使用以小写字母开头的字母数字名称。order 范围 -1000–1000,默认 0。插件不能覆写宿主设置或插入其他插件的设置。 - -type 支持 boolean、string(最长 8192 字符)、number(可声明 min/max)、select(声明 `options:[{label,value}]`)、model。默认值必须符合类型;model 值为 `{providerId,modelId}` 或 null。模型选择规则见下文,指定模型不可用时会失败,不会静默改用其他模型。 - -宿主自动保存设置,插件用 `context.configuration.get()` 读取,使用 `configuration.onChange` 注册可等待的热应用处理器;`context.events.on('configuration:changed', listener)` 仅观察已经保存的配置;订阅与失败回退见 [作用域事件](events.md)。修改设置取消当前插件调用,Agent 贡献变化从下一轮生效;关闭功能后旧工具也被拒绝。配置与 storage 私有业务数据独立,不维护第二份持久设置真源。已发布 key 保持类型和含义稳定,新增 key 使用 default。升级后不符合新选项或范围的旧值会被保留并在设置中标记,用户可以逐项修改或恢复默认值;修复前 Agent 贡献不可用。禁用和普通卸载保留配置;用户选择“卸载并清理”时删除配置、私有数据和视图状态。 - -设置项的 `enabledWhen: {condition,params?}` 引用插件注册的[条件函数](conditions.md)。关闭条件仅禁用输入并保留原值;控制开关自身保持可编辑。 +先查询当前宿主的 runtime 能力目录,确认所需能力可用;API 主版本相同的旧宿主不一定提供全部增量能力。设置声明见 [声明式设置](settings.md)。 ## Agent 入口 @@ -57,6 +41,7 @@ context.agent.registerTool(`${context.extension.id}.tool`, async (input, invocat 以上三个能力仅 `AgentToolContext` 提供,后台动作不提供。嵌套 schema、scope、产物发布和图片结果要求 `engines.lexora: ">=0.15.0"`。 + ## 任务动作 动作不依赖主模型决定是否调用,不占用主对话的工具消息。以下是一个只读取任务信息的最小声明;将 `example.helper` 替换为插件实际 ID: @@ -89,6 +74,7 @@ context.agent.registerAction(`${context.extension.id}.inspect`, async (invocatio 自动命名可以在输入提交后处理 `fallback` 标题,成功后保留 `generated` 标题;上下文不足时返回 skipped,下一次输入仍可重试。需要随目标变化更新时,通过插件设置控制完成事件的处理;默认成功命名一次即可,避免轻微追问反复改名。`user` 动作可实现“重新生成标题”,允许替换原有手动或历史标题。 + ## 模型与任务权限 `permissions.models: true` 开放 `invocation.models.generateText({prompt,system?,model?,maxTokens?})`,返回 `{text,model}`。未指定模型时,工具继承原运行的模型,动作使用调用开始时的任务模型选择或对应输入的对话模型;没有可用模型时失败。请求仅有文本,没有工具或隐式对话历史;不允许自定义凭据、URL 或 Provider 参数。凭据留在宿主。工具用量计入原运行,动作用量属于独立调用,同样出现在任务用量统计中。每次调用最多 4 次模型请求,同时最多 1 次,每次最多 4096 输出 token;取消由调用任务或 AbortSignal 控制,不设整次调用的固定时长配额。 @@ -103,10 +89,5 @@ context.agent.registerAction(`${context.extension.id}.inspect`, async (invocatio 以上接口均绑定原任务,不接受任意任务 ID,也不提供任意字段或数据库写入。宿主和其他插件仍可通过各自授权入口修改同一任务,过期结果不能覆盖新状态。 -## 工具中的用户引用 - -声明 `permissions.references: true` 后,工具回调可用 `invocation.references.list()` 获取 `{items:[{referenceId,sourceMessageId,title,excerpt}],truncated}`。列表最多保留最近 100 条本插件消息引用快照,excerpt 最长 512 字符;引用来自当前运行已经提交的用户输入及同一可见分支的历史用户输入,因此对话压缩不影响引用身份。不同任务、隐藏分支、其他插件、助手与工具生成的引用不在范围内。未发送草稿和当前运行尚未接受的新输入不授予资格。任务动作不提供该 API。 - -`invocation.references.resolve(referenceId, {sourceMessageId?,offset?,limit?})` 返回 `{referenceId,sourceMessageId,title,text,textLength,textDigest,offset,nextOffset,locator,scope:{taskId,branchId}}`,locator 为插件原始定位信息或 null。`text` 为 UTF-16 单元分页,不切断代理对;起点落在代理对中间时拒绝读取,默认 offset 为 0,limit 默认为 32768、最大 32768;nextOffset 为 null 表示已到末尾。textLength 是完整快照长度,textDigest 是完整原文 UTF-8 字节的 SHA-256,不是当前页摘要。长引用按需读取;定位验证可使用完整快照摘要与长度,不要把第一页误当全文。sourceMessageId 指向携带这份引用快照的用户消息,referenceId 是宿主生成的引用 ID,不是消息里的引用序号。将两者一起保留并传给 resolve;同一个 referenceId 可出现在多条消息中。旧调用省略 sourceMessageId 时,仅在所有匹配快照完全相同时兼容,否则返回 EXTENSION_REFERENCE_AMBIGUOUS,须从列表选择精确消息身份。已知的历史 ID 即使不在最近 100 条列表中也可解析。引用本身不授予读取任意文件或修改文档的能力。任务结束、取消、输入或分支变更后,旧调用失效。 -插件应只向模型返回适量文字、referenceId 和 sourceMessageId,将 locator 留在插件内处理。编辑前重新解析引用,核对目标文档、会话和版本,使用现有视图消息与命令回执完成操作;不要按当前焦点选择目标,也不要把复制来的文本当作新的引用授权。新能力通过当前宿主的 runtime 目录确认,并按实际宿主版本声明兼容范围。 +工具读取用户引用见 [工具中的用户引用](references.md#工具中的用户引用)。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/authoring.md b/apps/buddy/service/resources/skills/plugin-creator/references/authoring.md new file mode 100644 index 000000000..c46813d51 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/authoring.md @@ -0,0 +1,41 @@ +# 对话创作与交付 + +先完成[技能流程](../SKILL.md)中的需求判断、能力选择和专题阅读,再按下面的步骤生成身份、构建与交付。 + +## 身份与源码 + +新建独立插件时通过 `lexora_tool_search` 查找 `lexora_plugin_identity`,传入按功能选定的简短英文 `slug`,将返回的 `id`、`author` 和 `engines` 写入 `extension.json`。作者未指定时省略参数,使用已保存的默认署名;明确不署名时传空字符串。作者名支持中文,不要求用户提供英文名、平台账号或唯一名称,也不从系统用户名、Git 配置或目录推断署名。不要自行编造 ID 或继续生成 `local.*`。修改、升级、换电脑维护及分发沿用源码内已有 ID,不重新调用身份生成工具;包括已有的 `local.*`。只有创建独立副本时生成新 ID 并同步内部引用。 + +修改作者署名时直接更新清单 `author`,保留已有 `id` 和所有贡献引用,无需调用身份生成工具。ID 的前缀不会自动成为署名;用户说“用户名”或“发布者”且含义不明确时,先区分作者署名与更换插件身份。用户明确要求更换 ID 时,先说明它会被当作另一份插件,既有数据不会自动转移。 + +用户给出的作者署名或调用名不符合规则时,说明具体原因并给出 1–3 个保留原意的可用候选,通过对话请用户确定后继续。不要静默截断、转拼音、替换为系统账号或生成陌生名称;中文作者署名本身合法,无需改成英文。尚未指定调用名时可以按插件功能提出简短名称。 + +## 编译与修正 + +通过 `lexora_tool_search` 查找 `lexora_plugin_build`,然后调用它: + +如果当前明确是在独立 Lexora Plugin SDK 仓库中开发、没有 Lexora 内置工具,使用 [SDK 工作流](sdk-workflow.md)。普通 Lexora 对话不需要这条分支。 + +```json +{ "source": "plugins/my-plugin", "output": "my-plugin-1.0.0.lexora-extension", "review": true } +``` + +`output` 是已存在父目录中的新文件,放在源码目录之外;现有文件不会被覆盖。工具使用内置隔离编译器,不执行 npm 安装脚本或插件代码。错误结果包含 `code` 与有限的 `diagnostics`:修正对应问题后重新构建,避免重复提交相同错误。需要重新生成包时使用新的输出文件名。 + +`review: true` 请求显示安装确认,用户确认后才会安装。只要求源码或打包时设为 `false`。不要绕过确认或通过内部配置安装。交付前核对构建结果中的 `id`、`name`、`author` 和 `version`。`author` 为空表示安装界面会显示未署名;用户要求署名时,修正清单并重新构建,不能把 ID 前缀当作署名已经生效。构建结果中的 `installation` 只表示是否发起审核,不代表当前安装状态;旧版本返回的 `installed: false` 也不能用于断言用户尚未安装。 + +需要判断安装、待应用更新或加载失败时,通过 `lexora_tool_search` 查找 `lexora_plugin_inspect`,以插件 `id` 读取当前宿主状态。比较当前版本与 `pendingVersion`,检查可见命令、导航入口、视图 ready 和错误。仍待用户审核时交付结果即可,不循环轮询;工具不可用时明确安装状态未核实。ready 只证明入口完成加载,不能代替实际功能验收。 + +排查失败时先用检查结果中的错误码、方法、阶段和字段路径定位,再对照 [API 类型](api.d.ts) 与对应协议、插件源码核验请求。独立线索合批查询,定位后只读相关范围;诊断不足时明确缺少什么证据,不反复扫描安装包来猜接口。 + +通过 `lexora_output_present` 交付源码目录与安装包。不要只在回答中粘贴代码,也不要下载 SDK 或建立另一套构建工程。 + +## 验证与迭代 + +按所选能力检查主要动作,以及真正影响它的边界:文件视图验证两份资源的状态互不覆盖;组合面板验证导航切换和设置刷新;控件验证宿主允许值、失败回退与明确重试;后台命令验证关闭页面后仍按约定运行。再验证禁用后的停止行为,避免机械套用某个示例的测试步骤。 + +有持久设置时,检查修改后的实际行为、离开页面再返回及重启后的恢复;开关需要验证关闭与重新开启,不能只确认表单显示成功提示。 + +编译通过只证明包可构建,不证明界面和运行效果通过。Lexora 的浏览器工具只能控制资源浏览器,不能操作插件安装弹窗或窗口效果;不要为此创建另一个 HTML 模拟宿主来代替安装验收。有能操作真实插件界面的工具时执行检查;否则交付编译后的包和待验证项,让用户安装后反馈,不在等待安装期间反复调用无关工具。不要编造截图、通知送达、运行成功或性能数据。 + +交付简短说明:完成了什么、源码与包的位置、如何触发、已检查和仍待体验的行为。用户反馈后读取现有源码,修正实现、重新编译并再次验证相同场景。保持 ID 与数据含义稳定。发布市场或同步远端单独遵循用户指示。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/capabilities.md b/apps/buddy/service/resources/skills/plugin-creator/references/capabilities.md index ea8d92651..2b1dd9054 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/capabilities.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/capabilities.md @@ -1,57 +1,44 @@ # 按需求选择插件能力 -先决定数据从哪里来、用户在哪里操作、关闭页面后是否继续运行、哪些状态需要恢复。按下表选择能力,再查 [协议](protocol.md) 的清单字段和 [API 类型](api.d.ts) 的准确签名。能力可组合,但不必全部使用。 +先决定输入来自哪里、用户在哪里操作、关闭页面后是否继续运行、哪些状态需要恢复。下表用于选路;字段、权限、限制和示例由对应专题维护,准确签名见 [API 类型](api.d.ts)。一个插件可以组合多个能力。 -| 用户需要 | 清单与入口 | 状态和边界 | +## 界面与交互 + +| 用户目标 | 选择 | 深入阅读 | +| --- | --- | --- | +| 打开独立页面或文件标签 | `views`、`navigation`,按需要选择资源模式 | [视图](views.md) | +| 在工作台或分屏显示面板 | `placements` 的 `view` 与 `presentation` | [挂载](views.md#正式挂载点) | +| 替换或追加宿主内容 | `slot`、`control`,使用正式目标与真实默认回退 | [插槽](slots.md) | +| 输入命令或点击业务菜单 | `commands`、`slash`、`menus` | [命令](commands.md) | +| 临时覆盖交互、布局订阅 | `interactions` 与宿主布局快照 | [交互](interactions.md) | +| 复制粘贴、显示文档名和修改状态 | 交互视图的 `clipboard`、`setDocumentInfo` | [视图](views.md#视图类型) | +| 配色、欢迎内容、图标、窗口装饰 | 声明式主题或窗口样式、独立装饰视图 | [主题](themes.md)、[图标](file-icon-themes.md)、[样式](styles.md) | + +## 文件与内容 + +| 用户目标 | 选择 | 深入阅读 | +| --- | --- | --- | +| 为指定后缀提供预览或编辑入口 | `fileHandlers`;无文件入口用 `launchers` | [文件关联](resources.md#文件关联与独立入口) | +| 读取用户选择的文件、目录或远端文件 | `resources`,远端使用受控 `network` | [资源](resources.md) | +| 生成私有二进制、导出副本 | `resources.createFile`、`saveFile` | [资源与导出](resources.md#本地资源与导出) | +| 编辑原件、保存草稿、处理冲突与恢复 | `fileEditing` 与交互视图 `documents` | [文档编辑](documents.md) | +| 将材料放入任务草稿、定位原文 | 视图 `references`,工具按调用资格解析引用 | [引用](references.md) | +| 执行 WASM、解析二进制或密集计算 | 浏览器 WASM 与 `workers.create` | [Worker](workers.md) | + +## Agent 与后台 + +| 用户目标 | 选择 | 深入阅读 | | --- | --- | --- | -| 编辑器复制/粘贴文本和表格格式 | `permissions.clipboardRead` / `clipboardWrite`;视图 `clipboard.read/write` | 安装授权后仅前台可见且聚焦视图可用;文本与 HTML 合计 4 MiB,写入原子完成;不开放后台或浏览器剪贴板权限 | -| 页签显示当前文档名和修改状态 | 交互视图 `setDocumentInfo({title,dirty})` | 只更新当前实例;页签随布局恢复,导航页仅当前会话;文档保存与关闭保护仍由插件负责 | -| 随插件提供工作方法、参考资料或脚本,按需加载 | API 4;`contributes.skills`,见 [插件 Skills](skills.md) | 标准 Skill 文件;可搜索或显式 `$插件调用名:技能名` 选择,启停与更新由插件管理,不自动授予执行能力 | -| 检测并选择已有本机命令解释器 | API 4;contributes.shells、permissions.shells、原生 settings,见 [命令解释器](shells.md) | 用户启用后供模型选择;宿主执行,插件只能 detect(),不能运行命令或写解释器路径 | -| 由主模型主动调用插件、请求模型或修改任务标题 | API 3;contributes.agent 和 agent/models/tasks 权限,见 [Agent 与设置](agent-settings.md) | 工具入口绑定原任务与本轮模型,不导出凭据,不覆盖手动标题 | -| 输入提交、对话成功完成或任务菜单点击后执行 | API 3;contributes.agent.actions,见 [任务动作事件](events.md#任务动作事件) 与 [Agent 与设置](agent-settings.md) | 独立调用绑定原任务;正文需要 taskMessages 权限,标题更新由宿主保护并发修改 | -| 在原生设置页追加模块、分组或单项 | API 3;contributes.settings,见 [Agent 与设置](agent-settings.md) | 宿主保存配置并渲染控件;插件读取 configuration,不另存设置 | -| 对当前文件提供阅读、统计或专用展示 | `selectedResource: "read"`;`resource: "selected-file"` 的视图;宿主可见命令将收到的 `resource` 传给 `views.open`,视图用 `resources.readText(context.resource)` 读取 | 用户先打开文件,再执行插件命令。句柄不包含任意路径,每次打开是独立实例;不能枚举目录、读其他视图的文件或写回文件 | -| 输入 `/插件名:命令` 执行插件动作 | API 3 命令声明 `slash`,并在 `activate` 注册处理函数;见 [注册命令](commands.md) | 精确匹配,包内校验注册,跨插件命名空间隔离,不请求模型 | -| 需要真实分屏布局或临时覆盖交互 | API 3;见 [交互会话](interactions.md) | 订阅布局;稀疏点击区域或独占交互;宿主提供独立退出入口 | -| WASM 引擎、二进制解析或不阻塞界面的计算 | 后台与视图的 `workers.create`,见 [WebAssembly 与 Worker](workers.md) | 包内代码与原生浏览器 API;支持操作级取消,关闭视图不代替后台任务生命周期,不开放 Node 或真实文件系统 | -| 执行一次动作,无需常驻页面 | 宿主 `activate` 注册 `contributes.commands` 中的命令 | 可见命令进入命令面板和已安装插件卡片的“运行命令”菜单;命令收到的 `resource` 可能为空。只供自己视图调用的命令设为 `hidden: true` | -| 目录入口、较完整的页面或设置表单 | `contributes.navigation` 引用 `resource: "none"` 的视图 | API 2 可省略视图 `location`;页面由 `render` 绘制。`navigation.showHeader` 默认 `true`,设为 `false` 可隐藏该独立页的宿主标题栏,不影响资源页签或应用框架。需要持久配置时通过自身隐藏命令调用宿主私有存储 | -| 将自定义内容挂到页面区域 | `kind: "view"`、`target` 与 `presentation`;宿主命令调用 `placements.show/hide` | 通过能力查询选择全局、页面或分屏目标。插件渲染隔离内容,通过通用外框布局接口声明定位和尺寸。挂载口没有面板 UI 或交互 | -| 在业务区域执行操作 | `contributes.menus` 引用已声明命令;用能力查询选目标 | 输入、消息、文件或任务操作共享一套协议;仅点击时按权限提供所选内容,分屏标识固定到发起位置 | -| 替换或追加宿主内容 | `kind: "slot"`;按需查 [内容插槽目录](slots.md) | 按目录的单选或多选策略由用户选择提供者;不可用时回退。位置和外框归宿主,插件只绘制自己的内容 | -| 限定生效页面或响应页面变化 | 贡献的 `when`;视图的 `workbench/onWorkbenchChange` 与 `visible/onVisibilityChange` | 按稳定页面 ID 与公开上下文匹配;离开范围隐藏而保留实例,业务暂停或继续由插件决定。未知上下文不匹配,不读取父页面 DOM | -| 自定义浮动内容或运行时换区域 | `presentation.position: "absolute"`;`setPresentation` 更新目标及外框 CSS 布局;`mount/onMountChange` 获取局部几何 | 拖动、折叠、恢复入口等由插件自行实现。宿主不理解这些行为,只维持独立实例、投影和边界;换位置保留会话,关闭销毁 | -| 用户选择的文件或目录 | `localResources: true`;交互视图的 `resources.pickFiles/pickDirectory` | 返回不透明句柄;任意文件格式都可读取。以 `scanDirectory` 按需筛选授权目录,`readText/readBytes/getUrl` 分别用于文本、分段二进制和受控 URL;只保存 ID,URL 按视图重新获取 | -| 自动保存草稿或生成私有文件 | 宿主和视图的 `resources.createFile({name,data,mimeType?,signal?})` | 返回不可变的私有资源句柄;先保存新文件,再更新元数据,最后释放旧版本。无任意路径或原文件写回;能力检测及配额见 protocol.md | -| 将编辑结果或生成内容保存到本机 | `resourceExport: true`;交互视图调用 `resources.saveFile({name,data})` | 每次弹出系统另存为窗口,用户决定目标;取消返回 false。文本先编码为 Blob 或字节。读取授权不授予原文件覆写权限;保存成功后才报告完成 | -| 局部装饰、输入位置附近的短暂动画 | `windowEffects: true`;`kind: "decoration"` 与正式 `anchor` | 使用 `anchor/onAnchorChange` 的局部尺寸;输入活动经 `onActivity` 提供,可带局部 caret。不获取字符或全局键盘,不接收指针或焦点 | -| 在输入所在的整个分屏内绘制装饰 | `windowEffects: true`;`kind: "decoration"`、`anchor: "workbench.pane"` | 每个分屏独立实例,画布覆盖该分屏;只接收该分屏内的对话输入活动,caret 相对分屏。绘制位置由插件决定,不受输入框边界限制;不接收指针或焦点 | -| 跨分屏的主工作区、对话历史区或整个窗口背景与装饰 | 主题材质锚点或 `windowEffects: true` 的 `decoration` | 使用 `workbench.workspace`、`task.history` 或 `window`;范围见 themes.md 与 protocol.md。主题可声明静态材质,窗口 CSS 可独立调整外观,动态装饰使用隔离视图,不开放消息正文或父页面 DOM | -| 整个 Lexora 窗口上的透明效果 | `windowEffects: true`;`resource: "none"`、`location: "window-overlay"` | API 1 和 API 2 均可用,每插件一个全窗口实例;收到无坐标的对话输入活动,不区分分屏。插件按窗口尺寸绘制,不接收指针或焦点 | -| 改变宿主控件的操作方式 | `controls: ["model.reasoning"]`;`kind: "control"`、`target: "model.reasoning"` | 安装后由用户选择样式。读取 `control.snapshot/onChange`,以当前快照 revision 提交 `propose`;值和允许选项归宿主管理,不能自建一份业务真源 | -| 自定义整个窗口的样式 | `windowEffects: true`;宿主 `window.styles.set/remove` | 注入普通 CSS,支持背景、边框、布局、伪元素、动画及媒体查询。稳定区域通过 `data-lexora-region` 选择;图片、字体及包内样式表使用 `resources.getUrl`,停用撤回。作者也可组合主题入口 | -| 读取远端公开信息 | 声明实际需要的 HTTPS origins,以宿主或视图的 `network.get` 获取 | 只有 GET,不携带浏览器登录态;直接 fetch、远程图片或媒体不替代网络代理。需要其他方法、凭据或实时连接时先说明能力缺口 | -| 关闭页面后仍定期执行插件动作 | `schedules: true`、宿主入口与已声明命令;`schedules.get/set/remove` | 定时任务归应用,最短一分钟;应用退出时暂停,错过的不补发。页面计时器只适合页面自身交互,不承担后台任务 | -| 系统提示 | `notifications: true`;宿主 `notifications.show` | 通知受用户全局开关和限速约束,返回 false 不表示插件崩溃。按真实需要与命令、调度组合 | - -需要把选中内容或文件交给任务对话时,使用 API 4 的 `references` 权限与 `ViewContext.references`,详见 [引用到对话](protocol.md#引用到对话)。内容引用保留文字快照;可选 locator 由插件负责定位,`onReveal` 返回实际结果。整份文件使用已有资源句柄。 - -## 状态与模块 - -- `activate(context)` 管理命令、私有设置、通知和定时任务;`render(context, container)` 管理自己的隔离 UI。只需要视图时不创建空宿主;需要命令时必须同时声明宿主入口和命令 ID。 -- 资源页签及普通挂载面板用 `ViewContext.state/setState` 保存该实例的筛选、阅读位置等 JSON。升级比较 `stateVersion` 与 `expectedStateVersion`,转换后再保存。导航页面、装饰、内容插槽和替换控件不支持这条持久化路径。 -- 挂载目标和外框布局通过 `setPresentation` 更新;内容样式、交互及业务状态完全由插件实现,业务状态使用 `setState` 或宿主私有存储。宿主不会依据插件数量提供面板切换或自动避让。不能用父页面 CSS 选择器或 Vue Teleport 穿透隔离。 -- 扩展级配置用宿主 `storage.get/set`。视图通过自身命令读写,不访问宿主内存;更改数据结构时增加 `dataVersion` 并提供 `migrate`。不要把一个文件的阅读位置存成覆盖所有文件的全局值。 -- 每个视图具有独立 JS 环境。宿主通过 `views.broadcast(message)` 向本插件当前 generation 的视图同步状态,视图使用 `onMessage` 订阅;首次读取仍通过自身命令获取快照。共享状态由宿主持有,视图提交局部操作,避免旧快照覆盖其他视图的更改。源码中的纯函数可通过包内相对导入复用。 -- 视图通过 `signal` 清理监听、定时器、音频和异步回写;销毁时不要再异步保存状态,正常交互中及时保存,并处理保存失败;宿主通过 `subscriptions` 释放注册,也可用 `ExtensionContext.signal` 绑定异步工作;Worker 由创建它的上下文管理生命周期。恢复视图不代表自动播放音频或重放上次用户提交。 - -## 组合验收 - -验收从用户行为出发,不以采用某个样例的文件名或界面作为成功依据。例如文件阅读工具应验证两份不同文件、空内容、各自筛选状态及重开恢复;网络信息条应验证允许来源、请求失败后的状态、关闭与再次显示;自定义控件应验证当前快照、禁用或隐藏时提交被拒绝,以及故障后内置控件可用。 - -对每项需求明确已有 API 是否覆盖。只有当前宿主能力查询返回的正式位置,没有任意 DOM 插槽、父页面 DOM、静默覆写用户文件或通用 IPC;Agent 能力仅通过声明权限的受控工具或动作入口开放。核心要求超出已开放能力时先明确缺口与可实现范围,不能把样例资源或相近布局当作已经满足核心要求,也不用内部接口模拟完整实现。 - -文件与目录图标通过 [文件图标主题](file-icon-themes.md) 提供,独立于界面配色。 - -主题包与主题编辑器使用 [主题 API](themes.md):一个包可声明多个主题,欢迎文字和插画可以分别覆盖;替换整个欢迎区仍使用 `task.welcome` 插槽。 +| 让主模型主动调用插件 | `agent.tools`,声明读写类别 | [Agent](agent.md) | +| 输入、完成事件或任务菜单触发动作 | `agent.actions` 与精确触发声明 | [动作](agent.md#任务动作)、[事件](events.md) | +| 请求已配置模型、读取当前任务 | 本次调用的 `models` / `task`,按权限开放 | [模型与任务](agent.md#模型与任务权限) | +| 提供可搜索的工作方法与配套资源 | `contributes.skills` | [Skills](skills.md) | +| 原生设置与实时生效条件 | `settings`、`configuration`、`conditions` | [设置](settings.md)、[条件](conditions.md) | +| 关闭页面后仍通知或定时执行 | 后台命令、`notifications`、`schedules` | [命令](commands.md#通知与定时命令) | +| 检测用户已有命令解释器 | 声明式 `shells`;运行仍归宿主 | [Shell](shells.md) | + +## 状态归属与验收 + +后台入口、视图、私有业务数据、声明式配置和每个标签的状态分别归各自作用域,见 [生命周期与状态](lifecycle.md)。共享状态通过自己的命令和广播同步;异步结果绑定原实例,不根据后来焦点选择目标。 + +验证实际用户流程。例如阅读器检查两份文件的状态独立与重开恢复,设置检查即时生效和重启恢复,编辑器检查草稿、原件与取消,插槽检查撤回后的宿主默认内容。缺少核心能力时明确说明,不通过宿主内部接口或相近位置模拟交付。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/commands.md b/apps/buddy/service/resources/skills/plugin-creator/references/commands.md index 37af6a2b0..361e92bfc 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/commands.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/commands.md @@ -30,3 +30,19 @@ export function activate(context: ExtensionContext) { `invocation.target` 为 `slash`,`resource` 为 null,`arguments` 为最多 8192 字符的参数字符串。命令面板调用仍提供既有资源语义且参数为空;视图调用的参数仍为 JSON。插件应按实际调用入口验证自己的参数。 停止覆盖交互不能只依赖 Slash;参见 [交互会话](interactions.md) 的宿主退出机制。 + +## 操作菜单 + +API 3 的 `contributes.menus` 将已有命令放进业务区域的「更多操作」。先用 `lexora_plugin_capabilities({kind:"menu"})` 查询目标与输入契约,再声明 `{id,command,target,order?,when?}`。`hidden` 隐藏命令面板入口,不隐藏显式声明的菜单。 + +命令参数中的 `invocation` 包含 `target`、`instanceId`,仅在声明 `selectedContent` 且用户点击对应操作后附带 `content`。文件菜单通过原有 `selectedResource` 权限提供 `resource` 句柄。视图调用自己的命令时 `invocation.target` 为 `view`,只附带该视图的实例标识;命令面板和定时调用的 `invocation` 为空。 + +输入操作可返回 `{insertText:string}`。宿主在原草稿仍可编辑且内容未改变时,将结果作为纯文本插入原选择位置;更换草稿、编辑内容或关闭视图后忽略迟到结果,不自动发送。其他菜单忽略命令返回值。`when` 支持宿主公开上下文,消息菜单额外提供 `message.role`,文件菜单提供当前 `resource.scheme`;不能用条件读取未公开的数据。 + + +## 通知与定时命令 + +后台提醒使用 `context.schedules.set({ id, command, enabled, intervalMinutes })`,间隔最少 1 分钟;不要用页面 setInterval 代替跨页面提醒。Lexora 退出后暂停,重启继续,错过的提醒不会补发。 + + +`permissions.notifications` 开放 `context.notifications.show({title,body})`。返回 false 表示没有展示通知;通知仍受应用全局开关和限速约束。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/documents.md b/apps/buddy/service/resources/skills/plugin-creator/references/documents.md new file mode 100644 index 000000000..f15aafd9a --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/documents.md @@ -0,0 +1,26 @@ +# 文档编辑、保存与恢复 + +`Document` 表示具有编辑、保存和恢复生命周期的内容,不限定格式,也不要求已经存在磁盘文件。表格、图片、绘图或其他二进制内容都使用同一接口;格式解析和编辑由插件负责。 + +API 4 的 `fileEditing: true` 独立于读取与导出权限。`context.documents` 仅在交互视图中可用,不向后台命令或装饰视图开放。安装时审核该能力;原文件身份由系统打开窗口或宿主文件打开入口决定,插件不能传入绝对路径。 + +- `pick({filters?})`:选择一个文件并取得编辑句柄,取消返回 null。 +- `open(context.resource)`:仅接受当前视图由宿主传入的文件资源;Space、任务文件和任务产物都沿用当前来源授权,打开真实本地文件;每次保存重新校验来源。只读内存快照不授予原件编辑权。 +- `list()` / `resume({id})`:最近文件与恢复草稿;resume 后的句柄绑定当前视图。`writable=false` 时不能写回,另存为应建立独立身份。 +- `read(document,{recovery?,signal?})`:读取完整二进制,默认优先恢复内容;跨块版本变化会失败,不混合两份内容。原文件或目录不可用时,已保留的恢复内容仍可 resume、读取和另存为;读取原件快照继续核对来源授权。 +- `create({name,data,signal?})`:创建未命名草稿,不弹保存窗口、不写原件。 +- `backup(document,{data,signal?})`:检查 revision 后只更新恢复内容,返回新句柄;后续修改使用返回值,不能显示为已保存。 +- `save(document,{data,signal?})`:检查句柄的内容 revision、原件 version 和文件身份后写回,返回新句柄;后续修改使用返回值。冲突和提交失败保留恢复内容,失败后通过 resume 取得当前句柄;陈旧 revision 的请求被拒绝,不替换较新的恢复内容。 +- `saveAs({name,data,signal?})`:系统窗口确认新位置,返回新句柄;取消返回 null。调用者决定是否切换当前编辑器,并关闭旧句柄。 +- `status(document)`:查询外部修改;最终保存仍检查版本,不能仅依赖此提示。 +- `discardRecovery(document)` / `close(document)`:明确放弃草稿、释放当前视图会话。close 自身不弹提示、不删除草稿,插件使用 onBeforeClose 完成关闭保护。 +- `forget(document)` / `reveal(document)`:移除最近记录与授权、在系统文件管理器中定位。forget 不删除原文件,有恢复草稿或未确认保存时拒绝移除。 + +句柄只包含 ID、名称、简短 location、version、revision、大小、writable、kind、recovery 和 openedAt,不包含路径。version 表示原件基线,未保存内容为 null;revision 表示当前内容版本。文件与未保存内容都只允许一个视图修改,关闭后可由其他视图 resume 接管。每插件最多 240 条记录、1 GiB 快照与恢复数据,单文件最多 256 MiB,每视图同时最多 2 次上传。原件修改和内部恢复必须分别提示;另存为切换绑定,保存副本与产物导出应保留原绑定和脏状态。撤销编辑授权保留恢复数据及其读取、另存为能力,再次访问原件需要重新选择授权。保存结果未知时先 resume 核对回执;未确认完成的内容继续保留为恢复副本,后续保存仍检查原件基线,不会自动重放旧操作。能力采用增量 API,发布包的 engines.lexora 必须覆盖实际支持它的宿主版本。 + + +## 关闭保护 + +`context.onBeforeClose(handler)` 注册当前视图的一个异步关闭检查,返回可释放的订阅。关闭上下文标签、替换预览、离开独立插件页、切换到另一个插件页或正常退出应用前,宿主等待 `handler(signal)`;只有明确返回 true 才继续,false、异常或 30 秒未完成都会保留原视图。可在回调中等待自动保存队列并显示插件自己的保存失败状态;确认当前最新编辑已落盘后再允许关闭;可在回调中更新 `setState` 与 `setDocumentInfo`,宿主接纳保存后的状态并在提交前复验。正常退出可同时检查多个视图,检查完成后宿主可见的文档状态或会话变化会阻止本次退出;插件仍须同步自己的编辑状态。取消检查会中止 signal,过期结果不能关闭新视图。没有注册处理器时沿用直接关闭。 + +该检查不拦强制禁用、卸载、崩溃或系统强制终止,持续自动保存仍是恢复基础。不要依赖 iframe 的 beforeunload 或在关闭检查中打开、关闭其他工作台视图。此增量接口应先做 `typeof context.onBeforeClose === 'function'` 能力检测。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/events.md b/apps/buddy/service/resources/skills/plugin-creator/references/events.md index b57dfea33..3f8536d87 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/events.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/events.md @@ -43,7 +43,7 @@ subscription.dispose() ## 任务动作事件 -任务动作通过 `contributes.agent.actions[].triggers` 声明,并由 `context.agent.registerAction` 注册。完整声明、权限与示例见 [Agent 工具、任务动作与设置](agent-settings.md)。它们不会出现在 `context.events.on` 或 `**` 订阅中;动作由宿主创建独立调用,提供只属于原任务的 `invocation.task`、`invocation.models` 与 `invocation.signal`。 +任务动作通过 `contributes.agent.actions[].triggers` 声明,并由 `context.agent.registerAction` 注册。完整声明、权限与示例见 [Agent 工具与任务动作](agent.md)。它们不会出现在 `context.events.on` 或 `**` 订阅中;动作由宿主创建独立调用,提供只属于原任务的 `invocation.task`、`invocation.models` 与 `invocation.signal`。 | trigger / cause.type | cause.data | 触发时机 | | --- | --- | --- | @@ -74,4 +74,4 @@ triggers 只接受表中的精确值,不支持通配符。动作不会作为 动作的源输入、分支、标题、模型选择或任务有效性发生变化时,宿主拒绝过期操作;任务手动改名、删除,以及插件配置变化、禁用或更新会撤销相关调用。检查 `invocation.signal` 并处理 `task.rename()` 的 `{applied:false}`。`cause.data` 保存触发时的事实,读取接口返回读取时的状态;不要假定异步模型完成后任务仍未变化。 -引用定位通过 `ViewContext.references.onReveal(handler)` 请求并确认结果,不属于事件广播,也不参与事件重放;协议与取消处理见 [引用到对话](protocol.md#引用到对话)。 +引用定位通过 `ViewContext.references.onReveal(handler)` 请求并确认结果,不属于事件广播,也不参与事件重放;协议与取消处理见 [引用到对话](references.md#引用到对话)。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/lifecycle.md b/apps/buddy/service/resources/skills/plugin-creator/references/lifecycle.md new file mode 100644 index 000000000..191c681ba --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/lifecycle.md @@ -0,0 +1,40 @@ +# 入口、通信与状态 + +## 页面、宿主与设置 + +事件可通过作用域内的 `context.events.on` 统一订阅,支持命名空间和通配符,旧 `onMessage/onWorkbenchChange` 等入口继续可用;事件名与生命周期见 [作用域事件](events.md)。 + +完整类型见同目录 `api.d.ts`。每个视图是独立沙箱。`render` 可异步,前台加载须在 10 秒内完成;耗时内容先绘制加载状态,再异步填充。加载失败会结束该视图并提供重启入口,其他视图与宿主继续运行。没有 `mount`、全局 `lexora` 或 Vue 运行时。 + +宿主入口示意: + +```ts +import type { ExtensionContext } from './lexora' + +export function activate(context: ExtensionContext) { + context.subscriptions.add(context.commands.register(`${context.extension.id}.read-settings`, async () => context.storage.get())) +} +``` + +该命令须出现在 `contributes.commands` 中,例如 `{ "id":".read-settings", "title":"读取设置", "hidden":true }`。视图通过 `context.commands.execute(command, arguments)` 调用自身命令,返回 JSON。私有数据使用 JSON;更改结构时递增 `dataVersion` 并导出 `migrate(previous, from, to)`。 + +文件工具仍可通过可见命令提供打开入口;支持新关联声明的 API 4 宿主也可使用 [文件关联](resources.md#文件关联与独立入口)。命令方式中,用户先在文件面板打开文件,再从命令面板执行它;宿主将收到的资源句柄传入视图: + +```ts +context.subscriptions.add(context.commands.register(`${context.extension.id}.open`, async ({ resource }) => { + if (!resource) + throw new Error('请先打开一个文件') + await context.views.open(`${context.extension.id}.reader`, { resource, state: {} }) +})) +``` + +该视图声明 `resource: "selected-file"`,插件声明 `selectedResource: "read"`;视图从 `context.resource` 取得同一资源,通过 `resources.readText` 读取。句柄不是文件路径。只声明视图不会自动增加文件菜单或命令入口。 + +## 状态与模块 + +- `activate(context)` 管理命令、私有业务数据、通知和定时任务;`render(context, container)` 管理自己的隔离 UI。只需要视图时不创建空宿主;需要命令时必须同时声明宿主入口和命令 ID。 +- 资源页签及普通挂载面板用 `ViewContext.state/setState` 保存该实例的筛选、阅读位置等 JSON。升级比较 `stateVersion` 与 `expectedStateVersion`,转换后再保存。导航页面、装饰、内容插槽和替换控件不支持这条持久化路径。 +- 挂载目标和外框布局通过 `setPresentation` 更新;内容样式、交互及业务状态完全由插件实现,业务状态使用 `setState` 或宿主私有存储。宿主不会依据插件数量提供面板切换或自动避让。不能用父页面 CSS 选择器或 Vue Teleport 穿透隔离。 +- 扩展级私有业务数据用宿主 `storage.get/set`。视图通过自身命令读写,不访问宿主内存;更改数据结构时增加 `dataVersion` 并提供 `migrate`。不要把一个文件的阅读位置存成覆盖所有文件的全局值。 +- 每个视图具有独立 JS 环境。宿主通过 `views.broadcast(message)` 向本插件当前 generation 的视图同步状态,视图使用 `onMessage` 订阅;首次读取仍通过自身命令获取快照。共享状态由宿主持有,视图提交局部操作,避免旧快照覆盖其他视图的更改。源码中的纯函数可通过包内相对导入复用。 +- 视图通过 `signal` 清理监听、定时器、音频和异步回写;销毁时不要再异步保存状态,正常交互中及时保存,并处理保存失败;宿主通过 `subscriptions` 释放注册,也可用 `ExtensionContext.signal` 绑定异步工作;Worker 由创建它的上下文管理生命周期。恢复视图不代表自动播放音频或重放上次用户提交。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/permissions.md b/apps/buddy/service/resources/skills/plugin-creator/references/permissions.md new file mode 100644 index 000000000..f7e1bc008 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/permissions.md @@ -0,0 +1,24 @@ +# 权限与隔离 + +| permissions 字段 | 能力 | +| --- | --- | +| `references: true` | 交互视图向任务草稿添加材料和定位来源;Agent 工具读取本插件的已提交用户引用(API 4) | +| `clipboardRead: true` / `clipboardWrite: true` | 交互视图读取或写入文本与 HTML,读写权限独立 | +| `themeManagement: true` | 校验、预览和保存用户主题;仅声明主题无需此权限 | +| `shells: true` | 声明并检测固定命令解释器候选;执行仍归宿主 | +| `agent: true` | 追加 Agent 指令、可调用工具或任务动作 | +| `models: true` | 工具或动作调用期间请求已配置模型,凭据留在宿主 | +| `tasks: "read" / "title"` | 读取当前任务标题元数据;title 额外允许安全改名 | +| `taskMessages: true` | 工具或动作调用期间读取原任务可见分支的有限消息文本,详见 [任务权限](agent.md#模型与任务权限) | +| `windowEffects: true` | 窗口 CSS、锚点装饰、窗口效果与无内容的对话输入活动 | +| `controls: ["model.reasoning"]` | 提供可由用户选用的思考等级控件 | +| `notifications: true` | 宿主 `context.notifications.show({title,body})` | +| `schedules: true` | 宿主持久间隔任务,需要宿主入口与已声明命令 | +| `selectedResource: "read"` | 读取用户选择并明确分配的文件资源 | +| `selectedContent: true` | API 3;用户点击插件操作时获取当前草稿或该条消息文本,不订阅输入或读取对话历史 | +| `localResources: true` | 用户通过系统窗口选择文件或目录,视图读取文本、二进制或受控 URL | +| `fileEditing: true` | 在交互视图中打开用户明确选择的文件,按版本保存原件及恢复草稿(API 4) | +| `resourceExport: true` | 用户通过系统另存为窗口确认目标后,保存插件提供的字节 | +| `network: ["https://example.com"]` | 通过宿主向列出的 HTTPS origin 发 GET 请求、下载文件或打开外链 | + +未声明权限默认为关闭,清单只需列出实际申请的权限,SDK 打包时会省略默认值。宿主忽略清单顶层、`permissions` 和 `contributes` 中不认识的声明,不因此提供对应能力;已知字段仍按类型和权限校验。需要新版能力时,通过 `engines.lexora` 和 API 版本声明兼容范围。视图无法访问 Node、宿主 DOM、全局键盘、网络 fetch 或任意本地文件。资源和网络调用通过 API;用户在安装确认中查看权限。系统剪贴板使用独立的读写权限,仅面向当前前台可见且聚焦的交互视图,详见 [视图与剪贴板](views.md#视图类型)。不提供任意系统消息、屏幕截图或跨插件事件订阅。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/protocol.md b/apps/buddy/service/resources/skills/plugin-creator/references/protocol.md index 79eb817e5..86a612be2 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/protocol.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/protocol.md @@ -1,6 +1,6 @@ -# Lexora 插件协议 +# 插件清单与兼容性 -一个包包含根 `extension.json` 和包内文件。源码包支持自包含 `.ts`、`.mts`、`.js`、`.mjs`,以及静态 CSS、图片和声明的 Skill 目录等资源;原始 HTML、Vue 单文件组件、npm 裸导入不属于当前编译协议。第三方库需作者预先打包。不要生成 `package.json`、锁文件、构建脚本、`node_modules` 或远程 CDN 依赖。 +插件由根目录 `extension.json` 和包内文件组成。先用 [能力选择](capabilities.md) 确定入口与状态归属,再阅读本页和涉及的专题。[完整 API 类型](api.d.ts) 给出准确签名。 ## 清单 @@ -15,8 +15,8 @@ "author": "作者署名", "description": "插件完成的用户行为。", "version": "1.0.0", - "apiVersion": 3, - "engines": { "lexora": ">=0.9.0 <1.0.0" }, + "apiVersion": 4, + "engines": { "lexora": ">=0.16.2" }, "permissions": {}, "contributes": {} } @@ -26,357 +26,46 @@ 可选字段:`author`、`icon`(包内 SVG/PNG/JPEG/WebP,≤64 KiB)、`categories`、`tags`、宿主 `entry`、`dataVersion`。不要添加未定义的字段。命令、视图和挂载声明的 ID 以插件 ID 加 `.` 开头且不重复。文件路径相对包根,不能包含 `..` 或符号链接;最多 4096 个文件,其中非图片文件最多 512 个;归档最多 8192 个条目。单文件最多 64 MiB,压缩安装包最多 128 MiB,展开文件合计最多 256 MiB;目录安装、市场下载与构建使用相同预算。对话构建自动分块传输安装包,每块最多 256 KiB,不要把大包或二进制资源塞进普通 JSON 消息。源码编译仍受时间与内存限制,大型预构建 JS 库应使用 compiled 包。 -`contributes` 内的声明结构如下。省略不用的项;每项权限和运行方式见后文,无需查找应用实现或复制某个示例清单。 +`contributes` 内的声明结构如下。省略不用的项;每项权限和运行方式见对应专题,无需查找应用实现或复制某个示例清单。 | 字段 | 结构 | | --- | --- | | `commands` | 数组,每项必填 `id`、`title`;`hidden` 可选,默认 false;API 3 可声明 `slash: {name, description?}`,规则见 [注册命令](commands.md)。存在命令时必须设置清单顶层的宿主 `entry` | | `menus` | API 3;数组,每项必填 `id`、`command`、`target`,可选 `order`(-1000 至 1000,默认 0)、`when`;引用自己的已声明命令 | | `views` | 数组,每项必填 `id`、`title`、`entry`;`resource` 为 `selected-file`(默认)、`none`,或 API 4 的 `optional-file`;`location` 默认 `context`;`stateVersion` 为正整数,默认 1 | -| `placements` | 数组,每项必填 `id`、`view`、`kind`,并按 kind 添加后文规定的 `anchor`、`target` 或 `presentation`;不要把它们写入视图定义 | +| `placements` | 数组,每项必填 `id`、`view`、`kind`,并按 kind 添加[视图与挂载](views.md)规定的 `anchor`、`target` 或 `presentation`;不要把它们写入视图定义 | | `navigation` | 单个对象,必填 `title`、`view`,引用自己的无资源视图 | -| `fileHandlers` | API 4;数组,每项必填 `id`、`title`、`extensions`;`preview`、`open` 至少提供一个,各含 `view` 和可选 `requestDefault`(默认 `true`);见下文 | +| `fileHandlers` | API 4;数组,每项必填 `id`、`title`、`extensions`;`preview`、`open` 至少提供一个,各含 `view` 和可选 `requestDefault`(默认 `true`);见 [文件关联](resources.md#文件关联与独立入口) | | `launchers` | API 4;数组,每项必填 `id`、`title`、`view`,提供无需文件的独立入口 | | `skills` | API 4;数组,每项为 `{id,path}`,path 指向包内 `SKILL.md`;可独立提供,无需 entry 或额外权限,见 [插件 Skills](skills.md) | -| `agent` | API 3;指令、工具、任务动作和可选的 enabledWhen,见 [Agent 与设置](agent-settings.md) | -| `settings` | API 3;声明 modules/groups/items,由宿主渲染并保存,见 [Agent 与设置](agent-settings.md) | +| `agent` | API 3;指令、工具、任务动作和可选的 enabledWhen,见 [Agent 工具与动作](agent.md) | +| `settings` | API 3;声明 modules/groups/items,由宿主渲染并保存,见 [声明式设置](settings.md) | | `shells` | API 4;固定本机解释器候选与原生设置映射;可选 settingsGroup 将插件自有组渲染为一行一个 Shell 的原生表格,文件选择仅限原生设置,需独立 shells 权限,见 [命令解释器](shells.md) | 插件 ID 使用小写字母开头的两段名称,中间以 `.` 分隔,每段可含小写字母、数字与 `-`;贡献 ID 在插件 ID 后追加由小写字母、数字、`.`、`-` 组成的后缀。声明文件路径使用包内相对路径,`.ts` 文件名直接指向源码,编译工具负责产出运行文件。 -## 引用到对话 - -API 4 的 `context` 和 `page` 视图可声明 `permissions.references: true`,通过 `context.references.add(input)` 选择任务草稿、新建任务或复制引用。装饰、插槽与替换控件不支持此能力。成功加入草稿或复制引用返回 `true`,取消返回 `false`;授权或资源失效会抛错。等待用户选择没有固定的十秒期限;关闭视图或停用插件会取消请求。此操作不会保存源文档、发送消息或调用模型。 - -自有内容菜单使用 `references.capture(input)` 冻结材料和当时可接收的任务草稿,返回 `{ id, targets: [{ id, label }], defaultId, clipboard }`;视图不可用时返回 `null`。唯一目标的 `defaultId` 可作为主动作,多个目标由插件在子菜单呈现,同时提供新任务及允许时的复制入口。点击后调用 `references.commit(id, { kind: 'view', id: targetId })`、`{ kind: 'new-task' }` 或 `{ kind: 'clipboard' }`。提交会等待草稿持久化,不设固定十秒期限;成功返回 `true`,失效返回 `false`,不会悄悄选择后来激活的任务。菜单关闭时调用 `references.release(id)`。每个视图只保留最近一次捕获,隐藏、关闭、替换会话或已消费后失效;插件不能持久化或跨视图复用捕获 ID。捕获不保存源文档,也不修改草稿。该交互需要 Lexora 0.17.0 或更新版本。 - -```ts -await context.references.add({ - kind: 'content', - title: '汇总表', - text: selectedText, - locator: { version: 1, data: { sheet: 'Summary', cells: 'A1:B2' } }, -}) - -context.references.onReveal(async ({ locator, text }, signal) => { - if (!locator || locator.version !== 1) - return false - return revealCells(locator.data, { expectedText: text, signal }) -}) -``` - -`content` 保存当时的文字快照;`text` 必须非空,文字最多 262144 个 UTF-16 单位,locator 单独按下述 JSON 预算校验,`title` 最多 512 字符。来源身份由宿主生成,插件不能自报路径或来源 ID。默认绑定视图当前选中的资源,并在引用和定位时复核授权。若内容已导入插件自己的文档库,可显式传 `source: 'view'`,只绑定插件视图与 locator,不再依赖最初打开视图的文件;仅 `resource: 'none'` 或 `'optional-file'` 视图支持,`'selected-file'` 会拒绝。`locator` 可省略,是插件定义的只读 JSON,含正整数 `version` 和 `data`;数据最多 16 KiB、12 层,不允许函数、循环或特殊对象。宿主只负责传回,插件负责版本兼容和验证定位结果,不能把 locator 当作文件授权。短片段直接进入模型上下文,长片段提供预览并由宿主引用工具按需读取保存的快照;locator 与内部目标不会进入模型上下文。每条消息最多 16 条引用,快照合计最多 1048576 个 UTF-16 单位;这些存储限制与模型输入预算独立。 - -每个视图用 `references.onReveal(handler)` 注册一个定位处理器,返回可 `dispose()` 的注册对象。处理器收到只读的 `referenceId`、原始 `text`、`locator`(未提供时为 `null`)和取消信号;确认内容已定位后返回 `true`。无法找到、版本不支持、未注册或抛错均视为定位失败。异步处理必须遵守 `signal`,避免用户取消后继续滚动或改动界面。 - -定位优先复用原视图,不重载有效会话;绑定资源的引用会重新检查资源授权。内联预览保留当前未保存快照;原视图已关闭时,默认重新打开现有资源来源;`source: 'view'` 引用则以空资源重开该视图,由插件从自有内容恢复。插件应验证 locator 与当前内容是否匹配。插件停用或来源不可访问时,已保存的引用文字仍可查看。 - -整份文件使用 `references.add({ kind: 'resource', resource })`,传入 `context.resource` 或本插件已有的授权句柄。Space 来源沿用文件引用,临时快照、产物和其他已授权资源按内容复制为附件;不传路径,不扩大读取权限。读取所需的 `selectedResource` / `localResources` 权限仍需分别声明。使用这些新增 API 4 能力时,`engines.lexora` 的最低版本应覆盖实际支持该能力的宿主。 - -## 视图类型 - -| location | resource | 行为 | -| --- | --- | --- | -| `page` | `none` | 独立页面,可作为导航入口 | -| `context` | `selected-file`、`none` 或 API 4 的 `optional-file` | 工作区资源页签,由用户打开 | -| `window-overlay` | `none` | 启用后自动挂载的透明窗口效果,每插件最多一个;无法点击或聚焦 | - -页面与小视图由插件绘制,均使用 `render(context, container)`,共用隔离和通信 API。API 2 起将视图内容与挂载位置分开,通过 `contributes.placements` 选择正式扩展点;`page` 和 `window-overlay` 继续可用。插件不能查询或修改宿主 DOM。 - -所有视图的 `container` 默认填满隔离视口,无内边距,根元素与子元素使用 `border-box`,支持 `w-full`、`h-full` 和百分比高度。页面、插槽、控件和挂载面板使用相同盒模型,插件自行提供间距;宿主不为插件内部的 `main` 等语义元素增加样式。页面内容超出视口时可正常滚动;需要内部滚动时由插件明确设置自身滚动区域。装饰的绘制范围受锚点及祖先裁剪约束,装饰视口不滚动。 - -导航入口位于自动化下方,声明 `contributes.navigation: { "title": "插件名称", "view": ".settings" }`,引用一个 `resource: "none"` 的视图(API 1 仍需 `location: "page"`)。不需要设置页的效果插件可以没有导航。 - -导航页面、装饰、内容插槽和替换控件不能使用 `context.setState()`;需要持久设置时声明宿主入口和隐藏命令,以 `context.storage.get/set` 保存。多个视图使用同一设置时明确刷新时机,插件局部开关必须能在不重载视图的情况下关闭并重新启用。资源页签的 `setState` 用于保存该页签的 JSON 状态。 - -交互视图可用 `context.setDocumentInfo({title,dirty})` 更新自己的文档名称与未保存标记。标题去除首尾空白后为 1–256 个字符。资源页签随布局持久化,导航页面只保留当前会话的信息;不会更改文件内容、授予保存权限或代替 `onBeforeClose`。插件应在打开、重命名、编辑、保存和返回管理页时同步自己的真实状态,恢复文档后再次核对标记。 - -系统剪贴板使用独立的 `permissions.clipboardRead` 与 `permissions.clipboardWrite` 授权,默认均为 `false`。交互视图用 `context.clipboard.read()` 读取 `{text?,html?}`,用 `context.clipboard.write({text?,html?})` 原子替换这些格式;至少传一项,空字符串有效,文本与 HTML 的 UTF-8 总量最多 4 MiB。HTML 内容与属性保留,不传递图片、文件路径或其他原生格式。没有支持的格式时读取返回 `{}`;过大内容或失败操作会拒绝 Promise。 - -剪贴板调用要求安装授权及当前前台窗口中可见、聚焦的插件 iframe;读取交付前和写入前均复验视图身份与焦点。此授权不是逐次用户手势检查:已聚焦的插件可主动调用,用户无需每次确认。失焦、隐藏、关闭或插件停用后不可调用;后台入口、Worker、装饰、内容插槽和替换控件不提供此能力。纯视图插件无需后台入口。iframe 的浏览器 `navigator.clipboard` 权限仍被禁止,编辑器应注入此 SDK 适配器;浏览器原生 `paste` 事件的 `clipboardData` 与异步剪贴板 API 是不同路径。 - -这两项增量 API 应先检查 `typeof context.setDocumentInfo === 'function'` 和 `context.clipboard`,缺失时提示升级宿主;清单兼容范围使用实际支持此能力的宿主版本。 - -## 文件关联与独立入口 - -使用下列新增声明时,`apiVersion` 保持 `4`,`engines.lexora` 必须以首次提供这些能力的应用版本为下限;不能仅凭旧宿主也支持 API 4 就声明兼容。应用版本和 API 版本分别约束能力可用性与协议兼容性。 - -`contributes.fileHandlers` 声明文件处理器,每项具有稳定的 `id`、显示名称 `title` 和精确的小写带点后缀 `extensions`,例如 `[".md", ".markdown"]`。不接受通配符、MIME 推断或没有点的后缀;每项最多 64 个后缀、共 32 项。两种打开方式分别保存用户默认;多个已注册复合后缀匹配时采用最长者。 - -- `preview: { view, requestDefault? }`:在文件页内预览。 -- `open: { view, requestDefault? }`:在独立工作台视图中打开文件。 - -至少声明一种方式;省略的方式不注册,也不借用另一种方式的视图。两者可引用同一个视图或不同视图;视图必须属于本插件、`location` 为 `context`,资源模式为 `selected-file` 或 `optional-file`,并声明 `selectedResource: "read"`。宿主传递文件资源与管理视图生命周期,插件自行决定界面内容。 - -文件关联与引用重开得到的资源可包含 `sourceId`:同一插件内,同一宿主来源的稳定、不透明标识,跨新资源句柄保持一致。它可用于复用文档库记录;不同产物、来源类型或重绑定的目录仍是不同来源。它不代表内容哈希、文件路径或授权,读取仍使用当前 `id`;缺少该字段时不能按文件名猜测同源。 - -每种方式的 `requestDefault` 缺省为 `true`,安装确认页预勾选对应关联;设置为 `false` 时不预选,用户仍可主动勾选。该字段不影响文件权限或撤销已有默认。确认安装应用最终选择,更新的关联在新版本生效时应用。相同后缀和方式有多个处理器时,优先保留本插件中已经作为默认的处理器,否则按声明顺序预选第一个申请者。停用、失败、卸载时保留偏好,只回退内置处理器,不自动选择其他第三方。 - -`contributes.launchers` 提供“+”启动入口(最多16项),引用本插件的 `context` 视图,资源模式为 `none` 或 `optional-file`。启动时 `context.resource` 为 `null`;文件打开则传入文件资源。两个入口独立引用视图,文件打开无需注册或调用 launcher。 - -共用同一视图时,让视图接受可选文件并处理有文件、无文件两种状态: - -```json -{ - "apiVersion": 4, - "permissions": { "selectedResource": "read" }, - "contributes": { - "views": [{ "id": ".reader", "title": "阅读器", "entry": "view.ts", "resource": "optional-file" }], - "fileHandlers": [{ "id": ".files", "title": "阅读器", "extensions": [".md"], "preview": { "view": ".reader" }, "open": { "view": ".reader" } }], - "launchers": [{ "id": ".start", "title": "阅读器", "view": ".reader" }] - } -} -``` - -分别呈现时,新增 `selected-file` 视图,并让 `preview` 引用它;原视图仍用于启动和独立打开: - -```json -{ - "views": [ - { "id": ".preview", "title": "文件预览", "entry": "preview.ts", "resource": "selected-file" }, - { "id": ".reader", "title": "阅读器", "entry": "view.ts", "resource": "optional-file" } - ], - "fileHandlers": [{ "id": ".files", "title": "阅读器", "extensions": [".md"], "preview": { "view": ".preview", "requestDefault": false }, "open": { "view": ".reader" } }], - "launchers": [{ "id": ".start", "title": "阅读器", "view": ".reader" }] -} -``` - -只提供文件处理时,省略 `launchers` 即可;文件视图可使用 `selected-file`。只做预览则再省略处理器的 `open`。 - -旧 `fileOpeners` 声明仍受支持:其 `locations` 中的 `inline`、`standalone` 分别对应新结构的 `preview`、`open`,使用原 `view` 和 `requestDefault`。更新为新声明时保留处理器 ID,即可延续用户默认关联;同一 ID 不能同时声明在新旧数组中。 - -关联不增加目录、路径或写入权限。句柄来自用户已授权的 Space 文件或任务产物,每次读取重新核对原授权;只能由分配到该资源的视图读取。视图可使用 `resources.readText/readBytes/getUrl(context.resource)`,受控 URL 支持 HEAD、Range,关闭后失效。内联预览可能获得未保存文本的只读内存快照(最多 2 MiB UTF-8),宿主不会写回或持久保存快照,原 Space 授权撤回后即不能继续读取;不可保存此临时句柄供重启后复用。文件原件编辑需声明 fileEditing 并通过 documents.open 建立编辑会话;只读快照的导出使用 resources.saveFile。 - -## 权限 - -| permissions 字段 | 能力 | -| --- | --- | -| `agent: true` | 追加 Agent 指令、可调用工具或任务动作 | -| `models: true` | 工具或动作调用期间请求已配置模型,凭据留在宿主 | -| `tasks: "read" / "title"` | 读取当前任务标题元数据;title 额外允许安全改名 | -| `taskMessages: true` | 工具或动作调用期间读取原任务可见分支的有限消息文本,详见 [任务权限](agent-settings.md#模型与任务权限) | -| `windowEffects: true` | 窗口 CSS、锚点装饰、窗口效果与无内容的对话输入活动 | -| `controls: ["model.reasoning"]` | 提供可由用户选用的思考等级控件 | -| `notifications: true` | 宿主 `context.notifications.show({title,body})` | -| `schedules: true` | 宿主持久间隔任务,需要宿主入口与已声明命令 | -| `selectedResource: "read"` | 读取用户选择并明确分配的文件资源 | -| `selectedContent: true` | API 3;用户点击插件操作时获取当前草稿或该条消息文本,不订阅输入或读取对话历史 | -| `localResources: true` | 用户通过系统窗口选择文件或目录,视图读取文本、二进制或受控 URL | -| `fileEditing: true` | 在交互视图中打开用户明确选择的文件,按版本保存原件及恢复草稿(API 4) | -| `resourceExport: true` | 用户通过系统另存为窗口确认目标后,保存插件提供的字节 | -| `network: ["https://example.com"]` | 通过宿主向列出的 HTTPS origin 发 GET 请求、下载文件或打开外链 | - -未声明权限默认为关闭,清单只需列出实际申请的权限,SDK 打包时会省略默认值。宿主忽略清单顶层、`permissions` 和 `contributes` 中不认识的声明,不因此提供对应能力;已知字段仍按类型和权限校验。需要新版能力时,通过 `engines.lexora` 和 API 版本声明兼容范围。视图无法访问 Node、宿主 DOM、全局键盘、网络 fetch 或任意本地文件。资源和网络调用通过 API;用户在安装确认中查看权限。没有一般性消息订阅、系统剪贴板或屏幕截图接口。 - -## 窗口效果 - -视图入口形式为 `export function render(context: ViewContext, container: HTMLElement)`,类型使用 `import type { ViewContext } from './lexora'`。在 `render` 内调用 `context.onActivity(listener)`,在自己的 `container` 内绘制实际效果。输入锚点事件为 `{type:"composer-input", caret:{x,y,width,height}|null}`,坐标相对当前锚点。 - -`context.anchor` 和 `onAnchorChange` 提供 `{kind,visible,width,height}`,不包含屏幕坐标。呈现范围与事件来源按下表选择,不能因输入触发就默认选输入框装饰: - -| 呈现范围 | 声明 | 活动与坐标 | -| --- | --- | --- | -| 整个客户端窗口 | `kind: "decoration"`、`anchor: "window"` | 在所有页面显示,包括设置页、顶栏和两侧栏,输入活动坐标相对窗口;不接收指针与焦点 | -| 任务主工作区 | `kind: "decoration"`、`anchor: "workbench.workspace"` | 仅任务页的跨分屏连续区域;不包括顶栏、侧栏和资源面板,切换到其他页面时隐藏,输入活动坐标相对该区域 | -| 整个输入所在分屏 | `kind: "decoration"`、`anchor: "workbench.pane"` | 每个分屏独立实例,只接收自己内部的对话输入活动,caret 相对该分屏;可以覆盖对话区域,不能越过分屏边界 | -| 对话历史区 | `kind: "decoration"`、`anchor: "task.history"` | 每个可见历史视口独立实例,背景不随消息滚动;不提供消息内容,不覆盖输入框 | -| 输入框内部 | `kind: "decoration"`、`anchor: "composer.input"` | 只接收该输入框活动,caret 相对输入框,绘制裁剪在输入框内 | -| 整个 Lexora 窗口 | 视图 `location: "window-overlay"` | API 1、API 2 均只收到 `{type:"composer-input"}`,不含来源分屏与坐标;插件自行选择窗口内的位置 | - -分屏装饰随分屏创建、改变尺寸和关闭;页面隐藏时保留实例并更新可见性。事件绑定输入发生的分屏,不随之后的焦点切换转交其他分屏;插件无需读取任务或分屏 ID。多个分屏及多个插件的实例各自隔离。此锚点提供透明装饰区域,不提供交互面板外壳。 - -事件没有字符、按键、文本长度或任务 ID,最多约每秒 20 次,只来自前台 Lexora 对话输入框,包括输入法输入。宿主过滤程序性修改、其他输入框和减少动态效果模式。不要注册父窗口键盘事件。 - -宿主保持 iframe 透明且不拦截鼠标或焦点。插件使用 DOM、Canvas、Web Animations 或 requestAnimationFrame 绘制。保持 `html/body` 透明,不铺不透明全屏背景;无操作时不持续刷新。禁用、卸载和窗口销毁会关闭视图。通过 `context.signal` 清理事件、计时器与动画;粒子和同时运行的动画必须有上限。 - -## 正式挂载点 - -`placements` 中的 `view` 引用本插件的 `resource: "none"` 视图定义,省略 `location`(默认 `context`)。每种挂载均使用独立的隔离视图实例。 - -| kind | 必填字段 | 行为 | -| --- | --- | --- | -| `decoration` | `anchor` | 通过 `lexora_plugin_capabilities({kind:"decoration"})` 查询背景与装饰区域;每个锚点实例分别挂载,只在该区域内绘制,不接收指针和焦点;需要 `windowEffects` | -| `view` | `target` | 用 `lexora_plugin_capabilities({kind:"mount"})` 查询目标;`presentation` 描述区域内布局,宿主入口调用 `context.placements.show(id, instanceId?)` / `hide(id, instanceId?)` | -| `control` | `target` | 当前支持 `model.reasoning`;由插件自身配置启停,运行规则见 [内容插槽与替换控件](slots.md) | -| `slot` | `target` | 需要将 `apiVersion` 设为 3;由插件自身配置启停,目标、尺寸和默认内容回退见 [内容插槽](slots.md) | - -挂载口只提供容器,不提供面板外壳或交互。声明示意:`{id,view,kind:"view",target:"workbench",presentation:{position:"absolute",width:420,height:180,right:16,bottom:16}}`。目标所属范围由能力目录返回。API 3 的分屏挂载以 `instanceId` 区分实例;从命令 `invocation.instanceId` 传入发起操作的分屏,不要在异步完成后重新选择焦点。省略时使用调用时的活动分屏,标识失效则拒绝打开。每个分屏分别保存状态,分屏移除时结束该实例。挂载口暂时不可用时保留实例,恢复后继续投影。插件自行渲染全部内容、样式与交互;插件之间保留独立实例,宿主不生成切换器、不自动避让或折叠。 - -`presentation` 是隔离视图外框的布局声明:`position` 为 `static`(默认,正常布局)或 `absolute`(相对挂载口);`order` 为 -1000 至 1000(默认 1,与核心内容的 0 比较);`zIndex` 为 0–20 的扩展层内顺序,不覆盖核心弹窗。`width/height/top/right/bottom/left` 使用 0–8192 的像素数或 0%–100% 字符串;`null` 清除该 CSS 值。未指定尺寸时宽度 100%、高度 56px,最大尺寸及可见内容受目标容器边界约束。内容内部自由使用 DOM/CSS、Canvas 等;不访问父页面 DOM。 - -`context.setPresentation({...})` 合并更新上述字段,传 `target` 可转移挂载口。进入分屏区域要求该视图在创建时已绑定分屏,不会把全局实例隐式移入当前焦点分屏。实例、会话与状态保留;插件的定时器、媒体等不会因为换位置而重建。布局值由宿主持久化,业务状态由插件自己保存。`context.mount` / `onMountChange` 提供目标容器宽高、可见性和当前视图的局部矩形,供插件响应尺寸变化或实现拖动等交互,不包含屏幕坐标、其他插件信息或父 DOM。 - -宿主不认识“折叠”“展开”“贴边”或“播放器”。需要这些效果时,插件自己渲染触发入口、控制布局并保存自己的状态。缩小视图外框不销毁内容;普通挂载调用宿主命令中的 `placements.hide` 才关闭实例。插件负责再次打开的入口,可以使用自己的可见 UI、已声明命令或导航。不要依赖宿主替插件提供关闭栏或恢复按钮。 - -旧版 `location: workbench.top/workbench.bottom` 与 `height` 在读取时转换为工作台的 static/-1 或 static/1,旧悬浮声明转换为 absolute。新源码使用 target/presentation,不增加业务专用 location。`control.height` 仍为 32–160,默认 64。 -挂载点 `id` 是升级和恢复的稳定身份,更新时不要随意改名。同一 `id` 调整挂载位置或更换视图定义后,宿主保留实例及旧状态;需要转换状态时,比较 `stateVersion` 与 `expectedStateVersion` 并调用 `setState` 保存转换结果。移除贡献或禁用插件不会清空已保存的视图状态。 +## 包格式与版本 -控件通过 `context.control.snapshot` 读取 `{revision,value,options,disabled}`,`onChange` 订阅变化。`options` 是宿主允许的值及标签;插件不获取模型或任务身份,也不直接写入业务状态。提交用户选择使用 `control.propose(value, renderedSnapshot.revision)`;宿主拒绝旧版本、不可用值、禁用或隐藏控件的请求,窗口隐藏时也不能提交。请求失败后读取最新快照重新呈现,不重试旧选择。加载失败、失去响应、禁用或缺失时恢复内置控件,保留用户样式选择。关闭再打开弹层不会重试失败控件;用户重新选择样式或重启插件后才恢复加载。 +- `format: "source"`:自包含 TS/JS 与包内相对导入,由宿主固定编译器处理;原始 HTML、Vue 单文件组件和 npm 裸导入不属于该编译协议。 +- `format: "compiled"`:作者预先构建浏览器模块及其资源。Vue、React 和其他第三方库可在作者工程中使用,交付包保留其已编译结果;安装时不运行依赖安装或构建脚本。 +- `version` 标记插件版本;`apiVersion` 标记协议主版本;`engines.lexora` 标记实际支持的宿主版本范围。API 4 内的增量接口仍可能要求更新的宿主,不能仅凭主版本声明兼容。 +- `dataVersion` 对应插件私有数据迁移,视图 `stateVersion` 对应实例状态迁移,详见 [生命周期与状态](lifecycle.md)。升级维持插件和贡献 ID,保留用户数据。 -`context.onBeforeClose(handler)` 注册当前视图的一个异步关闭检查,返回可释放的订阅。关闭上下文标签、替换预览、离开独立插件页、切换到另一个插件页或正常退出应用前,宿主等待 `handler(signal)`;只有明确返回 true 才继续,false、异常或 30 秒未完成都会保留原视图。可在回调中等待自动保存队列并显示插件自己的保存失败状态;确认当前最新编辑已落盘后再允许关闭;可在回调中更新 `setState` 与 `setDocumentInfo`,宿主接纳保存后的状态并在提交前复验。正常退出可同时检查多个视图,检查完成后宿主可见的文档状态或会话变化会阻止本次退出;插件仍须同步自己的编辑状态。取消检查会中止 signal,过期结果不能关闭新视图。没有注册处理器时沿用直接关闭。 +示例版本仅用于说明清单结构。采用文件编辑、引用捕获等新增能力时,以对应专题和实际验收的宿主为准。 -该检查不拦强制禁用、卸载、崩溃或系统强制终止,持续自动保存仍是恢复基础。不要依赖 iframe 的 beforeunload 或在关闭检查中打开、关闭其他工作台视图。此增量接口应先做 `typeof context.onBeforeClose === 'function'` 能力检测。 +## 专题索引 -组合视图可用导航页管理设置、挂载面板展示持续内容、隐藏命令连接私有存储。设置更新后的刷新时机由插件明确处理,页面之间没有隐式共享 JS 状态。 - -## 操作菜单 - -API 3 的 `contributes.menus` 将已有命令放进业务区域的「更多操作」。先用 `lexora_plugin_capabilities({kind:"menu"})` 查询目标与输入契约,再声明 `{id,command,target,order?,when?}`。`hidden` 隐藏命令面板入口,不隐藏显式声明的菜单。 - -命令参数中的 `invocation` 包含 `target`、`instanceId`,仅在声明 `selectedContent` 且用户点击对应操作后附带 `content`。文件菜单通过原有 `selectedResource` 权限提供 `resource` 句柄。视图调用自己的命令时 `invocation.target` 为 `view`,只附带该视图的实例标识;命令面板和定时调用的 `invocation` 为空。 - -输入操作可返回 `{insertText:string}`。宿主在原草稿仍可编辑且内容未改变时,将结果作为纯文本插入原选择位置;更换草稿、编辑内容或关闭视图后忽略迟到结果,不自动发送。其他菜单忽略命令返回值。`when` 支持宿主公开上下文,消息菜单额外提供 `message.role`,文件菜单提供当前 `resource.scheme`;不能用条件读取未公开的数据。 - -## 页面上下文与生效条件 - -视图通过 `context.workbench` 读取只读快照:`values` 是公开语义上下文,`pages` 列出已登记页面的稳定 `id/title`。`onWorkbenchChange` 订阅变化。当前 `page.id` 使用 `lexora.tasks`、`lexora.automations`、`lexora.extensions`、`lexora.settings` 或 `extension:<插件ID>`;自动化和设置通过 `page.section` 提供子页标识,如 `plans`、`history`、`general`、`models`。其他页面不保留上一个页面的 section。未来页面通过登记扩展,不以名称、URL 或 DOM 选择器识别。这里不提供任务正文、输入内容、文件路径、路由参数或 Provider/模型身份。 - -`contributes.views`、`placements`、`commands`、`menus` 和 `navigation` 可声明可选的 `when`。例如 `"when": {"page.id": ["lexora.tasks", "lexora.automations"]}`;对象中的不同 key 为 AND,数组内为 OR,标量严格相等,缺失 key 不匹配。空对象或省略表示不限定,不执行表达式代码。最多 32 个 key,每个数组最多 32 项,值限字符串(256 字符)、布尔和有限数字。视图定义与挂载的条件同时满足才显示。只限定某个挂载实例时优先在 placement 声明;view 的条件影响该定义的所有实例,navigation 的条件只限定入口。 - -条件只控制 UI 的可用性,不授予权限、不启动插件,也不阻止插件自身调用命令或后台调度。命令菜单与导航入口随上下文更新。首次不满足条件的视图不激活;已打开视图离开范围时隐藏,挂载面板退出布局占位,实例、状态与会话保留,返回后恢复。控件不匹配时使用内置控件并拒绝旧提交。普通挂载的 `placements.hide` 仍表示关闭实例;slot/control 的运行期启停和视图 active 状态见 [内容插槽](slots.md)。 - -`context.visible/onVisibilityChange` 表达当前视图实际可见性(包含页面范围、区域隐藏和窗口隐藏)。插件自行决定隐藏时暂停动画、查询或媒体,宿主不推断业务行为。订阅随视图销毁自动释放;插件也可提前调用返回对象的 `dispose()`。受范围限制的功能应验收首次不匹配、进入、离开、返回、子页切换和重启后的条件恢复。 - -## 本地资源与导出 - -资源授权与文件格式无关。宿主不限制业务格式,不解析文档或工程文件;插件负责解析、显示和编辑。`mimeType` 是根据扩展名给出的提示,未知格式为 `application/octet-stream`,不是授权白名单。文件选择的 `filters: [{name,extensions}]` 与目录扫描的 `extensions` 由插件按需求提供;省略筛选即接受所有格式。 - -插件下载和私有文件资源不需要 `localResources` 或额外审批。`network.download(url,{name?,signal?})` 在已批准的网络 origin 范围内流式写盘,返回 `{id,name,mimeType,size}`;不设总大小和总时长配额,可用 AbortSignal 取消。重定向逐跳核对授权,关闭发起视图或停用插件会取消下载并清理未完成文件。资源属于插件私有数据,跨重启保存;普通卸载保留,清除数据时删除。用 `resources.release(file)` 回收不再需要的下载副本。 - -宿主和视图均可 `resources.listFiles/readBytes/readText` 读取已有资源。`resources.importFile(resource,{name?,signal?})` 将已有授权的本地文件、Space 文件或本插件私有文件复制到新的私有资源,默认保留文件名,不弹出选择窗口;不接受任意路径。声明式包内图片可直接作为主题资产的 `{packagePath}` 来源。 - -`resources.createFile({name,data,mimeType?,signal?})` 在宿主和视图中将 `Blob | ArrayBuffer | Uint8Array` 保存为新的私有资源,返回 `{id,name,mimeType,size}`。它不弹出文件窗口,不需要 `localResources` 或 `resourceExport`,不接受路径或覆写原文件。适合文档草稿、编辑结果和版本快照;保存新资源成功后,再更新插件元数据中的 ID,最后 `release` 已不需要的旧资源。失败时保留旧 ID,不能先释放旧版本。并发编辑应由插件以文档版本检查串行更新元数据。 - -创建资源时按块传输并原子发布,半成品不会出现在列表;关闭发起视图、停用或 signal 取消时回收未提交数据。每插件最多同时创建 2 个文件,单文件最多 256 MiB,生成资源及其私有副本合计最多 1 GiB、4096 项;外部导入和下载沿用原有规则。MIME 可包含参数,返回句柄使用规范化后的裸类型,例如 `text/plain;charset=utf-8` 保存为 `text/plain`。大数据优先传 Blob,避免字节数组转 Blob 的整份内存复制。此增量能力需先检查 `typeof context.resources.createFile === 'function'`;旧宿主提供清晰升级提示,不把文档二进制塞进有大小限制的 JSON storage。 - -视图 `openExternal(url)` 使用系统浏览器打开已批准 origin 的 HTTPS 链接,保留 fragment。无需逐次授权,不授予文件或自定义协议访问。 - -`network.get(url)` 适用于最多 1 MiB 的文本响应;媒体和大文件使用 `download` 后通过资源 URL 或分段接口读取。大文件不放进 JSON 或命令返回值。下载等待期间不计入宿主普通命令的响应期限;宿主失去响应、退出或用户取消仍会终止操作。 - -声明 `localResources: true` 后,在可见的交互视图中响应用户操作调用 `resources.pickFiles({filters?,multiple?})` 或 `resources.pickDirectory()`。取消分别返回 `[]` 和 `null`。宿主只接受系统窗口确认的路径,插件不能提交或取得绝对路径。文件句柄为 `{id,name,mimeType,size,relativePath?}`,目录句柄为 `{id,name}`;单次最多选择 100 个文件,文件大小不设产品配额,每插件最多 1000 个文件句柄和 32 个目录。 - -`resources.listFiles` 返回插件私有资源及已授权的本地文件,`listDirectories` 返回已有目录授权。`scanDirectory(directory,{extensions?,recursive?})` 返回授权根内的文件句柄及相对路径,默认递归,跳过符号链接,限制 10000 项与 16 层;超限明确报错,不静默截断。扫描负责刷新相同筛选范围,保留其他筛选范围的句柄;它不是自动监听。文件删除或变化后,刷新撤销旧句柄,变化的文件返回新 ID。独立选择和不同目录的授权分别管理。目录内引用可以根据返回的 `relativePath` 解析,但不能越过授权根。 - -按内容选择读取方式: - -- `resources.readText(file)`:UTF-8 文本,最多 1 MiB;更大文本或其他编码使用分段读取与插件自己的解码器。 -- `resources.readBytes(file,{offset?,length?})`:返回 `{data:Uint8Array,size,eof}`;默认 64 KiB,单次最多 128 KiB,适合二进制解析、增量加载和自定义格式。 -- `resources.getUrl(file)`:当前视图专用 URL,可供图片、音视频等元素或视图内 fetch 读取;支持 HEAD、Range 和 seek。只保存资源 ID,每次视图创建后重新取得 URL。不可作为脚本、样式或新页面执行。 - -授权跨重启保留;文件被移动、替换或修改后旧句柄读取失败,可重新选择或刷新已授权目录。`revokeFile(file)` 撤销单个句柄,保留的目录授权仍允许下次扫描重新发现它;`revokeDirectory(directory)` 撤销该目录及其派生句柄,其他独立授权保留。用户可从插件管理撤销全部本地资源授权,卸载也撤销。撤销、禁用及视图关闭会使相关 URL 和未完成的读取失效,不删除用户原文件。 - -保存另行声明 `resourceExport: true`。`resources.saveFile({name,data})` 接收建议文件名和 `Blob | ArrayBuffer | Uint8Array`,弹出系统另存为窗口;取消返回 false,完整写入后返回 true,失败抛错。目标由用户决定,插件得不到路径。写入先暂存,提交时验证目标未被其他程序修改,再替换;关闭、禁用、取消或未完整提交不会覆盖原文件。读取授权不包含静默写回或目录批量写入。 - -### 编辑内容与本地文件 - -`Document` 表示具有编辑、保存和恢复生命周期的内容,不限定格式,也不要求已经存在磁盘文件。表格、图片、绘图或其他二进制内容都使用同一接口;格式解析和编辑由插件负责。 - -API 4 的 `fileEditing: true` 独立于读取与导出权限。`context.documents` 仅在交互视图中可用,不向后台命令或装饰视图开放。安装时审核该能力;原文件身份由系统打开窗口或宿主文件打开入口决定,插件不能传入绝对路径。 - -- `pick({filters?})`:选择一个文件并取得编辑句柄,取消返回 null。 -- `open(context.resource)`:仅接受当前视图由宿主传入的文件资源;Space、任务文件和任务产物都沿用当前来源授权,打开真实本地文件;每次保存重新校验来源。只读内存快照不授予原件编辑权。 -- `list()` / `resume({id})`:最近文件与恢复草稿;resume 后的句柄绑定当前视图。`writable=false` 时不能写回,另存为应建立独立身份。 -- `read(document,{recovery?,signal?})`:读取完整二进制,默认优先恢复内容;跨块版本变化会失败,不混合两份内容。原文件或目录不可用时,已保留的恢复内容仍可 resume、读取和另存为;读取原件快照继续核对来源授权。 -- `create({name,data,signal?})`:创建未命名草稿,不弹保存窗口、不写原件。 -- `backup(document,{data,signal?})`:检查 revision 后只更新恢复内容,返回新句柄;后续修改使用返回值,不能显示为已保存。 -- `save(document,{data,signal?})`:检查句柄的内容 revision、原件 version 和文件身份后写回,返回新句柄;后续修改使用返回值。冲突和提交失败保留恢复内容,失败后通过 resume 取得当前句柄;陈旧 revision 的请求被拒绝,不替换较新的恢复内容。 -- `saveAs({name,data,signal?})`:系统窗口确认新位置,返回新句柄;取消返回 null。调用者决定是否切换当前编辑器,并关闭旧句柄。 -- `status(document)`:查询外部修改;最终保存仍检查版本,不能仅依赖此提示。 -- `discardRecovery(document)` / `close(document)`:明确放弃草稿、释放当前视图会话。close 自身不弹提示、不删除草稿,插件使用 onBeforeClose 完成关闭保护。 -- `forget(document)` / `reveal(document)`:移除最近记录与授权、在系统文件管理器中定位。forget 不删除原文件,有恢复草稿或未确认保存时拒绝移除。 - -句柄只包含 ID、名称、简短 location、version、revision、大小、writable、kind、recovery 和 openedAt,不包含路径。version 表示原件基线,未保存内容为 null;revision 表示当前内容版本。文件与未保存内容都只允许一个视图修改,关闭后可由其他视图 resume 接管。每插件最多 240 条记录、1 GiB 快照与恢复数据,单文件最多 256 MiB,每视图同时最多 2 次上传。原件修改和内部恢复必须分别提示;另存为切换绑定,保存副本与产物导出应保留原绑定和脏状态。撤销编辑授权保留恢复数据及其读取、另存为能力,再次访问原件需要重新选择授权。保存结果未知时先 resume 核对回执;未确认完成的内容继续保留为恢复副本,后续保存仍检查原件基线,不会自动重放旧操作。能力采用增量 API,发布包的 engines.lexora 必须覆盖实际支持它的宿主版本。 - -包内静态资源使用 `new URL('./assets/file.ext', import.meta.url).href`,遵循包大小上限。音视频支持流式读取和 seek,具体解码能力取决于运行平台;复杂格式可使用包内自包含 JS 解析器。通用文件通道不等于内置任意格式解码器、原生模块或外部可执行程序支持。 - -验收实际读取、解析、保存结果和取消路径。涉及音视频时验证 metadata、实际进度与 seek;关闭时停止工作、清除 URL 和定时器。交互期间保存进度,销毁后不得再异步回写状态。 - -## 窗口样式 - -API 3 的宿主 `context.window.styles.set(id, css)` 注入普通 CSS,需要 `windowEffects`。同一插件的同名样式表原位替换,`remove(id)` 仅撤回自己的样式表;停用、崩溃、更新和卸载会清理全部注入样式。样式不持久化,激活时由插件恢复;主页面重载会停止插件宿主并撤回样式,重新激活后可再次注入。 - -CSS 作用于整个应用文档,支持选择器、变量、伪元素、动画、媒体查询和其他浏览器支持的 CSS;不限定为工作台、背景或某一组属性。宿主不向插件开放父页面 DOM 或脚本执行。已有区域提供稳定的 `[data-lexora-region="window"]`、`app.sidebar`、`workbench.sidebar`、`workbench.workspace`、`workbench.pane`、`task.history` 和 `composer.input` 标识。跨插件按插件 ID、样式表 ID 升序放置,之后遵循标准 CSS 层叠规则。 - -窗口根节点的 `data-lexora-page` 与 `page.id` 一致:任务为 `lexora.tasks`,自动化为 `lexora.automations`,伙伴为 `lexora.buddies`,插件管理为 `lexora.extensions`,设置为 `lexora.settings`。插件自有页面为 `extension:<插件 ID>`;其 iframe 内部样式由插件自身管理。 - -`data-theme-surface` 标记可定制的宿主表面,输入框等嵌套部件的标记位于实际绘制底色的节点。值为 `layout`(页面布局和侧栏)、`header`(区域标题栏)、`panel`(内容卡片和设置分组)、`input`(任务和自动化提示词输入框)。标记本身不修改外观,插件可按类别选择透明化、边框等效果,或用以下语义 class 选取、排除具体部件: - -| 部件 | 稳定 class | +| 开发目标 | 阅读资料 | | --- | --- | -| 任务标题、输入框 | `desktop-chat-workspace-header`、`desktop-chat-composer` | -| 自动化列表、编辑页 | `desktop-automation-workbench`、`desktop-automation-editor`;各自的 `__header`、`__content` | -| 伙伴侧栏、资料页 | `desktop-buddies-sidebar`、`desktop-buddy-profile`;各自的 `__header` | -| 伙伴资料卡片、设置区 | `buddy-profile-card`、`buddy-settings-section` | -| 插件管理页 | `desktop-extension-workbench` 及其 `__header`、`__content` | -| 已安装、不可用、市场插件卡片 | `extension-card`、`extension-unavailable-card`、`extension-catalog-card` | -| 设置侧栏、设置页 | `desktop-settings-sidebar`、`desktop-settings-page`;各自的 `__header` | -| 设置分组 | `desktop-settings-group`;登记的分组另有 `data-settings-group` | - -例如 `[data-lexora-page="lexora.tasks"] [data-theme-surface]:not([data-theme-surface="input"])` 可选择任务页表面并保留输入框底色;`:not(.desktop-settings-group)` 可排除设置分组。表格之外的内部 DOM 层级与原子 class 不作为稳定接口。 - -`context.resources.getUrl({id})` 返回本插件私有文件或已授权本地文件的 URL;`getUrl({packagePath})` 返回已验证的包内资源 URL。视图也支持这两种形式。无需再次选图审批,不接受任意本机路径;URL 只在当前宿主 generation 有效,停用后失效,资源释放后不能再读取。资源 URL 可用于 CSS 图片、字体或包内样式表,不应保存进插件数据;每次激活重新取得。窗口的 CSP 继续限制网络和脚本。 - -```ts -const image = await context.resources.getUrl(file) -await context.window.styles.set('appearance', ` - [data-lexora-region="task.history"] { - border: 1px solid var(--buddy-accent); - background: url(${JSON.stringify(image)}) center / cover; - } - @media (width < 800px) { - [data-lexora-region="task.history"] { border-width: 0; } - } -`) -await context.window.styles.remove('appearance') -``` - -窗口样式不会修改主题选择或主题数据;CSS 可以覆盖主题的视觉输出,撤回后按正常层叠恢复。作者也可使用主题入口,或把主题和窗口样式组合使用。需要交互的按钮和面板继续通过视图、挂载与交互区域能力接入。 - -## 页面、宿主与设置 - -事件可通过作用域内的 `context.events.on` 统一订阅,支持命名空间和通配符,旧 `onMessage/onWorkbenchChange` 等入口继续可用;事件名与生命周期见 [作用域事件](events.md)。 - -完整类型见同目录 `api.d.ts`。每个视图是独立沙箱。`render` 可异步,前台加载须在 10 秒内完成;耗时内容先绘制加载状态,再异步填充。加载失败会结束该视图并提供重启入口,其他视图与宿主继续运行。没有 `mount`、全局 `lexora` 或 Vue 运行时。 - -宿主入口示意: - -```ts -import type { ExtensionContext } from './lexora' - -export function activate(context: ExtensionContext) { - context.subscriptions.add(context.commands.register(`${context.extension.id}.read-settings`, async () => context.storage.get())) -} -``` - -该命令须出现在 `contributes.commands` 中,例如 `{ "id":".read-settings", "title":"读取设置", "hidden":true }`。视图通过 `context.commands.execute(command, arguments)` 调用自身命令,返回 JSON。私有数据使用 JSON;更改结构时递增 `dataVersion` 并导出 `migrate(previous, from, to)`。 - -文件工具仍可通过可见命令提供打开入口;支持新关联声明的 API 4 宿主也可使用上述文件关联。命令方式中,用户先在文件面板打开文件,再从命令面板执行它;宿主将收到的资源句柄传入视图: - -```ts -context.subscriptions.add(context.commands.register(`${context.extension.id}.open`, async ({ resource }) => { - if (!resource) - throw new Error('请先打开一个文件') - await context.views.open(`${context.extension.id}.reader`, { resource, state: {} }) -})) -``` - -该视图声明 `resource: "selected-file"`,插件声明 `selectedResource: "read"`;视图从 `context.resource` 取得同一资源,通过 `resources.readText` 读取。句柄不是文件路径。只声明视图不会自动增加文件菜单或命令入口。 - -后台提醒使用 `context.schedules.set({ id, command, enabled, intervalMinutes })`,间隔最少 1 分钟;不要用页面 setInterval 代替跨页面提醒。Lexora 退出后暂停,重启继续,错过的提醒不会补发。 - -## 主题与样式 - -完整主题包、欢迎内容与主题编辑器 API 见 [主题](themes.md)。修改用户主题需要 `themeManagement: true`;仅贡献主题无需权限。 - -API 3 的 `contributes.iconThemes` 可贡献独立的文件与目录图标主题,无需脚本入口或权限,详见 [文件图标主题](file-icon-themes.md)。 - -源码包可在清单声明 `"styles": { "uno": true }`,编译器提取包内 TS/JS 的完整类名并生成 `lexora-uno.css`,宿主在视图渲染前自动加载。该文件名保留给编译器,源码不要自行创建。不需要安装 UnoCSS,也不执行插件的 UnoCSS 配置或构建脚本。预编译包使用同一编译工具生成并携带该 CSS。使用此能力时,`engines.lexora` 的最低版本须覆盖提供该能力的宿主。 - -例如 `container.className = 'flex items-center gap-2 rounded-micro bg-accent-surface text-accent-text p-3'`。常用语义类包括 `bg-surface`、`bg-raised`、`text-fg`、`text-muted`、`border-border`、`bg-accent`、`text-on-accent`、`hover:bg-accent-hover`、`text-danger`、`font-sans` 和 `font-mono`。普通交互背景使用 `hover:bg-hover`、`active:bg-pressed`,选中及选中后悬浮使用 `bg-selected`、`hover:bg-selected-hover`;实色按钮使用 `bg-accent hover:bg-accent-hover active:bg-accent-pressed text-on-accent`,强调文字使用 `text-accent-text`。这些颜色会一起响应主题切换;警告、错误和中性底色保留各自语义。动态状态使用完整类名映射;无法静态提取的完整类名放在 `styles.safelist` 数组,不拼接 `bg-${color}`。 - -颜色来自 `context.environment`,跟随当前主题实时更新,无需重建视图。原有 `--lexora-background`、`--lexora-text`、`--lexora-muted`、`--lexora-border`、`--lexora-accent`、`--lexora-accent-solid` 保持可用;其中 `accent` 是强调文字色,`bg-accent` 使用 `accent-solid`。更多语义变量与工具类同名,如 `--lexora-accent-surface`、`--lexora-focus`。Canvas 等绘制逻辑可通过 `context.onEnvironmentChange` 响应变化。 - -复杂效果仍可创建元素、设置样式,或引入包内 CSS:`link.href = new URL('./style.css', import.meta.url).href`。效果应柔和短暂,不遮挡正文或确认操作。CSS 只作用于插件自己的隔离页面。 - -临时覆盖层、分屏布局订阅与局部点击区域见 [交互会话](interactions.md)。 +| 后台入口、通信与状态 | [生命周期](lifecycle.md) | +| 页面、面板、文件标签、剪贴板 | [视图与挂载](views.md) | +| 命令、菜单、通知、定时 | [命令](commands.md) | +| 文件打开、资源读取与导出 | [文件资源](resources.md) | +| 原件保存、恢复与关闭保护 | [文档编辑](documents.md) | +| 引用到对话、来源定位、工具读取引用 | [引用](references.md) | +| Agent 工具、任务动作和模型调用 | [Agent](agent.md)、[事件](events.md) | +| 原生设置与生效条件 | [设置](settings.md)、[条件](conditions.md) | +| 主题、样式与装饰 | [样式](styles.md)、[主题](themes.md)、[文件图标](file-icon-themes.md) | +| Worker、WASM、插件 Skills、命令解释器 | [计算](workers.md)、[Skills](skills.md)、[Shell](shells.md) | +| 权限与独立开发 | [权限](permissions.md)、[SDK 工作流](sdk-workflow.md) | diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/references.md b/apps/buddy/service/resources/skills/plugin-creator/references/references.md new file mode 100644 index 000000000..a51504082 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/references.md @@ -0,0 +1,39 @@ +# 引用到对话与来源定位 + +## 引用到对话 + +API 4 的 `context` 和 `page` 视图可声明 `permissions.references: true`,通过 `context.references.add(input)` 选择任务草稿、新建任务或复制引用。装饰、插槽与替换控件不支持此能力。成功加入草稿或复制引用返回 `true`,取消返回 `false`;授权或资源失效会抛错。等待用户选择没有固定的十秒期限;关闭视图或停用插件会取消请求。此操作不会保存源文档、发送消息或调用模型。 + +自有内容菜单使用 `references.capture(input)` 冻结材料和当时可接收的任务草稿,返回 `{ id, targets: [{ id, label }], defaultId, clipboard }`;视图不可用时返回 `null`。唯一目标的 `defaultId` 可作为主动作,多个目标由插件在子菜单呈现,同时提供新任务及允许时的复制入口。点击后调用 `references.commit(id, { kind: 'view', id: targetId })`、`{ kind: 'new-task' }` 或 `{ kind: 'clipboard' }`。提交会等待草稿持久化,不设固定十秒期限;成功返回 `true`,失效返回 `false`,不会悄悄选择后来激活的任务。菜单关闭时调用 `references.release(id)`。每个视图只保留最近一次捕获,隐藏、关闭、替换会话或已消费后失效;插件不能持久化或跨视图复用捕获 ID。捕获不保存源文档,也不修改草稿。该交互需要 Lexora 0.17.0 或更新版本。 + +```ts +await context.references.add({ + kind: 'content', + title: '汇总表', + text: selectedText, + locator: { version: 1, data: { sheet: 'Summary', cells: 'A1:B2' } }, +}) + +context.references.onReveal(async ({ locator, text }, signal) => { + if (!locator || locator.version !== 1) + return false + return revealCells(locator.data, { expectedText: text, signal }) +}) +``` + +`content` 保存当时的文字快照;`text` 必须非空,文字最多 262144 个 UTF-16 单位,locator 单独按下述 JSON 预算校验,`title` 最多 512 字符。来源身份由宿主生成,插件不能自报路径或来源 ID。默认绑定视图当前选中的资源,并在引用和定位时复核授权。若内容已导入插件自己的文档库,可显式传 `source: 'view'`,只绑定插件视图与 locator,不再依赖最初打开视图的文件;仅 `resource: 'none'` 或 `'optional-file'` 视图支持,`'selected-file'` 会拒绝。`locator` 可省略,是插件定义的只读 JSON,含正整数 `version` 和 `data`;数据最多 16 KiB、12 层,不允许函数、循环或特殊对象。宿主只负责传回,插件负责版本兼容和验证定位结果,不能把 locator 当作文件授权。短片段直接进入模型上下文,长片段提供预览并由宿主引用工具按需读取保存的快照;locator 与内部目标不会进入模型上下文。每条消息最多 16 条引用,快照合计最多 1048576 个 UTF-16 单位;这些存储限制与模型输入预算独立。 + +每个视图用 `references.onReveal(handler)` 注册一个定位处理器,返回可 `dispose()` 的注册对象。处理器收到只读的 `referenceId`、原始 `text`、`locator`(未提供时为 `null`)和取消信号;确认内容已定位后返回 `true`。无法找到、版本不支持、未注册或抛错均视为定位失败。异步处理必须遵守 `signal`,避免用户取消后继续滚动或改动界面。 + +定位优先复用原视图,不重载有效会话;绑定资源的引用会重新检查资源授权。内联预览保留当前未保存快照;原视图已关闭时,默认重新打开现有资源来源;`source: 'view'` 引用则以空资源重开该视图,由插件从自有内容恢复。插件应验证 locator 与当前内容是否匹配。插件停用或来源不可访问时,已保存的引用文字仍可查看。 + +整份文件使用 `references.add({ kind: 'resource', resource })`,传入 `context.resource` 或本插件已有的授权句柄。Space 来源沿用文件引用,临时快照、产物和其他已授权资源按内容复制为附件;不传路径,不扩大读取权限。读取所需的 `selectedResource` / `localResources` 权限仍需分别声明。使用这些新增 API 4 能力时,`engines.lexora` 的最低版本应覆盖实际支持该能力的宿主。 + + +## 工具中的用户引用 + +声明 `permissions.references: true` 后,工具回调可用 `invocation.references.list()` 获取 `{items:[{referenceId,sourceMessageId,title,excerpt}],truncated}`。列表最多保留最近 100 条本插件消息引用快照,excerpt 最长 512 字符;引用来自当前运行已经提交的用户输入及同一可见分支的历史用户输入,因此对话压缩不影响引用身份。不同任务、隐藏分支、其他插件、助手与工具生成的引用不在范围内。未发送草稿和当前运行尚未接受的新输入不授予资格。任务动作不提供该 API。 + +`invocation.references.resolve(referenceId, {sourceMessageId?,offset?,limit?})` 返回 `{referenceId,sourceMessageId,title,text,textLength,textDigest,offset,nextOffset,locator,scope:{taskId,branchId}}`,locator 为插件原始定位信息或 null。`text` 为 UTF-16 单元分页,不切断代理对;起点落在代理对中间时拒绝读取,默认 offset 为 0,limit 默认为 32768、最大 32768;nextOffset 为 null 表示已到末尾。textLength 是完整快照长度,textDigest 是完整原文 UTF-8 字节的 SHA-256,不是当前页摘要。长引用按需读取;定位验证可使用完整快照摘要与长度,不要把第一页误当全文。sourceMessageId 指向携带这份引用快照的用户消息,referenceId 是宿主生成的引用 ID,不是消息里的引用序号。将两者一起保留并传给 resolve;同一个 referenceId 可出现在多条消息中。旧调用省略 sourceMessageId 时,仅在所有匹配快照完全相同时兼容,否则返回 EXTENSION_REFERENCE_AMBIGUOUS,须从列表选择精确消息身份。已知的历史 ID 即使不在最近 100 条列表中也可解析。引用本身不授予读取任意文件或修改文档的能力。任务结束、取消、输入或分支变更后,旧调用失效。 + +插件应只向模型返回适量文字、referenceId 和 sourceMessageId,将 locator 留在插件内处理。编辑前重新解析引用,核对目标文档、会话和版本,使用现有视图消息与命令回执完成操作;不要按当前焦点选择目标,也不要把复制来的文本当作新的引用授权。新能力通过当前宿主的 runtime 目录确认,并按实际宿主版本声明兼容范围。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/resources.md b/apps/buddy/service/resources/skills/plugin-creator/references/resources.md new file mode 100644 index 000000000..55c09374a --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/resources.md @@ -0,0 +1,89 @@ +# 文件关联、资源与导出 + +## 文件关联与独立入口 + +使用下列新增声明时,`apiVersion` 保持 `4`,`engines.lexora` 必须以首次提供这些能力的应用版本为下限;不能仅凭旧宿主也支持 API 4 就声明兼容。应用版本和 API 版本分别约束能力可用性与协议兼容性。 + +`contributes.fileHandlers` 声明文件处理器,每项具有稳定的 `id`、显示名称 `title` 和精确的小写带点后缀 `extensions`,例如 `[".md", ".markdown"]`。不接受通配符、MIME 推断或没有点的后缀;每项最多 64 个后缀、共 32 项。两种打开方式分别保存用户默认;多个已注册复合后缀匹配时采用最长者。 + +- `preview: { view, requestDefault? }`:在文件页内预览。 +- `open: { view, requestDefault? }`:在独立工作台视图中打开文件。 + +至少声明一种方式;省略的方式不注册,也不借用另一种方式的视图。两者可引用同一个视图或不同视图;视图必须属于本插件、`location` 为 `context`,资源模式为 `selected-file` 或 `optional-file`,并声明 `selectedResource: "read"`。宿主传递文件资源与管理视图生命周期,插件自行决定界面内容。 + +文件关联与引用重开得到的资源可包含 `sourceId`:同一插件内,同一宿主来源的稳定、不透明标识,跨新资源句柄保持一致。它可用于复用文档库记录;不同产物、来源类型或重绑定的目录仍是不同来源。它不代表内容哈希、文件路径或授权,读取仍使用当前 `id`;缺少该字段时不能按文件名猜测同源。 + +每种方式的 `requestDefault` 缺省为 `true`,安装确认页预勾选对应关联;设置为 `false` 时不预选,用户仍可主动勾选。该字段不影响文件权限或撤销已有默认。确认安装应用最终选择,更新的关联在新版本生效时应用。相同后缀和方式有多个处理器时,优先保留本插件中已经作为默认的处理器,否则按声明顺序预选第一个申请者。停用、失败、卸载时保留偏好,只回退内置处理器,不自动选择其他第三方。 + +`contributes.launchers` 提供“+”启动入口(最多16项),引用本插件的 `context` 视图,资源模式为 `none` 或 `optional-file`。启动时 `context.resource` 为 `null`;文件打开则传入文件资源。两个入口独立引用视图,文件打开无需注册或调用 launcher。 + +共用同一视图时,让视图接受可选文件并处理有文件、无文件两种状态: + +```json +{ + "apiVersion": 4, + "permissions": { "selectedResource": "read" }, + "contributes": { + "views": [{ "id": ".reader", "title": "阅读器", "entry": "view.ts", "resource": "optional-file" }], + "fileHandlers": [{ "id": ".files", "title": "阅读器", "extensions": [".md"], "preview": { "view": ".reader" }, "open": { "view": ".reader" } }], + "launchers": [{ "id": ".start", "title": "阅读器", "view": ".reader" }] + } +} +``` + +分别呈现时,新增 `selected-file` 视图,并让 `preview` 引用它;原视图仍用于启动和独立打开: + +```json +{ + "views": [ + { "id": ".preview", "title": "文件预览", "entry": "preview.ts", "resource": "selected-file" }, + { "id": ".reader", "title": "阅读器", "entry": "view.ts", "resource": "optional-file" } + ], + "fileHandlers": [{ "id": ".files", "title": "阅读器", "extensions": [".md"], "preview": { "view": ".preview", "requestDefault": false }, "open": { "view": ".reader" } }], + "launchers": [{ "id": ".start", "title": "阅读器", "view": ".reader" }] +} +``` + +只提供文件处理时,省略 `launchers` 即可;文件视图可使用 `selected-file`。只做预览则再省略处理器的 `open`。 + +旧 `fileOpeners` 声明仍受支持:其 `locations` 中的 `inline`、`standalone` 分别对应新结构的 `preview`、`open`,使用原 `view` 和 `requestDefault`。更新为新声明时保留处理器 ID,即可延续用户默认关联;同一 ID 不能同时声明在新旧数组中。 + +关联不增加目录、路径或写入权限。句柄来自用户已授权的 Space 文件或任务产物,每次读取重新核对原授权;只能由分配到该资源的视图读取。视图可使用 `resources.readText/readBytes/getUrl(context.resource)`,受控 URL 支持 HEAD、Range,关闭后失效。内联预览可能获得未保存文本的只读内存快照(最多 2 MiB UTF-8),宿主不会写回或持久保存快照,原 Space 授权撤回后即不能继续读取;不可保存此临时句柄供重启后复用。文件原件编辑需声明 fileEditing 并通过 documents.open 建立编辑会话;只读快照的导出使用 resources.saveFile。 + + +## 本地资源与导出 + +资源授权与文件格式无关。宿主不限制业务格式,不解析文档或工程文件;插件负责解析、显示和编辑。`mimeType` 是根据扩展名给出的提示,未知格式为 `application/octet-stream`,不是授权白名单。文件选择的 `filters: [{name,extensions}]` 与目录扫描的 `extensions` 由插件按需求提供;省略筛选即接受所有格式。 + +插件下载和私有文件资源不需要 `localResources` 或额外审批。`network.download(url,{name?,signal?})` 在已批准的网络 origin 范围内流式写盘,返回 `{id,name,mimeType,size}`;不设总大小和总时长配额,可用 AbortSignal 取消。重定向逐跳核对授权,关闭发起视图或停用插件会取消下载并清理未完成文件。资源属于插件私有数据,跨重启保存;普通卸载保留,清除数据时删除。用 `resources.release(file)` 回收不再需要的下载副本。 + +宿主和视图均可 `resources.listFiles/readBytes/readText` 读取已有资源。`resources.importFile(resource,{name?,signal?})` 将已有授权的本地文件、Space 文件或本插件私有文件复制到新的私有资源,默认保留文件名,不弹出选择窗口;不接受任意路径。声明式包内图片可直接作为主题资产的 `{packagePath}` 来源。 + +`resources.createFile({name,data,mimeType?,signal?})` 在宿主和视图中将 `Blob | ArrayBuffer | Uint8Array` 保存为新的私有资源,返回 `{id,name,mimeType,size}`。它不弹出文件窗口,不需要 `localResources` 或 `resourceExport`,不接受路径或覆写原文件。适合文档草稿、编辑结果和版本快照;保存新资源成功后,再更新插件元数据中的 ID,最后 `release` 已不需要的旧资源。失败时保留旧 ID,不能先释放旧版本。并发编辑应由插件以文档版本检查串行更新元数据。 + +创建资源时按块传输并原子发布,半成品不会出现在列表;关闭发起视图、停用或 signal 取消时回收未提交数据。每插件最多同时创建 2 个文件,单文件最多 256 MiB,生成资源及其私有副本合计最多 1 GiB、4096 项;外部导入和下载沿用原有规则。MIME 可包含参数,返回句柄使用规范化后的裸类型,例如 `text/plain;charset=utf-8` 保存为 `text/plain`。大数据优先传 Blob,避免字节数组转 Blob 的整份内存复制。此增量能力需先检查 `typeof context.resources.createFile === 'function'`;旧宿主提供清晰升级提示,不把文档二进制塞进有大小限制的 JSON storage。 + +视图 `openExternal(url)` 使用系统浏览器打开已批准 origin 的 HTTPS 链接,保留 fragment。无需逐次授权,不授予文件或自定义协议访问。 + +`network.get(url)` 适用于最多 1 MiB 的文本响应;媒体和大文件使用 `download` 后通过资源 URL 或分段接口读取。大文件不放进 JSON 或命令返回值。下载等待期间不计入宿主普通命令的响应期限;宿主失去响应、退出或用户取消仍会终止操作。 + +声明 `localResources: true` 后,在可见的交互视图中响应用户操作调用 `resources.pickFiles({filters?,multiple?})` 或 `resources.pickDirectory()`。取消分别返回 `[]` 和 `null`。宿主只接受系统窗口确认的路径,插件不能提交或取得绝对路径。文件句柄为 `{id,name,mimeType,size,relativePath?}`,目录句柄为 `{id,name}`;单次最多选择 100 个文件,文件大小不设产品配额,每插件最多 1000 个文件句柄和 32 个目录。 + +`resources.listFiles` 返回插件私有资源及已授权的本地文件,`listDirectories` 返回已有目录授权。`scanDirectory(directory,{extensions?,recursive?})` 返回授权根内的文件句柄及相对路径,默认递归,跳过符号链接,限制 10000 项与 16 层;超限明确报错,不静默截断。扫描负责刷新相同筛选范围,保留其他筛选范围的句柄;它不是自动监听。文件删除或变化后,刷新撤销旧句柄,变化的文件返回新 ID。独立选择和不同目录的授权分别管理。目录内引用可以根据返回的 `relativePath` 解析,但不能越过授权根。 + +按内容选择读取方式: + +- `resources.readText(file)`:UTF-8 文本,最多 1 MiB;更大文本或其他编码使用分段读取与插件自己的解码器。 +- `resources.readBytes(file,{offset?,length?})`:返回 `{data:Uint8Array,size,eof}`;默认 64 KiB,单次最多 128 KiB,适合二进制解析、增量加载和自定义格式。 +- `resources.getUrl(file)`:当前视图专用 URL,可供图片、音视频等元素或视图内 fetch 读取;支持 HEAD、Range 和 seek。只保存资源 ID,每次视图创建后重新取得 URL。不可作为脚本、样式或新页面执行。 + +授权跨重启保留;文件被移动、替换或修改后旧句柄读取失败,可重新选择或刷新已授权目录。`revokeFile(file)` 撤销单个句柄,保留的目录授权仍允许下次扫描重新发现它;`revokeDirectory(directory)` 撤销该目录及其派生句柄,其他独立授权保留。用户可从插件管理撤销全部本地资源授权,卸载也撤销。撤销、禁用及视图关闭会使相关 URL 和未完成的读取失效,不删除用户原文件。 + +保存另行声明 `resourceExport: true`。`resources.saveFile({name,data})` 接收建议文件名和 `Blob | ArrayBuffer | Uint8Array`,弹出系统另存为窗口;取消返回 false,完整写入后返回 true,失败抛错。目标由用户决定,插件得不到路径。写入先暂存,提交时验证目标未被其他程序修改,再替换;关闭、禁用、取消或未完整提交不会覆盖原文件。读取授权不包含静默写回或目录批量写入。 + +包内静态资源使用 `new URL('./assets/file.ext', import.meta.url).href`,遵循包大小上限。音视频支持流式读取和 seek,具体解码能力取决于运行平台;复杂格式可使用包内自包含 JS 解析器。通用文件通道不等于内置任意格式解码器、原生模块或外部可执行程序支持。 + +验收实际读取、解析、保存结果和取消路径。涉及音视频时验证 metadata、实际进度与 seek;关闭时停止工作、清除 URL 和定时器。交互期间保存进度,销毁后不得再异步回写状态。 + + +编辑原件和恢复草稿见 [文档编辑](documents.md)。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/sdk-workflow.md b/apps/buddy/service/resources/skills/plugin-creator/references/sdk-workflow.md index 6ca6c3f4e..84ee89829 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/sdk-workflow.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/sdk-workflow.md @@ -1,6 +1,6 @@ # 独立 SDK 工作流 -仅用于用户已提供 Lexora Plugin SDK 的外部开发环境。先确认 SDK 目录和目标插件仓库,不下载其他运行时或猜测私有路径。 +适用于已经取得独立 Lexora Plugin SDK 的作者。先确认 SDK 目录、插件仓库及 SDK package.json 指定的工具版本。应用内对话创作使用 [创作流程](authoring.md) 中的内置工具。 源码位于插件仓库 `plugins/<插件名>/`;类型从 SDK 的 `sdk/index.d.ts` 复制为插件内的 `lexora.d.ts`。许可证沿用插件仓库根文件,由 SDK 打包时附加,不在每个源码目录重复放置。 @@ -16,7 +16,7 @@ pnpm run package:source <插件仓库> 打包按插件 ID 增量更新市场目录,保留其他插件和已收录的同版本安装包信息;修改源码后需递增版本。只打包一个插件时,在命令末尾指定插件目录名,例如 `pnpm run package <插件仓库> auto-title`。 -在 Lexora 的「插件 → 安装插件包」选择输出包并确认权限。只有用户要求发布时才同步远端。 +在 Lexora 的「插件 → 安装插件包」选择输出包并确认权限。远端分发按实际发布渠道另行完成。 离线开发可从 SDK 的 `authoring.mjs` 导入 `queryWorkbenchCapabilities` 查询该 SDK 快照支持的目标;它不代表用户已安装宿主的版本。应用内优先查询 `lexora_plugin_capabilities`。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/settings.md b/apps/buddy/service/resources/skills/plugin-creator/references/settings.md new file mode 100644 index 000000000..b404d21d8 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/settings.md @@ -0,0 +1,17 @@ +# 声明式设置 + +## 设置 + +`contributes.settings` 包含三个可选数组: + +- `modules: [{id,title,order?}]`:设置侧栏中的独立模块。 +- `groups: [{id,module,title,order?}]`:模块中的分组。module 引用自己的模块或能力目录列出的内置模块。 +- `items: [{id,key,group,title,description?,type,default,order?,enabledWhen?}]`:分组中的单项。group 引用自己的分组或能力目录开放的内置分组。 + +所有 id 以插件 ID 加 `.` 开头且全包唯一;key 在本插件内唯一,使用以小写字母开头的字母数字名称。order 范围 -1000–1000,默认 0。插件不能覆写宿主设置或插入其他插件的设置。 + +type 支持 boolean、string(最长 8192 字符)、number(可声明 min/max)、select(声明 `options:[{label,value}]`)、model。默认值必须符合类型;model 值为 `{providerId,modelId}` 或 null。模型选择规则见 [模型与任务权限](agent.md#模型与任务权限),指定模型不可用时会失败,不会静默改用其他模型。 + +宿主自动保存设置,插件用 `context.configuration.get()` 读取,使用 `configuration.onChange` 注册可等待的热应用处理器;`context.events.on('configuration:changed', listener)` 仅观察已经保存的配置;订阅与失败回退见 [作用域事件](events.md)。修改设置取消当前插件调用,Agent 贡献变化从下一轮生效;关闭功能后旧工具也被拒绝。配置与 storage 私有业务数据独立,不维护第二份持久设置真源。已发布 key 保持类型和含义稳定,新增 key 使用 default。升级后不符合新选项或范围的旧值会被保留并在设置中标记,用户可以逐项修改或恢复默认值;修复前 Agent 贡献不可用。禁用和普通卸载保留配置;用户选择“卸载并清理”时删除配置、私有数据和视图状态。 + +设置项的 `enabledWhen: {condition,params?}` 引用插件注册的[条件函数](conditions.md)。关闭条件仅禁用输入并保留原值;控制开关自身保持可编辑。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/skills.md b/apps/buddy/service/resources/skills/plugin-creator/references/skills.md index d02fa17de..efb479442 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/skills.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/skills.md @@ -57,6 +57,6 @@ description: 检查和编辑工作簿,在修改单元格、公式或工作表 技能由所属插件统一启用、停用、更新和卸载,不另建独立安装副本。停用后不再提供可用技能;更新或移除来源后,旧选择会失效,需要重新选择。已经进入对话的正文和独立授予的普通文件访问权限不会因此撤回。 -Skill 提供工作方法和资源,不自动注册工具、启动 MCP 服务或授予文件、网络、命令执行权限。需要插件工具时另按 [Agent 与设置](agent-settings.md) 声明;脚本只有通过宿主已有的工具和授权流程才可执行。 +Skill 提供工作方法和资源,不自动注册工具、启动 MCP 服务或授予文件、网络、命令执行权限。需要插件工具时另按 [Agent 工具与动作](agent.md) 声明;脚本只有通过宿主已有的工具和授权流程才可执行。 验收至少覆盖:安装后可搜索和显式选择、相对资源可读取、停用后不可重新选择、更新后旧选择失效,以及卸载不影响用户独立安装的 Skills。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/styles.md b/apps/buddy/service/resources/skills/plugin-creator/references/styles.md new file mode 100644 index 000000000..d3390fec4 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/styles.md @@ -0,0 +1,80 @@ +# 主题样式与窗口装饰 + +## 主题与样式 + +完整主题包、欢迎内容与主题编辑器 API 见 [主题](themes.md)。修改用户主题需要 `themeManagement: true`;仅贡献主题无需权限。 + +API 3 的 `contributes.iconThemes` 可贡献独立的文件与目录图标主题,无需脚本入口或权限,详见 [文件图标主题](file-icon-themes.md)。 + +源码包可在清单声明 `"styles": { "uno": true }`,编译器提取包内 TS/JS 的完整类名并生成 `lexora-uno.css`,宿主在视图渲染前自动加载。该文件名保留给编译器,源码不要自行创建。不需要安装 UnoCSS,也不执行插件的 UnoCSS 配置或构建脚本。预编译包使用同一编译工具生成并携带该 CSS。使用此能力时,`engines.lexora` 的最低版本须覆盖提供该能力的宿主。 + +例如 `container.className = 'flex items-center gap-2 rounded-micro bg-accent-surface text-accent-text p-3'`。常用语义类包括 `bg-surface`、`bg-raised`、`text-fg`、`text-muted`、`border-border`、`bg-accent`、`text-on-accent`、`hover:bg-accent-hover`、`text-danger`、`font-sans` 和 `font-mono`。普通交互背景使用 `hover:bg-hover`、`active:bg-pressed`,选中及选中后悬浮使用 `bg-selected`、`hover:bg-selected-hover`;实色按钮使用 `bg-accent hover:bg-accent-hover active:bg-accent-pressed text-on-accent`,强调文字使用 `text-accent-text`。这些颜色会一起响应主题切换;警告、错误和中性底色保留各自语义。动态状态使用完整类名映射;无法静态提取的完整类名放在 `styles.safelist` 数组,不拼接 `bg-${color}`。 + +颜色来自 `context.environment`,跟随当前主题实时更新,无需重建视图。原有 `--lexora-background`、`--lexora-text`、`--lexora-muted`、`--lexora-border`、`--lexora-accent`、`--lexora-accent-solid` 保持可用;其中 `accent` 是强调文字色,`bg-accent` 使用 `accent-solid`。更多语义变量与工具类同名,如 `--lexora-accent-surface`、`--lexora-focus`。Canvas 等绘制逻辑可通过 `context.onEnvironmentChange` 响应变化。 + +复杂效果仍可创建元素、设置样式,或引入包内 CSS:`link.href = new URL('./style.css', import.meta.url).href`。效果应柔和短暂,不遮挡正文或确认操作。CSS 只作用于插件自己的隔离页面。 + +临时覆盖层、分屏布局订阅与局部点击区域见 [交互会话](interactions.md)。 + +## 窗口效果 + +视图入口形式为 `export function render(context: ViewContext, container: HTMLElement)`,类型使用 `import type { ViewContext } from './lexora'`。在 `render` 内调用 `context.onActivity(listener)`,在自己的 `container` 内绘制实际效果。输入锚点事件为 `{type:"composer-input", caret:{x,y,width,height}|null}`,坐标相对当前锚点。 + +`context.anchor` 和 `onAnchorChange` 提供 `{kind,visible,width,height}`,不包含屏幕坐标。呈现范围与事件来源按下表选择,不能因输入触发就默认选输入框装饰: + +| 呈现范围 | 声明 | 活动与坐标 | +| --- | --- | --- | +| 整个客户端窗口 | `kind: "decoration"`、`anchor: "window"` | 在所有页面显示,包括设置页、顶栏和两侧栏,输入活动坐标相对窗口;不接收指针与焦点 | +| 任务主工作区 | `kind: "decoration"`、`anchor: "workbench.workspace"` | 仅任务页的跨分屏连续区域;不包括顶栏、侧栏和资源面板,切换到其他页面时隐藏,输入活动坐标相对该区域 | +| 整个输入所在分屏 | `kind: "decoration"`、`anchor: "workbench.pane"` | 每个分屏独立实例,只接收自己内部的对话输入活动,caret 相对该分屏;可以覆盖对话区域,不能越过分屏边界 | +| 对话历史区 | `kind: "decoration"`、`anchor: "task.history"` | 每个可见历史视口独立实例,背景不随消息滚动;不提供消息内容,不覆盖输入框 | +| 输入框内部 | `kind: "decoration"`、`anchor: "composer.input"` | 只接收该输入框活动,caret 相对输入框,绘制裁剪在输入框内 | +| 整个 Lexora 窗口 | 视图 `location: "window-overlay"` | API 1、API 2 均只收到 `{type:"composer-input"}`,不含来源分屏与坐标;插件自行选择窗口内的位置 | + +分屏装饰随分屏创建、改变尺寸和关闭;页面隐藏时保留实例并更新可见性。事件绑定输入发生的分屏,不随之后的焦点切换转交其他分屏;插件无需读取任务或分屏 ID。多个分屏及多个插件的实例各自隔离。此锚点提供透明装饰区域,不提供交互面板外壳。 + +事件没有字符、按键、文本长度或任务 ID,最多约每秒 20 次,只来自前台 Lexora 对话输入框,包括输入法输入。宿主过滤程序性修改、其他输入框和减少动态效果模式。不要注册父窗口键盘事件。 + +宿主保持 iframe 透明且不拦截鼠标或焦点。插件使用 DOM、Canvas、Web Animations 或 requestAnimationFrame 绘制。保持 `html/body` 透明,不铺不透明全屏背景;无操作时不持续刷新。禁用、卸载和窗口销毁会关闭视图。通过 `context.signal` 清理事件、计时器与动画;粒子和同时运行的动画必须有上限。 + + +## 窗口样式 + +API 3 的宿主 `context.window.styles.set(id, css)` 注入普通 CSS,需要 `windowEffects`。同一插件的同名样式表原位替换,`remove(id)` 仅撤回自己的样式表;停用、崩溃、更新和卸载会清理全部注入样式。样式不持久化,激活时由插件恢复;主页面重载会停止插件宿主并撤回样式,重新激活后可再次注入。 + +CSS 作用于整个应用文档,支持选择器、变量、伪元素、动画、媒体查询和其他浏览器支持的 CSS;不限定为工作台、背景或某一组属性。宿主不向插件开放父页面 DOM 或脚本执行。已有区域提供稳定的 `[data-lexora-region="window"]`、`app.sidebar`、`workbench.sidebar`、`workbench.workspace`、`workbench.pane`、`task.history` 和 `composer.input` 标识。跨插件按插件 ID、样式表 ID 升序放置,之后遵循标准 CSS 层叠规则。 + +窗口根节点的 `data-lexora-page` 与 `page.id` 一致:任务为 `lexora.tasks`,自动化为 `lexora.automations`,伙伴为 `lexora.buddies`,插件管理为 `lexora.extensions`,设置为 `lexora.settings`。插件自有页面为 `extension:<插件 ID>`;其 iframe 内部样式由插件自身管理。 + +`data-theme-surface` 标记可定制的宿主表面,输入框等嵌套部件的标记位于实际绘制底色的节点。值为 `layout`(页面布局和侧栏)、`header`(区域标题栏)、`panel`(内容卡片和设置分组)、`input`(任务和自动化提示词输入框)。标记本身不修改外观,插件可按类别选择透明化、边框等效果,或用以下语义 class 选取、排除具体部件: + +| 部件 | 稳定 class | +| --- | --- | +| 任务标题、输入框 | `desktop-chat-workspace-header`、`desktop-chat-composer` | +| 自动化列表、编辑页 | `desktop-automation-workbench`、`desktop-automation-editor`;各自的 `__header`、`__content` | +| 伙伴侧栏、资料页 | `desktop-buddies-sidebar`、`desktop-buddy-profile`;各自的 `__header` | +| 伙伴资料卡片、设置区 | `buddy-profile-card`、`buddy-settings-section` | +| 插件管理页 | `desktop-extension-workbench` 及其 `__header`、`__content` | +| 已安装、不可用、市场插件卡片 | `extension-card`、`extension-unavailable-card`、`extension-catalog-card` | +| 设置侧栏、设置页 | `desktop-settings-sidebar`、`desktop-settings-page`;各自的 `__header` | +| 设置分组 | `desktop-settings-group`;登记的分组另有 `data-settings-group` | + +例如 `[data-lexora-page="lexora.tasks"] [data-theme-surface]:not([data-theme-surface="input"])` 可选择任务页表面并保留输入框底色;`:not(.desktop-settings-group)` 可排除设置分组。表格之外的内部 DOM 层级与原子 class 不作为稳定接口。 + +`context.resources.getUrl({id})` 返回本插件私有文件或已授权本地文件的 URL;`getUrl({packagePath})` 返回已验证的包内资源 URL。视图也支持这两种形式。无需再次选图审批,不接受任意本机路径;URL 只在当前宿主 generation 有效,停用后失效,资源释放后不能再读取。资源 URL 可用于 CSS 图片、字体或包内样式表,不应保存进插件数据;每次激活重新取得。窗口的 CSP 继续限制网络和脚本。 + +```ts +const image = await context.resources.getUrl(file) +await context.window.styles.set('appearance', ` + [data-lexora-region="task.history"] { + border: 1px solid var(--buddy-accent); + background: url(${JSON.stringify(image)}) center / cover; + } + @media (width < 800px) { + [data-lexora-region="task.history"] { border-width: 0; } + } +`) +await context.window.styles.remove('appearance') +``` + +窗口样式不会修改主题选择或主题数据;CSS 可以覆盖主题的视觉输出,撤回后按正常层叠恢复。作者也可使用主题入口,或把主题和窗口样式组合使用。需要交互的按钮和面板继续通过视图、挂载与交互区域能力接入。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/themes.md b/apps/buddy/service/resources/skills/plugin-creator/references/themes.md index 043681367..8fb2ce9a8 100644 --- a/apps/buddy/service/resources/skills/plugin-creator/references/themes.md +++ b/apps/buddy/service/resources/skills/plugin-creator/references/themes.md @@ -50,7 +50,7 @@ | `task.history` | 每个对话历史视口,背景固定,不随消息滚动,不包含标题和输入框 | | `composer.input` | 每个输入框内部 | -较小区域的材质叠加在所在的大区域背景之上。宿主不自动修改子表面的底色;作者通过主题颜色或窗口 CSS 决定透明、半透明或实色。若希望材质透过布局、卡片或输入框,需要同时设置对应表面的颜色,稳定选择器见 protocol.md。材质支持图片、底色、渐变覆盖、0–1 透明度、0–24 模糊、百分比位置与 cover/contain。`imageOpacity` 独立控制图片透明度,`scale` 在适配尺寸上缩放 0.1–2 倍。透明度和模糊只作用于背景,背景不接收点击。动画使用相同区域的 `decoration`,交互内容使用普通隔离视图扩展点。旧有锚点与主题文档保持兼容。 +较小区域的材质叠加在所在的大区域背景之上。宿主不自动修改子表面的底色;作者通过主题颜色或窗口 CSS 决定透明、半透明或实色。若希望材质透过布局、卡片或输入框,需要同时设置对应表面的颜色,稳定选择器见 [窗口样式](styles.md#窗口样式)。材质支持图片、底色、渐变覆盖、0–1 透明度、0–24 模糊、百分比位置与 cover/contain。`imageOpacity` 独立控制图片透明度,`scale` 在适配尺寸上缩放 0.1–2 倍。透明度和模糊只作用于背景,背景不接收点击。动画使用相同区域的 `decoration`,交互内容使用普通隔离视图扩展点。旧有锚点与主题文档保持兼容。 声明 `welcome` 后由主题提供欢迎内容,不受内置欢迎语选项影响;文字或图片省略、设为 `null` 时均不显示对应内容。不声明 `welcome` 就沿用用户选择的内置欢迎内容。`task.welcome` 是独立的整体内容插槽,可承载工具、导航或其他界面;已就绪的插槽插件优先,加载失败时回退到主题或内置欢迎内容。 diff --git a/apps/buddy/service/resources/skills/plugin-creator/references/views.md b/apps/buddy/service/resources/skills/plugin-creator/references/views.md new file mode 100644 index 000000000..c3c06fc94 --- /dev/null +++ b/apps/buddy/service/resources/skills/plugin-creator/references/views.md @@ -0,0 +1,64 @@ +# 视图、挂载与页面上下文 + +## 视图类型 + +| location | resource | 行为 | +| --- | --- | --- | +| `page` | `none` | 独立页面,可作为导航入口 | +| `context` | `selected-file`、`none` 或 API 4 的 `optional-file` | 工作区资源页签,由用户打开 | +| `window-overlay` | `none` | 启用后自动挂载的透明窗口效果,每插件最多一个;无法点击或聚焦 | + +页面与小视图由插件绘制,均使用 `render(context, container)`,共用隔离和通信 API。API 2 起将视图内容与挂载位置分开,通过 `contributes.placements` 选择正式扩展点;`page` 和 `window-overlay` 继续可用。插件不能查询或修改宿主 DOM。 + +所有视图的 `container` 默认填满隔离视口,无内边距,根元素与子元素使用 `border-box`,支持 `w-full`、`h-full` 和百分比高度。页面、插槽、控件和挂载面板使用相同盒模型,插件自行提供间距;宿主不为插件内部的 `main` 等语义元素增加样式。页面内容超出视口时可正常滚动;需要内部滚动时由插件明确设置自身滚动区域。装饰的绘制范围受锚点及祖先裁剪约束,装饰视口不滚动。 + +导航入口位于自动化下方,声明 `contributes.navigation: { "title": "插件名称", "view": ".settings" }`,引用一个 `resource: "none"` 的视图(API 1 仍需 `location: "page"`)。不需要设置页的效果插件可以没有导航。 + +导航页面、装饰、内容插槽和替换控件不能使用 `context.setState()`;需要持久设置时声明宿主入口和隐藏命令,以 `context.storage.get/set` 保存。多个视图使用同一设置时明确刷新时机,插件局部开关必须能在不重载视图的情况下关闭并重新启用。资源页签的 `setState` 用于保存该页签的 JSON 状态。 + +交互视图可用 `context.setDocumentInfo({title,dirty})` 更新自己的文档名称与未保存标记。标题去除首尾空白后为 1–256 个字符。资源页签随布局持久化,导航页面只保留当前会话的信息;不会更改文件内容、授予保存权限或代替 `onBeforeClose`。插件应在打开、重命名、编辑、保存和返回管理页时同步自己的真实状态,恢复文档后再次核对标记。 + +系统剪贴板使用独立的 `permissions.clipboardRead` 与 `permissions.clipboardWrite` 授权,默认均为 `false`。交互视图用 `context.clipboard.read()` 读取 `{text?,html?}`,用 `context.clipboard.write({text?,html?})` 原子替换这些格式;至少传一项,空字符串有效,文本与 HTML 的 UTF-8 总量最多 4 MiB。HTML 内容与属性保留,不传递图片、文件路径或其他原生格式。没有支持的格式时读取返回 `{}`;过大内容或失败操作会拒绝 Promise。 + +剪贴板调用要求安装授权及当前前台窗口中可见、聚焦的插件 iframe;读取交付前和写入前均复验视图身份与焦点。此授权不是逐次用户手势检查:已聚焦的插件可主动调用,用户无需每次确认。失焦、隐藏、关闭或插件停用后不可调用;后台入口、Worker、装饰、内容插槽和替换控件不提供此能力。纯视图插件无需后台入口。iframe 的浏览器 `navigator.clipboard` 权限仍被禁止,编辑器应注入此 SDK 适配器;浏览器原生 `paste` 事件的 `clipboardData` 与异步剪贴板 API 是不同路径。 + +这两项增量 API 应先检查 `typeof context.setDocumentInfo === 'function'` 和 `context.clipboard`,缺失时提示升级宿主;清单兼容范围使用实际支持此能力的宿主版本。 + + +## 正式挂载点 + +`placements` 中的 `view` 引用本插件的 `resource: "none"` 视图定义,省略 `location`(默认 `context`)。每种挂载均使用独立的隔离视图实例。 + +| kind | 必填字段 | 行为 | +| --- | --- | --- | +| `decoration` | `anchor` | 通过 `lexora_plugin_capabilities({kind:"decoration"})` 查询背景与装饰区域;每个锚点实例分别挂载,只在该区域内绘制,不接收指针和焦点;需要 `windowEffects` | +| `view` | `target` | 用 `lexora_plugin_capabilities({kind:"mount"})` 查询目标;`presentation` 描述区域内布局,宿主入口调用 `context.placements.show(id, instanceId?)` / `hide(id, instanceId?)` | +| `control` | `target` | 当前支持 `model.reasoning`;由插件自身配置启停,运行规则见 [内容插槽与替换控件](slots.md) | +| `slot` | `target` | 需要将 `apiVersion` 设为 3;由插件自身配置启停,目标、尺寸和默认内容回退见 [内容插槽](slots.md) | + +挂载口只提供容器,不提供面板外壳或交互。声明示意:`{id,view,kind:"view",target:"workbench",presentation:{position:"absolute",width:420,height:180,right:16,bottom:16}}`。目标所属范围由能力目录返回。API 3 的分屏挂载以 `instanceId` 区分实例;从命令 `invocation.instanceId` 传入发起操作的分屏,不要在异步完成后重新选择焦点。省略时使用调用时的活动分屏,标识失效则拒绝打开。每个分屏分别保存状态,分屏移除时结束该实例。挂载口暂时不可用时保留实例,恢复后继续投影。插件自行渲染全部内容、样式与交互;插件之间保留独立实例,宿主不生成切换器、不自动避让或折叠。 + +`presentation` 是隔离视图外框的布局声明:`position` 为 `static`(默认,正常布局)或 `absolute`(相对挂载口);`order` 为 -1000 至 1000(默认 1,与核心内容的 0 比较);`zIndex` 为 0–20 的扩展层内顺序,不覆盖核心弹窗。`width/height/top/right/bottom/left` 使用 0–8192 的像素数或 0%–100% 字符串;`null` 清除该 CSS 值。未指定尺寸时宽度 100%、高度 56px,最大尺寸及可见内容受目标容器边界约束。内容内部自由使用 DOM/CSS、Canvas 等;不访问父页面 DOM。 + +`context.setPresentation({...})` 合并更新上述字段,传 `target` 可转移挂载口。进入分屏区域要求该视图在创建时已绑定分屏,不会把全局实例隐式移入当前焦点分屏。实例、会话与状态保留;插件的定时器、媒体等不会因为换位置而重建。布局值由宿主持久化,业务状态由插件自己保存。`context.mount` / `onMountChange` 提供目标容器宽高、可见性和当前视图的局部矩形,供插件响应尺寸变化或实现拖动等交互,不包含屏幕坐标、其他插件信息或父 DOM。 + +宿主不认识“折叠”“展开”“贴边”或“播放器”。需要这些效果时,插件自己渲染触发入口、控制布局并保存自己的状态。缩小视图外框不销毁内容;普通挂载调用宿主命令中的 `placements.hide` 才关闭实例。插件负责再次打开的入口,可以使用自己的可见 UI、已声明命令或导航。不要依赖宿主替插件提供关闭栏或恢复按钮。 + +旧版 `location: workbench.top/workbench.bottom` 与 `height` 在读取时转换为工作台的 static/-1 或 static/1,旧悬浮声明转换为 absolute。新源码使用 target/presentation,不增加业务专用 location。`control.height` 仍为 32–160,默认 64。 + +挂载点 `id` 是升级和恢复的稳定身份,更新时不要随意改名。同一 `id` 调整挂载位置或更换视图定义后,宿主保留实例及旧状态;需要转换状态时,比较 `stateVersion` 与 `expectedStateVersion` 并调用 `setState` 保存转换结果。移除贡献或禁用插件不会清空已保存的视图状态。 + +控件通过 `context.control.snapshot` 读取 `{revision,value,options,disabled}`,`onChange` 订阅变化。`options` 是宿主允许的值及标签;插件不获取模型或任务身份,也不直接写入业务状态。提交用户选择使用 `control.propose(value, renderedSnapshot.revision)`;宿主拒绝旧版本、不可用值、禁用或隐藏控件的请求,窗口隐藏时也不能提交。请求失败后读取最新快照重新呈现,不重试旧选择。加载失败、失去响应、禁用或缺失时恢复内置控件,保留用户样式选择。关闭再打开弹层不会重试失败控件;用户重新选择样式或重启插件后才恢复加载。 + +组合视图可用导航页管理设置、挂载面板展示持续内容、隐藏命令连接私有存储。设置更新后的刷新时机由插件明确处理,页面之间没有隐式共享 JS 状态。 + + +## 页面上下文与生效条件 + +视图通过 `context.workbench` 读取只读快照:`values` 是公开语义上下文,`pages` 列出已登记页面的稳定 `id/title`。`onWorkbenchChange` 订阅变化。当前 `page.id` 使用 `lexora.tasks`、`lexora.automations`、`lexora.extensions`、`lexora.settings` 或 `extension:<插件ID>`;自动化和设置通过 `page.section` 提供子页标识,如 `plans`、`history`、`general`、`models`。其他页面不保留上一个页面的 section。未来页面通过登记扩展,不以名称、URL 或 DOM 选择器识别。这里不提供任务正文、输入内容、文件路径、路由参数或 Provider/模型身份。 + +`contributes.views`、`placements`、`commands`、`menus` 和 `navigation` 可声明可选的 `when`。例如 `"when": {"page.id": ["lexora.tasks", "lexora.automations"]}`;对象中的不同 key 为 AND,数组内为 OR,标量严格相等,缺失 key 不匹配。空对象或省略表示不限定,不执行表达式代码。最多 32 个 key,每个数组最多 32 项,值限字符串(256 字符)、布尔和有限数字。视图定义与挂载的条件同时满足才显示。只限定某个挂载实例时优先在 placement 声明;view 的条件影响该定义的所有实例,navigation 的条件只限定入口。 + +条件只控制 UI 的可用性,不授予权限、不启动插件,也不阻止插件自身调用命令或后台调度。命令菜单与导航入口随上下文更新。首次不满足条件的视图不激活;已打开视图离开范围时隐藏,挂载面板退出布局占位,实例、状态与会话保留,返回后恢复。控件不匹配时使用内置控件并拒绝旧提交。普通挂载的 `placements.hide` 仍表示关闭实例;slot/control 的运行期启停和视图 active 状态见 [内容插槽](slots.md)。 + +`context.visible/onVisibilityChange` 表达当前视图实际可见性(包含页面范围、区域隐藏和窗口隐藏)。插件自行决定隐藏时暂停动画、查询或媒体,宿主不推断业务行为。订阅随视图销毁自动释放;插件也可提前调用返回对象的 `dispose()`。受范围限制的功能应验收首次不匹配、进入、离开、返回、子页切换和重启后的条件恢复。 diff --git a/apps/buddy/shared/workbench/workbenchContributionCatalog.ts b/apps/buddy/shared/workbench/workbenchContributionCatalog.ts index 5854c2bbc..8d343a351 100644 --- a/apps/buddy/shared/workbench/workbenchContributionCatalog.ts +++ b/apps/buddy/shared/workbench/workbenchContributionCatalog.ts @@ -41,21 +41,21 @@ export const workbenchDecorations = workbenchRegions satisfies Record