botschat 0.1.20 → 0.1.21
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.
- package/package.json +1 -1
- package/packages/api/src/do/connection-do.ts +186 -382
- package/packages/api/src/index.ts +50 -67
- package/packages/api/src/routes/agents.ts +3 -3
- package/packages/api/src/routes/auth.ts +1 -0
- package/packages/api/src/routes/channels.ts +11 -11
- package/packages/api/src/routes/demo.ts +156 -0
- package/packages/api/src/routes/sessions.ts +5 -5
- package/packages/api/src/routes/tasks.ts +33 -33
- package/packages/plugin/dist/src/channel.js +50 -0
- package/packages/plugin/dist/src/channel.js.map +1 -1
- package/packages/plugin/package.json +18 -2
- package/packages/web/dist/assets/index-BtPyCBCl.css +1 -0
- package/packages/web/dist/assets/index-BtpsFe4Z.js +2 -0
- package/packages/web/dist/assets/index-CQbIYr6_.js +2 -0
- package/packages/web/dist/assets/{index-CYQMu_-c.js → index-C_GamcQc.js} +1 -1
- package/packages/web/dist/assets/index-LiBjPMg2.js +1 -0
- package/packages/web/dist/assets/{index-DYCO-ry1.js → index-MyoWvQAH.js} +1 -1
- package/packages/web/dist/assets/index-STIPTMK8.js +1516 -0
- package/packages/web/dist/assets/{index.esm-CvOpngZM.js → index.esm-BpQAwtdR.js} +1 -1
- package/packages/web/dist/assets/{web-D3LMODYp.js → web-BbTzVNLt.js} +1 -1
- package/packages/web/dist/assets/{web-1cdhq2RW.js → web-cnzjgNfD.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/src/App.tsx +9 -56
- package/packages/web/src/api.ts +5 -61
- package/packages/web/src/components/ChatWindow.tsx +9 -9
- package/packages/web/src/components/CronDetail.tsx +1 -1
- package/packages/web/src/components/ImageLightbox.tsx +96 -0
- package/packages/web/src/components/LoginPage.tsx +59 -1
- package/packages/web/src/components/MessageContent.tsx +17 -2
- package/packages/web/src/components/SessionTabs.tsx +1 -1
- package/packages/web/src/components/Sidebar.tsx +1 -3
- package/packages/web/src/hooks/useIMEComposition.ts +14 -9
- package/packages/web/src/store.ts +7 -39
- package/packages/web/src/ws.ts +0 -1
- package/scripts/dev.sh +0 -53
- package/migrations/0013_agents_table.sql +0 -29
- package/migrations/0014_agent_sessions.sql +0 -19
- package/migrations/0015_message_traces.sql +0 -27
- package/migrations/0016_multi_agent_channels_messages.sql +0 -9
- package/migrations/0017_rename_cron_job_id.sql +0 -2
- package/packages/api/src/protocol-v2.ts +0 -154
- package/packages/api/src/routes/agents-v2.ts +0 -192
- package/packages/api/src/routes/history-v2.ts +0 -221
- package/packages/api/src/routes/migrate-v2.ts +0 -110
- package/packages/web/dist/assets/index-BARPtt0v.css +0 -1
- package/packages/web/dist/assets/index-Bf-XL3te.js +0 -2
- package/packages/web/dist/assets/index-CYlvfpX9.js +0 -1519
- package/packages/web/dist/assets/index-CxcpA4Qo.js +0 -1
- package/packages/web/dist/assets/index-QebPVqwj.js +0 -2
- package/packages/web/src/components/AgentSettings.tsx +0 -328
- package/scripts/mock-openclaw-v2.mjs +0 -486
|
@@ -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 };
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import type { Env } from "../env.js";
|
|
3
|
-
import { generateId } from "../utils/id.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* One-time migration endpoint: converts legacy pairing_tokens into agents,
|
|
7
|
-
* backfills channels.default_agent_id, and sets messages.sender_agent_id.
|
|
8
|
-
*
|
|
9
|
-
* POST /api/v2/migrate — idempotent, safe to run multiple times.
|
|
10
|
-
* Only available in development mode.
|
|
11
|
-
*/
|
|
12
|
-
const migrateV2 = new Hono<{ Bindings: Env; Variables: { userId: string } }>();
|
|
13
|
-
|
|
14
|
-
migrateV2.post("/", async (c) => {
|
|
15
|
-
if (c.env.ENVIRONMENT !== "development") {
|
|
16
|
-
return c.json({ error: "Migration endpoint only available in development" }, 403);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const userId = c.get("userId");
|
|
20
|
-
const stats = { agentsCreated: 0, channelsUpdated: 0, messagesUpdated: 0, skipped: 0 };
|
|
21
|
-
|
|
22
|
-
// Step 1: Migrate pairing_tokens → agents (skip if agent with same token already exists)
|
|
23
|
-
const { results: tokens } = await c.env.DB.prepare(
|
|
24
|
-
"SELECT id, token, label, last_connected_at, last_ip, connection_count FROM pairing_tokens WHERE user_id = ? AND revoked_at IS NULL",
|
|
25
|
-
)
|
|
26
|
-
.bind(userId)
|
|
27
|
-
.all<{
|
|
28
|
-
id: string;
|
|
29
|
-
token: string;
|
|
30
|
-
label: string | null;
|
|
31
|
-
last_connected_at: number | null;
|
|
32
|
-
last_ip: string | null;
|
|
33
|
-
connection_count: number;
|
|
34
|
-
}>();
|
|
35
|
-
|
|
36
|
-
let defaultAgentId: string | null = null;
|
|
37
|
-
|
|
38
|
-
for (const pt of tokens ?? []) {
|
|
39
|
-
const existing = await c.env.DB.prepare(
|
|
40
|
-
"SELECT id FROM agents WHERE pairing_token = ?",
|
|
41
|
-
)
|
|
42
|
-
.bind(pt.token)
|
|
43
|
-
.first<{ id: string }>();
|
|
44
|
-
|
|
45
|
-
if (existing) {
|
|
46
|
-
if (!defaultAgentId) defaultAgentId = existing.id;
|
|
47
|
-
stats.skipped++;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const agentId = generateId("agt_");
|
|
52
|
-
await c.env.DB.prepare(
|
|
53
|
-
`INSERT INTO agents (id, user_id, name, type, role, pairing_token, capabilities,
|
|
54
|
-
status, last_connected_at, last_ip, connection_count)
|
|
55
|
-
VALUES (?, ?, ?, 'openclaw', 'general', ?, '["chat","streaming","cron","a2ui","media","code_edit","delegate"]',
|
|
56
|
-
'disconnected', ?, ?, ?)`,
|
|
57
|
-
)
|
|
58
|
-
.bind(
|
|
59
|
-
agentId,
|
|
60
|
-
userId,
|
|
61
|
-
pt.label ?? "OpenClaw",
|
|
62
|
-
pt.token,
|
|
63
|
-
pt.last_connected_at,
|
|
64
|
-
pt.last_ip,
|
|
65
|
-
pt.connection_count ?? 0,
|
|
66
|
-
)
|
|
67
|
-
.run();
|
|
68
|
-
|
|
69
|
-
if (!defaultAgentId) defaultAgentId = agentId;
|
|
70
|
-
stats.agentsCreated++;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// If no pairing tokens exist but user has no agents, skip backfill
|
|
74
|
-
if (!defaultAgentId) {
|
|
75
|
-
const anyAgent = await c.env.DB.prepare(
|
|
76
|
-
"SELECT id FROM agents WHERE user_id = ? LIMIT 1",
|
|
77
|
-
)
|
|
78
|
-
.bind(userId)
|
|
79
|
-
.first<{ id: string }>();
|
|
80
|
-
defaultAgentId = anyAgent?.id ?? null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Step 2: Backfill channels.default_agent_id (set to first agent if null)
|
|
84
|
-
if (defaultAgentId) {
|
|
85
|
-
const { meta } = await c.env.DB.prepare(
|
|
86
|
-
"UPDATE channels SET default_agent_id = ?, updated_at = unixepoch() WHERE user_id = ? AND default_agent_id IS NULL",
|
|
87
|
-
)
|
|
88
|
-
.bind(defaultAgentId, userId)
|
|
89
|
-
.run();
|
|
90
|
-
stats.channelsUpdated = meta.changes ?? 0;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Step 3: Backfill messages.sender_agent_id for agent messages (set to default agent)
|
|
94
|
-
if (defaultAgentId) {
|
|
95
|
-
const { meta } = await c.env.DB.prepare(
|
|
96
|
-
"UPDATE messages SET sender_agent_id = ? WHERE user_id = ? AND sender = 'agent' AND sender_agent_id IS NULL",
|
|
97
|
-
)
|
|
98
|
-
.bind(defaultAgentId, userId)
|
|
99
|
-
.run();
|
|
100
|
-
stats.messagesUpdated = meta.changes ?? 0;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return c.json({
|
|
104
|
-
ok: true,
|
|
105
|
-
defaultAgentId,
|
|
106
|
-
stats,
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
export { migrateV2 };
|