From b0da53ae3660cad10975420f38e500eb0bf39a42 Mon Sep 17 00:00:00 2001 From: Deniffer Date: Mon, 10 Aug 2026 15:38:04 +0800 Subject: [PATCH] feat: expose conversation capabilities and capture freshness --- README.md | 5 ++++ packages/wbot/README.md | 6 +++++ packages/wbot/platform-client.ts | 5 ++-- packages/wbot/platform-types.ts | 13 ++++++++++ packages/wbot/wbot-agent-tools.bdd.md | 12 +++++++++ packages/wbot/wbot-agent-tools.test.ts | 35 +++++++++++++++++++++++--- 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8645c31..a3d37e3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ WBOT_PLATFORM_URL=https://wbot-api-test.celados.com \ See [the package README](packages/wbot/README.md) for CLI, MCP, credential, and cursor semantics. +Conversation results expose the Tenant's `read` and `send` capabilities. Conversation and message +update results also expose capture freshness, allowing Agents to distinguish quiet conversations +from delayed, unavailable, or insufficient capture evidence without accessing private operator +diagnostics. + ## Public endpoints The Agent CLI and MCP use the production HTTP Actions origin diff --git a/packages/wbot/README.md b/packages/wbot/README.md index 7824cd4..8386b9f 100644 --- a/packages/wbot/README.md +++ b/packages/wbot/README.md @@ -41,6 +41,12 @@ For internal test-deployment dogfood, prefix the same commands with `WBOT_PLATFO History cursors page toward older messages. Updates cursors move forward by Platform ingestion order. Save each updates cursor in the calling Agent and explicitly pass it on the next call; wbot does not store a consumer checkpoint. +Each conversation includes `capabilities` and `captureFreshness`. Capabilities describe the +Tenant's current `read` and `send` authorization, while wbot itself remains read-only. Every +`messages.updates` result, including an empty page, includes capture freshness so an Agent can +distinguish a quiet conversation from delayed, unavailable, or insufficient capture evidence. +`captureFreshness` is operational recency evidence, not a guarantee of complete message history. + ## MCP Start the stdio server with: diff --git a/packages/wbot/platform-client.ts b/packages/wbot/platform-client.ts index 8c3f7cb..27ee4b9 100644 --- a/packages/wbot/platform-client.ts +++ b/packages/wbot/platform-client.ts @@ -3,6 +3,7 @@ import type { ConversationsQueryInput, MessageResult, MessagesHistoryInput, + MessagesQueryResult, MessageSendInput, MessageSendResult, MessagesQueryInput, @@ -14,7 +15,7 @@ import type { export type PlatformClient = { queryConversations: (input?: ConversationsQueryInput) => Promise>; - queryMessages: (input: MessagesQueryInput) => Promise>; + queryMessages: (input: MessagesQueryInput) => Promise; queryMessageHistory: (input: MessagesHistoryInput) => Promise>; sendMessage: (input: MessageSendInput) => Promise; getOutboundSend: (input: OutboundSendGetInput) => Promise; @@ -86,7 +87,7 @@ export const createPlatformClient = (config: PlatformClientConfig): PlatformClie return { queryConversations: (input = {}) => request>("/platform/v1/conversations/query", input), - queryMessages: (input) => request>("/platform/v1/messages/query", input), + queryMessages: (input) => request("/platform/v1/messages/query", input), queryMessageHistory: (input) => request>("/platform/v1/messages/history", input), sendMessage: (input) => request("/platform/v1/messages/send", input), diff --git a/packages/wbot/platform-types.ts b/packages/wbot/platform-types.ts index 93bb290..192ab0a 100644 --- a/packages/wbot/platform-types.ts +++ b/packages/wbot/platform-types.ts @@ -34,12 +34,21 @@ export type ConversationsQueryInput = { limit?: number; }; +export type ConversationCapability = "read" | "send"; + +export type CaptureFreshness = { + status: "unknown" | "current" | "delayed" | "unavailable"; + asOfMs: string | null; +}; + export type ConversationResult = { id: string; channel: string; channelConversationId: string; kind: "direct" | "room"; title: string | null; + capabilities: Array; + captureFreshness: CaptureFreshness; latestMessage: { id: string; direction: "in" | "out"; @@ -98,6 +107,10 @@ export type MessageResult = { }; }; +export type MessagesQueryResult = Page & { + captureFreshness: CaptureFreshness; +}; + export type MessageSendInput = { conversationId: string; requestId: string; diff --git a/packages/wbot/wbot-agent-tools.bdd.md b/packages/wbot/wbot-agent-tools.bdd.md index b802309..04ea128 100644 --- a/packages/wbot/wbot-agent-tools.bdd.md +++ b/packages/wbot/wbot-agent-tools.bdd.md @@ -20,6 +20,7 @@ status: accepted # draft | accepted | superseded - `wbot` Agent-first CLI 与稳定 JSON 结果 - Codex Plugin 与 Claude Code Plugin - 列出获授权会话、向过去读取历史、从显式游标读取新增消息 +- 会话能力与面向 Tenant 的采集新鲜度 - 一次性本地凭据配置、环境变量覆盖与敏感信息保护 - Caller-owned cursor 与无状态恢复 @@ -141,6 +142,8 @@ Given Tenant 拥有多个群聊或 DM 的有效读取授权 When Agent 执行 `wbot conversations list` Then Agent 得到不超过请求上限的会话 And 每个会话包含稳定标识、类型、可用标题与最近消息摘要 +And 每个会话明确返回当前 Tenant 的 read 与可选 send 能力 +And 每个会话返回 unknown、current、delayed 或 unavailable 的采集新鲜度 And 结果包含下一游标与是否仍有更多会话 **场景 3.4:Agent 显式带回会话游标读取下一页** @@ -178,6 +181,7 @@ Given Tenant 拥有目标会话的有效读取授权 When Agent 不带更新游标读取该会话 Then Agent 得到一个有界的当前消息页 And 结果包含后续读取新增消息所需的更新游标 +And 结果即使没有消息也包含采集新鲜度 **场景 5.2:带回更新游标后只返回后续入库消息** Given Agent 持有先前返回的更新游标 @@ -212,6 +216,14 @@ When Agent 使用旧游标读取消息 Then Platform 拒绝请求 And 不返回任何消息内容 +**场景 5.7:空更新页区分安静会话与采集异常** +Given Agent 使用更新游标读取一个暂时没有新增消息的会话 +When Platform 返回空消息页 +Then 结果仍包含采集状态和可证明时的最近成功检查时间 +And Agent 可以区分 current、delayed、unavailable 与 unknown +And 结果不根据最后消息时间推断采集健康 +And 结果不暴露设备、checkpoint、watermark 或 Operator 诊断 + ## 功能 6:Codex 与 Claude Code Plugin 提供等价能力 **场景 6.1:Codex Plugin 安装后提供 wbot 工具** diff --git a/packages/wbot/wbot-agent-tools.test.ts b/packages/wbot/wbot-agent-tools.test.ts index a6bd282..c379de9 100644 --- a/packages/wbot/wbot-agent-tools.test.ts +++ b/packages/wbot/wbot-agent-tools.test.ts @@ -151,7 +151,18 @@ describe("功能 2:CLI 与 MCP 复用安全的本机凭据", () => { describe("功能 3 至 5:CLI 与 MCP 保持显式分页语义", () => { test("场景 3.1:CLI 成功结果是 stdout 中唯一的 JSON", async () => { const platform = await createFakePlatformServer({ - items: [], + items: [ + { + id: "room-1", + channel: "wechat", + channelConversationId: "opaque-room-1", + kind: "room", + title: "Founders Circle", + capabilities: ["read", "send"], + captureFreshness: { status: "current", asOfMs: "1786000000000" }, + latestMessage: null, + }, + ], nextCursor: "next-conversation", hasMore: false, }); @@ -164,7 +175,12 @@ describe("功能 3 至 5:CLI 与 MCP 保持显式分页语义", () => { expect(result.exitCode, result.stderr).toBe(0); expect(result.stderr).toBe(""); expect(JSON.parse(result.stdout)).toEqual({ - items: [], + items: [ + expect.objectContaining({ + capabilities: ["read", "send"], + captureFreshness: { status: "current", asOfMs: "1786000000000" }, + }), + ], nextCursor: "next-conversation", hasMore: false, }); @@ -180,7 +196,12 @@ describe("功能 3 至 5:CLI 与 MCP 保持显式分页语义", () => { }, queryMessages: async (input) => { calls.push({ kind: "updates", input }); - return { items: [], nextCursor: "updates-next", hasMore: false }; + return { + items: [], + nextCursor: "updates-next", + hasMore: false, + captureFreshness: { status: "delayed", asOfMs: "1785999800000" }, + }; }, }), ); @@ -216,7 +237,12 @@ describe("功能 3 至 5:CLI 与 MCP 保持显式分页语义", () => { result: { items: [], nextCursor: "history-next", hasMore: true }, }); expect(updates.structuredContent).toEqual({ - result: { items: [], nextCursor: "updates-next", hasMore: false }, + result: { + items: [], + nextCursor: "updates-next", + hasMore: false, + captureFreshness: { status: "delayed", asOfMs: "1785999800000" }, + }, }); await fixture.close(); }); @@ -319,6 +345,7 @@ const createPlatformClientStub = (overrides: Partial = {}): Plat items: [], nextCursor: "updates-cursor", hasMore: false, + captureFreshness: { status: "unknown", asOfMs: null }, }), queryMessageHistory: async () => ({ items: [],