Skip to content

Latest commit

 

History

History
356 lines (277 loc) · 12.9 KB

File metadata and controls

356 lines (277 loc) · 12.9 KB

HaloForge 插件开发指南

这是 HaloForge 插件的标准开发规范,覆盖插件形态、Rust 后端契约、React 面板契约、Host SDK 使用、本地测试、发布流程,以及官方插件必须遵守的 UX 规则。

在线文档入口使用 https://docs.haloforge.dev/plugins

设计目标

HaloForge 插件应该像 HaloForge 的一部分,而不是嵌进来的独立网页。插件可以在主应用之外开发,但必须使用公开 SDK、宿主主题 token、宿主控件、局部 CSS 和明确权限。

插件开发者应把宿主当作黑盒:

  • 不要读取 window.__HF_HOST 或其他私有全局对象。
  • SDK 已提供 helper 时,不要直接调用私有 Tauri 命令。
  • 不要手写 plugin_invoke wire command;使用 invokePlugin()invokeOtherPlugin()
  • 不要假设企业版服务一定存在。只要功能能在社区版通过本地或自定义服务工作,就必须提供自定义端点路径。

能力级别

Level 使用场景 典型注册方式
0 侧边栏顶级模块 defineModulePlugin()definePlugin({ panel })
1 现有模块里的功能页 definePlugin({ panel }) 加 level 1 manifest 配置
2 UI slot 注入 definePlugin({ slots })
3 Assistant 注册 defineAssistantPlugin()
4 后台服务或 workflow 后端 Rust 命令和 manifest level 4 配置

优先选择能满足产品面的最小级别。Image Studio 这类完整工作区应该用 Level 0;工具栏按钮应该用 Level 2。

推荐目录结构

官方 UI 插件建议从 templates/level0-rust-react 开始。

my-plugin/
  manifest.json
  backend/
    Cargo.toml
    src/lib.rs
  app/
    package.json
    src/index.tsx
    src/Panel.tsx
    src/styles.css
  assets/
  dist/

这样可以把原生代码、前端代码、资源和发布产物分开,packager 可以构建两端并输出签名 .hfpkg

Manifest 契约

每个插件根目录都需要 manifest.json

{
  "id": "dev.example.my-plugin",
  "name": "My Plugin",
  "version": "0.1.0",
  "description": "Short user-facing summary.",
  "author": "Example",
  "compatibility": {
    "min_app_version": "0.8.0",
    "min_host_api_version": "0.2.18"
  },
  "capability_levels": [0],
  "host_capabilities": ["navigation", "file_intents", "theme_read"],
  "integration": {
    "level0": {
      "module_id": "my-plugin",
      "module_label": "My Plugin",
      "module_icon": "Sparkles",
      "sidebar_position": "main",
      "sidebar_order": 120,
      "panel_entry": "app/dist/index.js"
    }
  },
  "window": {
    "default_open_mode": "reuse_or_new",
    "reuse_key": "resource",
    "allow_multiple": true,
    "document_handlers": [
      {
        "id": "markdown",
        "label": "Markdown",
        "extensions": [".md", ".markdown"],
        "mime_types": ["text/markdown"],
        "route": "/document",
        "resource_param": "path"
      }
    ]
  },
  "entry": {
    "native": {
      "macos_arm64": "backend/target/release/libmy_plugin.dylib",
      "macos_x64": "backend/target/release/libmy_plugin.dylib",
      "windows_x64": "backend/target/release/my_plugin.dll",
      "linux_x64": "backend/target/release/libmy_plugin.so"
    },
    "frontend": "app/dist/index.js",
    "frontend_styles": "app/dist/styles.css"
  },
  "permissions": [
    { "type": "ipc_register" },
    { "type": "host_theme_read" }
  ],
  "commands": [
    { "id": "ping", "description": "Return plugin health information." }
  ]
}

规则:

  • id 发布后必须保持稳定,不要改名。
  • version 使用 semver,官方插件初版从 0.1.0 开始。
  • host_capabilities 必须匹配前端实际使用的 SDK helper。
  • 文件监听必须使用 watchHostFileuseHostFileWatch,并声明 file_watchhost_file_watch;不要硬编码宿主命令或事件。
  • permissions 必须保持最小权限。
  • entry.frontendintegration.*.panel_entry 应指向构建后的 bundle。
  • commands 里的原生命令 ID 要和 Rust 后端注册的名字一致,不能包含 SDK 自动添加的前缀。

窗口策略

插件如果希望自己的路由或资源进入 HaloForge 多窗口分发,可以在 manifest 里声明 window 配置。

  • default_open_modesmartcurrentnew_windowreuse_existingreuse_or_new
  • reuse_keypluginrouteresourcenone
  • allow_multiple:是否允许同一插件同时存在多个窗口。
  • document_handlers:把扩展名或 MIME 类型映射到插件 route 的文件/资源 handler。

真正的窗口创建、聚焦、session restore 和安全检查仍由宿主执行。插件只声明意图,HaloForge 决定最终落到哪个窗口。

document_handlers 是当前支持的插件侧「打开方式」能力。宿主菜单动作仍然可以按来源选择更严格的目标窗口:例如 File > Open Markdown 是当前窗口导航动作,而操作系统文件激活和 deep link 可以使用插件的多窗口策略。

应用菜单栏归宿主管理。当前 Host API 不暴露任意插件菜单注入;如果插件需要提供 File/Edit/View 菜单项,应先新增文档化的 manifest/SDK contribution,而不是依赖私有菜单实现。

Rust 后端

插件需要以下能力时应使用 Rust 后端:

  • 原生文件系统访问
  • 绕开浏览器 CORS 的外部 HTTP
  • 密钥处理
  • 本地进程集成
  • 长任务或宿主管理的任务
  • 需要 manifest 权限的高权限操作

纯前端插件适合简单 UI、slot 渲染,或只消费公开 host SDK hook 的面板。官方插件如果要直接调用外部 API,通常应该通过 Rust 命令封装。

最小后端示例:

use haloforge_plugin_api::*;

pub struct MyPlugin;

impl MyPlugin {
    pub fn new() -> Self { Self }
}

impl HaloForgePlugin for MyPlugin {
    fn metadata(&self) -> PluginMetadata {
        PluginMetadata {
            id: "dev.example.my-plugin".into(),
            name: "My Plugin".into(),
            version: "0.1.0".into(),
            description: "A sample HaloForge plugin".into(),
            author: "Example".into(),
            abi_version: PLUGIN_ABI_VERSION,
        }
    }

    fn on_load(
        &mut self,
        _ctx: &dyn PluginContext,
        ipc: &mut dyn IpcRegistrar,
    ) -> Result<(), PluginError> {
        ipc.register("ping", Box::new(|args, _ctx| {
            Ok(serde_json::json!({
                "ok": true,
                "echo": args
            }))
        }))?;
        Ok(())
    }
}

declare_plugin!(MyPlugin, MyPlugin::new);

前端契约

前端 bundle 只注册一次:

import { definePlugin, registerPlugin } from "@haloforge/plugin-sdk";
import { MyPanel } from "./Panel";

export default registerPlugin("dev.example.my-plugin", definePlugin({
  panel: MyPanel,
}));

宿主集成必须使用 SDK:

  • invokePlugin() 调用本插件 Rust 命令。
  • invokeOtherPlugin() 调用已声明依赖的其他插件。
  • useHostTheme()useAppTheme() 读取主题 token。
  • usePluginSettings() 读取宿主管理的插件设置。
  • useHostAI() 复用 AIChat transport。
  • enterpriseGateway() 调用宿主管理的生图网关。函数名为历史兼容命名,产品 UI 应显示为 “HaloForge Cloud gateway” 或 “managed image gateway”。
  • Level 0 插件面板如果有内部页面,使用 usePluginNavigation()。页面级跳转调用 pushRoute(),筛选/模式等状态修正用 replaceRoute(),并从 currentonRouteChange() 回写本地状态,这样 HaloForge 的后退/前进才能恢复插件内部页面。
  • 插件希望 HaloForge 按 manifest 的 window 策略把自己的 route 或 resource 打开到合适窗口时,使用 usePluginWindows()
  • AppSelect 用于 combo box 和下拉框。
  • pickHostFile()pickHostDirectory()saveHostFile() 用于宿主文件选择器。

SDK 已有控件时,不要使用原生 HTML select 自己做。

usePluginNavigation()usePluginWindows() 的边界不同:

import { usePluginNavigation, usePluginWindows } from "@haloforge/plugin-sdk";

function Panel() {
  const navigation = usePluginNavigation();
  const windows = usePluginWindows();

  function openLocalDetail(id: string) {
    navigation.pushRoute(`/detail/${id}`, { params: { id } });
  }

  async function openDocument(path: string) {
    await windows.openResource(path, {
      route: "/document",
      params: { path },
      reuseKey: "resource",
      openMode: "reuse_or_new",
    });
  }
}

当前窗口内的历史变化用 navigation。需要把 route/resource 交给宿主多窗口分发器时用 windows。插件不要调用私有 Tauri command 自己创建窗口。

当插件页面代表某个具体文件、任务或记录时,可以更新当前原生窗口标题:

import { usePluginWindowTitle } from "@haloforge/plugin-sdk";

usePluginWindowTitle(fileName, { subtitle: "Markdown" });

HaloForge 只接受当前激活插件模块或 route 所属插件发出的标题更新,后台隐藏面板不能覆盖其他插件的标题。

UX 与样式规则

官方插件必须适配 HaloForge shell:

  • CSS 作用域必须放在插件根类下,例如 .hfmy-root
  • 不要在插件里全局设置 bodyhtml 或不带根作用域的元素选择器。
  • 优先使用 HaloForge CSS 变量:--color-background--color-surface--color-foreground--color-foreground-secondary--color-border--color-primary,以及 --hf-shell-bg 等 shell 变量。
  • 必须支持 light 和 dark 主题,不要写死单一色盘。
  • 下拉框使用 AppSelect,文件选择使用 host file picker,图标按钮使用 lucide 图标。
  • 卡片圆角保持在 8px 或更小,除非宿主已有特殊规范。
  • 不要把页面 section 套进多层 card。
  • 不要在界面上写解释基础操作方式的说明文。
  • 窄屏下标签、按钮、chip、卡片文本不能溢出。

多语言

插件所有用户可见文本都应提供英文和中文。

推荐模式:

  • 建立 typed translation key map。
  • 可用时读取 localStorage["hf:locale"]
  • 回退到 navigator.language
  • 缺失 key 回退英文。
  • Provider 名称和 API 术语保持稳定,但按钮、状态、错误、空状态等都要本地化。

社区版里使用中性产品语言。例如显示 “HaloForge Cloud gateway” 或 “Managed gateway”,不要显示 “Enterprise gateway”,除非界面本身明确只属于企业版。

托管生图网关与社区版 fallback

生图插件应支持两条路径:

  1. 托管网关:调用 @haloforge/plugin-sdkenterpriseGateway(),声明 host_capabilities: ["enterprise_gateway"],并申请 { "type": "host_enterprise_gateway_access" }
  2. 自定义端点:允许社区版用户配置 OpenAI-compatible baseUrl 和可选 API key。涉及 CORS 或密钥处理时,HTTP 应经过 Rust 后端。

托管网关背后可能是 HaloForge Cloud,也可能是 Enterprise Server,取决于宿主登录状态。插件 UI 不应该区分二者,除非这个区别会影响用户操作。

本地开发流程

每次交付前使用同一套检查:

npm install
npm run typecheck
npm run build
cargo fmt --check
cargo check
npx @haloforge/plugin-pack check .
npx @haloforge/plugin-pack pack . --out dist/package

安装到本地 HaloForge:

cd /path/to/HaloForge
npm run hf -- plugin install local /path/to/my-plugin/dist/package/dev.example.my-plugin-0.1.0.hfpkg --json

然后启动 HaloForge 并确认:

  • 插件面板能挂载
  • bundle 注册的 plugin ID 和 manifest.json 一致
  • light/dark 主题都像宿主原生界面
  • SDK 控件正常渲染
  • Rust 命令返回预期数据
  • 安装权限提示符合预期
  • 日志没有面板注册、IPC 或权限错误

打包与发布

官方插件:

npx @haloforge/plugin-pack check .
npx @haloforge/plugin-pack pack . --release --out dist/package
npx @haloforge/plugin-pack metadata dist/package/dev.example.my-plugin-0.1.0.hfpkg \
  --signing-key-id haloforge-official-2026-05 \
  --signing-key-env HF_PLUGIN_SIGNING_PRIVATE_KEY \
  --pretty \
  --output dist/catalog-draft.json

官方仓库应通过 GitHub Actions 发布,并配置 repository secrets:

  • hf_plugin_signing_private_key
  • hf_plugin_signing_key_id
  • 需要提交 catalog metadata 的 workflow 再配置 HF_ADMIN_TOKEN

如果插件依赖新的 SDK 或权限能力,应先发布 SDK 和 packager,再发布插件。

Review Checklist

  • Manifest ID、version、entry、host capability、permission、command 都和实现一致。
  • 前端只注册一次,并使用 manifest 里的 plugin ID。
  • 没有直接使用私有 host bridge。
  • 没有手写 plugin_invoke wire name。
  • UI 使用 host token 和 SDK 控件。
  • 英文和中文覆盖全部可见文案。
  • 社区版路径不依赖 Enterprise Server。
  • 涉及外部 HTTP 和密钥时由 Rust 后端处理。
  • hf-pack check、前端构建、Rust check、本地安装均通过。