Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/wbot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions packages/wbot/platform-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
ConversationsQueryInput,
MessageResult,
MessagesHistoryInput,
MessagesQueryResult,
MessageSendInput,
MessageSendResult,
MessagesQueryInput,
Expand All @@ -14,7 +15,7 @@ import type {

export type PlatformClient = {
queryConversations: (input?: ConversationsQueryInput) => Promise<Page<ConversationResult>>;
queryMessages: (input: MessagesQueryInput) => Promise<Page<MessageResult>>;
queryMessages: (input: MessagesQueryInput) => Promise<MessagesQueryResult>;
queryMessageHistory: (input: MessagesHistoryInput) => Promise<Page<MessageResult>>;
sendMessage: (input: MessageSendInput) => Promise<MessageSendResult>;
getOutboundSend: (input: OutboundSendGetInput) => Promise<OutboundSendResult>;
Expand Down Expand Up @@ -86,7 +87,7 @@ export const createPlatformClient = (config: PlatformClientConfig): PlatformClie
return {
queryConversations: (input = {}) =>
request<Page<ConversationResult>>("/platform/v1/conversations/query", input),
queryMessages: (input) => request<Page<MessageResult>>("/platform/v1/messages/query", input),
queryMessages: (input) => request<MessagesQueryResult>("/platform/v1/messages/query", input),
queryMessageHistory: (input) =>
request<Page<MessageResult>>("/platform/v1/messages/history", input),
sendMessage: (input) => request<MessageSendResult>("/platform/v1/messages/send", input),
Expand Down
13 changes: 13 additions & 0 deletions packages/wbot/platform-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConversationCapability>;
captureFreshness: CaptureFreshness;
latestMessage: {
id: string;
direction: "in" | "out";
Expand Down Expand Up @@ -98,6 +107,10 @@ export type MessageResult = {
};
};

export type MessagesQueryResult = Page<MessageResult> & {
captureFreshness: CaptureFreshness;
};

export type MessageSendInput = {
conversationId: string;
requestId: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/wbot/wbot-agent-tools.bdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ status: accepted # draft | accepted | superseded
- `wbot` Agent-first CLI 与稳定 JSON 结果
- Codex Plugin 与 Claude Code Plugin
- 列出获授权会话、向过去读取历史、从显式游标读取新增消息
- 会话能力与面向 Tenant 的采集新鲜度
- 一次性本地凭据配置、环境变量覆盖与敏感信息保护
- Caller-owned cursor 与无状态恢复

Expand Down Expand Up @@ -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 显式带回会话游标读取下一页**
Expand Down Expand Up @@ -178,6 +181,7 @@ Given Tenant 拥有目标会话的有效读取授权
When Agent 不带更新游标读取该会话
Then Agent 得到一个有界的当前消息页
And 结果包含后续读取新增消息所需的更新游标
And 结果即使没有消息也包含采集新鲜度

**场景 5.2:带回更新游标后只返回后续入库消息**
Given Agent 持有先前返回的更新游标
Expand Down Expand Up @@ -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 工具**
Expand Down
35 changes: 31 additions & 4 deletions packages/wbot/wbot-agent-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -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,
});
Expand All @@ -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" },
};
},
}),
);
Expand Down Expand Up @@ -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();
});
Expand Down Expand Up @@ -319,6 +345,7 @@ const createPlatformClientStub = (overrides: Partial<PlatformClient> = {}): Plat
items: [],
nextCursor: "updates-cursor",
hasMore: false,
captureFreshness: { status: "unknown", asOfMs: null },
}),
queryMessageHistory: async () => ({
items: [],
Expand Down