7.2 KiB
7.2 KiB
# 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——守护进程的连接器适配器会将 endpoint 和 params 重写为实际的提供商调用。其他连接器(Linear、Stripe、Posthog)遵循相同的形态,使用各自提供商特有的 endpoint 字符串。
解析顺序(守护进程的执行流程)
-
从工件目录读取
connectors.json。 -
在 Composio 目录中查找
bindings[primary].provider。 -
根据守护进程的
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。
工件本身从不直接打开这些路径——它始终通过守护进程的轮询端点进行访问,守护进程会强制执行正确的查找顺序。切勿在工件中读取令牌。
- 当设置了
-
对于每个
reads[].endpoint,守护进程使用解析后的认证信息构建实际的 HTTP 请求,并从已解析的media-config.json#<provider>.*值中替换${...}占位符。 -
将响应缓存
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,并在页脚显示提示,请用户扩展连接器目录。