/** * Skill preferences — declarative per-skill settings. * * When a skill declares preferences, dddk: * 1. Refuses to run the skill until all `required: true` prefs are filled. * 2. Auto-renders a setup form (as a Surface) the first time. * 3. Stores values keyed by skill id via the dddk StorageAdapter. * 4. Provides `ctx.getPreferences()` inside the skill handler. */ type PreferenceKind = 'text' | 'password' | 'number' | 'checkbox' | 'select'; interface PreferenceField { name: string; title: string; kind: PreferenceKind; description?: string; required?: boolean; default?: unknown; placeholder?: string; /** For 'select' kind. */ options?: Array<{ value: string; label: string; }>; } /** * Skill type definitions — 4 kinds: Script / Prompt / Action / Surface * See ../../docs/05-skills-sdk.md for the full design. */ interface ScriptStep { page?: string; subtitle?: string; action?: (tools: SkillTools) => void | Promise; waitForUser?: boolean; } interface SkillTools { navigate(path: string): void; highlight(selector: string, color?: string, label?: string): string; border(selector: string, color?: string, label?: string): string; spotlight(selector: string): string; inject(selector: string, text: string, position?: 'before' | 'after'): string; subtitle(text: string): void; clearOverlays(): void; ask(question: string): Promise; wait(ms: number): Promise; llm?(prompt: string): Promise; runSkill?(id: string, vars?: Record): Promise; /** * Show an ad-hoc Surface (form / dialog / picker) mid-script and await the * user's submission. Resolves with the form data when the host calls * `dddk.submitSurface(data)`, or `null` if the user cancels via * `dddk.cancelSurface()` / Esc. * * The surface argument is a `PieceSurface` (root PieceNode + optional data). * `placement` defaults to `'modal'` (full-screen popup); use `'subtitle'` * for the subtitle bar, `'dock'` for a persistent side panel. */ surface(surface: unknown, opts?: { placement?: SurfacePlacement; }): Promise | null>; } /** * Surface placement (revised 2026-05-22). * * - `palette` — render inside an active PanelSkill's content area * - `subtitle` — render in the existing subtitle bar (lightest, ignorable) * - `dock` — persistent side panel, user can minimize/close * - `modal` — full-screen popup with backdrop (canonical "popup", supports forms) */ type SurfacePlacement = 'palette' | 'subtitle' | 'dock' | 'modal'; interface BaseSkill { id: string; name: string; description?: string; icon?: string; /** Hide from palette listing (still callable). */ hidden?: boolean; /** Function returning whether this skill is currently visible to the user. */ visible?: (ctx: { user?: unknown; }) => boolean; /** * Declarative preferences. If any `required: true` field is unfilled, * dddk auto-renders a setup Surface form before dispatching the skill. * See ./preferences.ts */ preferences?: Array; } interface ScriptSkill extends BaseSkill { type: 'script'; steps: ScriptStep[]; } interface PromptSkill extends BaseSkill { type: 'prompt'; /** System prompt or prompt template. Use `{{var}}` placeholders. */ prompt: string; variables?: Record; } interface ActionSkillContext { palette: { close(): void; replace(items: Array<{ id: string; name: string; handler: () => void; }>): void; }; subtitle: { show(opts: { text: string; type?: string; autoHide?: number; }): void; hide(): void; }; storage: { get(key: string): T | null; set(key: string, value: unknown): void; }; /** Read this skill's declared preferences (empty object if no schema). */ getPreferences>(): T; llm?(prompt: string): Promise; agent?(task: string): void; navigate(path: string): void; } interface ActionSkill extends BaseSkill { type: 'action'; handler: (ctx: ActionSkillContext) => void | Promise; } interface SurfaceSkillContext extends ActionSkillContext { } interface SurfaceSkill extends BaseSkill { type: 'surface'; build: (ctx: SurfaceSkillContext) => Promise; onSubmit?: (data: Record, ctx: SurfaceSkillContext) => Promise | unknown; } interface PanelSkillContext extends ActionSkillContext { /** Render a new PieceSurface into the panel area. */ render(surface: unknown): void; /** Pop this panel skill off the navigation stack. */ back(): void; /** Update the input box's placeholder. */ setPlaceholder(text: string): void; } interface PanelSkill extends BaseSkill { type: 'panel'; /** * `palette` — keep slash-command routing + ask-AI fallback; free-text goes * to onInput. Best for search / recommend / qa / classify. * `takeover` — input is purely passed to onInput. No command parsing. * Best for chat / continuous webagent. */ inputMode: 'palette' | 'takeover'; /** Custom placeholder for the input box while in this skill. */ inputPlaceholder?: string; /** Called once when the skill is entered (empty input). */ onEnter?: (ctx: PanelSkillContext) => Promise | unknown; /** Called on every input change (debounced by the palette renderer). */ onInput: (text: string, ctx: PanelSkillContext) => Promise | unknown; /** Called when the user submits (Enter). Optional; many panels use onInput live. */ onSubmit?: (text: string, ctx: PanelSkillContext) => Promise | unknown; /** Called when the user activates a piece action inside the panel. */ onAction?: (action: string, data: unknown, ctx: PanelSkillContext) => Promise | unknown; /** Called when the skill is popped from the stack. */ onLeave?: () => Promise | void; } type Skill = ScriptSkill | PromptSkill | ActionSkill | SurfaceSkill | PanelSkill; /** * SkillRegistry — register / lookup / match skills by id or `/command`. */ declare class SkillRegistry { private skills; constructor(initial?: Skill[]); register(skill: Skill): void; unregister(id: string): void; get(id: string): Skill | undefined; list(): Skill[]; listAll(): Skill[]; /** Match palette input like `/introduce` or `/translate en` to a skill id. */ match(input: string): Skill | undefined; /** Extract args after the skill name. e.g. `/translate en` → 'en' */ parseArgs(input: string): string; /** Resolve `{{var}}` placeholders in a PromptSkill template. */ resolvePrompt(skill: PromptSkill, vars?: Record): string; } /** * multi-step transaction skill pattern. * * Some product flows aren't a one-shot agent task. They're transactions: * step 1 needs to land BEFORE step 2 runs (e.g. create draft → add line * items → submit), and if step 3 fails the host wants to roll back what * steps 1+2 did. * * Existing `ScriptSkill.steps` runs forward only. This module adds a * `TransactionStep` extension where each step can: * - declare an `assert` predicate that gates progress to the next step * (returns false → step is treated as failed) * - declare a `compensate` rollback handler — on later failure, the * runner replays compensators of completed steps in REVERSE order * * Use: * * import { runTransaction } from '@perhapxin/dddk'; * * const outcome = await runTransaction({ * tools, // SkillTools from a Script skill * steps: [ * { * name: 'create_draft', * action: async (t) => t.navigate('/orders/new'), * assert: () => location.pathname.startsWith('/orders/new'), * compensate: async (t) => t.navigate('/orders'), * }, * { * name: 'add_line', * action: async (t) => t.navigate('/orders/new/line'), * assert: () => document.querySelector('.line-row') != null, * compensate: async () => { console.info('cleanup line-add'); }, * }, * ], * }); * * if (!outcome.ok) { * console.warn('transaction failed:', outcome.failedStep, outcome.error); * // outcome.compensated lists the names of compensators that ran. * } * * Compensators are best-effort: a throwing compensator is logged and * the runner continues to the next one, so a partial rollback is * better than no rollback. */ interface TransactionStep { name: string; action: (tools: SkillTools) => void | Promise; /** Optional gate — return true to advance, false to treat the step as * failed. Default = true (no gate). Synchronous only for now. */ assert?: () => boolean; /** Rollback handler. Runs in reverse order on later failure. */ compensate?: (tools: SkillTools) => void | Promise; /** Delay (ms) after the action before evaluating `assert`. Default 80. */ settleMs?: number; } interface TransactionOpts { tools: SkillTools; steps: TransactionStep[]; /** Optional signal — if aborted, the runner halts AND replays * compensators for completed steps. */ signal?: AbortSignal; } interface TransactionOutcome { ok: boolean; /** Names of steps whose action completed (and assert passed). */ completed: string[]; /** Set when ok=false: the step that failed. */ failedStep: string | null; /** Set when ok=false: error or reason. */ error: string | null; /** Names of compensators that ran during rollback (reverse order). */ compensated: string[]; } declare function runTransaction(opts: TransactionOpts): Promise; export { type ActionSkill, type ActionSkillContext, type BaseSkill, type PanelSkill, type PanelSkillContext, type PromptSkill, type ScriptSkill, type ScriptStep, type Skill, SkillRegistry, type SkillTools, type SurfacePlacement, type SurfaceSkill, type SurfaceSkillContext, type TransactionOpts, type TransactionOutcome, type TransactionStep, runTransaction };