dddk — Skills SDK

Skill = 可註冊的「dddk 行為單元」。四種類型涵蓋從純文字 prompt 到視覺導覽到 A2UI form。

四種 Skill 類型

1. ScriptSkill — 一連串視覺步驟

最適合 onboarding 導覽 / 教學 / 復演。

import type { ScriptSkill } from '@perhapxin/dddk';

const introduce: ScriptSkill = {
  id: 'introduce',
  type: 'script',
  name: '導覽我們的功能',
  description: '5 分鐘看完所有重點',
  steps: [
    {
      page: '/',
      subtitle: '歡迎!這是首頁。',
      action: (tools) => tools.spotlight('.hero'),
    },
    {
      page: '/pricing',
      subtitle: '這是我們的方案。',
      action: (tools) => tools.highlight('.plans', undefined, '看這裡'),
    },
    {
      page: '/dashboard',
      subtitle: '進來之後會看到儀表板。可以按 ctrl+k 隨時叫我。',
      waitForUser: true,  // 等用戶按 space 才往下
    },
  ],
};

每個 step:

  • page(optional)— 換頁
  • subtitle(optional)— 字幕條給用戶看
  • action(optional)— 視覺操作(highlight / spotlight / border / inject)
  • waitForUser(預設 true)— 等用戶按 space 才下一步

2. PromptSkill — 客製 agent system prompt

最適合「同一種任務反覆做」。

const translate: PromptSkill = {
  id: 'translate',
  type: 'prompt',
  name: '翻譯這頁',
  prompt: '使用 immersive_translate action 把這頁翻譯成 {{language}}。完成後 show_subtitle 告訴用戶完成。',
};

{{variable}} 從用戶輸入抽。

用戶在 palette 輸入 /translate 英文 → variable language = '英文' → 整段 prompt 套進 agent system prompt → agent 跑。

3. ActionSkill — 純 action,不必走 agent

最適合「立即執行、不需要 LLM 判斷」。

const clipboardHistory: ActionSkill = {
  id: 'clipboard-history',
  type: 'action',
  name: '剪貼簿歷史',
  handler: (ctx) => {
    const items = ctx.storage.get('clipboard') ?? [];
    ctx.palette.replace(items.map(i => ({
      id: i.id,
      name: i.text.slice(0, 50),
      handler: () => navigator.clipboard.writeText(i.text),
    })));
  },
};

handler 拿一個 context,可以:

  • ctx.palette — 操作 palette
  • ctx.subtitle — 顯示字幕
  • ctx.storage — 讀 / 存
  • ctx.llm — 需要時叫 LLM
  • ctx.agent — 需要時叫 agent

4. A2UISkill — 開出一份 A2UI surface

最適合「需要結構化輸入 / 顯示結構化結果」。

const orderStatus: A2UISkill = {
  id: 'order-status',
  type: 'a2ui',
  name: '查訂單狀態',
  build: async (ctx) => {
    return {
      version: 'v0.10',
      updateComponents: {
        surfaceId: 'order-status',
        catalogId: 'basic',
        components: [
          { id: 'root', component: 'Card', children: ['title', 'input', 'btn'] },
          { id: 'title', component: 'Heading', text: '查詢訂單' },
          { id: 'input', component: 'TextField', label: '訂單編號', bind: '/orderId' },
          { id: 'btn', component: 'Button', text: '查詢', action: 'submit' },
        ],
      },
    };
  },
  onSubmit: async ({ orderId }, ctx) => {
    const order = await fetch(`/api/orders/${orderId}`).then(r => r.json());
    return {
      // 第二個 surface 顯示結果
      version: 'v0.10',
      updateComponents: { ... },
    };
  },
};

build 產初始 surface,onSubmit 收用戶輸入 → 可以回新 surface(多步表單)或關閉。

SkillRegistry

class SkillRegistry {
  register(skill: Skill): void;
  unregister(id: string): void;
  get(id: string): Skill | undefined;
  list(): Skill[];
  match(command: string): Skill | undefined;  // "/introduce" → skill "introduce"
}

SkillRegistry 是 dddk 內建的,host 透過 config 傳 skills:

new DotDotDuck({
  skills: [introduce, translate, orderStatus, clipboardHistory],
});

或之後動態加:

dddk.skills.register(newSkill);

SkillTools(給 ScriptSkill 用)

interface SkillTools {
  navigate(path: string): void;
  highlight(selector: string, color?: string, label?: string): string;  // 回 overlay id
  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<string>;  // ask user 純文字
  wait(ms: number): Promise<void>;
  llm(prompt: string): Promise<string>;     // 一次性 LLM 呼叫
}

Skill ID 與 palette 整合

Skill id 開頭通常用斜線,因為 palette 把以斜線開頭的輸入當 skill:

用戶在 palette 打 "/introduce"
  → registry.match("/introduce") → 找到 introduce skill
  → dispatch(introduce, args)

不以斜線開頭也行(純 name 搜尋),習慣上 ScriptSkill / PromptSkill 用斜線,ActionSkill / A2UISkill 用 name。

套件附帶哪些 skill?— 零個

dddk 不附任何內建 skill。所有 skill 由企業 / 開發者自己寫,自己 register。

理由:

  • skill 是業務行為,沒有 universal 的「合適預設」
  • 一附帶就要負責維護、文件、i18n
  • 用戶會把它當「應該保留」的東西改不掉

需要寫範例 skill 時,看 10-cookbook-enterprise-skills.md(cookbook 是 doc,不是 export 出去的程式碼)。

多 skill 組合

ScriptSkill 步驟內可以呼叫其他 skill:

{
  page: '/dashboard',
  action: async (tools) => {
    await tools.runSkill('translate', { language: 'zh-TW' });
    tools.subtitle('翻譯完成,繼續導覽');
  },
}

PromptSkill 在 prompt 內也可以引用其他 skill:「先用 /clipboard-history 找最近複製的內容,再 ...」。

企業 cookbook

詳細範例見 10-cookbook-enterprise-skills.md