diff --git a/e2e/command-pairs.playwright.ts b/e2e/command-pairs.playwright.ts new file mode 100644 index 0000000..87d67be --- /dev/null +++ b/e2e/command-pairs.playwright.ts @@ -0,0 +1,10 @@ +// 双入口对拍的浏览器侧说明。CI 门禁在 backend command_pairs_test.go: +// 新内置命令未写入 testdata/command-pairs.json 时 go test 失败。 +// 安装 Playwright 后可用 npx playwright test e2e/command-pairs.spec.ts 做人工抽查。 +import { test, expect } from '@playwright/test' + +test('command menu and chat share builtin.help', async ({ page }) => { + test.skip(!process.env.PLAYWRIGHT_BASE_URL, 'set PLAYWRIGHT_BASE_URL to run') + await page.goto(process.env.PLAYWRIGHT_BASE_URL!) + await expect(page.getByText('帮助')).toBeVisible() +}) diff --git a/src/contracts.ts b/src/contracts.ts index 5ec5349..754287d 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -249,7 +249,11 @@ export interface PluginManifest { tools?: string[]; workflows?: string[]; intents?: string[]; prompts?: string[]; panels?: string[]; cards?: string[]; settings?: string[]; commands?: string[]; workspaces?: string[]; + skills?: string[]; mcpServers?: string[]; knowledge?: string[]; + parsers?: string[]; agents?: string[]; channels?: string[]; pages?: string[]; }; + scope?: { tenants?: string[]; projects?: string[] }; + backend?: { mode?: 'inproc' | 'sidecar'; command?: string; args?: string[]; schemaVersion?: number }; } export interface PluginPanel { @@ -261,6 +265,10 @@ export interface PluginWorkspace { badgeTool?: string; body: UINode[]; refreshTool?: string; } +export interface PluginPageDecl { + id: string; title: string; module: string; +} + // ---- 摄取 / 检索 ---- export interface ParsedDocument { diff --git a/src/pluginHost.ts b/src/pluginHost.ts new file mode 100644 index 0000000..a9131dc --- /dev/null +++ b/src/pluginHost.ts @@ -0,0 +1,14 @@ +import type { PluginPageDecl } from './contracts' + +export interface PluginPageModule { + mount(el: HTMLElement, bridge: unknown): void + unmount?: () => void +} + +// loadPluginPage 按声明加载 ESM。模块 URL 由插件页面声明给出,宿主不内嵌领域页面。 +export async function loadPluginPage(page: PluginPageDecl): Promise { + if (!page.module) throw new Error(`plugin page ${page.id} missing module`) + const mod = await import(/* @vite-ignore */ page.module) + if (typeof mod.mount !== 'function') throw new Error(`plugin page ${page.id} has no mount`) + return mod as PluginPageModule +}