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.
- package/README.md +68 -1
- 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 +23 -4
- package/packages/web/dist/assets/index-BaRi2ZPe.js +1517 -0
- package/packages/web/dist/assets/index-Be-wHtaM.js +1 -0
- package/packages/web/dist/assets/index-BtPyCBCl.css +1 -0
- package/packages/web/dist/assets/index-C8XPcaR1.js +2 -0
- package/packages/web/dist/assets/index-CyXci1aU.js +2 -0
- package/packages/web/dist/assets/{index-DYCO-ry1.js → index-DVvunB1I.js} +1 -1
- package/packages/web/dist/assets/{index-CYQMu_-c.js → index-SvEo7uGi.js} +1 -1
- package/packages/web/dist/assets/{index.esm-CvOpngZM.js → index.esm-DAPLRyFT.js} +1 -1
- package/packages/web/dist/assets/{web-D3LMODYp.js → web-C6s08DDW.js} +1 -1
- package/packages/web/dist/assets/{web-1cdhq2RW.js → web-K88lR3JA.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/src/App.tsx +44 -57
- 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 +78 -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,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,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 };
|