Desktop 插件 SDK
桌面插件让你在 Hermes Agent 桌面应用中注册一等 UI、webview、持久化、快捷方式、工作区面板与运行时能力。该 SDK 面向在桌面中提供面向用户功能的外部或内置包。
包结构与加载
一个桌面插件包是一个导出 default 注册器的 ES 模块:
import { defineDesktopPlugin } from "@hermes-agent/desktop-sdk";
export default defineDesktopPlugin((ctx) => {
ctx.registerRoute({
path: "hello",
nav: { label: "Hello", icon: "sparkles" },
screen: async () => () => import("./screens/hello.js"),
});
});
最小有效清单需要:
{
"name": "@hermes-agent/desktop-plugin-hello",
"version": "0.1.0",
"desktopPlugin": {
"id": "hello",
"entry": "./index.js",
"displayName": "Hello Plugin"
}
}
插件运行时加载 desktopPlugin.entry,用来自插件包或运行时配置的描述调用注册器。
发现与启用
插件经配置清单发现,例如:
{
"plugins": {
"desktop": [
{
"id": "hello",
"package": "@hermes-agent/desktop-plugin-hello",
"version": "0.1.0",
"enabled": true
}
]
}
}
开发模式可以指向本地入口路径。用户插件也可经桌面设置 UI 安装与切换。
UI 注册器
ctx.registerRoute 向应用导航注册一个屏幕。它接收:
path:一个稳定路由片段,例如hello。nav:标签、图标与可选的排序分组。screen:异步组件加载器,通常为动态import()。details:可选的元数据,如描述、作者、徽章。
路由在应用主侧边栏中渲染。多个插件可贡献路由;它们按导航排序组合。
生命周期
defineDesktopPlugin 接收一个 DesktopPluginContext:
interface DesktopPluginContext {
readonly plugin: DesktopPluginManifest;
readonly runtime: DesktopRuntime;
registerRoute(route: DesktopRouteRegistration): void;
registerSidebarSection(section: DesktopSidebarSection): void;
registerStatusBadge(badge: DesktopStatusBadge): void;
registerCommand(command: DesktopCommandRegistration): void;
registerTheme?(theme: DesktopThemeRegistration): void;
registerStorage(namespace: string): DesktopPluginStorage;
registerConnection<T>(connection: DesktopConnectionDefinition<T>): DesktopConnectionHandle<T>;
registerPanel(panel: DesktopPanelRegistration): void;
registerDevtool?(tool: DesktopDevtoolRegistration): void;
}
当应用就绪时,插件运行其注册器;导航、状态、存储与连接按需要可用。插件在应用生命周期内注册一次。
导航与屏幕
注册路由
ctx.registerRoute({
path: "notes",
nav: {
label: "Notes",
icon: "notebook",
order: 30,
},
screen: async () => () => import("./screens/notes.js"),
details: {
description: "Per-profile notes workspace",
author: "Hermes",
},
});
屏幕是一个返回 React 组件的模块:
export default function NotesScreen() {
return <main>Notes workspace</main>;
}
插件用经运行时或框架 provider 暴露的现有桌面设计系统组件。不要在插件中硬编码应用 shell 间距;优先使用共享布局组件。
侧边栏分组
用 registerSidebarSection 把多个路由归入一个有标签的组:
ctx.registerSidebarSection({
id: "productivity",
label: "Productivity",
order: 40,
});
在路由的 nav.sectionId 中引用该 section id。
状态徽章
插件可在应用外壳中贡献一个小型状态指示器:
ctx.registerStatusBadge({
id: "sync",
label: "Sync",
icon: "cloud",
getState: () => ({ tone: "ok", text: "Up to date" }),
});
徽章应保持无内容——状态文本应简短且面向运维。
持久化与配置
registerStorage(namespace) 返回一个作用域化存储句柄:
const storage = ctx.registerStorage("notes");
await storage.set("lastView", { view: "grid" });
const lastView = await storage.get<{ view: string }>("lastView");
存储命名空间与插件 id 绑定,因此不同插件不会读取彼此的配置。该存储是异步持久化的;UI 状态可保留在组件局部内存中。
作用域级别
桌面存储后端按 profile 或安装存储键。插件通常应该经其命名空间使用默认作用域,而不是直接操作 localStorage。
对于用户配置,提供一个插件自己的设置屏幕并从 ctx.registerRoute({ path: "settings" }) 挂载它。
连接与 IPC
插件注册浏览器运行时与后端服务之间的类型化连接。连接用于受权限约束的桥,而不是把机密暴露给渲染器。
type NotesContract = {
list(): Promise<Array<{ id: string; title: string }>>;
create(input: { title: string }): Promise<{ id: string }>;
};
const notes = ctx.registerConnection<NotesContract>({
id: "notes",
methods: {
list: { description: "List notes", input: "readonly" },
create: { description: "Create note", input: "write" },
},
});
UI 组件可经注入或运行时 helper 调用这些方法:
const notes = usePluginConnection<NotesContract>("notes");
const items = await notes.list();
连接实现住在桌面后端的受管插件宿主中。浏览器界面绝不直接访问 Node.js、文件系统、子进程或环境变量。
安全模型
- 渲染器只看到类型化方法及其声明的访问级别。
- 后端 handler 应用权限、路径规范化与输入校验。
- 不透明 id 应在边界处验证;用户路径绝不直接流入 shell 命令。
- 连接失败应在 UI 中优雅降级,绝不导致未捕获渲染错误。
运行时能力
ctx.runtime 暴露有限的环境:
interface DesktopRuntime {
readonly platform: "darwin" | "win32" | "linux";
readonly version: string;
readonly hermesHome: string;
readonly profileName: string;
readonly notify(message: DesktopNotification): Promise<void>;
}
插件可用它适配平台行为,但不应为了绕过隔离直接读 process.env。
通知
await ctx.runtime.notify({
title: "Notes synced",
body: "Your notes are up to date.",
});
通知应简短、非侵入,且应可由用户禁用。
快捷键
经 registerCommand 注册命令:
ctx.registerCommand({
id: "notes.new",
label: "New note",
shortcut: "CmdOrCtrl+Shift+N",
run: () => openNotesEditor(),
});
快捷键应避免覆盖应用级编辑与导航命令。
工作区面板
桌面应用支持可停靠工作区面板。插件注册一个面板:
ctx.registerPanel({
id: "notes-sidebar",
label: "Notes",
location: "right",
icon: "notebook",
panel: async () => () => import("./panels/notes.js"),
});
面板与全屏路由共享 UI 原语,但针对一个窄工作区表面设计。保持交互小且上下文相关。
主题与样式
若提供品牌化 UI,优先使用应用设计系统 token:
ctx.registerTheme?.({
id: "notes",
label: "Notes",
tokens: {
surface: "#1f2430",
accent: "#8ab4ff",
},
});
避免注入全局样式表或修改基础元素选择器。插件应只给自己的面板与路由加样式。
Devtools
开发插件可注册一个开发者工具面板:
ctx.registerDevtool?.({
id: "notes-debug",
label: "Notes Debug",
panel: async () => () => import("./devtools/notes-debug.js"),
});
Devtools 只应出现在开发构建或显式调试模式中。
完整示例:一个小型 Notes 插件
import { defineDesktopPlugin } from "@hermes-agent/desktop-sdk";
export default defineDesktopPlugin((ctx) => {
const storage = ctx.registerStorage("notes");
const notes = ctx.registerConnection<{
list(): Promise<Array<{ id: string; title: string }>>;
create(input: { title: string }): Promise<{ id: string }>;
}>({
id: "notes",
methods: {
list: { description: "List notes", input: "readonly" },
create: { description: "Create note", input: "write" },
},
});
ctx.registerRoute({
path: "notes",
nav: { label: "Notes", icon: "notebook", order: 35 },
screen: async () => () => import("./screens/notes.js"),
});
ctx.registerCommand({
id: "notes.create",
label: "Create note",
shortcut: "CmdOrCtrl+Shift+N",
run: async () => {
const title = "New note";
await notes.create({ title });
},
});
ctx.registerStatusBadge({
id: "notes-count",
label: "Notes",
icon: "notebook",
getState: async () => {
const items = await notes.list();
return { tone: "neutral", text: `${items.length} notes` };
},
});
});
检查清单
- 插件包声明含
id、entry、displayName的desktopPlugin清单 - 默认导出
defineDesktopPlugin - UI 经
registerRoute或registerPanel注册,而非 hack 进应用外壳 - 受保护能力经
registerConnection,带类型化方法与访问级别 - 持久化经
registerStorage(namespace),而非裸localStorage - 命令使用描述性 id(
domain.action) - 快捷键避免应用级编辑/导航冲突
- Devtools 只在调试模式中注册
- 渲染器绝不直接访问 Node.js API、文件系统或 env
另见
- 多 profile gateway——桌面如何把插件与活动 profile 关联
- 构建 Hermes 插件——通用插件生命周期与加载