cue-console 0.1.0
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/LICENSE +201 -0
- package/README.md +33 -0
- package/bin/cue-console.js +82 -0
- package/components.json +22 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +11 -0
- package/package.json +61 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +304 -0
- package/src/app/layout.tsx +36 -0
- package/src/app/page.tsx +109 -0
- package/src/components/chat-composer.tsx +493 -0
- package/src/components/chat-view.tsx +1463 -0
- package/src/components/conversation-list.tsx +525 -0
- package/src/components/create-group-dialog.tsx +220 -0
- package/src/components/markdown-renderer.tsx +53 -0
- package/src/components/payload-card.tsx +275 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/button.tsx +63 -0
- package/src/components/ui/collapsible.tsx +46 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/scroll-area.tsx +59 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/lib/actions.ts +300 -0
- package/src/lib/db.ts +581 -0
- package/src/lib/types.ts +89 -0
- package/src/lib/utils.ts +135 -0
- package/tsconfig.json +34 -0
package/src/lib/db.ts
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import type { UserResponse, Group } from "./types";
|
|
5
|
+
|
|
6
|
+
const DB_PATH = join(homedir(), ".cue", "cue.db");
|
|
7
|
+
|
|
8
|
+
let db: Database.Database | null = null;
|
|
9
|
+
|
|
10
|
+
export function getDb(): Database.Database {
|
|
11
|
+
if (!db) {
|
|
12
|
+
db = new Database(DB_PATH);
|
|
13
|
+
db.pragma("journal_mode = WAL");
|
|
14
|
+
initTables();
|
|
15
|
+
}
|
|
16
|
+
return db;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function initTables() {
|
|
20
|
+
const database = db!;
|
|
21
|
+
|
|
22
|
+
database.exec(`
|
|
23
|
+
CREATE TABLE IF NOT EXISTS agent_profiles (
|
|
24
|
+
agent_id TEXT PRIMARY KEY,
|
|
25
|
+
display_name TEXT NOT NULL,
|
|
26
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
27
|
+
)
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
database.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS cue_requests (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
request_id TEXT UNIQUE,
|
|
34
|
+
agent_id TEXT NOT NULL,
|
|
35
|
+
prompt TEXT NOT NULL,
|
|
36
|
+
payload TEXT,
|
|
37
|
+
status TEXT NOT NULL,
|
|
38
|
+
created_at DATETIME NOT NULL,
|
|
39
|
+
updated_at DATETIME NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
database.exec(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS cue_responses (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
request_id TEXT UNIQUE,
|
|
47
|
+
response_json TEXT NOT NULL,
|
|
48
|
+
cancelled INTEGER NOT NULL,
|
|
49
|
+
created_at DATETIME NOT NULL,
|
|
50
|
+
FOREIGN KEY (request_id) REFERENCES cue_requests(request_id)
|
|
51
|
+
)
|
|
52
|
+
`);
|
|
53
|
+
|
|
54
|
+
// Groups table
|
|
55
|
+
database.exec(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS groups (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
name TEXT NOT NULL,
|
|
59
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
60
|
+
)
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
// Group members table
|
|
64
|
+
database.exec(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS group_members (
|
|
66
|
+
group_id TEXT NOT NULL,
|
|
67
|
+
agent_name TEXT NOT NULL,
|
|
68
|
+
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
69
|
+
PRIMARY KEY (group_id, agent_name),
|
|
70
|
+
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
|
71
|
+
)
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getAgentDisplayName(agentId: string): string | undefined {
|
|
76
|
+
const row = getDb()
|
|
77
|
+
.prepare(`SELECT display_name FROM agent_profiles WHERE agent_id = ?`)
|
|
78
|
+
.get(agentId) as { display_name: string } | undefined;
|
|
79
|
+
return row?.display_name;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function upsertAgentDisplayName(agentId: string, displayName: string): void {
|
|
83
|
+
const clean = displayName.trim();
|
|
84
|
+
if (!clean) return;
|
|
85
|
+
getDb()
|
|
86
|
+
.prepare(
|
|
87
|
+
`INSERT INTO agent_profiles (agent_id, display_name, updated_at)
|
|
88
|
+
VALUES (?, ?, datetime('now'))
|
|
89
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
90
|
+
display_name = excluded.display_name,
|
|
91
|
+
updated_at = datetime('now')`
|
|
92
|
+
)
|
|
93
|
+
.run(agentId, clean);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getAgentDisplayNames(agentIds: string[]): Record<string, string> {
|
|
97
|
+
const unique = Array.from(new Set(agentIds.filter(Boolean)));
|
|
98
|
+
if (unique.length === 0) return {};
|
|
99
|
+
const placeholders = unique.map(() => "?").join(",");
|
|
100
|
+
const rows = getDb()
|
|
101
|
+
.prepare(
|
|
102
|
+
`SELECT agent_id, display_name
|
|
103
|
+
FROM agent_profiles
|
|
104
|
+
WHERE agent_id IN (${placeholders})`
|
|
105
|
+
)
|
|
106
|
+
.all(...unique) as Array<{ agent_id: string; display_name: string }>;
|
|
107
|
+
const map: Record<string, string> = {};
|
|
108
|
+
for (const r of rows) map[r.agent_id] = r.display_name;
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Type definitions
|
|
113
|
+
export interface CueRequest {
|
|
114
|
+
id: number;
|
|
115
|
+
request_id: string;
|
|
116
|
+
agent_id: string;
|
|
117
|
+
prompt: string;
|
|
118
|
+
payload: string | null;
|
|
119
|
+
status: "PENDING" | "COMPLETED" | "CANCELLED";
|
|
120
|
+
created_at: string;
|
|
121
|
+
updated_at: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface CueResponse {
|
|
125
|
+
id: number;
|
|
126
|
+
request_id: string;
|
|
127
|
+
response_json: string;
|
|
128
|
+
cancelled: boolean;
|
|
129
|
+
created_at: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type AgentTimelineItem =
|
|
133
|
+
| {
|
|
134
|
+
item_type: "request";
|
|
135
|
+
time: string;
|
|
136
|
+
request: CueRequest;
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
item_type: "response";
|
|
140
|
+
time: string;
|
|
141
|
+
response: CueResponse;
|
|
142
|
+
request_id: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// UserResponse, Group, GroupMember are imported from types.ts
|
|
146
|
+
export type { UserResponse, Group, GroupMember } from "./types";
|
|
147
|
+
|
|
148
|
+
// Query functions
|
|
149
|
+
export function getPendingRequests(): CueRequest[] {
|
|
150
|
+
return getDb()
|
|
151
|
+
.prepare(
|
|
152
|
+
`SELECT * FROM cue_requests
|
|
153
|
+
WHERE status = 'PENDING'
|
|
154
|
+
ORDER BY created_at DESC`
|
|
155
|
+
)
|
|
156
|
+
.all() as CueRequest[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getRequestsByAgent(agentId: string): CueRequest[] {
|
|
160
|
+
return getDb()
|
|
161
|
+
.prepare(
|
|
162
|
+
`SELECT * FROM cue_requests
|
|
163
|
+
WHERE agent_id = ?
|
|
164
|
+
ORDER BY created_at ASC
|
|
165
|
+
LIMIT 50`
|
|
166
|
+
)
|
|
167
|
+
.all(agentId) as CueRequest[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function getResponsesByAgent(agentId: string): CueResponse[] {
|
|
171
|
+
return getDb()
|
|
172
|
+
.prepare(
|
|
173
|
+
`SELECT r.* FROM cue_responses r
|
|
174
|
+
JOIN cue_requests req ON r.request_id = req.request_id
|
|
175
|
+
WHERE req.agent_id = ?
|
|
176
|
+
ORDER BY r.created_at ASC
|
|
177
|
+
LIMIT 50`
|
|
178
|
+
)
|
|
179
|
+
.all(agentId) as CueResponse[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getAgentLastResponse(agentId: string): CueResponse | undefined {
|
|
183
|
+
return getDb()
|
|
184
|
+
.prepare(
|
|
185
|
+
`SELECT r.* FROM cue_responses r
|
|
186
|
+
JOIN cue_requests req ON r.request_id = req.request_id
|
|
187
|
+
WHERE req.agent_id = ?
|
|
188
|
+
ORDER BY r.created_at DESC
|
|
189
|
+
LIMIT 1`
|
|
190
|
+
)
|
|
191
|
+
.get(agentId) as CueResponse | undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function getAgentTimeline(
|
|
195
|
+
agentId: string,
|
|
196
|
+
before: string | null,
|
|
197
|
+
limit: number
|
|
198
|
+
): { items: AgentTimelineItem[]; nextCursor: string | null } {
|
|
199
|
+
const rows = getDb()
|
|
200
|
+
.prepare(
|
|
201
|
+
`SELECT * FROM (
|
|
202
|
+
SELECT
|
|
203
|
+
'request' AS item_type,
|
|
204
|
+
req.created_at AS time,
|
|
205
|
+
req.request_id AS request_id,
|
|
206
|
+
req.id AS req_id,
|
|
207
|
+
req.agent_id AS agent_id,
|
|
208
|
+
req.prompt AS prompt,
|
|
209
|
+
req.payload AS payload,
|
|
210
|
+
req.status AS status,
|
|
211
|
+
req.created_at AS req_created_at,
|
|
212
|
+
req.updated_at AS req_updated_at,
|
|
213
|
+
NULL AS resp_id,
|
|
214
|
+
NULL AS response_json,
|
|
215
|
+
NULL AS cancelled,
|
|
216
|
+
NULL AS resp_created_at
|
|
217
|
+
FROM cue_requests req
|
|
218
|
+
WHERE req.agent_id = ?
|
|
219
|
+
|
|
220
|
+
UNION ALL
|
|
221
|
+
|
|
222
|
+
SELECT
|
|
223
|
+
'response' AS item_type,
|
|
224
|
+
r.created_at AS time,
|
|
225
|
+
r.request_id AS request_id,
|
|
226
|
+
NULL AS req_id,
|
|
227
|
+
NULL AS agent_id,
|
|
228
|
+
NULL AS prompt,
|
|
229
|
+
NULL AS payload,
|
|
230
|
+
NULL AS status,
|
|
231
|
+
NULL AS req_created_at,
|
|
232
|
+
NULL AS req_updated_at,
|
|
233
|
+
r.id AS resp_id,
|
|
234
|
+
r.response_json AS response_json,
|
|
235
|
+
r.cancelled AS cancelled,
|
|
236
|
+
r.created_at AS resp_created_at
|
|
237
|
+
FROM cue_responses r
|
|
238
|
+
JOIN cue_requests req2 ON r.request_id = req2.request_id
|
|
239
|
+
WHERE req2.agent_id = ?
|
|
240
|
+
)
|
|
241
|
+
WHERE (? IS NULL OR time < ?)
|
|
242
|
+
ORDER BY time DESC
|
|
243
|
+
LIMIT ?`
|
|
244
|
+
)
|
|
245
|
+
.all(agentId, agentId, before, before, limit) as Array<
|
|
246
|
+
| {
|
|
247
|
+
item_type: "request";
|
|
248
|
+
time: string;
|
|
249
|
+
request_id: string;
|
|
250
|
+
req_id: number;
|
|
251
|
+
agent_id: string;
|
|
252
|
+
prompt: string;
|
|
253
|
+
payload: string | null;
|
|
254
|
+
status: CueRequest["status"];
|
|
255
|
+
req_created_at: string;
|
|
256
|
+
req_updated_at: string;
|
|
257
|
+
}
|
|
258
|
+
| {
|
|
259
|
+
item_type: "response";
|
|
260
|
+
time: string;
|
|
261
|
+
request_id: string;
|
|
262
|
+
resp_id: number;
|
|
263
|
+
response_json: string;
|
|
264
|
+
cancelled: 0 | 1;
|
|
265
|
+
resp_created_at: string;
|
|
266
|
+
}
|
|
267
|
+
>;
|
|
268
|
+
|
|
269
|
+
const items: AgentTimelineItem[] = rows.map((row) => {
|
|
270
|
+
if (row.item_type === "request") {
|
|
271
|
+
return {
|
|
272
|
+
item_type: "request",
|
|
273
|
+
time: row.time,
|
|
274
|
+
request: {
|
|
275
|
+
id: row.req_id,
|
|
276
|
+
request_id: row.request_id,
|
|
277
|
+
agent_id: row.agent_id,
|
|
278
|
+
prompt: row.prompt,
|
|
279
|
+
payload: row.payload,
|
|
280
|
+
status: row.status,
|
|
281
|
+
created_at: row.req_created_at,
|
|
282
|
+
updated_at: row.req_updated_at,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
item_type: "response",
|
|
288
|
+
time: row.time,
|
|
289
|
+
request_id: row.request_id,
|
|
290
|
+
response: {
|
|
291
|
+
id: row.resp_id,
|
|
292
|
+
request_id: row.request_id,
|
|
293
|
+
response_json: row.response_json,
|
|
294
|
+
cancelled: row.cancelled === 1,
|
|
295
|
+
created_at: row.resp_created_at,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const nextCursor = items.length > 0 ? items[items.length - 1].time : null;
|
|
301
|
+
return { items, nextCursor };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getAllAgents(): string[] {
|
|
305
|
+
const results = getDb()
|
|
306
|
+
.prepare(
|
|
307
|
+
`SELECT agent_id, MAX(created_at) as last_time FROM cue_requests
|
|
308
|
+
WHERE agent_id != ''
|
|
309
|
+
GROUP BY agent_id
|
|
310
|
+
ORDER BY last_time DESC`
|
|
311
|
+
)
|
|
312
|
+
.all() as { agent_id: string }[];
|
|
313
|
+
return results.map((r) => r.agent_id);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function getAgentLastRequest(
|
|
317
|
+
agentId: string
|
|
318
|
+
): CueRequest | undefined {
|
|
319
|
+
return getDb()
|
|
320
|
+
.prepare(
|
|
321
|
+
`SELECT * FROM cue_requests
|
|
322
|
+
WHERE agent_id = ?
|
|
323
|
+
ORDER BY created_at DESC
|
|
324
|
+
LIMIT 1`
|
|
325
|
+
)
|
|
326
|
+
.get(agentId) as CueRequest | undefined;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function getPendingCountByAgent(agentId: string): number {
|
|
330
|
+
const result = getDb()
|
|
331
|
+
.prepare(
|
|
332
|
+
`SELECT COUNT(*) as count FROM cue_requests
|
|
333
|
+
WHERE agent_id = ? AND status = 'PENDING'`
|
|
334
|
+
)
|
|
335
|
+
.get(agentId) as { count: number };
|
|
336
|
+
return result.count;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function sendResponse(
|
|
340
|
+
requestId: string,
|
|
341
|
+
response: UserResponse,
|
|
342
|
+
cancelled: boolean = false
|
|
343
|
+
): void {
|
|
344
|
+
const db = getDb();
|
|
345
|
+
|
|
346
|
+
// Insert response
|
|
347
|
+
db.prepare(
|
|
348
|
+
`INSERT OR IGNORE INTO cue_responses (request_id, response_json, cancelled, created_at)
|
|
349
|
+
VALUES (?, ?, ?, datetime('now'))`
|
|
350
|
+
).run(requestId, JSON.stringify(response), cancelled ? 1 : 0);
|
|
351
|
+
|
|
352
|
+
// Update request status
|
|
353
|
+
db.prepare(
|
|
354
|
+
`UPDATE cue_requests
|
|
355
|
+
SET status = ?
|
|
356
|
+
WHERE request_id = ?`
|
|
357
|
+
).run(cancelled ? "CANCELLED" : "COMPLETED", requestId);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Group-related functions
|
|
361
|
+
export function createGroup(id: string, name: string): void {
|
|
362
|
+
getDb()
|
|
363
|
+
.prepare(`INSERT INTO groups (id, name) VALUES (?, ?)`)
|
|
364
|
+
.run(id, name);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function getAllGroups(): Group[] {
|
|
368
|
+
return getDb()
|
|
369
|
+
.prepare(`SELECT * FROM groups ORDER BY created_at DESC`)
|
|
370
|
+
.all() as Group[];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function getGroupMembers(groupId: string): string[] {
|
|
374
|
+
const results = getDb()
|
|
375
|
+
.prepare(`SELECT agent_name FROM group_members WHERE group_id = ?`)
|
|
376
|
+
.all(groupId) as { agent_name: string }[];
|
|
377
|
+
return results.map((r) => r.agent_name);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function addGroupMember(groupId: string, agentName: string): void {
|
|
381
|
+
getDb()
|
|
382
|
+
.prepare(
|
|
383
|
+
`INSERT OR IGNORE INTO group_members (group_id, agent_name) VALUES (?, ?)`
|
|
384
|
+
)
|
|
385
|
+
.run(groupId, agentName);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function removeGroupMember(groupId: string, agentName: string): void {
|
|
389
|
+
getDb()
|
|
390
|
+
.prepare(`DELETE FROM group_members WHERE group_id = ? AND agent_name = ?`)
|
|
391
|
+
.run(groupId, agentName);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function deleteGroup(groupId: string): void {
|
|
395
|
+
getDb().prepare(`DELETE FROM groups WHERE id = ?`).run(groupId);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function updateGroupName(groupId: string, name: string): void {
|
|
399
|
+
const clean = name.trim();
|
|
400
|
+
if (!clean) return;
|
|
401
|
+
getDb().prepare(`UPDATE groups SET name = ? WHERE id = ?`).run(clean, groupId);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function getGroupPendingCount(groupId: string): number {
|
|
405
|
+
const members = getGroupMembers(groupId);
|
|
406
|
+
if (members.length === 0) return 0;
|
|
407
|
+
|
|
408
|
+
const placeholders = members.map(() => "?").join(",");
|
|
409
|
+
const result = getDb()
|
|
410
|
+
.prepare(
|
|
411
|
+
`SELECT COUNT(*) as count FROM cue_requests
|
|
412
|
+
WHERE agent_id IN (${placeholders}) AND status = 'PENDING'`
|
|
413
|
+
)
|
|
414
|
+
.get(...members) as { count: number };
|
|
415
|
+
return result.count;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function getGroupPendingRequests(groupId: string): CueRequest[] {
|
|
419
|
+
const members = getGroupMembers(groupId);
|
|
420
|
+
if (members.length === 0) return [];
|
|
421
|
+
|
|
422
|
+
const placeholders = members.map(() => "?").join(",");
|
|
423
|
+
return getDb()
|
|
424
|
+
.prepare(
|
|
425
|
+
`SELECT * FROM cue_requests
|
|
426
|
+
WHERE agent_id IN (${placeholders}) AND status = 'PENDING'
|
|
427
|
+
ORDER BY created_at ASC`
|
|
428
|
+
)
|
|
429
|
+
.all(...members) as CueRequest[];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function getGroupLastRequest(groupId: string): CueRequest | undefined {
|
|
433
|
+
const members = getGroupMembers(groupId);
|
|
434
|
+
if (members.length === 0) return undefined;
|
|
435
|
+
|
|
436
|
+
const placeholders = members.map(() => "?").join(",");
|
|
437
|
+
return getDb()
|
|
438
|
+
.prepare(
|
|
439
|
+
`SELECT * FROM cue_requests
|
|
440
|
+
WHERE agent_id IN (${placeholders})
|
|
441
|
+
ORDER BY created_at DESC
|
|
442
|
+
LIMIT 1`
|
|
443
|
+
)
|
|
444
|
+
.get(...members) as CueRequest | undefined;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function getGroupLastResponse(groupId: string): CueResponse | undefined {
|
|
448
|
+
const members = getGroupMembers(groupId);
|
|
449
|
+
if (members.length === 0) return undefined;
|
|
450
|
+
|
|
451
|
+
const placeholders = members.map(() => "?").join(",");
|
|
452
|
+
return getDb()
|
|
453
|
+
.prepare(
|
|
454
|
+
`SELECT r.* FROM cue_responses r
|
|
455
|
+
JOIN cue_requests req ON r.request_id = req.request_id
|
|
456
|
+
WHERE req.agent_id IN (${placeholders})
|
|
457
|
+
ORDER BY r.created_at DESC
|
|
458
|
+
LIMIT 1`
|
|
459
|
+
)
|
|
460
|
+
.get(...members) as CueResponse | undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function getGroupTimeline(
|
|
464
|
+
groupId: string,
|
|
465
|
+
before: string | null,
|
|
466
|
+
limit: number
|
|
467
|
+
): { items: AgentTimelineItem[]; nextCursor: string | null } {
|
|
468
|
+
const members = getGroupMembers(groupId);
|
|
469
|
+
if (members.length === 0) return { items: [], nextCursor: null };
|
|
470
|
+
|
|
471
|
+
const placeholders = members.map(() => "?").join(",");
|
|
472
|
+
const query = `SELECT * FROM (
|
|
473
|
+
SELECT
|
|
474
|
+
'request' AS item_type,
|
|
475
|
+
req.created_at AS time,
|
|
476
|
+
req.request_id AS request_id,
|
|
477
|
+
req.id AS req_id,
|
|
478
|
+
req.agent_id AS agent_id,
|
|
479
|
+
req.prompt AS prompt,
|
|
480
|
+
req.payload AS payload,
|
|
481
|
+
req.status AS status,
|
|
482
|
+
req.created_at AS req_created_at,
|
|
483
|
+
req.updated_at AS req_updated_at,
|
|
484
|
+
NULL AS resp_id,
|
|
485
|
+
NULL AS response_json,
|
|
486
|
+
NULL AS cancelled,
|
|
487
|
+
NULL AS resp_created_at
|
|
488
|
+
FROM cue_requests req
|
|
489
|
+
WHERE req.agent_id IN (${placeholders})
|
|
490
|
+
|
|
491
|
+
UNION ALL
|
|
492
|
+
|
|
493
|
+
SELECT
|
|
494
|
+
'response' AS item_type,
|
|
495
|
+
r.created_at AS time,
|
|
496
|
+
r.request_id AS request_id,
|
|
497
|
+
NULL AS req_id,
|
|
498
|
+
NULL AS agent_id,
|
|
499
|
+
NULL AS prompt,
|
|
500
|
+
NULL AS payload,
|
|
501
|
+
NULL AS status,
|
|
502
|
+
NULL AS req_created_at,
|
|
503
|
+
NULL AS req_updated_at,
|
|
504
|
+
r.id AS resp_id,
|
|
505
|
+
r.response_json AS response_json,
|
|
506
|
+
r.cancelled AS cancelled,
|
|
507
|
+
r.created_at AS resp_created_at
|
|
508
|
+
FROM cue_responses r
|
|
509
|
+
JOIN cue_requests req2 ON r.request_id = req2.request_id
|
|
510
|
+
WHERE req2.agent_id IN (${placeholders})
|
|
511
|
+
)
|
|
512
|
+
WHERE (? IS NULL OR time < ?)
|
|
513
|
+
ORDER BY time DESC
|
|
514
|
+
LIMIT ?`;
|
|
515
|
+
|
|
516
|
+
const rows = getDb()
|
|
517
|
+
.prepare(query)
|
|
518
|
+
.all(
|
|
519
|
+
...members,
|
|
520
|
+
...members,
|
|
521
|
+
before,
|
|
522
|
+
before,
|
|
523
|
+
limit
|
|
524
|
+
) as Array<
|
|
525
|
+
| {
|
|
526
|
+
item_type: "request";
|
|
527
|
+
time: string;
|
|
528
|
+
request_id: string;
|
|
529
|
+
req_id: number;
|
|
530
|
+
agent_id: string;
|
|
531
|
+
prompt: string;
|
|
532
|
+
payload: string | null;
|
|
533
|
+
status: CueRequest["status"];
|
|
534
|
+
req_created_at: string;
|
|
535
|
+
req_updated_at: string;
|
|
536
|
+
}
|
|
537
|
+
| {
|
|
538
|
+
item_type: "response";
|
|
539
|
+
time: string;
|
|
540
|
+
request_id: string;
|
|
541
|
+
resp_id: number;
|
|
542
|
+
response_json: string;
|
|
543
|
+
cancelled: 0 | 1;
|
|
544
|
+
resp_created_at: string;
|
|
545
|
+
}
|
|
546
|
+
>;
|
|
547
|
+
|
|
548
|
+
const items: AgentTimelineItem[] = rows.map((row) => {
|
|
549
|
+
if (row.item_type === "request") {
|
|
550
|
+
return {
|
|
551
|
+
item_type: "request",
|
|
552
|
+
time: row.time,
|
|
553
|
+
request: {
|
|
554
|
+
id: row.req_id,
|
|
555
|
+
request_id: row.request_id,
|
|
556
|
+
agent_id: row.agent_id,
|
|
557
|
+
prompt: row.prompt,
|
|
558
|
+
payload: row.payload,
|
|
559
|
+
status: row.status,
|
|
560
|
+
created_at: row.req_created_at,
|
|
561
|
+
updated_at: row.req_updated_at,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
item_type: "response",
|
|
567
|
+
time: row.time,
|
|
568
|
+
request_id: row.request_id,
|
|
569
|
+
response: {
|
|
570
|
+
id: row.resp_id,
|
|
571
|
+
request_id: row.request_id,
|
|
572
|
+
response_json: row.response_json,
|
|
573
|
+
cancelled: row.cancelled === 1,
|
|
574
|
+
created_at: row.resp_created_at,
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const nextCursor = items.length > 0 ? items[items.length - 1].time : null;
|
|
580
|
+
return { items, nextCursor };
|
|
581
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/**
|
|
3
|
+
/* This file was automatically generated from pydantic models by running pydantic2ts.
|
|
4
|
+
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Request status (SQLModel stores enum names in uppercase)
|
|
9
|
+
*/
|
|
10
|
+
export type RequestStatus = "PENDING" | "COMPLETED" | "CANCELLED";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Request from MCP -> client (cue-hub / simulator)
|
|
14
|
+
*/
|
|
15
|
+
export interface CueRequest {
|
|
16
|
+
id?: number | null;
|
|
17
|
+
request_id: string;
|
|
18
|
+
agent_id?: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
payload?: string | null;
|
|
21
|
+
status?: RequestStatus;
|
|
22
|
+
created_at?: string;
|
|
23
|
+
updated_at?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Response from client (cue-hub / simulator) -> MCP
|
|
27
|
+
*/
|
|
28
|
+
export interface CueResponse {
|
|
29
|
+
id?: number | null;
|
|
30
|
+
request_id: string;
|
|
31
|
+
response_json: string;
|
|
32
|
+
cancelled?: boolean;
|
|
33
|
+
created_at?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Image content
|
|
37
|
+
*/
|
|
38
|
+
export interface ImageContent {
|
|
39
|
+
mime_type: string;
|
|
40
|
+
base64_data: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Mention {
|
|
44
|
+
userId: string;
|
|
45
|
+
start: number;
|
|
46
|
+
length: number;
|
|
47
|
+
display: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* User response
|
|
51
|
+
*/
|
|
52
|
+
export interface UserResponse {
|
|
53
|
+
text?: string;
|
|
54
|
+
images?: ImageContent[];
|
|
55
|
+
mentions?: Mention[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============ Frontend extension types ============
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Group
|
|
62
|
+
*/
|
|
63
|
+
export interface Group {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
created_at: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Group member
|
|
71
|
+
*/
|
|
72
|
+
export interface GroupMember {
|
|
73
|
+
group_id: string;
|
|
74
|
+
agent_name: string;
|
|
75
|
+
joined_at: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Conversation list item
|
|
80
|
+
*/
|
|
81
|
+
export interface ConversationItem {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
displayName: string;
|
|
85
|
+
type: "agent" | "group";
|
|
86
|
+
lastMessage?: string;
|
|
87
|
+
lastTime?: string;
|
|
88
|
+
pendingCount: number;
|
|
89
|
+
}
|