webagent — Sitemap Tree(給 agent 用的網站結構)

在 20+ 頁的網站上,agent 不能每次都重 read DOM 摸索。Sitemap 是「網站給 agent 看的地圖」— 靜態定義,host 在 config 提供。

為什麼要 tree(不是 flat list)

之前 sitemap: SitemapEntry[] 是平的:

sitemap: [
  { path: '/orders', description: 'Orders' },
  { path: '/billing', description: 'Billing' },
]

對小網站 OK,對 20+ 頁不夠

  • agent 無法判斷層級關係(/orders/:id/orders 的子頁)
  • 沒有 auth gate 資訊(agent 衝去 /billing 才發現要登入)
  • 沒有「在這頁可以做什麼」的線索(actions: refund / ship)

升級成 tree

SitemapNode 結構

import type { SitemapNode } from '@perhapxin/webagent';

const sitemap: SitemapNode = {
  title: 'Acme',
  description: '電商後台',
  children: {
    'login': { title: 'Sign in' },
    'dashboard': {
      title: 'Dashboard',
      requiresAuth: true,
      actions: ['view'],
    },
    'orders': {
      title: 'Orders',
      description: '訂單列表',
      requiresAuth: true,
      actions: ['view', 'filter', 'export'],
      keywords: ['order', 'invoice', 'sales', '訂單'],
      children: {
        'new': {
          title: 'New order',
          requiresAuth: true,
          actions: ['create'],
        },
        ':id': {              // ← 動態 segment
          title: 'Order detail',
          requiresAuth: true,
          actions: ['view', 'edit', 'cancel', 'refund', 'ship'],
          notes: 'Refund 必須先 ship 才能做',
        },
      },
    },
    'settings': {
      title: 'Settings',
      requiresAuth: true,
      children: {
        'profile': { title: 'Profile' },
        'billing': {
          title: 'Billing',
          requiresAuth: true,
          requiresAdmin: true,
          actions: ['view', 'upgrade', 'cancel'],
        },
      },
    },
  },
};

new WebAgent({ llm, sitemap });

Sitemap 怎麼進入 agent prompt

prompt.ts 把 tree 序列化成 LLM 友善的縮排大綱(用 serializeSitemap()):

## Site map
/                        Acme — 電商後台
  /login                 Sign in
  /dashboard             Dashboard [auth]
    actions: view
  /orders                Orders [auth] — 訂單列表
    actions: view, filter, export
    keywords: order, invoice, sales, 訂單
    /orders/new          New order [auth]
      actions: create
    /orders/:id          Order detail [auth]
      actions: view, edit, cancel, refund, ship
      note: Refund 必須先 ship 才能做
  /settings              Settings [auth]
    /settings/profile    Profile
    /settings/billing    Billing [auth,admin]
      actions: view, upgrade, cancel

Agent 看完這份就知道:

  • 哪些頁需要登入(看 [auth] flag)
  • 在每頁可以做什麼(看 actions)
  • 「我要 refund」→ 該去 /orders/:id,且要先 ship
  • 動態 segment 用 :id 表達,agent 知道要塞具體值

Helper API

import {
  serializeSitemap,
  resolvePath,
  searchSitemap,
  listAuthGatedPaths,
  SAAS_SITEMAP_TEMPLATE,
} from '@perhapxin/webagent';

// 1. 序列化成 LLM 字串
const text = serializeSitemap(sitemap);

// 2. 從具體 URL 反向查(含 :param resolution)
const r = resolvePath(sitemap, '/orders/12345');
// → { path: '/orders/12345', node: ...orderDetailNode, params: { id: '12345' } }

// 3. 搜尋(用戶說「我要看帳單」→ 找對應路徑)
const matches = searchSitemap(sitemap, '帳單');
// → [{ path: '/settings/billing', node: ..., params: {} }]

// 4. 列所有 auth-gated 路徑
const gated = listAuthGatedPaths(sitemap);
// → ['/dashboard', '/orders', '/orders/new', '/orders/:id', '/settings', ...]

// 5. 通用模板(host 改一改就能用)
const sitemap = SAAS_SITEMAP_TEMPLATE;  // 內附 marketing / auth / orders / settings 結構

跟 webagent 的整合

Agent 收到 task 後,在「思考」階段:

  1. 先看 sitemap(serializeSitemap 的結果已在 system prompt)
  2. searchSitemap 找最相關的頁面(host 端在 ask_user 前可選擇主動先 resolve)
  3. 如果目標頁 requiresAuth 且當前未登入 → 先去 /login(agent 看到 sitemap 自然會這樣計畫)

通用模板(給 host 套用)

import { SAAS_SITEMAP_TEMPLATE } from '@perhapxin/webagent';

// 直接用
const sitemap = SAAS_SITEMAP_TEMPLATE;

// 或基於模板擴充
const sitemap = {
  ...SAAS_SITEMAP_TEMPLATE,
  children: {
    ...SAAS_SITEMAP_TEMPLATE.children,
    'reports': {     // 加自己的
      title: 'Reports',
      requiresAuth: true,
      actions: ['view', 'export', 'schedule'],
    },
  },
};

模板已含:marketing / auth / dashboard / orders (list+new+detail) / settings (profile+team+billing+api-keys)。

不在 v1 做的(避免 over-engineering)

  • ❌ Runtime sitemap 學習(agent 走過自動補)— v2 看需求
  • ❌ Sitemap auto-crawler(從 nav links 推斷)— v2 看需求
  • ❌ 跨 sitemap 圖搜尋演算法(Dijkstra 等)— 用 LLM 自己 reasoning 比較自然

複雜場景(大型 multi-product 平台)host 可以自己:

  • 寫多個 sitemap 切換
  • systemPrompt 補強策略提示
  • 用 dynamic skill 在 runtime 動態送 sitemap 給 agent

Skill 場景:palette as tool 配 sitemap

dddk.palette.addItem({
  id: 'goto-page',
  name: '前往頁面',
  prefix: ['goto:', '@'],
  prefixAcceptsAnyArg: true,
  agentTool: {
    description: 'Navigate to a page by name. Use sitemap to resolve.',
    parameters: { name: 'string' },
  },
  handler: async (palette, name) => {
    const matches = searchSitemap(sitemap, name ?? '');
    const top = matches[0];
    if (top) {
      palette.close();
      router.push(top.path);
    }
  },
});

Agent 想去某頁但不確定 URL → 呼叫 palette_goto_page({ name: '帳單' }) → palette 用 sitemap 找出 /settings/billing → host router 跳轉。這就是 palette + sitemap + agent 三件套的協作

寫完 docs/13-pieces.md 和 docs/10-sitemap-tree.md 給未來參考。