botschat 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +68 -1
  2. package/package.json +1 -1
  3. package/packages/api/src/do/connection-do.ts +186 -382
  4. package/packages/api/src/index.ts +50 -67
  5. package/packages/api/src/routes/agents.ts +3 -3
  6. package/packages/api/src/routes/auth.ts +1 -0
  7. package/packages/api/src/routes/channels.ts +11 -11
  8. package/packages/api/src/routes/demo.ts +156 -0
  9. package/packages/api/src/routes/sessions.ts +5 -5
  10. package/packages/api/src/routes/tasks.ts +33 -33
  11. package/packages/plugin/dist/src/channel.js +50 -0
  12. package/packages/plugin/dist/src/channel.js.map +1 -1
  13. package/packages/plugin/package.json +23 -4
  14. package/packages/web/dist/assets/index-BaRi2ZPe.js +1517 -0
  15. package/packages/web/dist/assets/index-Be-wHtaM.js +1 -0
  16. package/packages/web/dist/assets/index-BtPyCBCl.css +1 -0
  17. package/packages/web/dist/assets/index-C8XPcaR1.js +2 -0
  18. package/packages/web/dist/assets/index-CyXci1aU.js +2 -0
  19. package/packages/web/dist/assets/{index-DYCO-ry1.js → index-DVvunB1I.js} +1 -1
  20. package/packages/web/dist/assets/{index-CYQMu_-c.js → index-SvEo7uGi.js} +1 -1
  21. package/packages/web/dist/assets/{index.esm-CvOpngZM.js → index.esm-DAPLRyFT.js} +1 -1
  22. package/packages/web/dist/assets/{web-D3LMODYp.js → web-C6s08DDW.js} +1 -1
  23. package/packages/web/dist/assets/{web-1cdhq2RW.js → web-K88lR3JA.js} +1 -1
  24. package/packages/web/dist/index.html +2 -2
  25. package/packages/web/src/App.tsx +44 -57
  26. package/packages/web/src/api.ts +5 -61
  27. package/packages/web/src/components/ChatWindow.tsx +9 -9
  28. package/packages/web/src/components/CronDetail.tsx +1 -1
  29. package/packages/web/src/components/ImageLightbox.tsx +96 -0
  30. package/packages/web/src/components/LoginPage.tsx +78 -1
  31. package/packages/web/src/components/MessageContent.tsx +17 -2
  32. package/packages/web/src/components/SessionTabs.tsx +1 -1
  33. package/packages/web/src/components/Sidebar.tsx +1 -3
  34. package/packages/web/src/hooks/useIMEComposition.ts +14 -9
  35. package/packages/web/src/store.ts +7 -39
  36. package/packages/web/src/ws.ts +0 -1
  37. package/scripts/dev.sh +0 -53
  38. package/migrations/0013_agents_table.sql +0 -29
  39. package/migrations/0014_agent_sessions.sql +0 -19
  40. package/migrations/0015_message_traces.sql +0 -27
  41. package/migrations/0016_multi_agent_channels_messages.sql +0 -9
  42. package/migrations/0017_rename_cron_job_id.sql +0 -2
  43. package/packages/api/src/protocol-v2.ts +0 -154
  44. package/packages/api/src/routes/agents-v2.ts +0 -192
  45. package/packages/api/src/routes/history-v2.ts +0 -221
  46. package/packages/api/src/routes/migrate-v2.ts +0 -110
  47. package/packages/web/dist/assets/index-BARPtt0v.css +0 -1
  48. package/packages/web/dist/assets/index-Bf-XL3te.js +0 -2
  49. package/packages/web/dist/assets/index-CYlvfpX9.js +0 -1519
  50. package/packages/web/dist/assets/index-CxcpA4Qo.js +0 -1
  51. package/packages/web/dist/assets/index-QebPVqwj.js +0 -2
  52. package/packages/web/src/components/AgentSettings.tsx +0 -328
  53. package/scripts/mock-openclaw-v2.mjs +0 -486
@@ -1,29 +0,0 @@
1
- -- Multi-Agent Architecture: agents table
2
- -- Agent = Type (engine) x Role (persona), independent of channels.
3
-
4
- CREATE TABLE IF NOT EXISTS agents (
5
- id TEXT PRIMARY KEY, -- 'agt_xxx'
6
- user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
7
- name TEXT NOT NULL, -- display name: "OpenClaw", "Cursor", "PM小明"
8
- type TEXT NOT NULL CHECK (type IN ('openclaw', 'cursor_cli', 'cursor_cloud', 'claude_code', 'mock')),
9
- -- Role & Skills
10
- role TEXT NOT NULL DEFAULT 'general', -- 'product_manager', 'developer', 'qa', 'devops', 'general'
11
- system_prompt TEXT NOT NULL DEFAULT '',
12
- skills_json TEXT NOT NULL DEFAULT '[]', -- JSON: [{"name":"...","description":"..."}]
13
- -- Connection credentials (provider-specific, at most one populated)
14
- pairing_token TEXT, -- OpenClaw bc_pat_xxx
15
- api_key TEXT, -- Cursor / Claude API key
16
- config_json TEXT NOT NULL DEFAULT '{}', -- extra: { workspace, repository, ref, ... }
17
- -- Technical capabilities (derived from type)
18
- capabilities TEXT NOT NULL DEFAULT '["chat"]', -- JSON: ["chat","streaming","cron","a2ui","media","code_edit","delegate"]
19
- -- Runtime state
20
- status TEXT NOT NULL DEFAULT 'disconnected' CHECK (status IN ('connected', 'disconnected')),
21
- last_connected_at INTEGER,
22
- connection_count INTEGER NOT NULL DEFAULT 0,
23
- last_ip TEXT,
24
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
25
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
26
- );
27
- CREATE INDEX IF NOT EXISTS idx_agents_user ON agents(user_id);
28
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_pairing_token ON agents(pairing_token) WHERE pairing_token IS NOT NULL;
29
- CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_api_key ON agents(api_key) WHERE api_key IS NOT NULL;
@@ -1,19 +0,0 @@
1
- -- Multi-Agent Architecture: agent_sessions mapping table
2
- -- Maps BotsChat sessions to each agent's provider-side session identifier.
3
- -- e.g. BotsChat ses_001 x Cursor agent -> Cursor chatId "abc-123"
4
-
5
- CREATE TABLE IF NOT EXISTS agent_sessions (
6
- id TEXT PRIMARY KEY, -- 'as_xxx'
7
- session_id TEXT NOT NULL, -- BotsChat session (ses_xxx)
8
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
9
- provider_session_id TEXT, -- provider-specific session ID
10
- -- OpenClaw: session_key "agent:main:botschat:u_xxx:ses:ses_xxx"
11
- -- Cursor CLI: chatId (uuid from `agent create-chat`)
12
- -- Cursor Cloud: cloud agent id "bc_xxx"
13
- metadata_json TEXT NOT NULL DEFAULT '{}',
14
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
15
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
16
- UNIQUE(session_id, agent_id)
17
- );
18
- CREATE INDEX IF NOT EXISTS idx_agent_sessions_session ON agent_sessions(session_id);
19
- CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent ON agent_sessions(agent_id);
@@ -1,27 +0,0 @@
1
- -- Multi-Agent Architecture: message_traces table
2
- -- Stores agent execution traces at different verbosity levels.
3
- -- lv1 = conclusions (stored in messages table)
4
- -- lv2 = thinking / reasoning process
5
- -- lv3 = reference material / tool call results
6
-
7
- CREATE TABLE IF NOT EXISTS message_traces (
8
- id TEXT PRIMARY KEY, -- 'mt_xxx'
9
- message_id TEXT NOT NULL, -- parent lv1 message (messages.id)
10
- user_id TEXT NOT NULL,
11
- session_key TEXT NOT NULL,
12
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
13
- verbose_level INTEGER NOT NULL CHECK (verbose_level IN (2, 3)),
14
- trace_type TEXT NOT NULL,
15
- -- lv2: 'thinking', 'planning', 'reasoning', 'decision'
16
- -- lv3: 'file_read', 'file_write', 'command_exec', 'search_result', 'tool_call', 'reference'
17
- content BLOB NOT NULL,
18
- metadata_json TEXT NOT NULL DEFAULT '{}',
19
- -- lv3 file_read: { "path": "src/auth.ts", "lines": 42 }
20
- -- lv3 command_exec: { "command": "npm test", "exitCode": 0 }
21
- -- lv3 file_write: { "path": "src/auth.ts", "linesChanged": 5 }
22
- encrypted INTEGER NOT NULL DEFAULT 0,
23
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
24
- );
25
- CREATE INDEX IF NOT EXISTS idx_traces_message ON message_traces(message_id);
26
- CREATE INDEX IF NOT EXISTS idx_traces_session_level ON message_traces(session_key, verbose_level, created_at);
27
- CREATE INDEX IF NOT EXISTS idx_traces_agent ON message_traces(agent_id, created_at);
@@ -1,9 +0,0 @@
1
- -- Multi-Agent Architecture: extend channels and messages for multi-agent support.
2
-
3
- -- Channels: add default agent + rename openclaw-specific column
4
- ALTER TABLE channels ADD COLUMN default_agent_id TEXT REFERENCES agents(id);
5
- ALTER TABLE channels RENAME COLUMN openclaw_agent_id TO provider_agent_id;
6
-
7
- -- Messages: track which agent sent/received each message
8
- ALTER TABLE messages ADD COLUMN sender_agent_id TEXT REFERENCES agents(id);
9
- ALTER TABLE messages ADD COLUMN target_agent_id TEXT REFERENCES agents(id);
@@ -1,2 +0,0 @@
1
- -- Multi-Agent: rename openclaw_cron_job_id -> provider_job_id in tasks table
2
- ALTER TABLE tasks RENAME COLUMN openclaw_cron_job_id TO provider_job_id;
@@ -1,154 +0,0 @@
1
- /**
2
- * BotsChat v2 Multi-Agent Protocol
3
- *
4
- * Core Protocol: all agent bridges MUST implement these message types.
5
- * Provider Extensions: optional, provider-specific messages (OpenClaw cron, etc.)
6
- *
7
- * Agent = Type (engine) × Role (persona). Each agent connects via a separate
8
- * WebSocket and authenticates with its own agentId.
9
- */
10
-
11
- // ── Agent metadata shared across protocol ──
12
-
13
- export type AgentType = "openclaw" | "cursor_cli" | "cursor_cloud" | "claude_code" | "mock";
14
-
15
- export type AgentInfo = {
16
- id: string;
17
- name: string;
18
- type: AgentType;
19
- role: string;
20
- capabilities: string[];
21
- status: "connected" | "disconnected";
22
- };
23
-
24
- // ── Core Protocol: Agent → Cloud (outbound) ──
25
-
26
- export type CoreOutbound =
27
- | {
28
- type: "auth";
29
- token: string;
30
- agentId?: string;
31
- agentType?: AgentType;
32
- agents?: string[];
33
- model?: string;
34
- }
35
- | {
36
- type: "agent.text";
37
- agentId?: string;
38
- sessionKey: string;
39
- text: string;
40
- requestId?: string;
41
- replyToId?: string;
42
- threadId?: string;
43
- encrypted?: boolean;
44
- messageId?: string;
45
- notifyPreview?: string;
46
- }
47
- | {
48
- type: "agent.media";
49
- agentId?: string;
50
- sessionKey: string;
51
- mediaUrl: string;
52
- caption?: string;
53
- replyToId?: string;
54
- threadId?: string;
55
- encrypted?: boolean;
56
- mediaEncrypted?: boolean;
57
- messageId?: string;
58
- notifyPreview?: string;
59
- }
60
- | { type: "agent.stream.start"; agentId?: string; sessionKey: string; runId: string }
61
- | { type: "agent.stream.chunk"; agentId?: string; sessionKey: string; runId: string; text: string }
62
- | { type: "agent.stream.end"; agentId?: string; sessionKey: string; runId: string }
63
- | { type: "status"; connected: boolean; agents?: string[]; model?: string }
64
- | { type: "pong" };
65
-
66
- // ── Core Protocol: Cloud → Agent (inbound) ──
67
-
68
- export type CoreInbound =
69
- | {
70
- type: "auth.ok";
71
- userId: string;
72
- agentId?: string;
73
- availableAgents?: AgentInfo[];
74
- }
75
- | { type: "auth.fail"; reason: string }
76
- | {
77
- type: "user.message";
78
- sessionKey: string;
79
- text: string;
80
- userId: string;
81
- messageId: string;
82
- targetAgentId?: string;
83
- mediaUrl?: string;
84
- parentMessageId?: string;
85
- parentText?: string;
86
- parentSender?: string;
87
- parentEncrypted?: number;
88
- }
89
- | { type: "user.media"; sessionKey: string; mediaUrl: string; userId: string }
90
- | { type: "user.action"; sessionKey: string; action: string; params: Record<string, unknown> }
91
- | { type: "user.command"; sessionKey: string; command: string; args?: string }
92
- | { type: "ping" };
93
-
94
- // ── Agent-to-Agent Delegation ──
95
-
96
- export type AgentRequestMessage = {
97
- type: "agent.request";
98
- agentId: string;
99
- targetAgentId: string;
100
- sessionKey: string;
101
- text: string;
102
- requestId: string;
103
- depth: number;
104
- context?: {
105
- summary: string;
106
- constraints?: string[];
107
- expectedOutput?: string;
108
- };
109
- ephemeral?: boolean;
110
- };
111
-
112
- export type AgentResponseMessage = {
113
- type: "agent.response";
114
- requestId: string;
115
- fromAgentId: string;
116
- text: string;
117
- sessionKey: string;
118
- error?: string;
119
- };
120
-
121
- // ── Verbose Trace (lv2/lv3 execution traces) ──
122
-
123
- export type AgentTraceMessage = {
124
- type: "agent.trace";
125
- agentId: string;
126
- sessionKey: string;
127
- messageId: string;
128
- verboseLevel: 2 | 3;
129
- traceType: string;
130
- // lv2: "thinking" | "planning" | "reasoning" | "decision"
131
- // lv3: "file_read" | "file_write" | "command_exec" | "search_result" | "tool_call" | "reference"
132
- content: string;
133
- metadata?: Record<string, unknown>;
134
- encrypted?: boolean;
135
- };
136
-
137
- // ── Combined v2 Protocol Types ──
138
-
139
- export type V2Outbound = CoreOutbound | AgentRequestMessage | AgentTraceMessage;
140
- export type V2Inbound = CoreInbound | AgentResponseMessage;
141
-
142
- // ── Delegation safety constants ──
143
-
144
- export const MAX_DELEGATION_DEPTH = 5;
145
- export const DELEGATION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
146
-
147
- // ── Pending request tracking (used by ConnectionDO) ──
148
-
149
- export type PendingRequest = {
150
- fromAgentId: string;
151
- sessionKey: string;
152
- depth: number;
153
- createdAt: number;
154
- };
@@ -1,192 +0,0 @@
1
- import { Hono } from "hono";
2
- import type { Env } from "../env.js";
3
- import { generateId } from "../utils/id.js";
4
-
5
- /**
6
- * Agents v2 API — agents are first-class entities (team members),
7
- * independent of channels. Each agent has a type (engine) and role (persona).
8
- */
9
- const agentsV2 = new Hono<{ Bindings: Env; Variables: { userId: string } }>();
10
-
11
- export type AgentType = "openclaw" | "cursor_cli" | "cursor_cloud" | "claude_code" | "mock";
12
-
13
- export type AgentV2 = {
14
- id: string;
15
- name: string;
16
- type: AgentType;
17
- role: string;
18
- systemPrompt: string;
19
- skills: Array<{ name: string; description: string }>;
20
- capabilities: string[];
21
- status: "connected" | "disconnected";
22
- lastConnectedAt: number | null;
23
- createdAt: number;
24
- updatedAt: number;
25
- };
26
-
27
- const CAPABILITIES_BY_TYPE: Record<AgentType, string[]> = {
28
- openclaw: ["chat", "streaming", "cron", "a2ui", "media", "code_edit", "delegate"],
29
- cursor_cli: ["chat", "streaming", "code_edit"],
30
- cursor_cloud: ["chat", "code_edit"],
31
- claude_code: ["chat", "streaming", "code_edit"],
32
- mock: ["chat", "streaming"],
33
- };
34
-
35
- function dbRowToAgent(row: Record<string, unknown>): AgentV2 {
36
- return {
37
- id: row.id as string,
38
- name: row.name as string,
39
- type: row.type as AgentType,
40
- role: row.role as string,
41
- systemPrompt: row.system_prompt as string,
42
- skills: JSON.parse((row.skills_json as string) || "[]"),
43
- capabilities: JSON.parse((row.capabilities as string) || "[]"),
44
- status: (row.status as "connected" | "disconnected") ?? "disconnected",
45
- lastConnectedAt: row.last_connected_at as number | null,
46
- createdAt: row.created_at as number,
47
- updatedAt: row.updated_at as number,
48
- };
49
- }
50
-
51
- /** GET /api/v2/agents — list all agents for the current user */
52
- agentsV2.get("/", async (c) => {
53
- const userId = c.get("userId");
54
- const { results } = await c.env.DB.prepare(
55
- `SELECT id, name, type, role, system_prompt, skills_json, capabilities,
56
- status, last_connected_at, created_at, updated_at
57
- FROM agents WHERE user_id = ? ORDER BY created_at ASC`,
58
- )
59
- .bind(userId)
60
- .all();
61
-
62
- return c.json({ agents: (results ?? []).map(dbRowToAgent) });
63
- });
64
-
65
- /** POST /api/v2/agents — create a new agent */
66
- agentsV2.post("/", async (c) => {
67
- const userId = c.get("userId");
68
- const body = await c.req.json<{
69
- name: string;
70
- type: AgentType;
71
- role?: string;
72
- systemPrompt?: string;
73
- skills?: Array<{ name: string; description: string }>;
74
- pairingToken?: string;
75
- apiKey?: string;
76
- config?: Record<string, unknown>;
77
- }>();
78
-
79
- if (!body.name?.trim()) return c.json({ error: "Agent name is required" }, 400);
80
- if (!body.type) return c.json({ error: "Agent type is required" }, 400);
81
-
82
- const id = generateId("agt_");
83
- const capabilities = CAPABILITIES_BY_TYPE[body.type] ?? ["chat"];
84
-
85
- await c.env.DB.prepare(
86
- `INSERT INTO agents (id, user_id, name, type, role, system_prompt, skills_json,
87
- pairing_token, api_key, config_json, capabilities)
88
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
89
- )
90
- .bind(
91
- id,
92
- userId,
93
- body.name.trim(),
94
- body.type,
95
- body.role ?? "general",
96
- body.systemPrompt?.trim() ?? "",
97
- JSON.stringify(body.skills ?? []),
98
- body.pairingToken ?? null,
99
- body.apiKey ?? null,
100
- JSON.stringify(body.config ?? {}),
101
- JSON.stringify(capabilities),
102
- )
103
- .run();
104
-
105
- const row = await c.env.DB.prepare(
106
- `SELECT id, name, type, role, system_prompt, skills_json, capabilities,
107
- status, last_connected_at, created_at, updated_at
108
- FROM agents WHERE id = ?`,
109
- )
110
- .bind(id)
111
- .first();
112
-
113
- return c.json(dbRowToAgent(row!), 201);
114
- });
115
-
116
- /** GET /api/v2/agents/:id — get a single agent */
117
- agentsV2.get("/:id", async (c) => {
118
- const userId = c.get("userId");
119
- const agentId = c.req.param("id");
120
-
121
- const row = await c.env.DB.prepare(
122
- `SELECT id, name, type, role, system_prompt, skills_json, capabilities,
123
- status, last_connected_at, created_at, updated_at
124
- FROM agents WHERE id = ? AND user_id = ?`,
125
- )
126
- .bind(agentId, userId)
127
- .first();
128
-
129
- if (!row) return c.json({ error: "Agent not found" }, 404);
130
- return c.json(dbRowToAgent(row));
131
- });
132
-
133
- /** PATCH /api/v2/agents/:id — update an agent */
134
- agentsV2.patch("/:id", async (c) => {
135
- const userId = c.get("userId");
136
- const agentId = c.req.param("id");
137
- const body = await c.req.json<{
138
- name?: string;
139
- role?: string;
140
- systemPrompt?: string;
141
- skills?: Array<{ name: string; description: string }>;
142
- config?: Record<string, unknown>;
143
- }>();
144
-
145
- const sets: string[] = [];
146
- const values: unknown[] = [];
147
-
148
- if (body.name !== undefined) { sets.push("name = ?"); values.push(body.name.trim()); }
149
- if (body.role !== undefined) { sets.push("role = ?"); values.push(body.role); }
150
- if (body.systemPrompt !== undefined) { sets.push("system_prompt = ?"); values.push(body.systemPrompt.trim()); }
151
- if (body.skills !== undefined) { sets.push("skills_json = ?"); values.push(JSON.stringify(body.skills)); }
152
- if (body.config !== undefined) { sets.push("config_json = ?"); values.push(JSON.stringify(body.config)); }
153
-
154
- if (sets.length === 0) return c.json({ error: "No fields to update" }, 400);
155
-
156
- sets.push("updated_at = unixepoch()");
157
- values.push(agentId, userId);
158
-
159
- await c.env.DB.prepare(
160
- `UPDATE agents SET ${sets.join(", ")} WHERE id = ? AND user_id = ?`,
161
- )
162
- .bind(...values)
163
- .run();
164
-
165
- const row = await c.env.DB.prepare(
166
- `SELECT id, name, type, role, system_prompt, skills_json, capabilities,
167
- status, last_connected_at, created_at, updated_at
168
- FROM agents WHERE id = ? AND user_id = ?`,
169
- )
170
- .bind(agentId, userId)
171
- .first();
172
-
173
- if (!row) return c.json({ error: "Agent not found" }, 404);
174
- return c.json(dbRowToAgent(row));
175
- });
176
-
177
- /** DELETE /api/v2/agents/:id — delete an agent */
178
- agentsV2.delete("/:id", async (c) => {
179
- const userId = c.get("userId");
180
- const agentId = c.req.param("id");
181
-
182
- const { meta } = await c.env.DB.prepare(
183
- "DELETE FROM agents WHERE id = ? AND user_id = ?",
184
- )
185
- .bind(agentId, userId)
186
- .run();
187
-
188
- if (!meta.changes) return c.json({ error: "Agent not found" }, 404);
189
- return c.json({ ok: true });
190
- });
191
-
192
- export { agentsV2 };
@@ -1,221 +0,0 @@
1
- import { Hono } from "hono";
2
- import type { Env } from "../env.js";
3
-
4
- /**
5
- * History Query API v2 — supports verbose-level filtering, agent filtering,
6
- * trace type filtering, and keyword search across messages + message_traces.
7
- *
8
- * GET /api/v2/messages/query?sessionKey=...&verboseLevel=1&limit=50
9
- */
10
- const historyV2 = new Hono<{ Bindings: Env; Variables: { userId: string } }>();
11
-
12
- historyV2.get("/query", async (c) => {
13
- const userId = c.get("userId");
14
- const sessionKey = c.req.query("sessionKey");
15
- if (!sessionKey) return c.json({ error: "sessionKey required" }, 400);
16
-
17
- const verboseLevel = Math.min(Number(c.req.query("verboseLevel") ?? 1), 3);
18
- const limit = Math.min(Number(c.req.query("limit") ?? 50), 200);
19
- const senderFilter = c.req.query("senderFilter"); // "user" | "agent" | undefined
20
- const agentIdFilter = c.req.query("agentIdFilter");
21
- const traceTypeFilter = c.req.query("traceTypeFilter");
22
- const keyword = c.req.query("keyword");
23
- const beforeMessageId = c.req.query("beforeMessageId");
24
-
25
- // Build lv1 query (messages table)
26
- const conditions: string[] = ["m.session_key = ?", "m.user_id = ?"];
27
- const params: unknown[] = [sessionKey, userId];
28
-
29
- if (senderFilter === "user" || senderFilter === "agent") {
30
- conditions.push("m.sender = ?");
31
- params.push(senderFilter);
32
- }
33
- if (agentIdFilter) {
34
- conditions.push("(m.sender_agent_id = ? OR m.target_agent_id = ?)");
35
- params.push(agentIdFilter, agentIdFilter);
36
- }
37
- if (keyword) {
38
- conditions.push("m.text LIKE ?");
39
- params.push(`%${keyword}%`);
40
- }
41
- if (beforeMessageId) {
42
- conditions.push("m.created_at < (SELECT created_at FROM messages WHERE id = ?)");
43
- params.push(beforeMessageId);
44
- }
45
-
46
- params.push(limit);
47
-
48
- const { results: messages } = await c.env.DB.prepare(
49
- `SELECT m.id, m.sender, m.sender_agent_id, m.target_agent_id, m.text, m.media_url, m.encrypted, m.created_at
50
- FROM messages m
51
- WHERE ${conditions.join(" AND ")}
52
- ORDER BY m.created_at DESC
53
- LIMIT ?`,
54
- )
55
- .bind(...params)
56
- .all<{
57
- id: string;
58
- sender: string;
59
- sender_agent_id: string | null;
60
- target_agent_id: string | null;
61
- text: string;
62
- media_url: string | null;
63
- encrypted: number;
64
- created_at: number;
65
- }>();
66
-
67
- // Resolve agent names
68
- const agentIds = new Set<string>();
69
- for (const m of messages ?? []) {
70
- if (m.sender_agent_id) agentIds.add(m.sender_agent_id);
71
- if (m.target_agent_id) agentIds.add(m.target_agent_id);
72
- }
73
-
74
- const agentNames: Record<string, string> = {};
75
- if (agentIds.size > 0) {
76
- const placeholders = [...agentIds].map(() => "?").join(",");
77
- const { results: agents } = await c.env.DB.prepare(
78
- `SELECT id, name FROM agents WHERE id IN (${placeholders})`,
79
- )
80
- .bind(...agentIds)
81
- .all<{ id: string; name: string }>();
82
- for (const a of agents ?? []) {
83
- agentNames[a.id] = a.name;
84
- }
85
- }
86
-
87
- // Build response
88
- type MessageResponse = {
89
- id: string;
90
- sender: string;
91
- senderAgentId?: string;
92
- senderAgentName?: string;
93
- targetAgentId?: string;
94
- text: string;
95
- mediaUrl?: string;
96
- encrypted: boolean;
97
- timestamp: number;
98
- traces?: Array<{
99
- verboseLevel: number;
100
- traceType: string;
101
- content: string;
102
- metadata?: Record<string, unknown>;
103
- }>;
104
- };
105
-
106
- const result: MessageResponse[] = [];
107
- const messageIds = (messages ?? []).map((m) => m.id);
108
-
109
- // Fetch traces if verboseLevel >= 2
110
- let tracesByMessageId: Record<string, Array<{ verbose_level: number; trace_type: string; content: string; metadata_json: string }>> = {};
111
-
112
- if (verboseLevel >= 2 && messageIds.length > 0) {
113
- const traceConds: string[] = ["message_id IN (" + messageIds.map(() => "?").join(",") + ")"];
114
- const traceParams: unknown[] = [...messageIds];
115
-
116
- traceConds.push("verbose_level <= ?");
117
- traceParams.push(verboseLevel);
118
-
119
- if (traceTypeFilter) {
120
- traceConds.push("trace_type = ?");
121
- traceParams.push(traceTypeFilter);
122
- }
123
- if (agentIdFilter) {
124
- traceConds.push("agent_id = ?");
125
- traceParams.push(agentIdFilter);
126
- }
127
-
128
- const { results: traces } = await c.env.DB.prepare(
129
- `SELECT message_id, verbose_level, trace_type, content, metadata_json
130
- FROM message_traces
131
- WHERE ${traceConds.join(" AND ")}
132
- ORDER BY created_at ASC`,
133
- )
134
- .bind(...traceParams)
135
- .all<{ message_id: string; verbose_level: number; trace_type: string; content: string; metadata_json: string }>();
136
-
137
- for (const t of traces ?? []) {
138
- if (!tracesByMessageId[t.message_id]) tracesByMessageId[t.message_id] = [];
139
- tracesByMessageId[t.message_id].push(t);
140
- }
141
- }
142
-
143
- for (const m of messages ?? []) {
144
- const msg: MessageResponse = {
145
- id: m.id,
146
- sender: m.sender,
147
- senderAgentId: m.sender_agent_id ?? undefined,
148
- senderAgentName: m.sender_agent_id ? agentNames[m.sender_agent_id] : undefined,
149
- targetAgentId: m.target_agent_id ?? undefined,
150
- text: typeof m.text === "string" ? m.text : "",
151
- mediaUrl: m.media_url ?? undefined,
152
- encrypted: !!m.encrypted,
153
- timestamp: m.created_at,
154
- };
155
-
156
- if (verboseLevel >= 2 && tracesByMessageId[m.id]) {
157
- msg.traces = tracesByMessageId[m.id].map((t) => ({
158
- verboseLevel: t.verbose_level,
159
- traceType: t.trace_type,
160
- content: typeof t.content === "string" ? t.content : "",
161
- metadata: t.metadata_json ? JSON.parse(t.metadata_json) : undefined,
162
- }));
163
- }
164
-
165
- result.push(msg);
166
- }
167
-
168
- // Return in chronological order (query was DESC for LIMIT, reverse for output)
169
- result.reverse();
170
-
171
- return c.json({ messages: result, hasMore: (messages?.length ?? 0) === limit });
172
- });
173
-
174
- /** GET /api/v2/messages/traces/:messageId — fetch traces for a specific message */
175
- historyV2.get("/traces/:messageId", async (c) => {
176
- const userId = c.get("userId");
177
- const messageId = c.req.param("messageId");
178
- const verboseLevel = Math.min(Number(c.req.query("verboseLevel") ?? 3), 3);
179
-
180
- // Verify message belongs to user
181
- const msg = await c.env.DB.prepare(
182
- "SELECT id FROM messages WHERE id = ? AND user_id = ?",
183
- )
184
- .bind(messageId, userId)
185
- .first();
186
-
187
- if (!msg) return c.json({ error: "Message not found" }, 404);
188
-
189
- const { results: traces } = await c.env.DB.prepare(
190
- `SELECT id, verbose_level, trace_type, content, metadata_json, agent_id, encrypted, created_at
191
- FROM message_traces
192
- WHERE message_id = ? AND verbose_level <= ?
193
- ORDER BY created_at ASC`,
194
- )
195
- .bind(messageId, verboseLevel)
196
- .all<{
197
- id: string;
198
- verbose_level: number;
199
- trace_type: string;
200
- content: string;
201
- metadata_json: string;
202
- agent_id: string;
203
- encrypted: number;
204
- created_at: number;
205
- }>();
206
-
207
- return c.json({
208
- traces: (traces ?? []).map((t) => ({
209
- id: t.id,
210
- verboseLevel: t.verbose_level,
211
- traceType: t.trace_type,
212
- content: typeof t.content === "string" ? t.content : "",
213
- metadata: t.metadata_json ? JSON.parse(t.metadata_json) : undefined,
214
- agentId: t.agent_id,
215
- encrypted: !!t.encrypted,
216
- timestamp: t.created_at,
217
- })),
218
- });
219
- });
220
-
221
- export { historyV2 };