feat(web): standardize plugin terminology and enhance UI components

- Updated terminology from "Community" to "Official" across various components to reflect first-party plugin status.
- Enhanced the ChatComposer, HomeHero, and PluginsHomeSection components to improve user experience and clarity in plugin management.
- Improved CSS styles for better visual consistency and layout across plugin-related interfaces.
- Added tests to ensure proper functionality and visibility of official plugins in the UI.

This update reinforces the distinction between official and user-installed plugins, enhancing the overall user experience in plugin interactions.
This commit is contained in:
pftom 2026-05-13 12:19:29 +08:00
parent 528120de8e
commit c9cc3b88c0
30 changed files with 939 additions and 124 deletions

View file

@ -375,6 +375,36 @@ Each \`<section class="slide" data-screen-label="NN Title">\` is one slide rende
Real copy only no lorem ipsum, no invented metrics, no generic emoji icon rows. If you don't have a value, leave a short honest placeholder.
## Density and overflow discipline (the #1 cause of ugly decks)
Even with the visibility toggle working, slides go ugly when content overflows the 1920×1080 canvas. Specific failure modes that ship today:
- Title slides with a display headline 160px **plus** a multi-line subtitle/deck paragraph **plus** an absolutely-positioned \`.footer\` at \`bottom: ~56px\`. The flow content grows downward, the absolute footer occupies the bottom band, and the two collide in the last ~100px of the slide.
- Stat slides with three numbers + three captions + a footer. Split into three stat slides the framework counts slides for you, more slides cost nothing.
- "Magazine spread" attempts that pack masthead + display headline + body grid + sidebar + absolute footer all into a single 1080px slide.
Rules non-negotiable:
1. **Display headlines on cover/title slides: max ~140px font-size, max 8 words, max 3 lines.** If the headline doesn't fit those bounds, the slide is the wrong shape — split it, don't shrink the font and pack more in.
2. **Reserve a footer safe-zone.** If you use \`.footer { position: absolute; bottom: Npx; }\`, flow content above the footer must stop at least 80px before \`1080 footer_height N\`. Practically: don't let flow content extend into the bottom 200px of the slide. Easiest enforcement: make the slide's main content area its own \`<div style="height: 760px;">\` (or \`max-height\`), and the footer absolute below it.
3. **Body slides: 3 paragraphs, 56ch lead text width, 12 words per line.**
4. **One idea per slide.** Two ideas = two slides.
## Pre-emit self-check run this BEFORE writing the \`<artifact>\` tag
For every \`<section class="slide">\`, mentally render at 1920×1080 and answer:
- [ ] Does the slide's content fit inside the canvas without clipping or overflowing the bottom?
- [ ] If there's an absolutely-positioned footer/header, does flow content stop before the footer's reserved band? (See Rule 2 above.)
- [ ] Is the display headline 140px and 8 words?
- [ ] Does the slide carry one big idea? (No mashed-together masthead + display headline + subtitle + absolute footer + sidebar.)
If any answer is "no", redesign the slide BEFORE emitting. Decks that overflow are the most common single failure mode reported by users; the user has rejected one before and will reject one again.
## Prefer the simple-deck skill's layout vocabulary when reachable
If \`plugins/_official/examples/simple-deck/assets/template.html\` and its \`references/layouts.md\` are readable from the project workspace, **prefer those layouts over inventing your own**. The simple-deck skill ships eight paste-ready slide skeletons (cover, body, big-stat, three-point row, pipeline, dark quote, before/after, closing) with tested type scales, density rules, and a P0/P1/P2 checklist. Re-inventing those layouts is the source of most density / overflow bugs the framework can't catch.
## Canonical skeleton (this is exactly what the file you write looks like)
\`\`\`html

View file

@ -174,8 +174,8 @@ export const ChatComposer = forwardRef<ChatComposerHandle, Props>(
const [mcpTemplates, setMcpTemplates] = useState<McpTemplate[]>([]);
const [skills, setSkills] = useState<SkillSummary[]>([]);
// Installed plugins, fetched lazily for the tools-menu Plugins tab and
// the @-mention picker. Both surfaces share the same list so the
// count badge on the trigger stays consistent.
// the @-mention picker. Both surfaces share the same list so applying
// a plugin from either path lands on the same project context.
const [installedPlugins, setInstalledPlugins] = useState<InstalledPluginRecord[]>([]);
// Detail modal — opened from a context chip click (kind === 'plugin')
// or from the tools-menu "Details" affordance.
@ -1329,9 +1329,9 @@ function ToolsPluginsPanel({
aria-selected={source === 'community'}
className={`composer-tools-segment${source === 'community' ? ' active' : ''}`}
onClick={() => setSource('community')}
title={`${communityPlugins.length} installed community plugins`}
title={`${communityPlugins.length} installed official plugins`}
>
Community
Official
</button>
<button
type="button"
@ -1356,13 +1356,13 @@ function ToolsPluginsPanel({
<div className="composer-tools-empty">
{plugins.length === 0 ? (
<>
No plugins installed yet. Browse Community or add your own with{' '}
No plugins installed yet. Browse Official or add your own with{' '}
<code>od plugin install &lt;source&gt;</code>.
</>
) : query ? (
<>No {source === 'community' ? 'Community' : 'My plugins'} results for {query}.</>
<>No {source === 'community' ? 'Official' : 'My plugins'} results for {query}.</>
) : (
<>No {source === 'community' ? 'Community' : 'My plugins'} plugins available.</>
<>No {source === 'community' ? 'Official' : 'My plugins'} plugins available.</>
)}
</div>
) : (
@ -1653,7 +1653,7 @@ function mcpTemplateMatchesQuery(tpl: McpTemplate, query: string): boolean {
}
function pluginSourceLabel(plugin: InstalledPluginRecord): string {
return plugin.sourceKind === 'bundled' ? 'Community' : 'My plugin';
return plugin.sourceKind === 'bundled' ? 'Official' : 'My plugin';
}
function ToolsImportPanel({
@ -1859,12 +1859,12 @@ function MentionPopover({
}) {
const ref = useRef<HTMLDivElement | null>(null);
const [tab, setTab] = useState<MentionTab>('all');
const tabs: Array<{ id: MentionTab; label: string; count: number }> = [
{ id: 'all', label: 'All', count: plugins.length + skills.length + mcpServers.length + files.length },
{ id: 'plugins', label: 'Plugins', count: plugins.length },
{ id: 'skills', label: 'Skills', count: skills.length },
{ id: 'mcp', label: 'MCP', count: mcpServers.length },
{ id: 'files', label: 'Design files', count: files.length },
const tabs: Array<{ id: MentionTab; label: string }> = [
{ id: 'all', label: 'All' },
{ id: 'plugins', label: 'Plugins' },
{ id: 'skills', label: 'Skills' },
{ id: 'mcp', label: 'MCP' },
{ id: 'files', label: 'Design files' },
];
const showPlugins = tab === 'all' || tab === 'plugins';
const showSkills = tab === 'all' || tab === 'skills';
@ -1891,8 +1891,7 @@ function MentionPopover({
onMouseDown={(e) => e.preventDefault()}
onClick={() => setTab(item.id)}
>
<span>{item.label}</span>
{item.count > 0 ? <span>{item.count}</span> : null}
{item.label}
</button>
))}
</div>

View file

@ -551,7 +551,7 @@ function mcpServerMatchesQuery(server: McpServerConfig, query: string): boolean
}
function getPluginSourceLabel(plugin: InstalledPluginRecord): string {
return plugin.sourceKind === 'bundled' ? 'Community' : 'My plugin';
return plugin.sourceKind === 'bundled' ? 'Official' : 'My plugin';
}
interface RailGroupProps {

View file

@ -55,8 +55,8 @@ export function PluginsHomeSection({
onOpenDetails,
onPluginShareAction,
onCreatePlugin,
title = 'Community',
subtitle = 'Things you can do and tasks to complete — packaged as plugins. Pick one to load a starter prompt, or type freely above.',
title = 'Official',
subtitle = 'First-party Open Design workflows packaged as plugins. Pick one to load a starter prompt, or type freely above.',
emptyMessage = 'Catalog is empty. Bundled plugins ship with Open Design and should appear here automatically — try restarting the daemon if this persists.',
}: Props) {
const {

View file

@ -34,7 +34,7 @@ const PLUGINS_TABS: ReadonlyArray<{
hint: string;
disabled?: boolean;
}> = [
{ id: 'community', label: 'Community', hint: 'Official catalog' },
{ id: 'community', label: 'Official', hint: 'Open Design catalog' },
{ id: 'mine', label: 'My plugins', hint: 'User-installed' },
{ id: 'marketplaces', label: 'Marketplaces', hint: 'Coming soon', disabled: true },
{ id: 'team', label: 'Team / Enterprise', hint: 'Coming soon' },
@ -240,8 +240,8 @@ export function PluginsView({
onUse={(record) => void handleUsePlugin(record)}
onOpenDetails={setDetailsRecord}
onCreatePlugin={onCreatePlugin}
title="Community"
subtitle="Import, create, export, refine, or extend Open Design — packaged as plugins. Pick one to load a starter prompt, or use @ search from Home."
title="Official"
subtitle="First-party Open Design workflows packaged as plugins. Pick one to load a starter prompt, or use @ search from Home."
emptyMessage="No official plugins are registered yet. Restart the daemon if this looks wrong."
/>
) : null}

View file

@ -135,7 +135,8 @@ function githubProfileLabel(url: string): string {
const parsed = new URL(url);
if (/^(?:www\.)?github\.com$/.test(parsed.hostname)) {
const segments = parsed.pathname.split('/').filter(Boolean);
if (segments.length > 0) return `@${segments[0]}`;
if (segments.length >= 2) return `${segments[0]}/${segments[1]!.replace(/\.git$/, '')}`;
if (segments.length === 1) return `@${segments[0]}`;
}
return parsed.hostname + parsed.pathname.replace(/\/$/, '');
} catch {

View file

@ -662,7 +662,8 @@ function githubProfileLabel(url: string): string {
const parsed = new URL(url);
if (/^(?:www\.)?github\.com$/.test(parsed.hostname)) {
const segments = parsed.pathname.split('/').filter(Boolean);
if (segments.length > 0) return `@${segments[0]}`;
if (segments.length >= 2) return `${segments[0]}/${segments[1]!.replace(/\.git$/, '')}`;
if (segments.length === 1) return `@${segments[0]}`;
}
return parsed.hostname + parsed.pathname.replace(/\/$/, '');
} catch {

View file

@ -877,7 +877,11 @@ a.avatar-item:visited {
resize: vertical;
}
.composer textarea:focus { outline: none; box-shadow: none; }
.composer-input-wrap { position: relative; }
.composer-input-wrap {
position: relative;
display: flex;
flex-direction: column;
}
.composer-row {
display: flex;
align-items: center;
@ -1107,60 +1111,60 @@ a.avatar-item:visited {
/* -------- Mention popover ------------------------------------------- */
.mention-popover {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
margin-bottom: 4px;
order: -1;
flex: 0 0 clamp(248px, 38vh, 320px);
min-height: 248px;
max-height: 320px;
margin: 0 0 6px;
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-md);
z-index: 10;
overflow: hidden;
display: flex;
flex-direction: column;
}
.mention-tabs {
flex: 0 0 auto;
display: flex;
gap: 3px;
padding: 4px;
align-items: center;
gap: 4px;
min-height: 42px;
padding: 6px;
background: var(--bg-subtle);
border-bottom: 1px solid var(--border-soft);
overflow-x: auto;
scrollbar-width: none;
}
.mention-tabs::-webkit-scrollbar {
display: none;
}
.mention-tab {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
min-width: max-content;
padding: 5px 7px;
border: none;
min-height: 30px;
padding: 5px 10px;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-muted);
font-size: 10.5px;
font: inherit;
font-size: 11.5px;
line-height: 18px;
cursor: pointer;
}
.mention-tab.active {
background: var(--bg-panel);
color: var(--text);
border-color: var(--border);
box-shadow: var(--shadow-xs);
}
.mention-tab span:last-child:not(:first-child) {
min-width: 14px;
height: 14px;
padding: 0 4px;
border-radius: 7px;
background: var(--accent);
color: #fff;
font-size: 9px;
line-height: 14px;
text-align: center;
}
.mention-results {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
padding: 3px 0;
}

View file

@ -26,7 +26,7 @@ export interface PluginSourceLinks {
/** Friendly label for the source github slug, hostname, or path
* basename. Always present, never null. */
sourceLabel: string;
/** Display label for the source kind "GitHub", "Bundled",
/** Display label for the source kind "GitHub", "Official",
* "Marketplace", etc. */
sourceKindLabel: string;
/** manifest.author.name trimmed, or null. */
@ -49,6 +49,9 @@ export interface PluginSourceLinks {
contributeOnGithub: boolean;
}
const OPEN_DESIGN_REPO_URL = 'https://github.com/nexu-io/open-design';
const OPEN_DESIGN_REPO_LABEL = 'nexu-io/open-design';
const GITHUB_SOURCE_RE = /^github:([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)(?:@([A-Za-z0-9._/-]+))?(?:\/(.+))?$/;
const GITHUB_PROFILE_RE = /^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}[A-Za-z0-9])?)(?:[\/?#].*)?$/;
const GITHUB_REPO_RE = /^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}[A-Za-z0-9])?)\/([A-Za-z0-9._-]+?)(?:\.git)?(?:[\/?#].*)?$/;
@ -99,7 +102,7 @@ function basename(filesystemPath: string): string {
}
const SOURCE_KIND_LABELS: Record<InstalledPluginRecord['sourceKind'], string> = {
bundled: 'Bundled',
bundled: 'Official',
user: 'User',
project: 'Project',
marketplace: 'Marketplace',
@ -119,12 +122,13 @@ export function derivePluginSourceLinks(
const manifest = record.manifest ?? {};
const author = (manifest as { author?: { name?: unknown; url?: unknown } }).author ?? {};
const homepageRaw = (manifest as { homepage?: unknown }).homepage;
const officialBundled = record.sourceKind === 'bundled';
const authorName = typeof author.name === 'string' && author.name.trim().length > 0
? author.name.trim()
: null;
const authorProfileUrl = safeHttpUrl(author.url);
const homepageUrl = safeHttpUrl(homepageRaw);
const authorProfileUrl = officialBundled ? OPEN_DESIGN_REPO_URL : safeHttpUrl(author.url);
const homepageUrl = officialBundled ? OPEN_DESIGN_REPO_URL : safeHttpUrl(homepageRaw);
// Source URL + label resolution. The github:owner/repo case wins
// because we can produce a deep `tree/<ref>/<sub>` URL when the
@ -167,9 +171,12 @@ export function derivePluginSourceLinks(
}
} else if (record.sourceKind === 'marketplace') {
sourceLabel = record.source;
} else if (officialBundled) {
sourceUrl = OPEN_DESIGN_REPO_URL;
sourceLabel = OPEN_DESIGN_REPO_LABEL;
} else {
// bundled / user / project / local — the source string is a
// filesystem path. Show just the basename for compactness; the
// user / project / local — the source string is a filesystem
// path. Show just the basename for compactness; the
// full path stays available via the existing fsPath dt/dd.
sourceLabel = basename(record.source) || record.source;
}

View file

@ -170,7 +170,7 @@ describe('ChatComposer context pickers', () => {
expect(input.value).toBe('Use the @deck-builder skill. ');
});
it('lets the tools panel switch between Community and My plugins', async () => {
it('lets the tools panel switch between Official and My plugins', async () => {
renderComposer();
fireEvent.click(screen.getByLabelText('Open CLI and model settings'));

View file

@ -96,7 +96,7 @@ describe('HomeHero plugin picker', () => {
);
expect(screen.getByTestId('home-hero-plugin-picker')).toBeTruthy();
expect(screen.getByText('Community')).toBeTruthy();
expect(screen.getByText('Official')).toBeTruthy();
expect(screen.getByText('My plugin')).toBeTruthy();
fireEvent.mouseDown(screen.getByRole('option', { name: /sample user plugin/i }));

View file

@ -283,4 +283,18 @@ describe('PluginDetailsModal common metadata coverage', () => {
expect(html).toContain('plugin-meta-sections');
expect(html).not.toContain('plugin-meta-sections__heading');
});
it('routes official plugin author and source links to the Open Design repo', () => {
const html = render(
pluginWithMeta({
id: 'official-link-meta',
title: 'Official Link Meta',
mode: 'prototype',
}),
);
expect(html).toContain('href="https://github.com/nexu-io/open-design"');
expect(html).toContain('nexu-io/open-design');
expect(html).toContain('Official');
});
});

View file

@ -147,7 +147,7 @@ describe('PluginsView', () => {
expect(screen.queryByRole('dialog', { name: 'Create or import a plugin' })).toBeNull();
});
it('groups community and user-installed plugins while keeping marketplaces coming soon', async () => {
it('groups official and user-installed plugins while keeping marketplaces coming soon', async () => {
render(<PluginsView />);
await waitFor(() => expect(screen.getAllByText('Official Plugin').length).toBeGreaterThan(0));

View file

@ -129,16 +129,18 @@ describe('derivePluginSourceLinks · url + local + bundled sources', () => {
expect(out.sourceKindLabel).toBe('Local');
});
it('shows bundled label for bundled sources', () => {
it('routes bundled official sources to the Open Design repo', () => {
const out = derivePluginSourceLinks(
makeRecord({
sourceKind: 'bundled',
source: 'plugins/_official/scenarios/od-code-migration',
}),
);
expect(out.sourceUrl).toBeNull();
expect(out.sourceKindLabel).toBe('Bundled');
expect(out.sourceLabel).toBe('od-code-migration');
expect(out.sourceUrl).toBe('https://github.com/nexu-io/open-design');
expect(out.sourceKindLabel).toBe('Official');
expect(out.sourceLabel).toBe('nexu-io/open-design');
expect(out.authorProfileUrl).toBe('https://github.com/nexu-io/open-design');
expect(out.homepageUrl).toBe('https://github.com/nexu-io/open-design');
});
});

View file

@ -74,6 +74,16 @@ Hard layering rules
This section tracks **what exists in the repo today**. Update in the same PR that lands the module; never let it lie about reality.
### 3.0 Current architecture clarifications (2026-05-13)
These notes capture the product/implementation answers that otherwise get lost between the spec and the code:
- **No plugin selected does not mean a naked agent.** `composeSystemPrompt()` still always layers the Open Design base designer/discovery prompt, project metadata, active design system/craft, and daemon-owned safety/tooling guidance. Plugin context is additive: a selected plugin contributes snapshot-derived `## Active plugin`, `## Plugin inputs`, and active-stage atom blocks. Home free-form runs route through the bundled hidden `od-default` scenario, which shapes task type and then returns to the normal design pipeline.
- **The pipeline is plugin-assembled, not a fixed wizard.** The reference shorthand is `discovery -> plan -> generate -> critique`, but the runnable shape comes from `od.pipeline.stages[].atoms[]` on the applied plugin or bundled scenario fallback. `apps/daemon/src/plugins/pipeline-runner.ts` emits stage/GenUI events and `packages/contracts/src/prompts/atom-block.ts` renders the active stage body. Some atoms are still prompt fragments / permissive workers; observable atoms such as `diff-review`, `build-test`, and `handoff` now emit durable files or signals.
- **GenUI is controlled rendering.** Agents/plugins emit structured surface requests (`form`, `choice`, `confirmation`, `oauth-prompt`) and OD renders them with product-owned React/CLI components. Inline `<question-form>` chat UI follows the same principle: parse structured data, render through `QuestionForm`, and keep styling in OD. Plugin-bundled custom components are a separate sandboxed path behind `genui:custom-component`.
- **AG-UI is interoperability, not the product UI runtime.** `packages/agui-adapter` and `GET /api/runs/:runId/agui` are shipped so CopilotKit / AG-UI clients can consume an OD run. The internal web/desktop UI remains OD-native; adding CopilotKit itself is only justified for an explicit external embed/demo/client.
- **Scenario discovery still has one product gap.** `apps/web/src/components/home-hero/chips.ts` is a curated Home rail for high-frequency scenarios. `apps/web/src/components/plugins-home/facets.ts` is more data-driven and derives category/subcategory facets from plugin metadata. The desired next slice is a single scenario registry / manifest projection that feeds Home chips, plugin filters, composer tools, and `@search`.
### 3.1 Packages
| Path | Status | Notes |
@ -83,8 +93,8 @@ This section tracks **what exists in the repo today**. Update in the same PR tha
| `packages/contracts/src/plugins/apply.ts` | shipped | Phase 0 — `ApplyResult`, `AppliedPluginSnapshot`, `InputFieldSpec` |
| `packages/contracts/src/plugins/marketplace.ts` | shipped | Phase 0 — `MarketplaceManifest`, `TrustTier`, `MarketplaceTrust` |
| `packages/contracts/src/plugins/installed.ts` | shipped | Phase 0 — `InstalledPluginRecord`, `PluginSourceKind` |
| `packages/contracts/src/plugins/events.ts` | shipped | Phase 0 — placeholder variants for `pipeline_stage_*` and `genui_*` |
| `packages/contracts/src/prompts/plugin-block.ts` | absent | Phase 2A (PB1); `renderPluginBlock(snapshot)` pure function shared by daemon + contracts composers |
| `packages/contracts/src/plugins/events.ts` | shipped | Phase 0/2A`pipeline_stage_*` and `genui_*` event variants used by daemon SSE / ND-JSON |
| `packages/contracts/src/prompts/plugin-block.ts` | shipped | Phase 2A (PB1); `renderPluginBlock(snapshot)` pure function shared by daemon + contracts composers |
| `packages/plugin-runtime/` | shipped | Phase 1 — pure TS package: parsers, adapters, merge, resolve, validate, digest |
### 3.2 Daemon modules
@ -138,7 +148,7 @@ This section tracks **what exists in the repo today**. Update in the same PR tha
| `apps/daemon/src/plugins/events.ts` | shipped | Phase 4 — in-memory plugin event ring buffer + SSE feed backing `od plugin events tail` |
| `packages/plugin-runtime/src/pipeline-fallback.ts` | shipped | spec §23.3.3 — resolveAppliedPipeline falls back to a bundled scenario when od.pipeline is absent |
| `plugins/_official/atoms/<atom>/{SKILL.md,open-design.json}` | shipped | Phase 4 / 6 / 7 / 8 — 13 first-party atom plugins (4 implemented + 9 reserved fragments) |
| `plugins/_official/scenarios/<id>/{SKILL.md,open-design.json}` | shipped | Phase 4 (§23.3.3) — 4 default-pipeline scenario plugins (one per taskKind) |
| `plugins/_official/scenarios/<id>/{SKILL.md,open-design.json}` | shipped | Phase 4 (§23.3.3) — bundled scenario/router/export plugins, including the four taskKind defaults plus `od-default` Home free-form routing |
| `packages/agui-adapter/` | shipped | Phase 4 — pure-TS AG-UI canonical event encoder |
| `packages/contracts/src/prompts/atom-block.ts` | shipped | Phase 4 — `renderActiveStageBlock(stageId, bodies)` pure renderer |
| `tools/pack/docker-compose.yml` | shipped | Phase 5 — hosted-mode reference manifest |
@ -246,6 +256,8 @@ This section tracks **what exists in the repo today**. Update in the same PR tha
| `ChatComposer` plugin rail mount | shipped | Phase 2B — `PluginsSection variant='strip'` rendered above the composer input when a `projectId` is bound |
| `apps/web/src/components/MarketplaceView.tsx` | shipped | Phase 2B — catalog grid + trust filters + configured-catalogs panel; routes `/marketplace`. |
| `apps/web/src/components/PluginDetailView.tsx` | shipped | Phase 2B — `/marketplace/:id` (alias `/plugins/:id`); 'Use this plugin' calls applyPlugin → Home. |
| `apps/web/src/components/HomeHero.tsx` + `home-hero/chips.ts` | shipped | Current product entrypoint — curated scenario chip rail; transitional until a unified scenario registry drives Home + filters + composer tools |
| `apps/web/src/components/PluginsHomeSection.tsx` + `plugins-home/facets.ts` | shipped | Data-derived community filters from manifest/taskKind/scenario/tags/pipeline metadata plus curated category taxonomy |
---
@ -292,7 +304,7 @@ Three reads from the graph (drove the §6 phase reorder)
- [ ] **F5. `--json` output uses contracts types; no inline reshape in `cli.ts`.** Phase 1 CLI ships `--json` for `list/info/apply/doctor` returning the daemon JSON verbatim; the next CLI rev imports `ApplyResult` etc. from contracts to satisfy the compile-time guarantee.
- [x] **F6. `OD_MAX_DEVLOOP_ITERATIONS` lives in `apps/daemon/src/app-config.ts`, default 10, override via env.** Read via `readPluginEnvKnobs()`; consumed by Phase 2A `pipeline.ts`.
- [ ] **F7. `od plugin doctor` validates `od.connectors.required[]` against `connectorService.listAll()` from Phase 1.** Phase 1 doctor validates manifest schema, atoms, and resolved skill / DS / craft refs; the connector lookup wires in once `connectorService` is exposed to the doctor module (Phase 1 cleanup PR).
- [ ] **F8. Cross-conversation cache (`genui_surfaces` lookup) goes live with the table — i.e. Phase 2A — and a daemon test asserts the second `oauth-prompt` does not broadcast.** Pulled forward from spec §16 Phase 2A's e2e (e) so the behavior is verified at unit-test layer, not only e2e.
- [x] **F8. Cross-conversation cache (`genui_surfaces` lookup) goes live with the table — i.e. Phase 2A — and a daemon test asserts the second `oauth-prompt` does not broadcast.** Covered by `apps/daemon/tests/plugins-pipeline-runner.test.ts` (`reuses a project-tier surface answer across conversations`).
- [x] **F9. Snapshot lifecycle env vars (PB2)** live in `apps/daemon/src/app-config.ts` from Phase 1: `OD_SNAPSHOT_UNREFERENCED_TTL_DAYS` (default `30`, set to `0` to disable), `OD_SNAPSHOT_RETENTION_DAYS` (default unset, opt-in), `OD_SNAPSHOT_GC_INTERVAL_MS` (default `6 * 60 * 60 * 1000`). All three live in `readPluginEnvKnobs()`; `applied_plugin_snapshots.expires_at` is stamped on insert; the GC worker lands Phase 5.
---
@ -487,7 +499,7 @@ Deliverables
Validation
- [ ] **e2e-9 UI ↔ CLI parity**: pick 5 desktop UI workflows; replay each through `od …` only; produced artifacts byte-for-byte equal.
- [ ] AG-UI smoke: a CopilotKit React client subscribes to `/api/runs/:runId/agui` and renders surfaces unmodified.
- [ ] AG-UI smoke: a CopilotKit React client subscribes to `/api/runs/:runId/agui` and renders surfaces unmodified. This is an external-interop smoke, not a blocker for OD-native web/desktop rendering.
### Phase 5 — Cloud deployment (parallel; can start after Phase 1.5)
@ -571,9 +583,9 @@ Plus repo-wide gates
| Field | Value |
| --- | --- |
| Current phase | Phase 2A + 1 + 1.5 + 2B + 2C entry slice + 3 (full) + 4 (full incl. OD_BUNDLED_ATOM_PROMPTS default ON) + 5 (full incl. live S3 impl; postgres adapter still stubbed) + 6 (full incl. asset rasterisation) + 7 (all six atom impls) + 8 (full incl. GenUI \u2192 decision bridge) + scenarios bundle + bundled-scenario fallback resolver |
| Next planned PR | (a) Phase 2C — `od files write/upload/delete/diff` + `od project import` + `od conversation new`. (b) Phase 3 — Trust UI on `PluginDetailView` + bundle plugin installer. (c) Phase 4 e2e-9 — UI ↔ CLI parity walkthrough (5 workflows). (d) postgres adapter wiring inside the DaemonDb resolver. |
| Next planned PR | (a) Phase 2C — `od files write/upload/delete/diff` + `od project import` + `od conversation new`. (b) Phase 3 — Trust UI on `PluginDetailView` + bundle plugin installer. (c) Phase 4 e2e-9 — UI ↔ CLI parity walkthrough (5 workflows). (d) postgres adapter wiring inside the DaemonDb resolver. (e) Scenario registry convergence so Home chips, plugin filters, composer tools, and `@search` project from the same manifest/scenario taxonomy. |
| Open spec push-backs | none — PB1 / PB2 resolved (see §7) |
| Last sync against `docs/plugins-spec.md` | 2026-05-11 (Phase 2A.5 + 2B + CLI cleanup landed: project-pinned snapshot fallback in `/api/runs`; JsonSchemaFormSurface + `od ui show --schema`; sandboxed `/api/plugins/:id/preview` + `/example/:name` with \u00a79.2 CSP; PluginDetailView mounts the preview iframe + example links; bundled scenario plugins (`od-new-generation`, `od-tune-collab`, `od-figma-migration`, `od-code-migration`) end-to-end smoke confirms `pipeline_stage_*` events + devloop iterations rows; `parseFlags()` regression repaired (positional args silently passed to caller; TDZ on DAEMON/LIBRARY/PLUGIN_LIST flag sets resolved by hoisting); CLI parity walk green for `od plugin list/info/doctor/apply`, `od atoms list`, `od status`, `od daemon status`, `od plugin snapshots list`) |
| Last sync against `docs/plugins-spec.md` | 2026-05-13 (clarified default/no-plugin behavior, `od-default` routing, daemon system-prompt layering, plugin-assembled pipeline stages, OD-native controlled GenUI rendering, AG-UI adapter as interoperability only, and the current Home rail vs PluginsHome facet convergence gap) |
Update this table on every plugin-system PR merge. When the value of "Current phase" advances, also flip the matching deliverables in §6 and the modules in §3.

View file

@ -76,10 +76,12 @@ Concretely, this spec promotes the existing "first-party atoms" from a flat capa
- **Atom (§10):** a named capability exposed by the OD daemon and first-party tools (discovery-question-form, direction-picker, todo-write, file-read/write, research-search, media-image, live-artifact, critique-theater, etc.).
- **Pipeline (§5 / §10.1):** the plugin uses `od.pipeline` to compose atoms into ordered stages. The spec ships a default reference pipeline of `discovery → plan → generate → critique`; plugins can add, reorder, or loop over any stage.
- **Devloop (§10.2):** when a stage is marked `repeat: true` with an `until` termination condition (critique score, user confirmation, preview load success, etc.), the agent automatically iterates on the previous artifact until the condition holds or the user explicitly cancels.
- **Generative UI (§10.3):** when a stage needs human-in-the-loop input (information, authorization, direction picking, optimization confirmation), the agent triggers a surface that the plugin **declares ahead of time** in its manifest's `od.genui.surfaces[]`. The daemon broadcasts the request through an AG-UIcompatible event stream so every collaboration surface (web / desktop / CLI / other code agent) can render it. Once the user answers, the daemon writes the answer back to the project; the surface's `persist` field decides whether the answer is remembered at run / conversation / project tier so that multi-turn chats do not pester the user with the same question twice.
- **Generative UI (§10.3):** when a stage needs human-in-the-loop input (information, authorization, direction picking, optimization confirmation), the agent triggers a surface that the plugin **declares ahead of time** in its manifest's `od.genui.surfaces[]`. The daemon broadcasts the request through OD's native event stream, which can also be projected into AG-UI canonical events for external clients. Once the user answers, the daemon writes the answer back to the project; the surface's `persist` field decides whether the answer is remembered at run / conversation / project tier so that multi-turn chats do not pester the user with the same question twice.
In one sentence: **a plugin describes "what this long-running task's pipeline looks like and which GenUI surfaces it needs to collaborate with the user", the daemon supplies atoms and the surface bus, the agent runs a devloop on the pipeline, and artifacts carry provenance (§11.5) recording every plugin that touched the task.**
**Current implementation clarification:** `discovery -> plan -> generate -> critique` is a reference pipeline shape, not a fixed hard-coded wizard. A plugin snapshot can carry `od.pipeline.stages[].atoms[]`; the daemon resolves that snapshot, injects the active plugin block plus active stage atom blocks into the system prompt, emits stage events, and lets the agent work through the pipeline. When the user has not explicitly selected a plugin, OD still does **not** launch a generic naked agent: the base Open Design designer prompt and discovery rules are always present. Product entry points bind sensible defaults on top of that base: Home free-form input routes through the bundled, hidden `od-default` scenario, while typed New Project flows choose the default bundled scenario for the project kind. `od-default` is a router and task-shaper; it should guide the run into the normal design pipeline, not be treated as a standalone "make it beautiful" aesthetic engine.
### Four product scenarios
| Scenario (`od.taskKind`) | User entry point | Plugin contribution | Typical atom sequence |
@ -147,7 +149,7 @@ A third axis, derived from the second: **OD runs fully headless; the UI is a pro
A fourth axis, the foundation for ecosystem reach and commercial viability: **OD is one Docker image, deployable to any cloud.** Because the headless mode of (3) has no electron and no GUI dependencies, a single multi-arch container image (`linux/amd64` + `linux/arm64`) brings up the full daemon + CLI + web UI on AWS, Google Cloud, Azure, Alibaba, Tencent, Huawei, or any self-hosted Kubernetes / docker-compose / k3s setup, with no per-cloud rewrite. Self-hosted enterprises can run a private marketplace; partners can embed OD inside their stack; CI pipelines can spin up ephemeral OD containers for "generate slides for the daily report"-shaped tasks. The technical contract is in §15.
A fifth axis is the product-shape co-evolution with the agent: **UI is dynamically generated by the agent (Generative UI), not just developer-prewritten static components.** While running a long-horizon design pipeline, the agent will need to ask the user for information (figma OAuth, target-audience confirmation, etc.), seek authorization (approving an expensive media generation run), pick a direction (one of three critique alternatives), or fill missing content (a missing brand asset). These UIs are **not** pre-shipped marketplace chip strips; they are **declared by the plugin** in its manifest, **triggered by the agent** during the run, and **published by the daemon** through an AG-UIcompatible event stream that any collaboration surface (web / desktop / CLI / other code agent) can render (see §10.3). OD v1 ships four built-in surface kinds (`form` / `choice` / `confirmation` / `oauth-prompt`) as the minimum set; Phase 4 adds plugin-bundled React components and full wire-format alignment with the [AG-UI protocol](https://github.com/CopilotKit/CopilotKit). Tied to this axis, the project record persists a layer of **GenUI surface state**: an authorization or confirmation the user once gave is reused across multi-turn conversations and runs in the same project, instead of re-asking. This is the natural landing point of "plugin = long-horizon task wrapper" plus "project = long-lived work artifact".
A fifth axis is the product-shape co-evolution with the agent: **UI is requested by the agent but rendered by controlled product components (Generative UI), not by arbitrary agent-authored frontend code.** While running a long-horizon design pipeline, the agent will need to ask the user for information (figma OAuth, target-audience confirmation, etc.), seek authorization (approving an expensive media generation run), pick a direction (one of three critique alternatives), or fill missing content (a missing brand asset). These UIs are **not** pre-shipped marketplace chip strips; they are **declared by the plugin** in its manifest, **triggered by the agent** during the run, and **published by the daemon** through OD-native events that the web / desktop / CLI can render and external clients can consume through the AG-UI adapter (see §10.3). OD v1 ships four built-in surface kinds (`form` / `choice` / `confirmation` / `oauth-prompt`) as the minimum set; custom plugin-bundled React components stay behind the `genui:custom-component` capability gate and sandbox. Tied to this axis, the project record persists a layer of **GenUI surface state**: an authorization or confirmation the user once gave is reused across multi-turn conversations and runs in the same project, instead of re-asking. This is the natural landing point of "plugin = long-horizon task wrapper" plus "project = long-lived work artifact".
## 2. Goals and non-goals
@ -162,7 +164,7 @@ A fifth axis is the product-shape co-evolution with the agent: **UI is dynamical
7. **A plugin is a long-task wrapper.** Each plugin targets exactly one of the four product scenarios (new-generation / code-migration / figma-migration / tune-collab) and uses `od.pipeline` to assemble OD's first-party atoms into ordered stages plus an optional devloop (§10).
8. **Reproducible + auditable.** Every apply persists an immutable `AppliedPluginSnapshot` (§8.2.1); runs and artifacts back-reference the snapshot id. A plugin upgrade never breaks an old run's prompt reconstruction.
9. **Same artifact, many surfaces.** The artifact manifest (§11.5.1) records plugin provenance plus the export and deploy history across downstream surfaces (cli / other code agents / cloud / desktop) so subsequent tuning, migration, and collaboration always pick up the same artifact.
10. **Generative UI is a first-class plugin output.** Plugins declare `od.genui.surfaces[]` (§10.3) in the manifest; at runtime the agent emits form / choice / confirmation / oauth-prompt surfaces through an AG-UIcompatible event stream that any collaboration surface (web / desktop / CLI / other code agent) can render; the user's answer lands in project metadata at `run` / `conversation` / `project` persist tier and is reused across subsequent multi-turn chats and runs in the same project.
10. **Generative UI is a first-class plugin output.** Plugins declare `od.genui.surfaces[]` (§10.3) in the manifest; at runtime the agent emits form / choice / confirmation / oauth-prompt requests through OD's controlled event stream, and the product renderer owns the visual style; the user's answer lands in project metadata at `run` / `conversation` / `project` persist tier and is reused across subsequent multi-turn chats and runs in the same project. External AG-UI clients consume the same run through the adapter, not by replacing OD's internal renderer.
**Non-goals (v1)**
@ -794,11 +796,13 @@ Two hard constraints on devloop:
Each devloop iteration writes the round's artifact diff, critique output, and consumed tokens into `runs.devloop_iterations` (§11.4 SQLite extension), which feeds audit and a future per-iteration pricing model.
`GET /api/atoms` returns atoms plus the known reference pipelines. A future Phase 4 can extract each atom into `skills/_official/<atom>/SKILL.md` to make the system prompt fully data-driven, but that is **not required for v1** — the pipeline abstraction itself already grounds the "plugins assemble the core pipeline" claim.
`GET /api/atoms` returns atoms plus the known reference pipelines. The current implementation has already started the self-hosting path: first-party atom plugins live under `plugins/_official/atoms/**`, bundled scenario plugins live under `plugins/_official/scenarios/**`, and `renderActiveStageBlock(stageId, bodies)` injects the active stage's atom bodies into the prompt. The system prompt is therefore pipeline-aware today, but not yet fully data-driven: the base Open Design designer prompt, discovery philosophy, and some entry-point defaults still live in daemon/product code. That is enough to ground the "plugins assemble the core pipeline" claim without pretending every byte of behavior has moved into plugins.
### 10.3 Generative UI: AG-UIinspired surfaces
OD adopts the core mental model of [CopilotKit / the AG-UI protocol](https://github.com/CopilotKit/CopilotKit) — that the agent **dynamically generates UI** during the run — without locking into AG-UI's exact wire schema in v1. v1 ships our own `GenUISurface*` discriminated union and reuses the existing `PersistedAgentEvent` SSE / ND-JSON channel. Phase 4 introduces a full AG-UI wire-format adapter so an OD plugin can be consumed unchanged by any AG-UIcompatible frontend (CopilotKit React, other SDKs).
OD adopts the useful part of [CopilotKit / the AG-UI protocol](https://github.com/CopilotKit/CopilotKit): an agent can ask for interactive UI during a run. OD does **not** let the agent freely invent app UI or styling in the main product surface. v1 ships our own `GenUISurface*` discriminated union and reuses the existing `PersistedAgentEvent` SSE / ND-JSON channel; `@open-design/agui-adapter` projects those events into AG-UI canonical events for external clients.
The product rule is: **agent/plugin output is data; OD owns the renderer.** A plugin can declare a `form`, `choice`, `confirmation`, or `oauth-prompt` surface, with schema and prompt data. The web / desktop / CLI renderer decides layout, typography, controls, validation affordances, accessibility, and persistence UX. This keeps plugin UI extensible across scenarios while preserving a coherent product system. Arbitrary visual or code output belongs in generated artifacts, or behind the separate custom-component sandbox and `genui:custom-component` capability gate; it does not replace the built-in renderer for core collaboration UI.
#### 10.3.1 Four built-in surface kinds (v1)
@ -923,14 +927,14 @@ Prefill writes rows in `resolved` state; when the plugin triggers the surface, t
#### 10.3.5 Alignment roadmap with the AG-UI protocol
| Dimension | v1 (OD-native) | Phase 4 / AG-UI compatible |
| Dimension | v1 (OD-native) | AG-UI adapter / external compatibility |
| --- | --- | --- |
| Wire format | OD-native `PersistedAgentEvent` over SSE / ND-JSON | Also emit AG-UI canonical events (`agent.message`, `tool_call`, `state_update`, `ui.surface_requested`, `ui.surface_responded`) |
| Surface kinds | Four built-ins + plugin-declared in manifest | Adopt AG-UI's three tiers (Static / Declarative / Open-Ended); plugins can ship a React component path (capability gate `genui:custom-component`) |
| Shared state | `genui_surfaces` table + `genui_state_synced` event | Two-way sync against AG-UI's `state` channel, mapped onto `applied_plugin_snapshots` + `genui_surfaces` |
| Frontend SDK compatibility | OD desktop / web with built-in renderer | Ship `@open-design/agui-adapter` so CopilotKit / other AG-UI clients can consume an OD run unchanged |
| Surface kinds | Four built-ins + plugin-declared in manifest | Keep OD's built-ins as the product source of truth; custom plugin React paths require the `genui:custom-component` gate and sandbox |
| Shared state | `genui_surfaces` table + `genui_state_synced` event | Map persisted OD state onto AG-UI's `state` channel for external consumers |
| Frontend SDK compatibility | OD desktop / web with built-in renderer | `@open-design/agui-adapter` lets CopilotKit / other AG-UI clients consume an OD run unchanged |
Phase 4 does **not** modify the v1 manifest schema — only adds new emitters in the daemon and a new adapter package. v1 plugins **need no change** to be consumable inside the AG-UI ecosystem.
The adapter is an interoperability surface, not the internal UI source of truth. OD should not add CopilotKit as a required product dependency unless a separate external embed/demo/client explicitly needs it. v1 plugins need no change to be consumable inside the AG-UI ecosystem because the adapter is a projection of OD's own events.
## 11. Architecture — what changes in the existing repo
@ -961,7 +965,7 @@ Pure TypeScript, no Next/Express/SQLite/browser deps:
| New `apps/daemon/src/plugins/pipeline.ts` | Parses `od.pipeline` (including the `until` expression evaluator), schedules stages, and drives §10.2 devloop (with `OD_MAX_DEVLOOP_ITERATIONS` ceiling and break signaling). |
| New `apps/daemon/src/genui/{registry,events,store}.ts` | §10.3 GenUI: registers surfaces from `od.genui.surfaces[]`, publishes `genui_surface_*` events, reads/writes the cross-conversation persisted state, and serializes the AG-UIinspired event union. |
| New `apps/daemon/src/plugins/connector-gate.ts` | §9 connector capability gate: (a) `apply.ts` calls into it to resolve `od.connectors.required[]` against `connectorService.listAll()`, populating `connectorsResolved` and deriving the implicit `oauth-prompt` GenUI surface (§10.3.1) for any not-yet-connected required connector; (b) before [`apps/daemon/src/tool-tokens.ts`](../apps/daemon/src/tool-tokens.ts) issues a connector tool token, this module validates plugin trust × `connector:<id>` capability (a `trusted` plugin implicitly carries `connector:*`; a `restricted` plugin must list each id explicitly); (c) `/api/tools/connectors/execute` re-validates on every call, so a token replacement attack never bypasses the gate. This module is the runtime landing point for the P5 path in §9. |
| [`apps/daemon/src/prompts/system.ts`](../apps/daemon/src/prompts/system.ts) `composeSystemPrompt()` | Accepts optional `pluginContext: ResolvedContext` and `pipelineStage: PipelineStage`; appends `## Active plugin`, `## Active pipeline stage`, and `## Plugin inputs` blocks above the project metadata block. Existing layer order untouched. Daemon-only implementation until Phase 4 (§11.8). |
| [`apps/daemon/src/prompts/system.ts`](../apps/daemon/src/prompts/system.ts) `composeSystemPrompt()` | Assembles the base OD designer/discovery prompt, optional design system/craft/skill blocks, snapshot-derived `renderPluginBlock(snapshot)`, and active-stage atom blocks from `renderActiveStageBlock(stageId, bodies)`. Existing layer order remains intentional; plugin-driven fallback mode is still rejected per §11.8 even though the plugin-block renderer now lives in contracts. |
| New SQLite migration | `installed_plugins`, `plugin_marketplaces`, `applied_plugin_snapshots`, `run_devloop_iterations`, plus `applied_plugin_snapshot_id` ALTERs on `runs` / `conversations` / `projects` (§11.4). |
| [`apps/daemon/src/server.ts`](../apps/daemon/src/server.ts) | Mount new endpoints (§11.5); `POST /api/projects` and `POST /api/runs` accept optional `pluginId` / `pluginInputs` / `appliedPluginSnapshotId`; new `GET /api/applied-plugins/:snapshotId`, `POST /api/runs/:runId/replay`, `GET /api/runs/:runId/devloop-iterations`. |
| [`apps/daemon/src/cli.ts`](../apps/daemon/src/cli.ts) | New `plugin`, `marketplace`, `project`, `run`, and `files` subcommand routers (Phase 1 ships plugin verbs plus the headless MVP project/run/files loop; §16). |
@ -1174,6 +1178,8 @@ The web surface gets two coexisting surfaces backed by the same primitives:
Both surfaces share `ContextChipStrip`, `PluginInputsForm`, `InlinePluginsRail` (used as a strip on Home and a slim row in ChatComposer), and the `applyPlugin(pluginId, projectId?)` state helper.
**Current discovery-surface status:** the shipped web app has two related but not yet unified discovery projections. The Home hero's chip rail (`Prototype`, `Live artifact`, `Slide deck`, `Image`, `Video`, etc.) is a curated high-frequency scenario list, currently owned as product UI data. The Community / Plugins home filters are more data-driven: they derive counts and facets from plugin manifest metadata such as mode, task kind, scenario, tags, and pipeline atoms, then layer a curated category taxonomy over the catalog. The target extension model is one scenario registry / manifest projection that feeds Home chips, plugin filters, composer tools, and `@search` uniformly. Until that lands, treat the Home rail as a transitional product shortcut, not as the plugin extension primitive.
| File | Change |
| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| [`apps/web/src/router.ts`](../apps/web/src/router.ts) | Extend `Route` union with `marketplace` and `marketplace-detail`. |
@ -1237,6 +1243,8 @@ If the `## Active plugin` block is added only to the daemon composer, web API-fa
The consequence: of the three consumption modes in §14.2 (skill-only / headless OD / full OD), only the latter two ever carry plugin context in v1 — consistent with §1, where a plugin is a wrapper around a long-running task that needs the daemon (or its headless equivalent) to assemble. Phase 4 closes that gap if and when fallback-mode plugin support is wanted.
This also answers the "no plugin selected" path: a run without an applied plugin still receives the base OD prompt stack (`DISCOVERY_AND_PHILOSOPHY`, the official designer instructions, project metadata, active design system/craft when present, and any default scenario routing chosen by the entry point). Plugin context is additive. Selecting a plugin adds the snapshot-derived `## Active plugin`, `## Plugin inputs`, and active-stage atom blocks; it does not turn a non-design agent into a design agent from scratch.
## 12. CLI surface
The CLI (`od …`) is **the canonical agent-facing API** for Open Design. Plugin verbs are one slice of it; the rest of the CLI wraps the daemon's core capabilities — projects, conversations, runs, file operations, design library introspection, daemon control — so that any code agent can drive OD end-to-end through shell calls. This is the "natural-language project + task creation through CLI" path: a code agent reads a user's request, then issues a sequence of `od …` calls instead of speaking HTTP.
@ -1917,7 +1925,7 @@ The installer fans out nested skills/design-systems/craft into the registry unde
| Arbitrary GitHub install = supply-chain risk | `restricted` default; capability prompt mandatory before bash/hooks/MCP; pinned-ref recording. |
| `composeSystemPrompt()` is already 200+ lines | The `## Active plugin` block is appended in the existing place; no reordering of layers. |
| ExamplesTab vs Marketplace overlap | Phase 2 keeps ExamplesTab as is; Phase 3 folds it into Marketplace as a "Local skills" tab. |
| Atoms-as-plugins is large | Phase 4 only; v1 atoms are declarative refs, not extracted code. |
| Atoms-as-plugins is large | Entry slice shipped: bundled atom SKILL.md bodies + `renderActiveStageBlock()` exist, while the base OD designer/discovery prompt remains in daemon code until the remaining §23 migration is complete. |
| Project-local plugins committed to user repos | Discovery only at `<projectCwd>/.open-design/plugins/`; opt-in via `od plugin install --project`. |
| Trust model leaves community plugins half-functional by default | Detail page surfaces a clear capability checklist with a one-click "Grant all" action; restricted-mode behavior is explicit, not silent. |
| Plugins shipping their own MCP servers may fail to start | `od plugin doctor` runs a dry-launch of declared MCP commands; failures surfaced before "Use". |
@ -1929,7 +1937,7 @@ The installer fans out nested skills/design-systems/craft into the registry unde
| Sovereign-cloud customers (Aliyun / Tencent / Huawei) need provider-specific secret + storage integrations | S3-compatible adapter covers all three for blob storage (Phase 5); env-var-based secrets work everywhere; cloud-specific KMS integrations are non-blocking (post-v1). |
| Multi-cloud testing matrix is large | Phase 5 ships a single canonical compose smoke (one cloud), then adds clouds incrementally; per-cloud one-click templates live in `open-design/deploy` and can move at their own cadence (§15.5). |
| Malicious plugins phishing the user via GenUI surfaces | `od.genui.surfaces[]` must be declared in the manifest and pass `od plugin doctor`; runtime rejects undeclared surface kinds / surface ids; `oauth-prompt` and `confirmation` always show "from plugin <id>, vetted by marketplace <id>"; restricted plugins must explicitly grant `network` before raising an `oauth-prompt` (§9). |
| AG-UI ecosystem may evolve, drifting OD's wire format from canonical AG-UI | v1 locks the OD-native `GenUIEvent` union and does not expose it to external clients; Phase 4 adds the one-way emitter through `@open-design/agui-adapter`; the adapter ships independently from daemon main, so upstream protocol revs do not couple to the daemon release cadence. |
| AG-UI ecosystem may evolve, drifting OD's wire format from canonical AG-UI | OD-native `GenUIEvent` remains the internal source of truth. `@open-design/agui-adapter` is an external projection layer, so upstream protocol revs do not couple to the daemon or web renderer release cadence. |
| Cross-conversation reuse via `genui_surfaces` may make users "forget what they authorized" | The web `GenUIInbox` and `od ui list --project <id>` must enumerate every `persist=project` resolved row with revoke entry points; hosted mode can default-expire via `OD_GENUI_PROJECT_TTL_DAYS`; revoke writes an audit log entry. |
Open questions worth confirming before code lands:
@ -1945,9 +1953,9 @@ Open questions worth confirming before code lands:
- ~~**`AppliedPluginSnapshot` retention**~~**resolved (PB2, see `docs/plans/plugins-implementation.md` §7).** Snapshots referenced by any run / conversation / project stay pinned forever (`expires_at = NULL`); unreferenced snapshots get `expires_at = applied_at + OD_SNAPSHOT_UNREFERENCED_TTL_DAYS` (default `30`, `0` disables). The "expire even referenced rows" knob `OD_SNAPSHOT_RETENTION_DAYS` is operator-opt-in only (default unset), and applies only when the referencing row is terminal. The `expires_at` column lands in Phase 1 (§11.4); the GC worker lands in Phase 5 (§16). The `od plugin snapshots prune` CLI remains as a forced-cleanup escape hatch.
- **Devloop billing granularity** — should each stage `iteration` be billed / audited / cancelled independently? (Default: independent audit + cancel; billing granularity follows the provider's actual consumption rather than introducing a new spec-level unit.)
- **Whether `od.taskKind` becomes a first-class marketplace filter** — does the existing `kind` / `mode` / `scenario` UI need a reorder to surface the new `taskKind`? (Default: marketplace adds a top-level `taskKind` tab; existing filters drop to a secondary tier.)
- **Should `od.genui.surfaces[].component` ship in v1?** — v1 spec locks GenUI to four built-in surface kinds (`form` / `choice` / `confirmation` / `oauth-prompt`); plugin-bundled React components are deferred to Phase 4. Some third-party authors may want higher-fidelity custom surfaces immediately — should v1 expose a low-cost "render iframe pointing at plugin-bundled HTML" surface as a stopgap? (Default: no iframe surface in v1; Phase 4 jumps directly to React components with proper sandboxing.)
- ~~**Should `od.genui.surfaces[].component` ship in v1?**~~**resolved as a gated extension path.** The manifest schema accepts the field and `od plugin doctor` enforces `genui:custom-component` plus traversal guards. The built-in product renderer remains the default for `form` / `choice` / `confirmation` / `oauth-prompt`; custom components are sandboxed add-ons, not a replacement for core collaboration UI.
- **Coupling between GenUI persisted state and `AppliedPluginSnapshot`** — when a plugin upgrades and `surface.schema` changes, old rows auto-`invalidate`; should we additionally **force a re-apply** (generating a new `AppliedPluginSnapshot`) or allow the surface to invalidate while leaving the snapshot untouched? (Default: surface only; `od plugin doctor` flags schema drift; replay still uses the old snapshot.)
- **Timing of AG-UI protocol adoption** — should the Phase 4 `@open-design/agui-adapter` move forward to Phase 2A so OD plugins are consumable by CopilotKit / other AG-UI clients from day one? (Default: Phase 4; v1 stabilizes the internal `GenUIEvent` union and persistence first, then aligns with the external protocol.)
- ~~**Timing of AG-UI protocol adoption**~~**resolved.** `@open-design/agui-adapter` and `GET /api/runs/:runId/agui` have shipped as optional interoperability. OD-native GenUI remains the internal renderer and CopilotKit is not a required product dependency.
## 19. Why this is a meaningful step for Open Design
@ -2267,7 +2275,7 @@ These three are not ergonomics gaps — they are closed-vocabulary decisions in
1. **`until` signal vocabulary is closed** (§10.1). Only `critique.score`, `iterations`, `user.confirmed`, `preview.ok` are accepted. A plugin needing `build.passing`, `tests.passing`, `visual_diff.delta < 0.05`, or any other custom convergence signal must encode it through `critique.score` (loss of clarity) or through a `confirmation` surface (loss of automation). Phase 7 (§21.4) and patch 1 in §23.3.1 jointly extend this vocabulary so atoms can declare named signals; until then, scenario 2's "build/test pass = converged" cannot be expressed natively.
2. **Atom registry is closed**. `od.pipeline.stages[*].atoms[]` strings must resolve against §10's catalog (implemented + reserved). Plugin authors cannot register a new atom name. Workaround: ship the new capability as an MCP tool, attach it to the nearest generic atom (`file-write` / `todo-write` / `critique-theater`); the daemon-emitted `pipeline_stage_started` / `pipeline_stage_completed` events lose granularity for the new step.
3. **GenUI surface kinds are closed in v1** (§10.3.1). Only `form` / `choice` / `confirmation` / `oauth-prompt`. Plugin authors who want side-by-side diff review, canvas annotation, 3D preview, or any other high-fidelity UI must wait for Phase 4's `od.genui.surfaces[].component` plus React component sandbox.
3. **Core GenUI surface kinds are closed in v1** (§10.3.1). The product-owned renderer has only `form` / `choice` / `confirmation` / `oauth-prompt` as built-ins. Plugin authors who want side-by-side diff review, canvas annotation, 3D preview, or other high-fidelity UI must use the separate gated `od.genui.surfaces[].component` sandbox path when available; they cannot mint new built-in surface kinds or override the main renderer's styling contract.
The first limit hits scenario 2 hardest, the third hits scenario 4 hardest, the second affects everyone equally but is the easiest to work around via MCP.
@ -2286,7 +2294,7 @@ This is how the spec stays honest: the absence of a first-party atom is never a
§22 establishes that **third-party** plugins can extend OD on the v1 substrate. This section establishes the symmetric property: **OD's own hard-coded flow can be re-expressed as first-party plugins running on the same substrate**, with no privileged path that third parties cannot reach. The plugin substrate is genuinely the floor; OD-the-product is just one configuration of it.
§10.2 already foreshadows this for atoms: "A future Phase 4 can extract each atom into `skills/_official/<atom>/SKILL.md` to make the system prompt fully data-driven." This section makes that promise concrete by drawing the kernel/userspace boundary explicitly, listing the five spec patches required, and defining the new `bundled` trust tier.
§10.2 originally foreshadowed this for atoms; the current implementation has now landed the entry slice. Bundled atom plugins exist under `plugins/_official/atoms/**`, bundled scenario plugins exist under `plugins/_official/scenarios/**`, and active stage blocks can be rendered from atom bodies. This section keeps the kernel/userspace boundary explicit and records what remains before the prompt and entrypoint selection are fully self-hosted.
### 23.1 Kernel/userspace boundary
@ -2302,19 +2310,22 @@ The category-B list is the **kernel boundary**: not "these things would be hard
The category-A list is what **moves** when the spec commits to self-bootstrapping. Most of it is `apps/daemon/src/prompts/system.ts`'s string constants today.
### 23.2 What v1 already self-hosts (the easy half)
### 23.2 What v1 already self-hosts (the easy half plus the first atom slices)
Category C is the half v1 has shipped. As of this spec:
Category C is the half v1 has shipped, and the first pieces of category A have also moved. As of this spec:
- The active SKILL.md, DESIGN.md, and craft .md files are loaded by `apps/daemon/src/skills.ts` / `design-systems.ts` / `craft.ts`, which §11.3 already refactors to delegate to `apps/daemon/src/plugins/registry.ts`. After Phase 1, every active behavioral artifact in the prompt is plugin-loaded, even if its content is bundled.
- Plugin-declared MCP servers, connector requirements, GenUI surfaces, and `od.pipeline.stages[]` are all consumed via the same registry and resolver paths that a third-party plugin uses.
- The active design system + craft injection that drives "consistency" in §1's product brief is already a plugin-substrate read: there is no privileged path for first-party DESIGN.md vs. third-party DESIGN.md.
- First-party atom plugins under `plugins/_official/atoms/**` carry atom SKILL.md bodies and manifest metadata; `packages/contracts/src/prompts/atom-block.ts` renders active stage blocks from those bodies.
- Bundled scenario plugins under `plugins/_official/scenarios/**` carry default pipeline shapes, including `od-default` for Home free-form routing and task shaping. `packages/plugin-runtime/src/pipeline-fallback.ts` resolves an applied pipeline through these bundled scenarios when a plugin omits `od.pipeline`.
- `@open-design/agui-adapter` and `/api/runs/:runId/agui` provide external AG-UI event projection without changing OD's internal GenUI renderer.
This is why §22 holds: the first half of the substrate is already self-hosting; only the atom layer and the default-pipeline selector remain hard-coded.
This is why §22 holds: the substrate is already self-hosting for plugin artifacts, snapshots, GenUI declarations, pipeline declarations, bundled scenarios, and the first atom-body injection path. The remaining hard-coded parts are narrower and more product-shaped: the base OD designer/discovery prompt, some stage-entry selection logic, Home's curated scenario rail, and the closed signal / surface vocabularies listed in §22.4.
### 23.3 What v1 still hard-codes (the work to finish self-hosting)
Five spec patches lift category A out of the daemon. Together they make the entire prompt + pipeline data-driven from plugin manifests; afterwards, `composeSystemPrompt()` becomes a pure assembler with no behavioral content of its own.
The following patches finish lifting category A out of the daemon. Some have partially landed as entry slices, so read them as the completion checklist, not as a claim that nothing exists yet. Together they make the entire prompt + pipeline data-driven from plugin manifests; afterwards, `composeSystemPrompt()` becomes a pure assembler with no behavioral content of its own.
#### 23.3.1 Patch 1 — Formal contract for `od.kind: 'atom'`
@ -2326,15 +2337,15 @@ Today §5's `od.kind` enum lists `'atom'` but never defines an atom plugin's sha
- Optional `od.atom.untilSignals[]` declares the named signal variables this atom emits, contributing to the `until` vocabulary in §10.1. This is how patch 1 also lifts §22.4's limit 1: each atom owns its own signals (e.g. `build-test` declares `build.passing` and `tests.passing`), and the `until` evaluator looks them up against the active stage's atoms instead of a hard-coded list.
- Optional `od.atom.toolGating[]` declares which agent-side tools (file-read/write/edit, bash) the atom expects to be available.
#### 23.3.2 Patch 2 — Migrate atom prompt fragments out of `system.ts`
#### 23.3.2 Patch 2 — Finish migrating atom prompt fragments out of `system.ts`
The string constants in `apps/daemon/src/prompts/system.ts` (`DISCOVERY_AND_PHILOSOPHY`, the critique addendum, TodoWrite policy, file-ops policy, etc.) move into `plugins/_official/atoms/<atom>/SKILL.md`. `composeSystemPrompt()` resolves the active stage's `atoms[]`, reads each atom plugin's SKILL.md fragment, and concatenates them in stage order under a stable header (`## Active stage: <stage-id>` followed by the atom fragments).
Atom SKILL.md fragments and the active-stage block renderer now exist, but the migration is not complete. The remaining string constants in `apps/daemon/src/prompts/system.ts` (`DISCOVERY_AND_PHILOSOPHY`, the base official designer prompt, critique addenda, TodoWrite policy, file-ops policy, etc.) either move into `plugins/_official/atoms/<atom>/SKILL.md` or are explicitly reclassified as kernel-level assembly policy. `composeSystemPrompt()` resolves the active stage's `atoms[]`, reads each atom plugin's SKILL.md fragment, and concatenates them in stage order under a stable header (`## Active stage: <stage-id>` followed by the atom fragments).
The migration is mechanical: every prose constant in `system.ts` has a one-to-one home under `plugins/_official/atoms/<atom>/SKILL.md`. Reviewers should reject migrations that leave behavioral prose constants behind in `system.ts` after the patch lands.
#### 23.3.3 Patch 3 — Migrate default reference pipelines into bundled scenario plugins
#### 23.3.3 Patch 3 — Finish migrating default reference pipelines into bundled scenario plugins
§10.1 today says "When `od.pipeline` is omitted, the daemon picks a reference pipeline based on `od.taskKind`." That logic moves out of daemon code into a bundled scenario plugin per `taskKind`:
Bundled scenario plugins and the pipeline fallback resolver now exist. The remaining work is to remove any product entrypoint that still hand-builds a default stage list instead of selecting a scenario plugin. The target remains: when `od.pipeline` is omitted, daemon/product code resolves a bundled scenario plugin per `taskKind` or explicit entrypoint route, then uses that scenario's `od.pipeline`.
- `plugins/_official/scenarios/od-new-generation/open-design.json`
- `plugins/_official/scenarios/od-figma-migration/open-design.json` (after Phase 6)

View file

@ -76,10 +76,12 @@ OD 的核心不是「一次 prompt 一次输出」,而是 **long-running desig
- **Atom§10**OD daemon 与 first-party tools 暴露的具名能力discovery-question-form、direction-picker、todo-write、file-read/write、research-search、media-image、live-artifact、critique-theater 等)。
- **Pipeline§5 / §10.1**:插件通过 `od.pipeline` 把若干 atoms 组装成有序 stagesspec 默认提供一条「discovery → plan → generate → critique」的 reference pipeline插件可以增删、重排或循环其中任何一步。
- **Devloop§10.2**:当一条 stage 标记 `repeat: true` 并附带 `until` 终止条件critique score、用户确认、preview 加载成功等agent 基于上一轮 artifact 自动进入下一轮,直到条件满足或显式取消。
- **Generative UI§10.3**pipeline 的某个 stage 需要人类介入提供信息、授权、方向选择、优化确认agent 触发插件预先在 manifest `od.genui.surfaces[]` 中**声明**过的 surfacedaemon 通过 AG-UI兼容的事件流广播给所有协作面web / desktop / CLI / 其他 code agent用户回应后 daemon 把答案写回 projectrun 继续。Surface 的 `persist` 字段决定答案在 run / conversation / project 三个层级中哪个层级被记住,让多轮对话不会反复打扰用户。
- **Generative UI§10.3**pipeline 的某个 stage 需要人类介入提供信息、授权、方向选择、优化确认agent 触发插件预先在 manifest `od.genui.surfaces[]` 中**声明**过的 surfacedaemon 通过 OD 原生事件流广播给所有协作面web / desktop / CLI / 其他 code agent并可投影成 AG-UI canonical events 供外部 client 使用。用户回应后 daemon 把答案写回 projectrun 继续。Surface 的 `persist` 字段决定答案在 run / conversation / project 三个层级中哪个层级被记住,让多轮对话不会反复打扰用户。
一句话:**插件描述「这次长程任务的 pipeline 该长什么样、需要哪些 GenUI surface 与用户协作」daemon 提供 atoms 与 surface 总线agent 在 pipeline 上跑 devloopartifact 带 provenance§11.5)记录这条长程任务跑过谁。**
**当前实现澄清:** `discovery -> plan -> generate -> critique` 是 reference pipeline 形态,不是一套写死的 wizard。插件 snapshot 可以携带 `od.pipeline.stages[].atoms[]`daemon 解析 snapshot 后,把 active plugin block 与 active stage atom blocks 注入 system prompt同时发出 stage events让 agent 按 pipeline 推进。如果用户没有显式选择插件OD 也**不是**启动一个通用裸 agentOpen Design 基础 designer prompt 与 discovery 规则始终存在。产品入口会在此基础上绑定合理默认值Home 自由输入走内置隐藏的 `od-default` scenario按类型创建新 project 时走对应 project kind 的 bundled scenario。`od-default` 是 router / task shaper它的职责是把请求导回正常设计 pipeline不应被理解成一个独立的「美化生成器」。
### 四类产品场景
| 场景 (`od.taskKind`) | 用户起点 | 插件贡献 | 典型 atom 序列 |
@ -147,7 +149,7 @@ Open Design 变成一套 **server + CLI + atomic core engine + plugin/marketplac
第四条轴线是生态覆盖与商业价值的基础:**OD 是一个 Docker image可以部署到任意云。** 因为第三条轴线里的 headless mode 没有 electron、没有 GUI 依赖,一个 multi-arch container image`linux/amd64` + `linux/arm64`)就能在 AWS、Google Cloud、Azure、阿里云、腾讯云、华为云或任何自托管 Kubernetes / docker-compose / k3s 环境里启动完整 daemon + CLI + web UI不需要针对云厂商重写。自托管企业可以运行私有 marketplace合作伙伴可以把 OD 嵌入自己的 stackCI pipeline 可以拉起临时 OD container 来完成「为日报生成 slides」这类任务。技术 contract 见 §15。
第五条轴线是与 agent 共演进的产品形态:**UI 是 agent 动态生成的 (Generative UI),不只是开发者预先写好的静态组件。** 长程 design agent 在跑 pipeline 的过程中会随时需要向用户索取信息figma OAuth、确认目标受众、寻求授权批准一次 high-cost media 生成)、收敛方向(例:从 3 个 critique 选项里选一个)、补内容(例:缺失的品牌资产);这些 UI **不是**预制的 marketplace chip strip而是 plugin 在 manifest 中**声明**、agent 在 run 中**触发**、daemon 用 AG-UI兼容的事件流**发布**给前端 / CLI / 其他 code agent 渲染的 surface详见 §10.3。OD v1 提供 4 类内置 surface`form` / `choice` / `confirmation` / `oauth-prompt`)作为最小集;Phase 4 加 plugin 自带 React 组件与 [AG-UI 协议](https://github.com/CopilotKit/CopilotKit) 的完整 wire-format 对齐。对应这条轴线project 表多记录一组 GenUI surface 的 **persisted state**:用户做过一次的授权与确认在同一 project 的多轮对话、多次 run 之间复用,不会被反复打扰;这是「插件 = 长程任务封装」与「project = 长期工作产物」的自然落点。
第五条轴线是与 agent 共演进的产品形态:**UI 由 agent 请求,但由产品内的受控组件渲染 (Generative UI),而不是任意 agent-authored frontend code。** 长程 design agent 在跑 pipeline 的过程中会随时需要向用户索取信息figma OAuth、确认目标受众、寻求授权批准一次 high-cost media 生成)、收敛方向(例:从 3 个 critique 选项里选一个)、补内容(例:缺失的品牌资产);这些 UI **不是**预制的 marketplace chip strip而是 plugin 在 manifest 中**声明**、agent 在 run 中**触发**、daemon 用 OD 原生事件**发布**给前端 / CLI 渲染,并通过 AG-UI adapter 供外部 client 消费的 surface详见 §10.3。OD v1 提供 4 类内置 surface`form` / `choice` / `confirmation` / `oauth-prompt`)作为最小集;plugin 自带 React 组件必须经过 `genui:custom-component` capability gate 与 sandbox。对应这条轴线project 表多记录一组 GenUI surface 的 **persisted state**:用户做过一次的授权与确认在同一 project 的多轮对话、多次 run 之间复用,不会被反复打扰;这是「插件 = 长程任务封装」与「project = 长期工作产物」的自然落点。
## 2. 目标与非目标
@ -162,7 +164,7 @@ Open Design 变成一套 **server + CLI + atomic core engine + plugin/marketplac
7. **插件即长程任务封装**每个插件覆盖四类产品场景之一new-generation / code-migration / figma-migration / tune-collab通过 `od.pipeline` 把 OD 一方 atoms 组装成有序 stages + 可选 devloop§10
8. **可复现 + 可审计**:每次 apply 落一份不可变 `AppliedPluginSnapshot`§8.2.1run / artifact 通过 snapshot id 反查 plugin source插件升级不破坏历史 run 的 prompt 还原。
9. **同一 artifact 跨协作面流转**artifact manifest§11.5.1)记录 plugin provenance + 各下游协作面cli / 其他 code agent / 云 / 桌面端)的 export 与 deploy 历史,让后续二次调优、迁移、协作围绕同一 artifact 接续。
10. **Generative UI 是 plugin 一等输出**:插件在 manifest 中声明 `od.genui.surfaces[]`§10.3agent 运行时通过 AG-UI兼容的事件流向所有协作面web / desktop / CLI / 其他 code agent发布 form / choice / confirmation / oauth-prompt 等 surface;用户的回应按 `persist` 等级run / conversation / project落到 project metadata被同 project 后续多轮对话、多次 run 复用。
10. **Generative UI 是 plugin 一等输出**:插件在 manifest 中声明 `od.genui.surfaces[]`§10.3agent 运行时通过 OD 受控事件流发布 form / choice / confirmation / oauth-prompt 请求,产品 renderer 负责视觉样式;用户的回应按 `persist` 等级run / conversation / project落到 project metadata被同 project 后续多轮对话、多次 run 复用。外部 AG-UI client 通过 adapter 消费同一条 run而不是替换 OD 内部 renderer。
**非目标v1**
@ -793,11 +795,13 @@ Devloop 的两条硬约束:
每一轮 devloop 都把当轮 artifact diff、critique 输出、消耗 tokens 写入 `runs.devloop_iterations`§11.4 SQLite 扩展),用于审计与按 iteration 计费的未来商业模型。
`GET /api/atoms` 返回 atoms 与已知 reference pipelines。未来 Phase 4 可以把每个 atom 提取到 `skills/_official/<atom>/SKILL.md`,使 system prompt 完全 data-driven但 v1 **不需要**这么做——pipeline 抽象本身已经把"插件组装核心管线"这条主张落地
`GET /api/atoms` 返回 atoms 与已知 reference pipelines。当前实现已经开始自举:一方 atom plugins 位于 `plugins/_official/atoms/**`bundled scenario plugins 位于 `plugins/_official/scenarios/**``renderActiveStageBlock(stageId, bodies)` 会把 active stage 的 atom bodies 注入 prompt。因此 system prompt 现在已经 pipeline-aware但还不是完全 data-drivenOpen Design 基础 designer prompt、discovery philosophy 和部分入口默认逻辑仍在 daemon / product code 中。这足以支撑"插件组装核心管线"这条主张,但不假装所有行为字节都已经迁到插件
### 10.3 Generative UIAG-UIinspired surfaces
OD 接受 [CopilotKit / AG-UI 协议](https://github.com/CopilotKit/CopilotKit) 中"agent 在 run 中**动态生成 UI**"的核心心智,但不绑死它的具体 wire schemav1 提供我们自己的 `GenUISurface*` discriminated union跟现有 `PersistedAgentEvent`、SSE / ND-JSON 流共用通道Phase 4 引入 AG-UI 协议的完整 wire-format adapter使 OD plugin 可以被任意 AG-UI 兼容的 frontendCopilotKit React 组件、其他 SDK原样消费。
OD 接受 [CopilotKit / AG-UI 协议](https://github.com/CopilotKit/CopilotKit) 中有价值的部分agent 可以在 run 中请求交互 UI。OD **不**允许 agent 在主产品表面自由发明 app UI 或视觉样式。v1 提供自己的 `GenUISurface*` discriminated union跟现有 `PersistedAgentEvent`、SSE / ND-JSON 流共用通道;`@open-design/agui-adapter` 会把这些事件投影成 AG-UI canonical events 供外部 client 使用。
产品规则是:**agent / plugin 输出的是数据OD 掌握 renderer。** 插件可以声明 `form`、`choice`、`confirmation`、`oauth-prompt` surface带 schema 与 prompt dataweb / desktop / CLI renderer 决定 layout、typography、controls、validation、accessibility 和 persistence UX。这让插件 UI 能覆盖未来场景,同时保持产品系统一致。任意视觉或代码输出属于生成的 artifact或者必须走独立 custom-component sandbox 与 `genui:custom-component` capability gate它不能替换核心协作 UI 的内置 renderer。
#### 10.3.1 4 类内置 surfacev1
@ -922,14 +926,14 @@ prefill 只把 row 写成 `resolved`,等待 plugin 触发时按 cache 读出
#### 10.3.5 与 AG-UI 协议的对齐路线
| 维度 | v1OD 原生) | Phase 4 / AG-UI 兼容 |
| 维度 | v1OD 原生) | AG-UI adapter / 外部兼容 |
| --- | --- | --- |
| Wire format | OD 自有 SSE / ND-JSON `PersistedAgentEvent` | 同时输出 AG-UI canonical events包括 `agent.message`、`tool_call`、`state_update`、`ui.surface_requested`、`ui.surface_responded` |
| Surface kinds | 4 类内置 + plugin 在 manifest 中声明 | 引入 AG-UI [Static / Declarative / Open-Ended] 三档plugin 可以 ship React 组件路径capability gate `genui:custom-component` |
| Shared state | `genui_surfaces` 表 + `genui_state_synced` 事件 | 双向同步 AG-UI 的 `state` 通道map 到 `applied_plugin_snapshots` + `genui_surfaces` |
| Frontend SDK 兼容 | OD desktop / web 自带 renderer | 提供 `@open-design/agui-adapter`让 CopilotKit / 其他 AG-UI client 直接消费 OD run |
| Surface kinds | 4 类内置 + plugin 在 manifest 中声明 | OD 内置 surface 仍是产品 source of truthplugin React 组件路径必须经过 `genui:custom-component` gate 与 sandbox |
| Shared state | `genui_surfaces` 表 + `genui_state_synced` 事件 | 把 OD 持久化状态映射到 AG-UI 的 `state` channel供外部消费者使用 |
| Frontend SDK 兼容 | OD desktop / web 自带 renderer | `@open-design/agui-adapter` 让 CopilotKit / 其他 AG-UI client 直接消费 OD run |
Phase 4 不修改 v1 manifest schema只在 daemon 与新 adapter package 中加输出器。这样 v1 plugin **不需要改**就可以在 AG-UI ecosystem 内被消费。
Adapter 是互操作表面,不是内部 UI 的 source of truth。除非另有外部 embed / demo / client 明确需要,否则 OD 不应把 CopilotKit 加成主产品依赖。v1 plugin **不需要改**就可以在 AG-UI ecosystem 内被消费,因为 adapter 是 OD 自有事件的一层投影
## 11. 架构:现有 repo 要改什么
@ -960,7 +964,7 @@ Phase 4 不修改 v1 manifest schema只在 daemon 与新 adapter package 中
| 新 `apps/daemon/src/plugins/pipeline.ts` | 解析 `od.pipeline`(含 `until` expression 求值器)、调度 stages、控制 §10.2 devloop`OD_MAX_DEVLOOP_ITERATIONS` 上限与 break 信号)。 |
| 新 `apps/daemon/src/genui/{registry,events,store}.ts` | §10.3 GenUI surface 注册(来自 `od.genui.surfaces[]`)、`genui_surface_*` event 发布、cross-conversation persisted state 读写、AG-UIinspired event union 序列化。 |
| 新 `apps/daemon/src/plugins/connector-gate.ts` | §9 connector capability gate(a) `apply.ts` 通过它解析 `od.connectors.required[]``connectorService.listAll()`,标记 `connectorsResolved` 与 implicit `oauth-prompt` GenUI surface§10.3.1(b) 在 [`apps/daemon/src/tool-tokens.ts`](../apps/daemon/src/tool-tokens.ts) 颁发 connector tool token 之前注入 plugin trust × `connector:<id>` capability 校验trusted 隐含 `connector:*`restricted 必须显式列出);(c) `/api/tools/connectors/execute` 执行前再校验一次(防止 token 颁发后被替换)。这条 gate 是 §9 中 P5 决策路径的实际落点。 |
| [`apps/daemon/src/prompts/system.ts`](../apps/daemon/src/prompts/system.ts) `composeSystemPrompt()` | 接受可选 `pluginContext: ResolvedContext``pipelineStage: PipelineStage`;在 project metadata block 上方追加 `## Active plugin`、`## Active pipeline stage`、`## Plugin inputs` blocks。现有层级顺序不变。Phase 4 之前为 daemon-only 实现§11.8。 |
| [`apps/daemon/src/prompts/system.ts`](../apps/daemon/src/prompts/system.ts) `composeSystemPrompt()` | 组装 OD 基础 designer/discovery prompt、可选 design system/craft/skill blocks、snapshot 派生的 `renderPluginBlock(snapshot)`,以及来自 `renderActiveStageBlock(stageId, bodies)` 的 active-stage atom blocks。现有层级顺序保持有意设计plugin-block renderer 已在 contracts 中,但 plugin-driven fallback mode 仍按 §11.8 拒绝。 |
| 新 SQLite migration | `installed_plugins`、`plugin_marketplaces`、`applied_plugin_snapshots`、`run_devloop_iterations`,以及 `runs` / `conversations` / `projects` 上的 `applied_plugin_snapshot_id` ALTER§11.4)。 |
| [`apps/daemon/src/server.ts`](../apps/daemon/src/server.ts) | 挂载新 endpoints§11.5`POST /api/projects` 与 `POST /api/runs` 接受可选 `pluginId` / `pluginInputs` / `appliedPluginSnapshotId`;新增 `GET /api/applied-plugins/:snapshotId`、`POST /api/runs/:runId/replay`、`GET /api/runs/:runId/devloop-iterations`。 |
| [`apps/daemon/src/cli.ts`](../apps/daemon/src/cli.ts) | 新增 `plugin`、`marketplace`、`project`、`run`、`files` subcommand routersPhase 1 含 plugin verbs + headless MVP project/run/files§16。 |
@ -1172,6 +1176,8 @@ Web 表面获得两个共存入口,并由相同 primitives 支撑:
二者共享 `ContextChipStrip`、`PluginInputsForm`、`InlinePluginsRail`Home 上是 strip/gridChatComposer 中是 slim row以及 `applyPlugin(pluginId, projectId?)` state helper。
**当前 discovery surface 状态:** 已发布的 web app 里有两个相关但尚未统一的 discovery 投影。Home hero 的 chip rail`Prototype`、`Live artifact`、`Slide deck`、`Image`、`Video` 等)是一组精选高频场景,目前仍由产品 UI 数据直接维护。Community / Plugins home filters 更接近数据驱动:它们从 plugin manifest 的 mode、task kind、scenario、tags、pipeline atoms 等元数据推导 counts 与 facets再叠加 curated category taxonomy。目标扩展模型是一份 scenario registry / manifest projection 同时驱动 Home chips、plugin filters、composer tools 与 `@search`。在这件事落地前Home rail 应被视为过渡性的产品快捷入口,而不是 plugin extension primitive。
| 文件 | 变更 |
| --- | --- |
| [`apps/web/src/router.ts`](../apps/web/src/router.ts) | 扩展 `Route` union加入 `marketplace``marketplace-detail`。 |
@ -1230,11 +1236,13 @@ OD 当前有**两份** `composeSystemPrompt()` 实现:
如果把 `## Active plugin` block 只加到 daemon composerAPI fallback 模式生成的 prompt 会缺失 plugin context如果两边各加一套几乎必然漂移。spec 锁定如下规则:
1. **v1plugin 驱动的 run 仅在 daemon-orchestrated mode 下支持**desktop / web 走 daemon HTTPheadless / Docker 走 daemon directly。Web API-fallback 模式(浏览器直连 providerdaemon 不在路径上)在 v1 中**不**支持 `pluginId`:当客户端在 fallback 模式下尝试创建带 `pluginId` 的 run 时daemon 缺席使得请求落在 web sidecar 上sidecar 检测到该 fallback path 必须回 `409 plugin-requires-daemon`UI 提示用户启动 daemon 或切换到 desktop/headless。这条限制在 §16 Phase 2A validation 中明确测试。
2. **Phase 4把 plugin prompt block 提到 contracts 共享层**。一旦 contracts 端 `composeSystemPrompt()` 的 input shape 加入可选 `pluginContext: ResolvedContext`daemon composer 委托 contracts 输出再追加自己的 daemon-only blocksDB-backed metadata、SQLite project tags 等)。这样 plugin context 的 prompt 表达**只有一处定义**API fallback 自动获得插件支持
3. **测试约束**CI 在 `apps/daemon/tests/prompts/system.test.ts``packages/contracts/tests/package-runtime.test.ts` 中加 cross-check fixture给定相同 `ResolvedContext`,两个 composer 输出的 plugin block 必须 **byte-for-byte** 相同。Phase 4 落地前用一个简单 string template 共享,避免在 v1 阶段两边手写漂移。
2. **Phase 2A把 plugin prompt block renderer 提到 contracts 共享层。** 纯函数 `renderPluginBlock(snapshot: AppliedPluginSnapshot): string` 位于 [`packages/contracts/src/prompts/plugin-block.ts`](../packages/contracts/src/prompts/plugin-block.ts),并被 `apps/daemon/src/prompts/system.ts``packages/contracts/src/prompts/system.ts` 同时 import。daemon composer 会调用它contracts composer 保留 import但在 v1 fallback rejection 规则 (1) 下不调用它。这样 plugin context 的 prompt 表达只有一处定义,不再需要两边 byte-for-byte fixture 防漂移,同时保留 v1 fallback 拒绝策略
3. **Phase 4打开 fallback plugin support。** renderer 已在 contracts 中后,支持 fallback-mode plugin run 只需要在 contracts composer 里接线并移除 web sidecar 的 409不再需要 prompt shape 重构或新的 cross-check guard。最初计划是 Phase 4 才迁出 block并在 Phase 1-4 之间用 CI fixture 防漂移;实施计划 PB1 已把迁移提前到 Phase 2A因此 fixture 不再需要。)
由此§14.2 列出的「skill-only consumption / headless OD / full OD」三种消费模式中只有后两者拥有 plugin context这与 §1 中「插件是 long-task design agent 的封装」一致——长程任务必须有 daemon 或 daemon 等价物headless才能被组装。
这也回答「没有选择插件」路径:没有 applied plugin 的 run 仍然会收到 OD 基础 prompt stack`DISCOVERY_AND_PHILOSOPHY`、official designer instructions、project metadata、存在时的 active design system/craft以及入口选择的默认 scenario routing。Plugin context 是增量项。选择插件会追加 snapshot 派生的 `## Active plugin`、`## Plugin inputs` 与 active-stage atom blocks它不是把一个非设计 agent 从零变成设计 agent 的唯一来源。
## 12. CLI 表面
CLI`od …`)是 **Open Design 面向 agent 的 canonical API**。Plugin verbs 只是其中一部分CLI 的其它部分包装 daemon core capabilitiesprojects、conversations、runs、file operations、design library introspection、daemon control因此任何 code agent 都能通过 shell calls 端到端驱动 OD。这是「通过 CLI 用自然语言创建 project + task」的路径code agent 读取用户请求,然后发出一串 `od …` 调用,而不是直接说 HTTP。
@ -1912,19 +1920,19 @@ installer 会把 nested skills/design-systems/craft fan out 到 registry 的 nam
| 任意 GitHub install = supply-chain risk | 默认 `restricted`bash/hooks/MCP 前必须 capability prompt记录 pinned-ref。 |
| `composeSystemPrompt()` 已经超过 200 行 | `## Active plugin` block 追加在现有位置;不重排 layers。 |
| ExamplesTab 与 Marketplace overlap | Phase 2 保持 ExamplesTabPhase 3 折叠为 Marketplace 的「Local skills」tab。 |
| Atoms-as-plugins 范围大 | Phase 4 onlyv1 atoms 是 declarative refs不提取代码。 |
| Atoms-as-plugins 范围大 | Entry slice 已落地bundled atom SKILL.md bodies 与 `renderActiveStageBlock()` 已存在OD 基础 designer/discovery prompt 仍留在 daemon code等待 §23 剩余迁移完成。 |
| Project-local plugins 被提交到用户 repo | 仅发现 `<projectCwd>/.open-design/plugins/`;通过 `od plugin install --project` opt-in。 |
| Trust model 让 community plugins 默认半功能 | 详情页提供清晰 capability checklist 与一键「Grant all」restricted 行为显式,不静默。 |
| 插件自带 MCP servers 可能无法启动 | `od plugin doctor` dry-launch declared MCP commands在「Use」前暴露失败。 |
| `applied_plugin_snapshots` 表无界增长 | snapshot 不删除reproducibility 优先);提供 `od plugin snapshots prune --before <date> --keep-referenced` 让 operator 显式清理;`status='stale'` 行可批量 archive 到外部存储。 |
| daemon `composeSystemPrompt` 与 contracts `composeSystemPrompt` 漂移 | §11.8 锁定 v1 daemon-only plugin runsCI cross-check fixture 强制两个 composer 对相同 `ResolvedContext` 输出 byte-for-byte 相同的 plugin block。 |
| `applied_plugin_snapshots` 表无界增长 | 按 PB2已决未被引用的 snapshot 默认 30 天后过期;被 run / conversation / project 引用的 snapshot 永久 pinreproducibility 优先GC worker 与 `od plugin snapshots prune --before <ts>` 提供清理通道。 |
| daemon `composeSystemPrompt` 与 contracts `composeSystemPrompt` 漂移 | 按 PB1已决plugin block renderer 从 Phase 2A 起位于 `packages/contracts/src/prompts/plugin-block.ts`;两份 composer import 同一个函数,不再需要 byte-for-byte fixture。 |
| `od.pipeline` devloop 无限循环烧 quota | `until` 必填且 syntax 受限;`OD_MAX_DEVLOOP_ITERATIONS` 上限(默认 10UI 与 CLI 提供「Stop refining」打断。 |
| `OD_HOST` / `OD_BIND_HOST` 命名漂移 | Spec 全部使用现有 daemon 已读取的 `OD_BIND_HOST`;不引入 `OD_HOST` 别名§15.3 显式标注与历史草稿的差异。 |
| 没有 bound-API-token guard 的 hosted deployments 可能公开泄露 APIPhase 5 之前依赖 reverse proxy | Phase 5 落地后 daemon 在无 `OD_API_TOKEN` 时拒绝绑定 `OD_BIND_HOST=0.0.0.0``/api/*` enforce bearer-token middleware§15.3 / §15.7 记录当前 vs. 目标差异。 |
| Sovereign-cloud customers阿里云 / 腾讯云 / 华为云)需要 provider-specific secret + storage integrations | S3-compatible adapter 覆盖三者的 blob storagePhase 5env-var secrets 各云都可用cloud-specific KMS integrations post-v1。 |
| Multi-cloud testing matrix 太大 | Phase 5 先发布一个 canonical compose smoke单云再逐步加云per-cloud one-click templates 放在 `open-design/deploy` 独立演进§15.5)。 |
| 恶意 plugin 通过 GenUI surface 钓鱼用户敏感信息 | `od.genui.surfaces[]` schema 必须列入 manifest 并由 `od plugin doctor` 校验;运行时拒绝未声明 surface kind / surface id`oauth-prompt` 与 `confirmation` 的 issuer / capability 信息显示「来自 plugin <id>,由 marketplace <id> 验证」restricted 插件触发 `oauth-prompt` 前还需要 `network` capability 显式 grant§9。 |
| AG-UI 协议生态可能演进OD 自有 wire-format 与 AG-UI canonical 漂移 | v1 锁定 OD 自有 `GenUIEvent` union 不暴露给外部 clientPhase 4 通过 `@open-design/agui-adapter` package 单向输出 AG-UI canonical eventsadapter 与 upstream 协议升级独立 releasedaemon 主线不绑死。 |
| AG-UI 协议生态可能演进OD 自有 wire-format 与 AG-UI canonical 漂移 | OD-native `GenUIEvent` 仍是内部 source of truth`@open-design/agui-adapter` 是外部投影层,因此 upstream 协议升级不绑死 daemon 或 web renderer release cadence。 |
| `genui_surfaces` 表 跨 conversation 复用导致用户「忘记自己授过权」 | UI 的 `GenUIInbox`、CLI 的 `od ui list --project <id>` 必须列出所有 `persist=project` 的 resolved row 与 revoke 入口hosted mode 提供 `OD_GENUI_PROJECT_TTL_DAYS` 让 operator 设置默认过期revoke 操作写 audit 日志。 |
落代码前值得确认的开放问题:
@ -1936,13 +1944,13 @@ installer 会把 nested skills/design-systems/craft fan out 到 registry 的 nam
- **Hosted mode 的 trust propagation**:当前 spec 已锁定 arbitrary GitHub / URL / local 插件默认 `restricted`,第三方 marketplace 也默认不传递 trust。还需确认 hosted deployments 是否允许 operator 通过 `OD_TRUSTED_PLUGINS` 直接 trust 单个插件,还是必须先 trust 它所属 marketplace。
- **Discovery-time hot reload**daemon 是否 watch `~/.open-design/plugins/`(开发体验好),还是只在 `od plugin install/update/uninstall` 后 reload稳定性高默认watch500ms debounce。
- **Versioning policy**:安装时 pin tag/SHA还是默认跟踪 default branch 并提供 opt-in pin默认安装时 pin resolved ref`od plugin update` 重新 resolve。
- **Plugin prompt block 提到 contracts 共享层的时机**§11.8 锁定 v1 daemon-only。当 `apps/web` 的 API-fallback 模式仍是真实使用路径时,是否要把 plugin prompt block 提前到 Phase 2A 一并落到 contracts避免后续二次重构默认v1 daemon-onlyPhase 4 再共享到 contracts。
- **`AppliedPluginSnapshot` 留存策略**永久保留reproducibility 优先vs. 限制每个 project 仅保留最近 N 份 snapshot存储成本vs. operator 通过 `OD_SNAPSHOT_RETENTION_DAYS` 配置过期。(默认:永久保留,提供显式 `od plugin snapshots prune` CLIhosted operator 可启用时间窗口。)
- ~~**Plugin prompt block 提到 contracts 共享层的时机**~~**已按 PB1 解决。** Phase 2A 已把 `renderPluginBlock(snapshot)` 放到 `packages/contracts/src/prompts/plugin-block.ts`;两份 composer import 同一函数v1 fallback rejection 规则保留。
- ~~**`AppliedPluginSnapshot` 留存策略**~~**已按 PB2 解决。** 被 run / conversation / project 引用的 snapshot 永久 pin未引用 snapshot 默认 30 天过期;`OD_SNAPSHOT_RETENTION_DAYS` 是 operator opt-in`od plugin snapshots prune` 保留为强制清理出口。
- **Devloop 计费颗粒度**:每次 stage 的 `iteration` 是否单独按 token 计费 / 单独 audit / 单独 cancellable默认单独 audit + 可单独 cancel计费颗粒度跟随 provider 实际消耗,不在 spec 层定义新颗粒。)
- **`od.taskKind` 是否成为 marketplace 一等过滤维度**:现有 `kind` / `mode` / `scenario` 是否需要为新增的 `taskKind` 重排 UI filter默认marketplace 顶部增加 `taskKind` tab现有 filter 保留为二级。)
- **`od.genui.surfaces[].component` 是否进 v1**v1 spec 锁定为 4 类内置 surface kinds`form` / `choice` / `confirmation` / `oauth-prompt`plugin 自带 React 组件留到 Phase 4。第三方插件作者可能立刻就需要更高保真的自定义 surface是否应在 v1 提供「render iframe pointing at plugin-bundled HTML」作为低成本前置默认v1 不开 iframe surfacePhase 4 直接上 React 组件 + sandboxing。
- ~~**`od.genui.surfaces[].component` 是否进 v1**~~**已作为 gated extension path 解决。** Manifest schema 接受该字段,`od plugin doctor` 校验 `genui:custom-component` 与 path traversal内置 `form` / `choice` / `confirmation` / `oauth-prompt` renderer 仍是主产品默认。
- **GenUI persisted state 与 `AppliedPluginSnapshot` 的耦合**:当 plugin 升级且 `surface.schema` 变了,旧 row 自动 `invalidated`;但是否要同时**强制重新 apply** plugin生成新 `AppliedPluginSnapshot`),还是允许仅 surface 失效、其余 snapshot 不变?(默认:仅 surface 失效;`od plugin doctor` 提示 schema driftreplay 仍走旧 snapshot。
- **AG-UI 协议引入时机**Phase 4 的 `@open-design/agui-adapter` 是否值得提前到 Phase 2A使 OD plugin 一开始就能被 CopilotKit / 其他 AG-UI client 消费默认Phase 4v1 优先把内部 `GenUIEvent` union 与 persistence 跑稳,再做外部协议对齐。)
- ~~**AG-UI 协议引入时机**~~**已解决。** `@open-design/agui-adapter``GET /api/runs/:runId/agui` 已作为可选互操作表面交付OD-native GenUI 仍是内部 rendererCopilotKit 不是主产品必需依赖。
## 19. 为什么这是 Open Design 的重要一步
@ -2262,7 +2270,7 @@ plugin 作者在 v1 不应该试图把整段 handoff 都在 OD 内部完成。
1. **`until` 信号词汇表是封闭的**§10.1)。只接受 `critique.score`、`iterations`、`user.confirmed`、`preview.ok`。plugin 想用 `build.passing`、`tests.passing`、`visual_diff.delta < 0.05` 这种自定义信号必须塞进 `critique.score`语义模糊或塞进 `confirmation` surface失去自动化)。Phase 7(§21.4 §23.3.1 patch 1 联合放开这条 —— atom 可以声明自己的命名信号在那之前场景 2 "build/test 通过即收敛"无法 native 表达
2. **atom registry 是封闭的**。`od.pipeline.stages[*].atoms[]` 必须命中 §10 catalogimplemented + reserved。plugin 作者无法注册新 atom 名字。变通:把新能力封成 MCP tool挂到最近的通用 atom`file-write` / `todo-write` / `critique-theater`daemon 发出的 `pipeline_stage_started` / `pipeline_stage_completed` 事件流就丢失这一步的粒度。
3. **GenUI surface kind 在 v1 是封闭枚举**§10.3.1)。只有 `form` / `choice` / `confirmation` / `oauth-prompt`。plugin 作者想要分屏 diff review、画板涂改、3D 预览或任何高保真 UI必须等 Phase 4 的 `od.genui.surfaces[].component` + React 组件 sandbox
3. **核心 GenUI surface kind 在 v1 是封闭枚举**§10.3.1)。产品自有 renderer 只有 `form` / `choice` / `confirmation` / `oauth-prompt` 四类内置 surface。plugin 作者想要分屏 diff review、画板涂改、3D 预览或其他高保真 UI只能在可用时走独立的 gated `od.genui.surfaces[].component` sandbox path不能新增内置 surface kind也不能覆盖主 renderer 的 styling contract
第 1 条对场景 2 影响最大,第 3 条对场景 4 影响最大,第 2 条所有人都受一点影响、但通过 MCP 最容易绕过去。
@ -2281,7 +2289,7 @@ plugin 作者在 v1 不应该试图把整段 handoff 都在 OD 内部完成。
§22 确立了**第三方** plugin 在 v1 substrate 上的可扩展性。本节确立对称性质:**OD 自己写死的流程也可以被重新表达为运行在同一份 substrate 上的一方 plugin**,没有任何只有 OD 能用、第三方触不到的特权路径。plugin substrate 是真正的"地板"OD-the-product 只是它上面的一种配置。
§10.2 已经为 atom 暗示了这条路径:"A future Phase 4 can extract each atom into `skills/_official/<atom>/SKILL.md` to make the system prompt fully data-driven。" 本节把这句承诺落地:明确画出 kernel/userspace 边界,列出需要的 5 项 spec patch并定义新的 `bundled` trust tier
§10.2 最初为 atom 暗示了这条路径;当前实现已经落下 entry slice。Bundled atom plugins 位于 `plugins/_official/atoms/**`bundled scenario plugins 位于 `plugins/_official/scenarios/**`active stage blocks 已经可以从 atom bodies 渲染。本节继续明确 kernel/userspace 边界,并记录在 prompt 与入口选择完全自举前还剩什么
### 23.1 kernel/userspace 边界
@ -2297,19 +2305,22 @@ B 类的清单是**内核边界**:不是"难以 plugin 化",而是"出于安
A 类是 spec 承诺自举之后**会移动**的部分。今天它们大都是 `apps/daemon/src/prompts/system.ts` 里的字符串常量。
### 23.2 v1 已经自举的部分(容易的一半)
### 23.2 v1 已经自举的部分(容易的一半 + 第一批 atom slice
C 类是 v1 已交付的那一半。截至本 spec
C 类是 v1 已交付的那一半A 类的第一批也已经移动。截至本 spec
- 当前 SKILL.md、DESIGN.md、craft .md 由 `apps/daemon/src/skills.ts` / `design-systems.ts` / `craft.ts` 加载§11.3 已把它们重构为委托 `apps/daemon/src/plugins/registry.ts`。Phase 1 之后prompt 中所有进入的行为内容都是 plugin-loaded 的,即使内容本身是 bundled。
- plugin 声明的 MCP server、connector 需求、GenUI surface、`od.pipeline.stages[]` 全部走的是和第三方 plugin 同一套 registry / resolver 路径。
- 驱动 §1 产品 brief 里"一致性"的 active design system + craft 注入,已经是 plugin substrate 的读:一方 DESIGN.md 没有比第三方 DESIGN.md 多任何特权路径。
- `plugins/_official/atoms/**` 下的一方 atom plugins 已经携带 atom SKILL.md body 与 manifest metadata`packages/contracts/src/prompts/atom-block.ts` 可以从这些 bodies 渲染 active stage blocks。
- `plugins/_official/scenarios/**` 下的 bundled scenario plugins 已经携带默认 pipeline 形态,其中包括用于 Home 自由输入 routing / task shaping 的 `od-default`。`packages/plugin-runtime/src/pipeline-fallback.ts` 会在 plugin 省略 `od.pipeline` 时,通过这些 bundled scenarios 解析 applied pipeline。
- `@open-design/agui-adapter``/api/runs/:runId/agui` 提供外部 AG-UI event projection同时不改变 OD 内部 GenUI renderer。
这就是 §22 成立的原因substrate 的前一半已经自举完了,只剩 atom 层和默认 pipeline 选择器还在硬编码。
这就是 §22 成立的原因substrate 已经在 plugin artifacts、snapshots、GenUI declarations、pipeline declarations、bundled scenarios 与第一条 atom-body injection path 上自举。剩余硬编码部分更窄也更偏产品入口OD 基础 designer/discovery prompt、部分 stage-entry 选择逻辑、Home curated scenario rail以及 §22.4 中列出的封闭 signal / surface 词汇表
### 23.3 v1 还在硬编码的部分(完成自举要做的工作)
下面 5 项 spec patch 把 A 类从 daemon 里抽出来。做完之后,整套 prompt + pipeline 都由 plugin manifest 驱动;`composeSystemPrompt()` 变成纯 assembler自身不再携带任何行为内容。
下面这些 patch 继续把 A 类从 daemon 里抽出来。部分已经以 entry slice 形式落地,所以应把它们读成完成清单,而不是“现在还不存在”。做完之后,整套 prompt + pipeline 都由 plugin manifest 驱动;`composeSystemPrompt()` 变成纯 assembler自身不再携带任何行为内容。
#### 23.3.1 Patch 1 —— `od.kind: 'atom'` 的正式契约
@ -2321,15 +2332,15 @@ C 类是 v1 已交付的那一半。截至本 spec
- 可选的 `od.atom.untilSignals[]` 声明该 atom 发出的命名信号变量,贡献到 §10.1 的 `until` 词汇表。这正是 patch 1 同时放开 §22.4 limit 1 的方式:每个 atom 自带信号(例如 `build-test` 声明 `build.passing``tests.passing``until` 求值器对照当前 stage 的 atoms 而非硬编码列表来查表。
- 可选的 `od.atom.toolGating[]` 声明该 atom 期望可用的 agent 端工具file-read/write/edit、bash
#### 23.3.2 Patch 2 —— 把 atom prompt fragment 从 `system.ts` 迁出
#### 23.3.2 Patch 2 —— 完成把 atom prompt fragment 从 `system.ts` 迁出
`apps/daemon/src/prompts/system.ts` 里的字符串常量(`DISCOVERY_AND_PHILOSOPHY`、critique addendum、TodoWrite policy、file-ops policy 等)迁到 `plugins/_official/atoms/<atom>/SKILL.md`。`composeSystemPrompt()` 解析当前 stage 的 `atoms[]`,逐个 atom plugin 读 SKILL.md fragment按 stage 顺序拼接,并加稳定 header`## Active stage: <stage-id>` 后跟 atom fragment
Atom SKILL.md fragments 与 active-stage block renderer 现在已经存在,但迁移尚未完成。`apps/daemon/src/prompts/system.ts` 中剩余的字符串常量(`DISCOVERY_AND_PHILOSOPHY`、base official designer prompt、critique addenda、TodoWrite policy、file-ops policy 等)要么迁到 `plugins/_official/atoms/<atom>/SKILL.md`,要么被显式重新归类为 kernel-level assembly policy。`composeSystemPrompt()` 解析当前 stage 的 `atoms[]`,逐个 atom plugin 读 SKILL.md fragment按 stage 顺序拼接,并加稳定 header`## Active stage: <stage-id>` 后跟 atom fragment
迁移是机械工作:`system.ts` 里每条 prose 常量都有一对一的目的地 `plugins/_official/atoms/<atom>/SKILL.md`。Reviewer 应当拒绝 patch 落地后仍把行为类 prose 常量留在 `system.ts` 里的迁移。
#### 23.3.3 Patch 3 —— 把默认 reference pipeline 迁到 bundled scenario plugin
#### 23.3.3 Patch 3 —— 完成把默认 reference pipeline 迁到 bundled scenario plugin
§10.1 今天写的"`od.pipeline` 缺省时daemon 按 `od.taskKind` 选 reference pipeline" —— 这条逻辑从 daemon 代码迁到 per-`taskKind` 的 bundled scenario plugin
Bundled scenario plugins 与 pipeline fallback resolver 现在已经存在。剩余工作是移除任何仍然手写 default stage list 的产品入口,让它们改为选择 scenario plugin。目标仍然是`od.pipeline` 缺省时daemon / product code 根据 `taskKind` 或显式入口 route 解析一个 bundled scenario plugin然后使用该 scenario 的 `od.pipeline`
- `plugins/_official/scenarios/od-new-generation/open-design.json`
- `plugins/_official/scenarios/od-figma-migration/open-design.json`Phase 6 后)

View file

@ -375,6 +375,36 @@ Each \`<section class="slide" data-screen-label="NN Title">\` is one slide rende
Real copy only no lorem ipsum, no invented metrics, no generic emoji icon rows. If you don't have a value, leave a short honest placeholder.
## Density and overflow discipline (the #1 cause of ugly decks)
Even with the visibility toggle working, slides go ugly when content overflows the 1920×1080 canvas. Specific failure modes that ship today:
- Title slides with a display headline 160px **plus** a multi-line subtitle/deck paragraph **plus** an absolutely-positioned \`.footer\` at \`bottom: ~56px\`. The flow content grows downward, the absolute footer occupies the bottom band, and the two collide in the last ~100px of the slide.
- Stat slides with three numbers + three captions + a footer. Split into three stat slides the framework counts slides for you, more slides cost nothing.
- "Magazine spread" attempts that pack masthead + display headline + body grid + sidebar + absolute footer all into a single 1080px slide.
Rules non-negotiable:
1. **Display headlines on cover/title slides: max ~140px font-size, max 8 words, max 3 lines.** If the headline doesn't fit those bounds, the slide is the wrong shape — split it, don't shrink the font and pack more in.
2. **Reserve a footer safe-zone.** If you use \`.footer { position: absolute; bottom: Npx; }\`, flow content above the footer must stop at least 80px before \`1080 footer_height N\`. Practically: don't let flow content extend into the bottom 200px of the slide. Easiest enforcement: make the slide's main content area its own \`<div style="height: 760px;">\` (or \`max-height\`), and the footer absolute below it.
3. **Body slides: 3 paragraphs, 56ch lead text width, 12 words per line.**
4. **One idea per slide.** Two ideas = two slides.
## Pre-emit self-check run this BEFORE writing the \`<artifact>\` tag
For every \`<section class="slide">\`, mentally render at 1920×1080 and answer:
- [ ] Does the slide's content fit inside the canvas without clipping or overflowing the bottom?
- [ ] If there's an absolutely-positioned footer/header, does flow content stop before the footer's reserved band? (See Rule 2 above.)
- [ ] Is the display headline 140px and 8 words?
- [ ] Does the slide carry one big idea? (No mashed-together masthead + display headline + subtitle + absolute footer + sidebar.)
If any answer is "no", redesign the slide BEFORE emitting. Decks that overflow are the most common single failure mode reported by users; the user has rejected one before and will reject one again.
## Prefer the simple-deck skill's layout vocabulary when reachable
If \`plugins/_official/examples/simple-deck/assets/template.html\` and its \`references/layouts.md\` are readable from the project workspace, **prefer those layouts over inventing your own**. The simple-deck skill ships eight paste-ready slide skeletons (cover, body, big-stat, three-point row, pipeline, dark quote, before/after, closing) with tested type scales, density rules, and a P0/P1/P2 checklist. Re-inventing those layouts is the source of most density / overflow bugs the framework can't catch.
## Canonical skeleton (this is exactly what the file you write looks like)
\`\`\`html

35
plugins/AGENTS.md Normal file
View file

@ -0,0 +1,35 @@
# Plugin Directory Guide
This directory owns Open Design plugin content and plugin authoring material.
## Boundaries
- `plugins/_official/` contains bundled first-party plugins. The daemon boot walker scans only this subtree and registers it as `source_kind='bundled'`.
- `plugins/community/` is the community authoring kit. It is documentation, starter material, and example source for contributors; it must not be treated as an installed first-party catalog.
- Keep runnable plugin examples portable: every example should have a `SKILL.md`; add `open-design.json` only as the OD sidecar.
- Keep `SKILL.md` bodies free of OD-only marketplace metadata. Put OD display, inputs, preview, pipeline, capabilities, and source information in `open-design.json`.
- Do not import app-private code from plugin content. A plugin may reference OD atoms, design systems, craft docs, assets, scripts, MCP servers, or connectors through the manifest.
## Authoring Rules
- New community examples belong under `plugins/community/examples/<plugin-id>/`.
- New first-party bundled plugins belong under `plugins/_official/<tier>/<plugin-id>/` only when the product should auto-register them on daemon startup.
- Use the v1 JSON schema at `docs/schemas/open-design.plugin.v1.json`.
- Prefer TypeScript for project-owned scripts. Avoid adding new `.js`, `.mjs`, or `.cjs` files unless they are generated, vendored, or explicitly allowlisted by `scripts/guard.ts`.
- Keep example plugins concise and agent-readable. Move long reference material to `references/` and tell the agent when to load it.
## Validation
For plugin content changes, run:
```bash
pnpm guard
pnpm --filter @open-design/plugin-runtime typecheck
```
When the daemon CLI is built and available, also validate runnable plugin folders with:
```bash
od plugin validate ./plugins/community/examples/<plugin-id>
```

17
plugins/README.md Normal file
View file

@ -0,0 +1,17 @@
# Open Design Plugins
This directory has two different jobs:
- `_official/` - first-party plugins bundled with Open Design. The daemon scans this tree at startup and registers these plugins as official.
- `community/` - the public authoring kit for people and agents who want to build plugins, test them, publish them, or open a PR back to Open Design.
The common contract is the same everywhere: a plugin is a portable agent skill folder with a `SKILL.md`, plus an optional `open-design.json` sidecar that gives Open Design marketplace metadata, inputs, previews, pipelines, and trust/capability hints.
Start here:
- Community authoring kit: [`community/README.md`](community/README.md)
- Community plugin spec: [`community/SPEC.md`](community/SPEC.md)
- Agent handoff guide: [`community/AGENT-DEVELOPMENT.md`](community/AGENT-DEVELOPMENT.md)
- Full product spec: [`../docs/plugins-spec.md`](../docs/plugins-spec.md)
- Manifest schema: [`../docs/schemas/open-design.plugin.v1.json`](../docs/schemas/open-design.plugin.v1.json)

View file

@ -0,0 +1,79 @@
# Agent Development Handoff
Give this file to a coding agent when you want it to create or improve an Open Design plugin.
## Mission
Create a portable Open Design plugin that can:
1. Run as a normal Agent Skill through `SKILL.md`.
2. Install into Open Design through `open-design.json`.
3. Be validated locally.
4. Be published as an independent open source repo or submitted as a PR to Open Design.
## Required Reading
Read these files before editing:
- `plugins/community/SPEC.md`
- `docs/schemas/open-design.plugin.v1.json`
- `docs/plugins-spec.md` when you need deeper product semantics
- A nearby example under `plugins/community/examples/`
## Build Procedure
1. Choose a lowercase plugin id, for example `import-screenshot-to-prototype`.
2. Create a folder with at least:
```text
<plugin-id>/
SKILL.md
open-design.json
README.md
```
3. Keep the `SKILL.md` portable. It may mention Open Design behavior, but the core workflow must still make sense in any Agent Skills compatible agent.
4. Put OD-specific display, inputs, preview, pipeline, atoms, connectors, and capabilities in `open-design.json`.
5. Add `examples/`, `preview/`, `assets/`, or `references/` only when they materially help the agent produce better results.
6. Add `evals/evals.json` when the plugin has enough behavior to regress.
## Quality Bar
The plugin is not done until:
- `SKILL.md` has a clear "Use this plugin when..." description.
- The workflow states the expected output files or handoff result.
- `open-design.json` validates against the v1 shape.
- The declared atoms are known first-party atoms or clearly marked future work.
- The declared capabilities are the minimum needed.
- Visual plugins include a preview or concrete example output.
- Share, deploy, connector, and network plugins require explicit confirmation before externally visible actions.
## Validation Commands
Run what is available in this environment:
```bash
pnpm guard
pnpm --filter @open-design/plugin-runtime typecheck
```
If the daemon CLI is built:
```bash
od plugin validate ./<plugin-id>
od plugin install ./<plugin-id>
od plugin apply <plugin-id> --input key=value
```
## PR Output
When opening or preparing a PR, include:
- Plugin id and lane.
- What user request should trigger it.
- Files changed.
- Validation commands and results.
- Capabilities requested.
- Screenshots, preview URLs, or example artifacts for visual plugins.

View file

@ -0,0 +1,45 @@
# Contributing Community Plugins
Community plugins can live in this repo as examples, or in their own public repositories with a PR that adds them to a marketplace index.
## Accepted Contributions
- New example plugins under `plugins/community/examples/<plugin-id>/`.
- Improvements to templates, authoring docs, evals, or PR checklists.
- Fixes that make plugin folders more portable across Agent Skills compatible clients.
- Marketplace index updates that point to public plugin repos.
## Review Checklist
Reviewers should check:
- The plugin has a portable `SKILL.md`.
- `open-design.json` does not duplicate the skill body.
- The plugin lane is clear: import, create, export, share, deploy, refine, or extend.
- The output mode is clear for create plugins: prototype, deck, live-artifact, image, video, hyperframes, audio, or design-system.
- Capabilities are minimal.
- Externally visible actions are guarded by user confirmation.
- Visual examples include a preview or concrete output.
- JSON is valid and validation commands are listed in the PR.
## PR Template
```markdown
## Plugin
- ID:
- Lane:
- Mode:
- Source:
## What it does
## Trigger examples
## Capabilities
## Validation
## Screenshots or example outputs
```

View file

@ -0,0 +1,58 @@
# Open Design Community Plugin Kit
This folder is the shareable developer kit for Open Design plugin authors. It is meant to work for a human reading the repo and for an external coding agent such as Claude Code, Codex, Cursor, OpenClaw, Hermes Agent, or another Agent Skills compatible tool.
Open Design plugins follow the same portable shape as Agent Skills: a folder with `SKILL.md` plus optional assets, references, scripts, and examples. Open Design adds `open-design.json` as a sidecar so the same folder can appear in the OD plugin gallery, hydrate the home composer, declare inputs and GenUI surfaces, run an OD atom pipeline, and participate in publish or PR flows.
## Folder Map
- [`SPEC.md`](SPEC.md) - the community-facing spec and taxonomy.
- [`AGENT-DEVELOPMENT.md`](AGENT-DEVELOPMENT.md) - copy this into an external agent session to build and validate a plugin.
- [`CONTRIBUTING.md`](CONTRIBUTING.md) - PR standards for community plugins.
- [`templates/`](templates/) - blank starter files.
- [`examples/`](examples/) - complete example plugin folders and a sample marketplace index.
## What To Build
Workflow lanes:
- Import - Figma, GitHub, code folders, URLs, screenshots, PDFs, PPTX, Framer, Webflow.
- Create - prototypes, slide decks, live artifacts, image assets, video prompts, HyperFrames compositions, audio assets.
- Export - PPTX, PDF, HTML, ZIP, Markdown, Figma handoff, Next.js, React, Vue, Svelte, Astro, Angular, Tailwind.
- Share - public links, GitHub PRs, Gists, Slack, Discord, Notion, Linear, Jira.
- Deploy - Vercel, Cloudflare Pages, Netlify, GitHub Pages, Fly.io, Render.
- Refine - critique, patch, tune, brand swap, A/B variants, stakeholder review.
- Extend - plugin authoring, marketplace publishing, internal catalog automation.
## Five Minute Start
1. Copy `templates/` to a new plugin folder.
2. Rename the folder and frontmatter `name` to a lowercase id such as `launch-deck`.
3. Write a pushy `description` in `SKILL.md`: "Use this plugin when..."
4. Fill `open-design.json`: title, version, tags, `od.taskKind`, `od.mode`, `od.useCase.query`, `od.pipeline`, inputs, and capabilities.
5. Add a small `examples/` or `preview/` artifact if the plugin is visual.
6. Validate locally:
```bash
pnpm guard
pnpm --filter @open-design/plugin-runtime typecheck
```
When the daemon CLI is built:
```bash
od plugin validate ./path/to/plugin
od plugin install ./path/to/plugin
od plugin apply <plugin-id> --input key=value
```
## Compatibility Promise
A folder with `SKILL.md` can be used as a plain skill in Agent Skills compatible clients. Adding `open-design.json` should never make the skill less portable; it only adds Open Design product behavior.
References:
- Agent Skills overview: https://agentskills.io/home
- Agent Skills specification: https://agentskills.io/specification
- Open Design plugin spec: ../../docs/plugins-spec.md

211
plugins/community/SPEC.md Normal file
View file

@ -0,0 +1,211 @@
# Community Plugin Spec
This spec is the compact contract for community Open Design plugins. The canonical product spec remains `docs/plugins-spec.md`; this document is optimized for contributors and external coding agents.
## 1. Minimum Plugin
Every publishable plugin should be a directory with a `SKILL.md`:
```text
my-plugin/
SKILL.md
```
`SKILL.md` is the portable agent contract. It must have YAML frontmatter with:
```yaml
---
name: my-plugin
description: Use this plugin when the user wants...
---
```
The folder name, `name`, and manifest `name` should match. Use lowercase letters, numbers, and hyphens.
## 2. Enriched Open Design Plugin
Add `open-design.json` when the plugin should appear in Open Design as a marketplace card or starter:
```text
my-plugin/
SKILL.md
open-design.json
README.md
preview/
examples/
assets/
references/
evals/
```
`open-design.json` points at the skill and declares the product surface:
```json
{
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
"name": "my-plugin",
"title": "My Plugin",
"version": "0.1.0",
"description": "One sentence marketplace description.",
"license": "MIT",
"tags": ["create", "prototype"],
"compat": {
"agentSkills": [{ "path": "./SKILL.md" }]
},
"od": {
"kind": "skill",
"taskKind": "new-generation",
"mode": "prototype",
"scenario": "product",
"useCase": {
"query": "Create a prototype for {{audience}} about {{topic}}."
},
"pipeline": {
"stages": [
{ "id": "discovery", "atoms": ["discovery-question-form"] },
{ "id": "plan", "atoms": ["direction-picker", "todo-write"] },
{ "id": "generate", "atoms": ["file-write", "live-artifact"] },
{
"id": "critique",
"atoms": ["critique-theater"],
"repeat": true,
"until": "critique.score>=4 || iterations>=3"
}
]
},
"inputs": [
{ "name": "audience", "type": "string", "required": true },
{ "name": "topic", "type": "string", "required": true }
],
"capabilities": ["prompt:inject", "fs:write"]
}
}
```
## 3. Workflow Taxonomy
Use one primary lane. Put the lane in `tags`, `od.scenario`, or `od.mode` so search and facets can classify the plugin.
| Lane | Use when | Typical `taskKind` | Useful atoms |
| --- | --- | --- | --- |
| `import` | Bring external sources into OD | `figma-migration` or `code-migration` | `figma-extract`, `code-import`, `design-extract`, `token-map`, `rewrite-plan` |
| `create` | Generate a new artifact | `new-generation` | `discovery-question-form`, `direction-picker`, `todo-write`, `file-write`, `live-artifact`, `media-image`, `media-video`, `media-audio`, `critique-theater` |
| `export` | Convert an accepted artifact to a downstream format | `tune-collab` or `code-migration` | `file-read`, `file-write`, `handoff`, `diff-review` |
| `share` | Publish or send an artifact to collaborators | `tune-collab` | `file-read`, `handoff`, `connector` |
| `deploy` | Ship an artifact to hosted infrastructure | `code-migration` or `tune-collab` | `file-read`, `build-test`, `handoff`, `connector` |
| `refine` | Improve an existing artifact | `tune-collab` | `file-read`, `patch-edit`, `critique-theater`, `diff-review` |
| `extend` | Help authors create more plugins | `new-generation` | `file-read`, `file-write`, `todo-write`, `critique-theater` |
## 4. Create Modes
Use `od.mode` for the main output surface:
| Mode | Output |
| --- | --- |
| `prototype` | Interactive single-page web artifact |
| `deck` | Slide deck artifact |
| `live-artifact` | Dashboard, report, calculator, simulator, or other live UI |
| `image` | Generated image, storyboard frame, poster, ad, or visual asset |
| `video` | Video prompt, storyboard, rendered clip, or motion package |
| `hyperframes` | HyperFrames-ready HTML motion composition |
| `audio` | Voice, music, sonic branding, or sound-design asset |
| `design-system` | Reusable brand or interface system |
HyperFrames plugins may use `od.mode: "video"` plus a `hyperframes` tag when they should appear beside video tooling, or `od.mode: "hyperframes"` when the distinction matters more than the broad video bucket.
## 5. `SKILL.md` Authoring Rules
- Write the description for activation: "Use this plugin when..."
- Keep `SKILL.md` under 500 lines when possible.
- Put long API notes, visual rules, or exporter details in `references/`.
- Reference support files by relative path from the plugin root.
- Include an explicit workflow with checkpoints and expected outputs.
- Describe what to ask the user only when the input is genuinely missing.
- Avoid OD-only marketplace data in `SKILL.md`; keep it portable.
## 6. Manifest Rules
- `name` is the stable plugin id.
- `version` is required. Use semver when possible.
- `compat.agentSkills[0].path` should point to `./SKILL.md`.
- `od.taskKind` must be one of `new-generation`, `figma-migration`, `code-migration`, or `tune-collab`.
- `od.pipeline.stages[].atoms[]` should use known first-party atoms unless the plugin clearly targets a future OD release.
- A repeated stage must include `until`.
- `od.capabilities` should start small. Restricted installs get `prompt:inject` by default.
Known v1 capabilities:
- `prompt:inject`
- `fs:read`
- `fs:write`
- `mcp`
- `subprocess`
- `bash`
- `network`
- `connector`
- `connector:<id>`
## 7. Inputs And GenUI
Use `od.inputs` for simple apply-time values. Use `od.genui.surfaces[]` when the agent needs controlled human input during a run.
Built-in GenUI surface kinds:
- `form`
- `choice`
- `confirmation`
- `oauth-prompt`
Persistence options:
- `run` - only this run.
- `conversation` - future turns in the same conversation.
- `project` - future runs in the same project.
## 8. Examples And Preview
Visual plugins should include one of:
- `preview/index.html`
- `preview/poster.png`
- `preview/demo.mp4`
- `examples/<case>/index.html`
- `examples/<case>/README.md`
The preview should show the real output shape, not a decorative splash screen.
## 9. Evals
Add `evals/evals.json` for repeatable quality checks:
```json
{
"skill_name": "my-plugin",
"evals": [
{
"id": "happy-path",
"prompt": "Create a prototype for a B2B SaaS onboarding flow.",
"expected_output": "A usable HTML artifact with states, polished layout, and no text overflow.",
"assertions": [
"The output includes a runnable artifact file",
"The visual hierarchy is clear",
"The workflow has meaningful empty/loading/success states"
]
}
]
}
```
Also add `evals/trigger-queries.json` for activation testing when the description is easy to over-broaden.
## 10. Publish And PR
Before opening a PR:
1. Validate JSON syntax.
2. Run `pnpm guard`.
3. Run `pnpm --filter @open-design/plugin-runtime typecheck`.
4. If available, run `od plugin validate ./path/to/plugin`.
5. Include one screenshot, rendered preview, or example output when the plugin is visual.
6. Explain trust and capabilities in the PR body.

View file

@ -0,0 +1,21 @@
# Community Plugin Examples
These examples are source material for plugin authors. They are not bundled first-party plugins and are not scanned by the daemon at startup.
Coverage:
- `import-screenshot-to-prototype` - import lane.
- `create-prototype-dashboard` - prototype create mode.
- `create-slides-pitch` - slide deck create mode.
- `create-live-artifact-ops` - live artifact create mode.
- `create-image-campaign` - image create mode.
- `create-video-storyboard` - video create mode.
- `create-hyperframes-launch` - HyperFrames create mode.
- `export-nextjs-handoff` - export lane.
- `share-github-pr` - share lane.
- `deploy-vercel-static` - deploy lane.
- `refine-critique-loop` - refine lane.
- `extend-plugin-author` - extend lane.
Use the examples as copyable patterns, then trim aggressively for your actual plugin.

View file

@ -0,0 +1,87 @@
{
"$schema": "https://open-design.ai/schemas/marketplace.v1.json",
"name": "open-design-community-examples",
"owner": {
"name": "Open Design Community",
"url": "https://github.com/nexu-io/open-design"
},
"metadata": {
"description": "Example community plugin catalog for authoring and PR workflows.",
"version": "0.1.0"
},
"plugins": [
{
"name": "import-screenshot-to-prototype",
"source": "./import-screenshot-to-prototype",
"version": "0.1.0",
"tags": ["import", "screenshot", "prototype"]
},
{
"name": "create-prototype-dashboard",
"source": "./create-prototype-dashboard",
"version": "0.1.0",
"tags": ["create", "prototype", "dashboard"]
},
{
"name": "create-slides-pitch",
"source": "./create-slides-pitch",
"version": "0.1.0",
"tags": ["create", "deck", "slides"]
},
{
"name": "create-live-artifact-ops",
"source": "./create-live-artifact-ops",
"version": "0.1.0",
"tags": ["create", "live-artifact", "dashboard"]
},
{
"name": "create-image-campaign",
"source": "./create-image-campaign",
"version": "0.1.0",
"tags": ["create", "image", "campaign"]
},
{
"name": "create-video-storyboard",
"source": "./create-video-storyboard",
"version": "0.1.0",
"tags": ["create", "video", "storyboard"]
},
{
"name": "create-hyperframes-launch",
"source": "./create-hyperframes-launch",
"version": "0.1.0",
"tags": ["create", "video", "hyperframes"]
},
{
"name": "export-nextjs-handoff",
"source": "./export-nextjs-handoff",
"version": "0.1.0",
"tags": ["export", "nextjs", "handoff"]
},
{
"name": "share-github-pr",
"source": "./share-github-pr",
"version": "0.1.0",
"tags": ["share", "github-pr", "pull-request"]
},
{
"name": "deploy-vercel-static",
"source": "./deploy-vercel-static",
"version": "0.1.0",
"tags": ["deploy", "vercel", "static"]
},
{
"name": "refine-critique-loop",
"source": "./refine-critique-loop",
"version": "0.1.0",
"tags": ["refine", "critique", "patch-edit"]
},
{
"name": "extend-plugin-author",
"source": "./extend-plugin-author",
"version": "0.1.0",
"tags": ["extend", "plugin-authoring", "community-plugin"]
}
]
}

View file

@ -0,0 +1,29 @@
# Plugin Title
One paragraph explaining what this plugin helps a user do.
## Use Cases
- Example request 1.
- Example request 2.
- Example request 3.
## Try It
```bash
od plugin validate .
od plugin install .
od plugin apply plugin-id --input artifact=prototype --input audience=founders --input topic=onboarding
```
## Files
- `SKILL.md` - portable agent instructions.
- `open-design.json` - Open Design marketplace and apply metadata.
- `examples/` - sample output or fixture prompts.
- `evals/` - repeatable quality checks.
## Capabilities
Explain why each requested capability is needed.

View file

@ -0,0 +1,40 @@
---
name: plugin-id
description: Use this plugin when the user wants to ...
license: MIT
metadata:
author: your-name-or-org
version: "0.1.0"
---
# Plugin Title
## When To Use
Use this plugin for ...
Do not use it for ...
## Workflow
1. Confirm the user's goal and required inputs.
2. Inspect any provided source files or artifacts.
3. Create a concise plan.
4. Generate or transform the artifact.
5. Validate the result.
6. Return the output path, preview, or handoff summary.
## Output Contract
Produce:
- `<artifact-name>` - describe the primary output.
- `summary.md` - explain what changed, if useful.
## Quality Checks
- The output is usable without hidden follow-up work.
- Text does not overflow its containers.
- Visual work has a coherent hierarchy and polished spacing.
- Any external publish, share, or deploy action is confirmed by the user first.

View file

@ -0,0 +1,26 @@
{
"skill_name": "plugin-id",
"evals": [
{
"id": "happy-path",
"prompt": "Create a polished artifact for a realistic user request.",
"expected_output": "A usable artifact plus a concise summary.",
"assertions": [
"The expected output file exists",
"The output directly addresses the user request",
"The output includes no obvious placeholder text",
"The final response names the produced artifact"
]
},
{
"id": "missing-inputs",
"prompt": "Create the artifact, but omit one critical input.",
"expected_output": "The agent asks only for the missing required input or chooses a safe default when the plugin allows it.",
"assertions": [
"The agent does not fabricate external facts",
"The follow-up question is specific and minimal"
]
}
]
}

View file

@ -0,0 +1,45 @@
{
"$schema": "https://open-design.ai/schemas/plugin.v1.json",
"name": "plugin-id",
"title": "Plugin Title",
"version": "0.1.0",
"description": "One sentence marketplace description.",
"license": "MIT",
"tags": ["create", "prototype"],
"compat": {
"agentSkills": [{ "path": "./SKILL.md" }]
},
"od": {
"kind": "skill",
"taskKind": "new-generation",
"mode": "prototype",
"scenario": "general",
"useCase": {
"query": "Create {{artifact}} for {{audience}} about {{topic}}."
},
"context": {
"skills": [{ "path": "./SKILL.md" }],
"atoms": ["discovery-question-form", "todo-write", "file-write", "critique-theater"]
},
"pipeline": {
"stages": [
{ "id": "discovery", "atoms": ["discovery-question-form"] },
{ "id": "plan", "atoms": ["todo-write"] },
{ "id": "generate", "atoms": ["file-write"] },
{
"id": "critique",
"atoms": ["critique-theater"],
"repeat": true,
"until": "critique.score>=4 || iterations>=3"
}
]
},
"inputs": [
{ "name": "artifact", "type": "string", "required": true, "label": "Artifact" },
{ "name": "audience", "type": "string", "required": true, "label": "Audience" },
{ "name": "topic", "type": "string", "required": true, "label": "Topic" }
],
"capabilities": ["prompt:inject", "fs:write"]
}
}