前端 SDK
前端 bundle 必须只注册一次,并使用和 manifest.json 完全一致的 plugin ID。
import { definePlugin, registerPlugin } from "@haloforge/plugin-sdk";import { MyPanel } from "./Panel";import "./styles.css";
export default registerPlugin("dev.example.my-plugin", definePlugin({ panel: MyPanel,}));核心 Helper
Section titled “核心 Helper”| Helper | 用途 |
|---|---|
definePlugin() | Level 1/2 面板和 slot |
defineModulePlugin() | Level 0 模块 |
defineAssistantPlugin() | Level 3 assistant |
registerPlugin() | 将 bundle 注册到 HaloForge runtime |
invokePlugin() | 调用本插件 Rust 命令 |
invokeOtherPlugin() | 调用其他插件命令 |
usePluginSettings() | 读取和更新插件设置 |
useHostTheme() | 读取宿主主题和 token |
useHostAI() | 复用 AIChat 模型和 transport |
usePluginNavigation() | 让 Level 0 插件内部页面接入 HaloForge 后退/前进 |
usePluginWindows() | 请求 HaloForge 通过多窗口分发器打开插件 route/resource |
pluginCurrentWindow() / usePluginCurrentWindow() | 更新或重置当前 HaloForge 窗口标题 |
usePluginWindowTitle() | 让系统窗口预览标题跟随当前插件文档 |
AppSelect | 渲染 HaloForge 风格下拉控件 |
log() / createPluginLogger() | 将插件诊断写入 HaloForge 应用日志 |
调用 Rust 命令
Section titled “调用 Rust 命令”import { invokePlugin } from "@haloforge/plugin-sdk";
interface PingResult { ok: boolean; message: string;}
export async function ping() { return invokePlugin<PingResult>("ping", { source: "panel" });}需要在 HaloForge 诊断日志中保留下来的前端事件,使用 createPluginLogger(),不要只依赖 DevTools 里的 console.log。
import { createPluginLogger } from "@haloforge/plugin-sdk";
const logger = createPluginLogger("image-generation");
await logger.info("Generation started", { model: "gpt-image-2.0", size: "1024x1024", count: 1,});
await logger.error("Generation failed", { status: 502, elapsedMs: 1842, error: "upstream gateway timeout",});HaloForge 会写入 ~/.haloforge/logs/haloforge.log.YYYY-MM-DD。details 保持 JSON 可序列化,不要写入 API key、bearer token、完整 prompt 或原始图片/base64。
插件路由与窗口
Section titled “插件路由与窗口”当前插件窗口内部页面变化使用 usePluginNavigation()。插件希望 HaloForge 把自己的 route 或 resource 打开到最合适的宿主窗口时,使用 usePluginWindows()。
import { usePluginNavigation, usePluginWindows } from "@haloforge/plugin-sdk";
export function DocumentsPanel() { const navigation = usePluginNavigation(); const windows = usePluginWindows();
function openDetail(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", }); }}usePluginWindows() 会和插件 manifest 的 window 策略一起交给宿主分发。插件声明意图;窗口创建、聚焦、复用、恢复和安全检查仍由 HaloForge 控制。
原生窗口标题
Section titled “原生窗口标题”插件可以更新操作系统窗口预览里显示的原生标题,例如 Windows 任务栏缩略图标题。面向文档的插件可以用它显示当前文件名。
import { usePluginWindowTitle } from "@haloforge/plugin-sdk";
export function MarkdownPanel({ activeModule, document }) { usePluginWindowTitle( document?.name?.trim() || null, { subtitle: "Markdown", enabled: activeModule === "markdown", }, );
return <main>{document?.name ?? "No document"}</main>;}HaloForge 会把插件标题格式化成 Title - Subtitle - HaloForge,并在拥有该标题的面板卸载或禁用 hook 时重置标题。隐藏或后台插件面板不能覆盖其他插件当前激活窗口的标题。
AppSelect
Section titled “AppSelect”combo box 和下拉框使用 AppSelect,不要直接写原生 <select>。
import { AppSelect } from "@haloforge/plugin-sdk";
export function ModePicker({ value, onChange }) { return ( <AppSelect value={value} onChange={(event) => onChange(event.target.value)}> <option value="overview">Overview</option> <option value="diagnostics">Diagnostics</option> </AppSelect> );}不要使用私有 bridge
Section titled “不要使用私有 bridge”避免:
window.__HF_HOSTwindow.__HALOFORGE_PLUGIN_HOST__invoke("plugin_invoke", ...)如果 SDK 没有你需要的宿主能力,先在 haloforge-plugin-api 中新增公开 helper。