Files
2026-07-13 21:35:48 +08:00

7.2 KiB
Raw Permalink Blame History

# Connectors

`live-dashboard` 是一个**动态工件**。它显示的值并非硬编码——而是在运行时从连接器轮询获取。OD 守护进程(0.4.0+)附带一个 Composio 连接器目录,以及一份 `connectors.json` 契约文件,工件会随 `index.html` 一同生成该文件。

当 `inputs.connector === mock`(或守护进程无法解析已配置的连接器)时,工件会回退到预设的样本数据。这保证了截图、选择器预览和离线使用的正常运作。

> **状态说明——与 `skills/live-artifact/` 的关系。**
>
> 当前正式发布中的动态工件契约规范位于
> [`skills/live-artifact/SKILL.md`](../../live-artifact/SKILL.md):它采用**文件形态**`artifact.json` + `template.html` + `data.json` +
> `provenance.json`),在代理端则采用 **CLI 形态**(代理调用
> `"$OD_NODE_BIN" "$OD_BIN" tools live-artifacts {create,update}` 和
> `tools connectors {list,execute}` 而非 HTTP)。渲染器是仅支持标量的 `html_template_v1``apps/daemon/src/live-artifacts/render.ts`)。
>
> `live-dashboard` 是一个**互补的**浏览器运行时变体:
> 工件以单个自包含 HTML 页面形式渲染,而动态行为(打开时刷新、手动刷新、自动刷新、过期标记)在页面内运行,而非在模板渲染时处理。因此轮询需要一种 HTTP 形态,这正是本文件其余部分所描述的内容(`POST /api/od/connectors/poll`)。
>
> 请将下面的 HTTP 形态视为一份**前瞻性提案**,它与文件/CLI 契约并行存在:守护进程目前尚未暴露
> `POST /api/od/connectors/poll``apps/daemon/src/server.ts` /
> `apps/daemon/src/live-artifacts/`),因此开箱即用时工件会使用预设的样本数据进行渲染,刷新按钮仅对模拟数据进行过渡动画。当守护进程团队的路由就绪后,只需将模板中的 `seedNextChange()` 替换为本文档中说明的 `poll()`
> 辅助函数即可——`connectors.json` 形态已经是一份可用的声明式事实来源,下游工具(动态工件 CLI、MCP 封装、审计日志)今天即可读取。

---

## `connectors.json` 模式

在项目根目录下,`index.html` 旁边生成一个 `connectors.json`

```json
{
  "schema": "od.connector/1",
  "primary": "notion",
  "freshness": {
    "auto_refresh_seconds": 30,
    "warn_after_seconds": 90,
    "fail_after_seconds": 600
  },
  "bindings": {
    "notion": {
      "provider": "composio.notion",
      "auth_ref": "media-config.json#notion.token",
      "reads": [
        {
          "id": "tasks_active",
          "endpoint": "databases.{id}.query",
          "params": { "id": "${notion.tasks_db_id}",
                      "filter": { "property": "Status", "status": { "does_not_equal": "Done" } },
                      "sorts":  [{ "property": "Updated", "direction": "descending" }] },
          "shape": "task[]"
        },
        {
          "id": "kpi_total",
          "endpoint": "databases.{id}.query",
          "params": { "id": "${notion.tasks_db_id}", "page_size": 1 },
          "extract": "$.metadata.total_count",
          "shape": "integer"
        },
        {
          "id": "activity_recent",
          "endpoint": "search",
          "params": { "filter": { "property": "object", "value": "page" },
                      "sort":   { "direction": "descending", "timestamp": "last_edited_time" },
                      "page_size": 8 },
          "shape": "activity[]"
        }
      ],
      "events": [
        { "id": "task_changed", "type": "page.updated", "selector": "$.tasks_db_id" }
      ]
    }
  }
}

该形态刻意贴近 Notion 的 REST API——守护进程的连接器适配器会将 endpointparams 重写为实际的提供商调用。其他连接器(Linear、Stripe、Posthog)遵循相同的形态,使用各自提供商特有的 endpoint 字符串。


解析顺序(守护进程的执行流程)

  1. 从工件目录读取 connectors.json

  2. 在 Composio 目录中查找 bindings[primary].provider

  3. 根据守护进程的 media-config.json 解析 auth_ref。实际的查找过程是环境感知的(参见 apps/daemon/src/media-config.ts 中的 configFile(),优先级从高到低):

    • 当设置了 OD_MEDIA_CONFIG_DIR 环境变量时,使用 <OD_MEDIA_CONFIG_DIR>/media-config.json
    • 否则,当设置了 OD_DATA_DIR 时,使用 <OD_DATA_DIR>/media-config.json(相对路径锚定到当前项目根目录,$HOME~ 简写会被展开);
    • 否则,使用当前项目的 <projectRoot>/.od/media-config.json

    工件本身从不直接打开这些路径——它始终通过守护进程的轮询端点进行访问,守护进程会强制执行正确的查找顺序。切勿在工件中读取令牌。

  4. 对于每个 reads[].endpoint,守护进程使用解析后的认证信息构建实际的 HTTP 请求,并从已解析的 media-config.json#<provider>.* 值中替换 ${...} 占位符。

  5. 将响应缓存 freshness.auto_refresh_seconds 秒。Refresh 按钮会发出一个绕过缓存的显式轮询请求。


index.html 中的接入方式

工件直接调用 Composio,而是调用 OD 守护进程的本地代理:

async function poll(readId) {
  const res = await fetch(`/api/od/connectors/poll`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ project: "<project_id>", read: readId })
  });
  if (!res.ok) throw new Error(`poll ${readId} failed: ${res.status}`);
  return res.json();
}

<project_id> 由守护进程在渲染时通过一个 <meta name="od:project" content="..."> 标签注入。工件在挂载时读取一次。


回退行为

  • fetch 错误时:保留之前渲染的值,将动态标记切换为琥珀色的 Stale · <ago>,在页脚写入一行灰色小提示(Source: Notion · last good poll 4 min ago)。
  • inputs.connector === mock 时:完全跳过 poll(),使用 index.html 中的 seedMock() 函数。动态标记显示灰色的 Sample data,不带圆点动画。
  • 401/403 错误时:显示一次性提示 Reconnect Notion in Settings → Connectors,并停止后续轮询,直到用户手动点击刷新。

各提供商速查表

Connector provider 单行数据形态 典型 KPI
Notion composio.notion task = {title, status, assignee, due, prio, updated} 总任务 · 本周完成 · 成员 · 待审
Linear composio.linear issue = {title, state, assignee, priority, updated} 积压 · 进行中 · 阻塞 · 迭代进度
Stripe composio.stripe event = {type, amount, customer, created} MRR · 流失 · 新订阅 · 退款
Posthog composio.posthog event = {name, distinct_id, $current_url, ts} DAU · 注册 · 功能采用 · 错误

不要自行发明各提供商的数据形态。如果用户需要上表中未列出的内容,请回退到 mock,并在页脚显示提示,请用户扩展连接器目录。