@sanqianx/ai-coding-event-bridge-console 0.5.0 → 0.5.1
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/console/index.html +1 -0
- package/package.json +1 -1
- package/src/server.js +81 -24
package/console/index.html
CHANGED
|
@@ -495,6 +495,7 @@ function fetchTurns(options) {
|
|
|
495
495
|
if (!append) { state.turns = []; state.cursor = null; }
|
|
496
496
|
renderLive();
|
|
497
497
|
var query = '/api/turns?project=' + encodeURIComponent(state.project.id)
|
|
498
|
+
+ '&order=desc'
|
|
498
499
|
+ (state.cursor ? '&cursor=' + encodeURIComponent(state.cursor) : '');
|
|
499
500
|
return api(query).then(function (payload) {
|
|
500
501
|
if (!state.project || payload.project !== state.project.id) return; // 切换已发生,丢弃过期响应
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanqianx/ai-coding-event-bridge-console",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Local console for the AI coding event bridge: project import with per-project journal storage, REST query API and a built-in conversation explorer page.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
package/src/server.js
CHANGED
|
@@ -60,6 +60,16 @@ function decodeCursor(raw) {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function decodeSeqCursor(raw) {
|
|
64
|
+
try {
|
|
65
|
+
const decoded = JSON.parse(Buffer.from(String(raw), 'base64url').toString('utf8'));
|
|
66
|
+
if (decoded && decoded.v === 1 && typeof decoded.seq === 'number') return decoded;
|
|
67
|
+
} catch (_) {
|
|
68
|
+
// fall through
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
function pickFolderEnabled() {
|
|
64
74
|
return process.env.BRIDGE_CONSOLE_PICK_FOLDER !== '0';
|
|
65
75
|
}
|
|
@@ -294,7 +304,7 @@ function createConsoleServer({ home }) {
|
|
|
294
304
|
* project journal after its last commit boundary. Everything earlier is
|
|
295
305
|
* archived under <store>/commits and served by the commit-doc endpoints.
|
|
296
306
|
*/
|
|
297
|
-
async function currentTurns(project, { limit, cursorRaw }) {
|
|
307
|
+
async function currentTurns(project, { limit, cursorRaw, order }) {
|
|
298
308
|
const dir = projectJournalDir(project);
|
|
299
309
|
const journal = existingJournal(dir);
|
|
300
310
|
if (!journal) {
|
|
@@ -307,24 +317,43 @@ function createConsoleServer({ home }) {
|
|
|
307
317
|
const records = await journal.readEvents({ fromSequence: lastBoundary + 1 });
|
|
308
318
|
const turns = queryAt(dir)._projectTurns(records.filter(isConversationEvent));
|
|
309
319
|
turns.sort((a, b) => a.startSequence - b.startSequence);
|
|
310
|
-
let
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
let pageAsc;
|
|
321
|
+
let nextCursor = null;
|
|
322
|
+
if (order === 'desc') {
|
|
323
|
+
// Newest on top: page 1 holds the newest turns; each cursor step pages
|
|
324
|
+
// further into the past.
|
|
325
|
+
let start;
|
|
326
|
+
if (cursorRaw) {
|
|
327
|
+
const decoded = decodeSeqCursor(cursorRaw);
|
|
328
|
+
if (!decoded || (decoded.o && decoded.o !== 'desc')) return null;
|
|
329
|
+
start = turns.findIndex((turn) => turn.startSequence < decoded.seq);
|
|
330
|
+
if (start === -1) start = turns.length;
|
|
331
|
+
} else {
|
|
332
|
+
start = Math.max(0, turns.length - limit);
|
|
333
|
+
}
|
|
334
|
+
pageAsc = turns.slice(start, start + limit);
|
|
335
|
+
if (start > 0) {
|
|
336
|
+
nextCursor = encodeCursor({ v: 1, seq: turns[start].startSequence, o: 'desc' });
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
let start = 0;
|
|
340
|
+
if (cursorRaw) {
|
|
341
|
+
const decoded = decodeSeqCursor(cursorRaw);
|
|
342
|
+
if (!decoded || (decoded.o && decoded.o !== 'asc')) return null;
|
|
343
|
+
start = turns.findIndex((turn) => turn.startSequence > decoded.seq);
|
|
344
|
+
if (start === -1) start = turns.length;
|
|
345
|
+
}
|
|
346
|
+
pageAsc = turns.slice(start, start + limit);
|
|
347
|
+
if (start + limit < turns.length && pageAsc.length) {
|
|
348
|
+
nextCursor = encodeCursor({ v: 1, seq: pageAsc[pageAsc.length - 1].startSequence, o: 'asc' });
|
|
317
349
|
}
|
|
318
|
-
if (!decoded || decoded.v !== 1 || typeof decoded.seq !== 'number') return null;
|
|
319
|
-
start = turns.findIndex((turn) => turn.startSequence > decoded.seq);
|
|
320
|
-
if (start === -1) start = turns.length;
|
|
321
350
|
}
|
|
322
|
-
const page =
|
|
351
|
+
const page = order === 'desc' ? pageAsc.slice().reverse() : pageAsc;
|
|
323
352
|
return {
|
|
324
353
|
project: project.id,
|
|
325
354
|
date: null,
|
|
326
355
|
turns: page,
|
|
327
|
-
nextCursor
|
|
356
|
+
nextCursor,
|
|
328
357
|
totalTurns: turns.length,
|
|
329
358
|
annotations: {}
|
|
330
359
|
};
|
|
@@ -618,11 +647,12 @@ function createConsoleServer({ home }) {
|
|
|
618
647
|
|
|
619
648
|
if (route === 'GET /api/turns') {
|
|
620
649
|
const limit = Math.max(1, Math.min(Number(url.searchParams.get('limit')) || 50, 200));
|
|
650
|
+
const order = url.searchParams.get('order') === 'desc' ? 'desc' : 'asc';
|
|
621
651
|
if (!project.auto) {
|
|
622
652
|
// Registered projects: the journal is a conveyor — serve only the
|
|
623
653
|
// current (uncommitted) conversation and keep it maintained.
|
|
624
654
|
await maintainProject(project);
|
|
625
|
-
const current = await currentTurns(project, { limit, cursorRaw: url.searchParams.get('cursor') });
|
|
655
|
+
const current = await currentTurns(project, { limit, cursorRaw: url.searchParams.get('cursor'), order });
|
|
626
656
|
if (!current) {
|
|
627
657
|
sendJson(res, 400, { error: { code: 'CURSOR_INVALID', message: 'cursor is invalid; restart the query' } });
|
|
628
658
|
return;
|
|
@@ -633,22 +663,49 @@ function createConsoleServer({ home }) {
|
|
|
633
663
|
const date = url.searchParams.get('date') || null;
|
|
634
664
|
const merged = await mergedTurns(project, { date });
|
|
635
665
|
const cursor = url.searchParams.get('cursor');
|
|
636
|
-
let
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
666
|
+
let pageAsc;
|
|
667
|
+
let nextCursor = null;
|
|
668
|
+
if (order === 'desc') {
|
|
669
|
+
// Newest on top: page 1 holds the newest turns; each cursor step
|
|
670
|
+
// pages further into the past.
|
|
671
|
+
let start;
|
|
672
|
+
if (cursor) {
|
|
673
|
+
const decoded = decodeCursor(cursor);
|
|
674
|
+
if (!decoded || (decoded.o && decoded.o !== 'desc')) {
|
|
675
|
+
sendJson(res, 400, { error: { code: 'CURSOR_INVALID', message: 'cursor is invalid; restart the query' } });
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
start = merged.findIndex((turn) => turnSortKey(turn) < decoded.key);
|
|
679
|
+
if (start === -1) start = merged.length;
|
|
680
|
+
} else {
|
|
681
|
+
start = Math.max(0, merged.length - limit);
|
|
682
|
+
}
|
|
683
|
+
pageAsc = merged.slice(start, start + limit);
|
|
684
|
+
if (start > 0) {
|
|
685
|
+
nextCursor = encodeCursor({ v: 1, key: turnSortKey(merged[start]), o: 'desc' });
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
let start = 0;
|
|
689
|
+
if (cursor) {
|
|
690
|
+
const decoded = decodeCursor(cursor);
|
|
691
|
+
if (!decoded || (decoded.o && decoded.o !== 'asc')) {
|
|
692
|
+
sendJson(res, 400, { error: { code: 'CURSOR_INVALID', message: 'cursor is invalid; restart the query' } });
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
start = merged.findIndex((turn) => turnSortKey(turn) > decoded.key);
|
|
696
|
+
if (start === -1) start = merged.length;
|
|
697
|
+
}
|
|
698
|
+
pageAsc = merged.slice(start, start + limit);
|
|
699
|
+
if (start + limit < merged.length && pageAsc.length) {
|
|
700
|
+
nextCursor = encodeCursor({ v: 1, key: turnSortKey(pageAsc[pageAsc.length - 1]), o: 'asc' });
|
|
642
701
|
}
|
|
643
|
-
start = merged.findIndex((turn) => turnSortKey(turn) > decoded.key);
|
|
644
|
-
if (start === -1) start = merged.length;
|
|
645
702
|
}
|
|
646
|
-
const page =
|
|
703
|
+
const page = order === 'desc' ? pageAsc.slice().reverse() : pageAsc;
|
|
647
704
|
sendJson(res, 200, {
|
|
648
705
|
project: project.id,
|
|
649
706
|
date,
|
|
650
707
|
turns: page,
|
|
651
|
-
nextCursor
|
|
708
|
+
nextCursor,
|
|
652
709
|
totalTurns: merged.length,
|
|
653
710
|
annotations: await annotationsFor(project, page)
|
|
654
711
|
});
|