agentgui 1.0.151 → 1.0.153
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/database.js +112 -66
- package/lib/claude-runner.js +23 -2
- package/package.json +1 -1
- package/server.js +326 -129
- package/static/index.html +95 -33
- package/static/js/client.js +129 -128
- package/static/js/conversations.js +63 -15
- package/static/js/event-processor.js +1 -3
- package/static/js/streaming-renderer.js +130 -158
- package/static/js/syntax-highlighter.js +1 -3
- package/static/js/ui-components.js +1 -3
- package/static/js/websocket-manager.js +16 -27
- package/static/styles.css +0 -1989
package/database.js
CHANGED
|
@@ -19,6 +19,10 @@ try {
|
|
|
19
19
|
db.run('PRAGMA journal_mode = WAL');
|
|
20
20
|
db.run('PRAGMA foreign_keys = ON');
|
|
21
21
|
db.run('PRAGMA encoding = "UTF-8"');
|
|
22
|
+
db.run('PRAGMA synchronous = NORMAL');
|
|
23
|
+
db.run('PRAGMA cache_size = -64000');
|
|
24
|
+
db.run('PRAGMA mmap_size = 268435456');
|
|
25
|
+
db.run('PRAGMA temp_store = MEMORY');
|
|
22
26
|
} catch (e) {
|
|
23
27
|
try {
|
|
24
28
|
const sqlite3 = require('better-sqlite3');
|
|
@@ -26,6 +30,10 @@ try {
|
|
|
26
30
|
db.pragma('journal_mode = WAL');
|
|
27
31
|
db.pragma('foreign_keys = ON');
|
|
28
32
|
db.pragma('encoding = "UTF-8"');
|
|
33
|
+
db.pragma('synchronous = NORMAL');
|
|
34
|
+
db.pragma('cache_size = -64000');
|
|
35
|
+
db.pragma('mmap_size = 268435456');
|
|
36
|
+
db.pragma('temp_store = MEMORY');
|
|
29
37
|
} catch (e2) {
|
|
30
38
|
throw new Error('SQLite database is required. Please run with bun (recommended) or install better-sqlite3: npm install better-sqlite3');
|
|
31
39
|
}
|
|
@@ -123,6 +131,8 @@ function initSchema() {
|
|
|
123
131
|
CREATE INDEX IF NOT EXISTS idx_chunks_session ON chunks(sessionId, sequence);
|
|
124
132
|
CREATE INDEX IF NOT EXISTS idx_chunks_conversation ON chunks(conversationId, sequence);
|
|
125
133
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_chunks_unique ON chunks(sessionId, sequence);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_conv_created ON chunks(conversationId, created_at);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_sess_created ON chunks(sessionId, created_at);
|
|
126
136
|
`);
|
|
127
137
|
}
|
|
128
138
|
|
|
@@ -244,15 +254,26 @@ try {
|
|
|
244
254
|
console.error('[Migration] Error:', err.message);
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
const stmtCache = new Map();
|
|
258
|
+
function prep(sql) {
|
|
259
|
+
let s = stmtCache.get(sql);
|
|
260
|
+
if (!s) {
|
|
261
|
+
s = db.prepare(sql);
|
|
262
|
+
stmtCache.set(sql, s);
|
|
263
|
+
}
|
|
264
|
+
return s;
|
|
265
|
+
}
|
|
266
|
+
|
|
247
267
|
function generateId(prefix) {
|
|
248
268
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
249
269
|
}
|
|
250
270
|
|
|
251
271
|
export const queries = {
|
|
272
|
+
_db: db,
|
|
252
273
|
createConversation(agentType, title = null, workingDirectory = null) {
|
|
253
274
|
const id = generateId('conv');
|
|
254
275
|
const now = Date.now();
|
|
255
|
-
const stmt =
|
|
276
|
+
const stmt = prep(
|
|
256
277
|
`INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
257
278
|
);
|
|
258
279
|
stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory);
|
|
@@ -269,17 +290,17 @@ export const queries = {
|
|
|
269
290
|
},
|
|
270
291
|
|
|
271
292
|
getConversation(id) {
|
|
272
|
-
const stmt =
|
|
293
|
+
const stmt = prep('SELECT * FROM conversations WHERE id = ?');
|
|
273
294
|
return stmt.get(id);
|
|
274
295
|
},
|
|
275
296
|
|
|
276
297
|
getAllConversations() {
|
|
277
|
-
const stmt =
|
|
298
|
+
const stmt = prep('SELECT * FROM conversations WHERE status != ? ORDER BY updated_at DESC');
|
|
278
299
|
return stmt.all('deleted');
|
|
279
300
|
},
|
|
280
301
|
|
|
281
302
|
getConversationsList() {
|
|
282
|
-
const stmt =
|
|
303
|
+
const stmt = prep(
|
|
283
304
|
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
284
305
|
);
|
|
285
306
|
return stmt.all('deleted');
|
|
@@ -293,7 +314,7 @@ export const queries = {
|
|
|
293
314
|
const title = data.title !== undefined ? data.title : conv.title;
|
|
294
315
|
const status = data.status !== undefined ? data.status : conv.status;
|
|
295
316
|
|
|
296
|
-
const stmt =
|
|
317
|
+
const stmt = prep(
|
|
297
318
|
`UPDATE conversations SET title = ?, status = ?, updated_at = ? WHERE id = ?`
|
|
298
319
|
);
|
|
299
320
|
stmt.run(title, status, now, id);
|
|
@@ -307,41 +328,41 @@ export const queries = {
|
|
|
307
328
|
},
|
|
308
329
|
|
|
309
330
|
setClaudeSessionId(conversationId, claudeSessionId) {
|
|
310
|
-
const stmt =
|
|
331
|
+
const stmt = prep('UPDATE conversations SET claudeSessionId = ?, updated_at = ? WHERE id = ?');
|
|
311
332
|
stmt.run(claudeSessionId, Date.now(), conversationId);
|
|
312
333
|
},
|
|
313
334
|
|
|
314
335
|
getClaudeSessionId(conversationId) {
|
|
315
|
-
const stmt =
|
|
336
|
+
const stmt = prep('SELECT claudeSessionId FROM conversations WHERE id = ?');
|
|
316
337
|
const row = stmt.get(conversationId);
|
|
317
338
|
return row?.claudeSessionId || null;
|
|
318
339
|
},
|
|
319
340
|
|
|
320
341
|
setIsStreaming(conversationId, isStreaming) {
|
|
321
|
-
const stmt =
|
|
342
|
+
const stmt = prep('UPDATE conversations SET isStreaming = ?, updated_at = ? WHERE id = ?');
|
|
322
343
|
stmt.run(isStreaming ? 1 : 0, Date.now(), conversationId);
|
|
323
344
|
},
|
|
324
345
|
|
|
325
346
|
getIsStreaming(conversationId) {
|
|
326
|
-
const stmt =
|
|
347
|
+
const stmt = prep('SELECT isStreaming FROM conversations WHERE id = ?');
|
|
327
348
|
const row = stmt.get(conversationId);
|
|
328
349
|
return row?.isStreaming === 1;
|
|
329
350
|
},
|
|
330
351
|
|
|
331
352
|
markSessionIncomplete(sessionId, errorMsg) {
|
|
332
|
-
const stmt =
|
|
353
|
+
const stmt = prep('UPDATE sessions SET status = ?, error = ?, completed_at = ? WHERE id = ?');
|
|
333
354
|
stmt.run('incomplete', errorMsg || 'unknown', Date.now(), sessionId);
|
|
334
355
|
},
|
|
335
356
|
|
|
336
357
|
getSessionsProcessingLongerThan(minutes) {
|
|
337
358
|
const cutoff = Date.now() - (minutes * 60 * 1000);
|
|
338
|
-
const stmt =
|
|
359
|
+
const stmt = prep("SELECT * FROM sessions WHERE status IN ('active', 'pending') AND started_at < ?");
|
|
339
360
|
return stmt.all(cutoff);
|
|
340
361
|
},
|
|
341
362
|
|
|
342
363
|
cleanupOrphanedSessions(days) {
|
|
343
364
|
const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
|
|
344
|
-
const stmt =
|
|
365
|
+
const stmt = prep("DELETE FROM sessions WHERE status IN ('active', 'pending') AND started_at < ?");
|
|
345
366
|
const result = stmt.run(cutoff);
|
|
346
367
|
return result.changes || 0;
|
|
347
368
|
},
|
|
@@ -356,12 +377,12 @@ export const queries = {
|
|
|
356
377
|
const now = Date.now();
|
|
357
378
|
const storedContent = typeof content === 'string' ? content : JSON.stringify(content);
|
|
358
379
|
|
|
359
|
-
const stmt =
|
|
380
|
+
const stmt = prep(
|
|
360
381
|
`INSERT INTO messages (id, conversationId, role, content, created_at) VALUES (?, ?, ?, ?, ?)`
|
|
361
382
|
);
|
|
362
383
|
stmt.run(id, conversationId, role, storedContent, now);
|
|
363
384
|
|
|
364
|
-
const updateConvStmt =
|
|
385
|
+
const updateConvStmt = prep('UPDATE conversations SET updated_at = ? WHERE id = ?');
|
|
365
386
|
updateConvStmt.run(now, conversationId);
|
|
366
387
|
|
|
367
388
|
const message = {
|
|
@@ -380,7 +401,7 @@ export const queries = {
|
|
|
380
401
|
},
|
|
381
402
|
|
|
382
403
|
getMessage(id) {
|
|
383
|
-
const stmt =
|
|
404
|
+
const stmt = prep('SELECT * FROM messages WHERE id = ?');
|
|
384
405
|
const msg = stmt.get(id);
|
|
385
406
|
if (msg && typeof msg.content === 'string') {
|
|
386
407
|
try {
|
|
@@ -393,7 +414,7 @@ export const queries = {
|
|
|
393
414
|
},
|
|
394
415
|
|
|
395
416
|
getConversationMessages(conversationId) {
|
|
396
|
-
const stmt =
|
|
417
|
+
const stmt = prep(
|
|
397
418
|
'SELECT * FROM messages WHERE conversationId = ? ORDER BY created_at ASC'
|
|
398
419
|
);
|
|
399
420
|
const messages = stmt.all(conversationId);
|
|
@@ -410,10 +431,10 @@ export const queries = {
|
|
|
410
431
|
},
|
|
411
432
|
|
|
412
433
|
getPaginatedMessages(conversationId, limit = 50, offset = 0) {
|
|
413
|
-
const countStmt =
|
|
434
|
+
const countStmt = prep('SELECT COUNT(*) as count FROM messages WHERE conversationId = ?');
|
|
414
435
|
const total = countStmt.get(conversationId).count;
|
|
415
436
|
|
|
416
|
-
const stmt =
|
|
437
|
+
const stmt = prep(
|
|
417
438
|
'SELECT * FROM messages WHERE conversationId = ? ORDER BY created_at ASC LIMIT ? OFFSET ?'
|
|
418
439
|
);
|
|
419
440
|
const messages = stmt.all(conversationId, limit, offset);
|
|
@@ -440,7 +461,7 @@ export const queries = {
|
|
|
440
461
|
const id = generateId('sess');
|
|
441
462
|
const now = Date.now();
|
|
442
463
|
|
|
443
|
-
const stmt =
|
|
464
|
+
const stmt = prep(
|
|
444
465
|
`INSERT INTO sessions (id, conversationId, status, started_at, completed_at, response, error) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
445
466
|
);
|
|
446
467
|
stmt.run(id, conversationId, 'pending', now, null, null, null);
|
|
@@ -457,12 +478,12 @@ export const queries = {
|
|
|
457
478
|
},
|
|
458
479
|
|
|
459
480
|
getSession(id) {
|
|
460
|
-
const stmt =
|
|
481
|
+
const stmt = prep('SELECT * FROM sessions WHERE id = ?');
|
|
461
482
|
return stmt.get(id);
|
|
462
483
|
},
|
|
463
484
|
|
|
464
485
|
getConversationSessions(conversationId) {
|
|
465
|
-
const stmt =
|
|
486
|
+
const stmt = prep(
|
|
466
487
|
'SELECT * FROM sessions WHERE conversationId = ? ORDER BY started_at DESC'
|
|
467
488
|
);
|
|
468
489
|
return stmt.all(conversationId);
|
|
@@ -478,7 +499,7 @@ export const queries = {
|
|
|
478
499
|
const error = data.error !== undefined ? data.error : session.error;
|
|
479
500
|
const completed_at = data.completed_at !== undefined ? data.completed_at : session.completed_at;
|
|
480
501
|
|
|
481
|
-
const stmt =
|
|
502
|
+
const stmt = prep(
|
|
482
503
|
`UPDATE sessions SET status = ?, response = ?, error = ?, completed_at = ? WHERE id = ?`
|
|
483
504
|
);
|
|
484
505
|
|
|
@@ -497,21 +518,21 @@ export const queries = {
|
|
|
497
518
|
},
|
|
498
519
|
|
|
499
520
|
getLatestSession(conversationId) {
|
|
500
|
-
const stmt =
|
|
521
|
+
const stmt = prep(
|
|
501
522
|
'SELECT * FROM sessions WHERE conversationId = ? ORDER BY started_at DESC LIMIT 1'
|
|
502
523
|
);
|
|
503
524
|
return stmt.get(conversationId) || null;
|
|
504
525
|
},
|
|
505
526
|
|
|
506
527
|
getSessionsByStatus(conversationId, status) {
|
|
507
|
-
const stmt =
|
|
528
|
+
const stmt = prep(
|
|
508
529
|
'SELECT * FROM sessions WHERE conversationId = ? AND status = ? ORDER BY started_at DESC'
|
|
509
530
|
);
|
|
510
531
|
return stmt.all(conversationId, status);
|
|
511
532
|
},
|
|
512
533
|
|
|
513
534
|
getActiveSessions() {
|
|
514
|
-
const stmt =
|
|
535
|
+
const stmt = prep(
|
|
515
536
|
"SELECT * FROM sessions WHERE status IN ('active', 'pending') ORDER BY started_at DESC"
|
|
516
537
|
);
|
|
517
538
|
return stmt.all();
|
|
@@ -521,7 +542,7 @@ export const queries = {
|
|
|
521
542
|
const id = generateId('evt');
|
|
522
543
|
const now = Date.now();
|
|
523
544
|
|
|
524
|
-
const stmt =
|
|
545
|
+
const stmt = prep(
|
|
525
546
|
`INSERT INTO events (id, type, conversationId, sessionId, data, created_at) VALUES (?, ?, ?, ?, ?, ?)`
|
|
526
547
|
);
|
|
527
548
|
stmt.run(id, type, conversationId, sessionId, JSON.stringify(data), now);
|
|
@@ -537,7 +558,7 @@ export const queries = {
|
|
|
537
558
|
},
|
|
538
559
|
|
|
539
560
|
getEvent(id) {
|
|
540
|
-
const stmt =
|
|
561
|
+
const stmt = prep('SELECT * FROM events WHERE id = ?');
|
|
541
562
|
const row = stmt.get(id);
|
|
542
563
|
if (row) {
|
|
543
564
|
return {
|
|
@@ -549,7 +570,7 @@ export const queries = {
|
|
|
549
570
|
},
|
|
550
571
|
|
|
551
572
|
getConversationEvents(conversationId) {
|
|
552
|
-
const stmt =
|
|
573
|
+
const stmt = prep(
|
|
553
574
|
'SELECT * FROM events WHERE conversationId = ? ORDER BY created_at ASC'
|
|
554
575
|
);
|
|
555
576
|
const rows = stmt.all(conversationId);
|
|
@@ -560,7 +581,7 @@ export const queries = {
|
|
|
560
581
|
},
|
|
561
582
|
|
|
562
583
|
getSessionEvents(sessionId) {
|
|
563
|
-
const stmt =
|
|
584
|
+
const stmt = prep(
|
|
564
585
|
'SELECT * FROM events WHERE sessionId = ? ORDER BY created_at ASC'
|
|
565
586
|
);
|
|
566
587
|
const rows = stmt.all(sessionId);
|
|
@@ -580,19 +601,19 @@ export const queries = {
|
|
|
580
601
|
}
|
|
581
602
|
|
|
582
603
|
const deleteStmt = db.transaction(() => {
|
|
583
|
-
const sessionIds =
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
604
|
+
const sessionIds = prep('SELECT id FROM sessions WHERE conversationId = ?').all(id).map(r => r.id);
|
|
605
|
+
prep('DELETE FROM stream_updates WHERE conversationId = ?').run(id);
|
|
606
|
+
prep('DELETE FROM chunks WHERE conversationId = ?').run(id);
|
|
607
|
+
prep('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
587
608
|
if (sessionIds.length > 0) {
|
|
588
609
|
const placeholders = sessionIds.map(() => '?').join(',');
|
|
589
610
|
db.prepare(`DELETE FROM stream_updates WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
590
611
|
db.prepare(`DELETE FROM chunks WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
591
612
|
db.prepare(`DELETE FROM events WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
592
613
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
614
|
+
prep('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
|
615
|
+
prep('DELETE FROM messages WHERE conversationId = ?').run(id);
|
|
616
|
+
prep('DELETE FROM conversations WHERE id = ?').run(id);
|
|
596
617
|
});
|
|
597
618
|
|
|
598
619
|
deleteStmt();
|
|
@@ -633,9 +654,9 @@ export const queries = {
|
|
|
633
654
|
const now = Date.now();
|
|
634
655
|
|
|
635
656
|
const cleanupStmt = db.transaction(() => {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
657
|
+
prep('DELETE FROM events WHERE created_at < ?').run(thirtyDaysAgo);
|
|
658
|
+
prep('DELETE FROM sessions WHERE completed_at IS NOT NULL AND completed_at < ?').run(thirtyDaysAgo);
|
|
659
|
+
prep('DELETE FROM idempotencyKeys WHERE (created_at + ttl) < ?').run(now);
|
|
639
660
|
});
|
|
640
661
|
|
|
641
662
|
cleanupStmt();
|
|
@@ -645,14 +666,14 @@ export const queries = {
|
|
|
645
666
|
const now = Date.now();
|
|
646
667
|
const ttl = 24 * 60 * 60 * 1000;
|
|
647
668
|
|
|
648
|
-
const stmt =
|
|
669
|
+
const stmt = prep(
|
|
649
670
|
'INSERT OR REPLACE INTO idempotencyKeys (key, value, created_at, ttl) VALUES (?, ?, ?, ?)'
|
|
650
671
|
);
|
|
651
672
|
stmt.run(key, JSON.stringify(value), now, ttl);
|
|
652
673
|
},
|
|
653
674
|
|
|
654
675
|
getIdempotencyKey(key) {
|
|
655
|
-
const stmt =
|
|
676
|
+
const stmt = prep('SELECT * FROM idempotencyKeys WHERE key = ?');
|
|
656
677
|
const entry = stmt.get(key);
|
|
657
678
|
|
|
658
679
|
if (!entry) return null;
|
|
@@ -763,7 +784,7 @@ export const queries = {
|
|
|
763
784
|
|
|
764
785
|
for (const conv of discovered) {
|
|
765
786
|
try {
|
|
766
|
-
const existingConv =
|
|
787
|
+
const existingConv = prep('SELECT id, status FROM conversations WHERE id = ?').get(conv.id);
|
|
767
788
|
if (existingConv) {
|
|
768
789
|
imported.push({ id: conv.id, status: 'skipped', reason: existingConv.status === 'deleted' ? 'deleted' : 'exists' });
|
|
769
790
|
continue;
|
|
@@ -776,13 +797,13 @@ export const queries = {
|
|
|
776
797
|
const messages = this.parseJsonlMessages(conv.jsonlPath);
|
|
777
798
|
|
|
778
799
|
const importStmt = db.transaction(() => {
|
|
779
|
-
|
|
800
|
+
prep(
|
|
780
801
|
`INSERT INTO conversations (id, agentId, title, created_at, updated_at, status) VALUES (?, ?, ?, ?, ?, ?)`
|
|
781
802
|
).run(conv.id, 'claude-code', displayTitle, conv.created, conv.modified, 'active');
|
|
782
803
|
|
|
783
804
|
for (const msg of messages) {
|
|
784
805
|
try {
|
|
785
|
-
|
|
806
|
+
prep(
|
|
786
807
|
`INSERT INTO messages (id, conversationId, role, content, created_at) VALUES (?, ?, ?, ?, ?)`
|
|
787
808
|
).run(msg.id, conv.id, msg.role, msg.content, msg.created_at);
|
|
788
809
|
} catch (_) {}
|
|
@@ -805,12 +826,12 @@ export const queries = {
|
|
|
805
826
|
|
|
806
827
|
// Use transaction to ensure atomic sequence number assignment
|
|
807
828
|
const transaction = db.transaction(() => {
|
|
808
|
-
const maxSequence =
|
|
829
|
+
const maxSequence = prep(
|
|
809
830
|
'SELECT MAX(sequence) as max FROM stream_updates WHERE sessionId = ?'
|
|
810
831
|
).get(sessionId);
|
|
811
832
|
const sequence = (maxSequence?.max || -1) + 1;
|
|
812
833
|
|
|
813
|
-
|
|
834
|
+
prep(
|
|
814
835
|
`INSERT INTO stream_updates (id, sessionId, conversationId, updateType, content, sequence, created_at)
|
|
815
836
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
816
837
|
).run(id, sessionId, conversationId, updateType, JSON.stringify(content), sequence, now);
|
|
@@ -832,7 +853,7 @@ export const queries = {
|
|
|
832
853
|
},
|
|
833
854
|
|
|
834
855
|
getSessionStreamUpdates(sessionId) {
|
|
835
|
-
const stmt =
|
|
856
|
+
const stmt = prep(
|
|
836
857
|
`SELECT id, sessionId, conversationId, updateType, content, sequence, created_at
|
|
837
858
|
FROM stream_updates WHERE sessionId = ? ORDER BY sequence ASC`
|
|
838
859
|
);
|
|
@@ -844,14 +865,14 @@ export const queries = {
|
|
|
844
865
|
},
|
|
845
866
|
|
|
846
867
|
clearSessionStreamUpdates(sessionId) {
|
|
847
|
-
const stmt =
|
|
868
|
+
const stmt = prep('DELETE FROM stream_updates WHERE sessionId = ?');
|
|
848
869
|
stmt.run(sessionId);
|
|
849
870
|
},
|
|
850
871
|
|
|
851
872
|
createImportedConversation(data) {
|
|
852
873
|
const id = generateId('conv');
|
|
853
874
|
const now = Date.now();
|
|
854
|
-
const stmt =
|
|
875
|
+
const stmt = prep(
|
|
855
876
|
`INSERT INTO conversations (
|
|
856
877
|
id, agentId, title, created_at, updated_at, status,
|
|
857
878
|
agentType, source, externalId, firstPrompt, messageCount,
|
|
@@ -879,21 +900,21 @@ export const queries = {
|
|
|
879
900
|
},
|
|
880
901
|
|
|
881
902
|
getConversationByExternalId(agentType, externalId) {
|
|
882
|
-
const stmt =
|
|
903
|
+
const stmt = prep(
|
|
883
904
|
'SELECT * FROM conversations WHERE agentType = ? AND externalId = ?'
|
|
884
905
|
);
|
|
885
906
|
return stmt.get(agentType, externalId);
|
|
886
907
|
},
|
|
887
908
|
|
|
888
909
|
getConversationsByAgentType(agentType) {
|
|
889
|
-
const stmt =
|
|
910
|
+
const stmt = prep(
|
|
890
911
|
'SELECT * FROM conversations WHERE agentType = ? AND status != ? ORDER BY updated_at DESC'
|
|
891
912
|
);
|
|
892
913
|
return stmt.all(agentType, 'deleted');
|
|
893
914
|
},
|
|
894
915
|
|
|
895
916
|
getImportedConversations() {
|
|
896
|
-
const stmt =
|
|
917
|
+
const stmt = prep(
|
|
897
918
|
'SELECT * FROM conversations WHERE source = ? AND status != ? ORDER BY updated_at DESC'
|
|
898
919
|
);
|
|
899
920
|
return stmt.all('imported', 'deleted');
|
|
@@ -958,7 +979,7 @@ export const queries = {
|
|
|
958
979
|
const now = Date.now();
|
|
959
980
|
const dataBlob = typeof data === 'string' ? data : JSON.stringify(data);
|
|
960
981
|
|
|
961
|
-
const stmt =
|
|
982
|
+
const stmt = prep(
|
|
962
983
|
`INSERT INTO chunks (id, sessionId, conversationId, sequence, type, data, created_at)
|
|
963
984
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
964
985
|
);
|
|
@@ -976,7 +997,7 @@ export const queries = {
|
|
|
976
997
|
},
|
|
977
998
|
|
|
978
999
|
getChunk(id) {
|
|
979
|
-
const stmt =
|
|
1000
|
+
const stmt = prep(
|
|
980
1001
|
`SELECT id, sessionId, conversationId, sequence, type, data, created_at FROM chunks WHERE id = ?`
|
|
981
1002
|
);
|
|
982
1003
|
const row = stmt.get(id);
|
|
@@ -993,7 +1014,7 @@ export const queries = {
|
|
|
993
1014
|
},
|
|
994
1015
|
|
|
995
1016
|
getSessionChunks(sessionId) {
|
|
996
|
-
const stmt =
|
|
1017
|
+
const stmt = prep(
|
|
997
1018
|
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
998
1019
|
FROM chunks WHERE sessionId = ? ORDER BY sequence ASC`
|
|
999
1020
|
);
|
|
@@ -1010,8 +1031,13 @@ export const queries = {
|
|
|
1010
1031
|
});
|
|
1011
1032
|
},
|
|
1012
1033
|
|
|
1034
|
+
getConversationChunkCount(conversationId) {
|
|
1035
|
+
const stmt = prep('SELECT COUNT(*) as count FROM chunks WHERE conversationId = ?');
|
|
1036
|
+
return stmt.get(conversationId).count;
|
|
1037
|
+
},
|
|
1038
|
+
|
|
1013
1039
|
getConversationChunks(conversationId) {
|
|
1014
|
-
const stmt =
|
|
1040
|
+
const stmt = prep(
|
|
1015
1041
|
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
1016
1042
|
FROM chunks WHERE conversationId = ? ORDER BY created_at ASC`
|
|
1017
1043
|
);
|
|
@@ -1028,8 +1054,28 @@ export const queries = {
|
|
|
1028
1054
|
});
|
|
1029
1055
|
},
|
|
1030
1056
|
|
|
1057
|
+
getRecentConversationChunks(conversationId, limit) {
|
|
1058
|
+
const stmt = prep(
|
|
1059
|
+
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
1060
|
+
FROM chunks WHERE conversationId = ?
|
|
1061
|
+
ORDER BY created_at DESC LIMIT ?`
|
|
1062
|
+
);
|
|
1063
|
+
const rows = stmt.all(conversationId, limit);
|
|
1064
|
+
rows.reverse();
|
|
1065
|
+
return rows.map(row => {
|
|
1066
|
+
try {
|
|
1067
|
+
return {
|
|
1068
|
+
...row,
|
|
1069
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data
|
|
1070
|
+
};
|
|
1071
|
+
} catch (e) {
|
|
1072
|
+
return row;
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
},
|
|
1076
|
+
|
|
1031
1077
|
getChunksSince(sessionId, timestamp) {
|
|
1032
|
-
const stmt =
|
|
1078
|
+
const stmt = prep(
|
|
1033
1079
|
`SELECT id, sessionId, conversationId, sequence, type, data, created_at
|
|
1034
1080
|
FROM chunks WHERE sessionId = ? AND created_at > ? ORDER BY sequence ASC`
|
|
1035
1081
|
);
|
|
@@ -1047,19 +1093,19 @@ export const queries = {
|
|
|
1047
1093
|
},
|
|
1048
1094
|
|
|
1049
1095
|
deleteSessionChunks(sessionId) {
|
|
1050
|
-
const stmt =
|
|
1096
|
+
const stmt = prep('DELETE FROM chunks WHERE sessionId = ?');
|
|
1051
1097
|
const result = stmt.run(sessionId);
|
|
1052
1098
|
return result.changes || 0;
|
|
1053
1099
|
},
|
|
1054
1100
|
|
|
1055
1101
|
getMaxSequence(sessionId) {
|
|
1056
|
-
const stmt =
|
|
1102
|
+
const stmt = prep('SELECT MAX(sequence) as max FROM chunks WHERE sessionId = ?');
|
|
1057
1103
|
const result = stmt.get(sessionId);
|
|
1058
1104
|
return result?.max ?? -1;
|
|
1059
1105
|
},
|
|
1060
1106
|
|
|
1061
1107
|
getEmptyConversations() {
|
|
1062
|
-
const stmt =
|
|
1108
|
+
const stmt = prep(`
|
|
1063
1109
|
SELECT c.* FROM conversations c
|
|
1064
1110
|
LEFT JOIN messages m ON c.id = m.conversationId
|
|
1065
1111
|
WHERE c.status != 'deleted'
|
|
@@ -1079,12 +1125,12 @@ export const queries = {
|
|
|
1079
1125
|
}
|
|
1080
1126
|
|
|
1081
1127
|
const deleteStmt = db.transaction(() => {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1128
|
+
prep('DELETE FROM stream_updates WHERE conversationId = ?').run(id);
|
|
1129
|
+
prep('DELETE FROM chunks WHERE conversationId = ?').run(id);
|
|
1130
|
+
prep('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
1131
|
+
prep('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
|
1132
|
+
prep('DELETE FROM messages WHERE conversationId = ?').run(id);
|
|
1133
|
+
prep('DELETE FROM conversations WHERE id = ?').run(id);
|
|
1088
1134
|
});
|
|
1089
1135
|
|
|
1090
1136
|
deleteStmt();
|
package/lib/claude-runner.js
CHANGED
|
@@ -46,7 +46,8 @@ class AgentRunner {
|
|
|
46
46
|
const {
|
|
47
47
|
timeout = 300000,
|
|
48
48
|
onEvent = null,
|
|
49
|
-
onError = null
|
|
49
|
+
onError = null,
|
|
50
|
+
onRateLimit = null
|
|
50
51
|
} = config;
|
|
51
52
|
|
|
52
53
|
const args = this.buildArgs(prompt, config);
|
|
@@ -60,6 +61,8 @@ class AgentRunner {
|
|
|
60
61
|
const outputs = [];
|
|
61
62
|
let timedOut = false;
|
|
62
63
|
let sessionId = null;
|
|
64
|
+
let rateLimited = false;
|
|
65
|
+
let retryAfterSec = 60;
|
|
63
66
|
|
|
64
67
|
const timeoutHandle = setTimeout(() => {
|
|
65
68
|
timedOut = true;
|
|
@@ -103,6 +106,14 @@ class AgentRunner {
|
|
|
103
106
|
proc.stderr.on('data', (chunk) => {
|
|
104
107
|
const errorText = chunk.toString();
|
|
105
108
|
console.error(`[${this.id}] stderr:`, errorText);
|
|
109
|
+
|
|
110
|
+
const rateLimitMatch = errorText.match(/rate.?limit|429|too many requests|overloaded|throttl/i);
|
|
111
|
+
if (rateLimitMatch) {
|
|
112
|
+
rateLimited = true;
|
|
113
|
+
const retryMatch = errorText.match(/retry.?after[:\s]+(\d+)/i);
|
|
114
|
+
if (retryMatch) retryAfterSec = parseInt(retryMatch[1], 10) || 60;
|
|
115
|
+
}
|
|
116
|
+
|
|
106
117
|
if (onError) {
|
|
107
118
|
try { onError(errorText); } catch (e) {}
|
|
108
119
|
}
|
|
@@ -112,7 +123,17 @@ class AgentRunner {
|
|
|
112
123
|
clearTimeout(timeoutHandle);
|
|
113
124
|
if (timedOut) return;
|
|
114
125
|
|
|
115
|
-
|
|
126
|
+
if (rateLimited) {
|
|
127
|
+
const err = new Error(`Rate limited - retry after ${retryAfterSec}s`);
|
|
128
|
+
err.rateLimited = true;
|
|
129
|
+
err.retryAfterSec = retryAfterSec;
|
|
130
|
+
if (onRateLimit) {
|
|
131
|
+
try { onRateLimit({ retryAfterSec }); } catch (e) {}
|
|
132
|
+
}
|
|
133
|
+
reject(err);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
const success = code === 0 || (outputs.length > 0 && this.allowNonZeroExit);
|
|
117
138
|
|
|
118
139
|
if (success) {
|