@aexol/spectral 0.9.173 → 0.9.174
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/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +1 -0
- package/dist/memory/config.d.ts +2 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +25 -0
- package/dist/relay/dispatcher.d.ts +7 -2
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +25 -18
- package/dist/server/handlers/agent-settings.d.ts +6 -0
- package/dist/server/handlers/agent-settings.d.ts.map +1 -1
- package/dist/server/handlers/agent-settings.js +88 -1
- package/dist/server/handlers/paths-autocomplete.d.ts +4 -4
- package/dist/server/handlers/paths-autocomplete.d.ts.map +1 -1
- package/dist/server/handlers/paths-autocomplete.js +10 -10
- package/dist/server/handlers/projects.d.ts +6 -0
- package/dist/server/handlers/projects.d.ts.map +1 -1
- package/dist/server/handlers/projects.js +18 -0
- package/dist/server/handlers/sessions.d.ts +12 -0
- package/dist/server/handlers/sessions.d.ts.map +1 -1
- package/dist/server/handlers/sessions.js +68 -0
- package/dist/server/handlers/settings-lock.d.ts +19 -0
- package/dist/server/handlers/settings-lock.d.ts.map +1 -1
- package/dist/server/handlers/settings-lock.js +65 -1
- package/dist/server/handlers/settings.d.ts +4 -0
- package/dist/server/handlers/settings.d.ts.map +1 -1
- package/dist/server/handlers/settings.js +38 -2
- package/dist/server/sqlite-adapter.d.ts +17 -0
- package/dist/server/sqlite-adapter.d.ts.map +1 -1
- package/dist/server/sqlite-adapter.js +18 -0
- package/dist/server/storage.d.ts +44 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +166 -8
- package/package.json +1 -1
package/dist/server/storage.js
CHANGED
|
@@ -190,8 +190,16 @@ export class SessionStore {
|
|
|
190
190
|
db;
|
|
191
191
|
closed = false;
|
|
192
192
|
// Project statements
|
|
193
|
+
stmtListProjectsSQL = `
|
|
194
|
+
SELECT p.id, p.name, p.path, p.created_at, p.updated_at,
|
|
195
|
+
p.studio_project_id, p.studio_project_name, p.studio_team_id,
|
|
196
|
+
(SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count
|
|
197
|
+
FROM projects p
|
|
198
|
+
ORDER BY p.updated_at DESC
|
|
199
|
+
`;
|
|
193
200
|
stmtListProjects;
|
|
194
201
|
stmtGetProject;
|
|
202
|
+
stmtCountSessionsByProject;
|
|
195
203
|
stmtCreateProject;
|
|
196
204
|
stmtUpdateProject;
|
|
197
205
|
stmtSetProjectStudioBinding;
|
|
@@ -375,6 +383,7 @@ export class SessionStore {
|
|
|
375
383
|
ORDER BY p.updated_at DESC
|
|
376
384
|
`);
|
|
377
385
|
this.stmtGetProject = this.db.prepare(`SELECT id, name, path, created_at, updated_at, studio_project_id, studio_project_name, studio_team_id FROM projects WHERE id = ?`);
|
|
386
|
+
this.stmtCountSessionsByProject = this.db.prepare(`SELECT COUNT(*) AS count FROM sessions WHERE project_id = ?`);
|
|
378
387
|
this.stmtCreateProject = this.db.prepare(`INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`);
|
|
379
388
|
this.stmtUpdateProject = this.db.prepare(`UPDATE projects SET name = ?, path = ?, updated_at = ? WHERE id = ?`);
|
|
380
389
|
this.stmtSetProjectStudioBinding = this.db.prepare(`UPDATE projects SET studio_project_id = ?, studio_project_name = ?, studio_team_id = ?, updated_at = ? WHERE id = ?`);
|
|
@@ -500,11 +509,9 @@ export class SessionStore {
|
|
|
500
509
|
.all();
|
|
501
510
|
return rows.map((r) => r.name);
|
|
502
511
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
listProjects() {
|
|
507
|
-
return this.stmtListProjects.all().map((r) => {
|
|
512
|
+
/** Shared row→WireProject mapping for the synchronous and async list paths. */
|
|
513
|
+
mapProjectRowsWithCount(rows) {
|
|
514
|
+
return rows.map((r) => {
|
|
508
515
|
const project = {
|
|
509
516
|
id: r.id,
|
|
510
517
|
name: r.name,
|
|
@@ -523,6 +530,21 @@ export class SessionStore {
|
|
|
523
530
|
return applyBindingFields(project, rowBinding);
|
|
524
531
|
});
|
|
525
532
|
}
|
|
533
|
+
// ----------------------------------------------------------------------
|
|
534
|
+
// Projects
|
|
535
|
+
// ----------------------------------------------------------------------
|
|
536
|
+
listProjects() {
|
|
537
|
+
return this.mapProjectRowsWithCount(this.stmtListProjects.all());
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Async twin of {@link listProjects}. Uses the adapter's `queryAll`
|
|
541
|
+
* trampoline so the Node adapter yields to the event loop before the
|
|
542
|
+
* potentially-large aggregate query runs. Wire shape is identical.
|
|
543
|
+
*/
|
|
544
|
+
async listProjectsAsync() {
|
|
545
|
+
const rows = await this.db.queryAll(this.stmtListProjectsSQL);
|
|
546
|
+
return this.mapProjectRowsWithCount(rows);
|
|
547
|
+
}
|
|
526
548
|
createProject(input) {
|
|
527
549
|
const id = input.id ?? randomUUID();
|
|
528
550
|
const name = input.name.trim() || "Untitled project";
|
|
@@ -543,15 +565,40 @@ export class SessionStore {
|
|
|
543
565
|
const row = this.stmtGetProject.get(id);
|
|
544
566
|
if (!row)
|
|
545
567
|
return null;
|
|
546
|
-
|
|
547
|
-
const
|
|
568
|
+
const withCount = this.stmtCountSessionsByProject.get(id);
|
|
569
|
+
const project = {
|
|
570
|
+
id: row.id,
|
|
571
|
+
name: row.name,
|
|
572
|
+
path: row.path,
|
|
573
|
+
createdAt: row.created_at,
|
|
574
|
+
updatedAt: row.updated_at,
|
|
575
|
+
sessionCount: withCount?.count ?? 0,
|
|
576
|
+
};
|
|
577
|
+
const rowBinding = row.studio_project_id
|
|
578
|
+
? {
|
|
579
|
+
projectId: row.studio_project_id,
|
|
580
|
+
...(row.studio_project_name ? { name: row.studio_project_name } : {}),
|
|
581
|
+
...(row.studio_team_id ? { teamId: row.studio_team_id } : {}),
|
|
582
|
+
}
|
|
583
|
+
: null;
|
|
584
|
+
return applyBindingFields(project, rowBinding);
|
|
585
|
+
}
|
|
586
|
+
/** Async twin of {@link getProject}. */
|
|
587
|
+
async getProjectAsync(id) {
|
|
588
|
+
const rows = await this.db.queryAll(`SELECT id, name, path, created_at, updated_at,
|
|
589
|
+
studio_project_id, studio_project_name, studio_team_id
|
|
590
|
+
FROM projects WHERE id = ?`, id);
|
|
591
|
+
const row = rows[0];
|
|
592
|
+
if (!row)
|
|
593
|
+
return null;
|
|
594
|
+
const countRows = await this.db.queryAll(`SELECT COUNT(*) AS count FROM sessions WHERE project_id = ?`, id);
|
|
548
595
|
const project = {
|
|
549
596
|
id: row.id,
|
|
550
597
|
name: row.name,
|
|
551
598
|
path: row.path,
|
|
552
599
|
createdAt: row.created_at,
|
|
553
600
|
updatedAt: row.updated_at,
|
|
554
|
-
sessionCount:
|
|
601
|
+
sessionCount: countRows[0]?.count ?? 0,
|
|
555
602
|
};
|
|
556
603
|
const rowBinding = row.studio_project_id
|
|
557
604
|
? {
|
|
@@ -607,6 +654,22 @@ export class SessionStore {
|
|
|
607
654
|
messageCount: r.message_count,
|
|
608
655
|
}));
|
|
609
656
|
}
|
|
657
|
+
/** Async twin of {@link listSessionsByProject}. */
|
|
658
|
+
async listSessionsByProjectAsync(projectId) {
|
|
659
|
+
const rows = await this.db.queryAll(`SELECT s.id, s.project_id, s.title, s.created_at, s.updated_at,
|
|
660
|
+
(SELECT COUNT(*) FROM messages m WHERE m.session_id = s.id) AS message_count
|
|
661
|
+
FROM sessions s
|
|
662
|
+
WHERE s.project_id = ?
|
|
663
|
+
ORDER BY s.updated_at DESC`, projectId);
|
|
664
|
+
return rows.map((r) => ({
|
|
665
|
+
id: r.id,
|
|
666
|
+
projectId: r.project_id,
|
|
667
|
+
title: r.title,
|
|
668
|
+
createdAt: r.created_at,
|
|
669
|
+
updatedAt: r.updated_at,
|
|
670
|
+
messageCount: r.message_count,
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
610
673
|
/**
|
|
611
674
|
* Batched variant of {@link listSessionsByProject} for many projects at once.
|
|
612
675
|
*
|
|
@@ -654,6 +717,42 @@ export class SessionStore {
|
|
|
654
717
|
}
|
|
655
718
|
return result;
|
|
656
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Async twin of {@link listSessionsByProjectIds}. Runs the same single
|
|
722
|
+
* LEFT JOIN + GROUP BY query through the adapter trampoline so large
|
|
723
|
+
* aggregate reads yield to the event loop. Returns an identical Map with
|
|
724
|
+
* every requested project id present (empty arrays for projects with no
|
|
725
|
+
* sessions) and identical per-project `updated_at DESC` ordering.
|
|
726
|
+
*/
|
|
727
|
+
async listSessionsByProjectIdsAsync(projectIds) {
|
|
728
|
+
const result = new Map();
|
|
729
|
+
for (const id of projectIds)
|
|
730
|
+
result.set(id, []);
|
|
731
|
+
if (projectIds.length === 0)
|
|
732
|
+
return result;
|
|
733
|
+
const placeholders = projectIds.map(() => "?").join(", ");
|
|
734
|
+
const rows = await this.db.queryAll(`SELECT s.id, s.project_id, s.title, s.created_at, s.updated_at,
|
|
735
|
+
COUNT(m.id) AS message_count
|
|
736
|
+
FROM sessions s
|
|
737
|
+
LEFT JOIN messages m ON m.session_id = s.id
|
|
738
|
+
WHERE s.project_id IN (${placeholders})
|
|
739
|
+
GROUP BY s.id, s.project_id, s.title, s.created_at, s.updated_at
|
|
740
|
+
ORDER BY s.updated_at DESC, s.created_at DESC, s.id DESC`, ...projectIds);
|
|
741
|
+
for (const r of rows) {
|
|
742
|
+
const list = result.get(r.project_id);
|
|
743
|
+
if (!list)
|
|
744
|
+
continue;
|
|
745
|
+
list.push({
|
|
746
|
+
id: r.id,
|
|
747
|
+
projectId: r.project_id,
|
|
748
|
+
title: r.title,
|
|
749
|
+
createdAt: r.created_at,
|
|
750
|
+
updatedAt: r.updated_at,
|
|
751
|
+
messageCount: r.message_count,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
657
756
|
// ----------------------------------------------------------------------
|
|
658
757
|
// Sessions
|
|
659
758
|
// ----------------------------------------------------------------------
|
|
@@ -727,6 +826,34 @@ export class SessionStore {
|
|
|
727
826
|
updatedAt: row.updated_at,
|
|
728
827
|
};
|
|
729
828
|
}
|
|
829
|
+
/** Async twin of {@link getSessionMeta}. */
|
|
830
|
+
async getSessionMetaAsync(id) {
|
|
831
|
+
const rows = await this.db.queryAll(`SELECT id, project_id, title, created_at, updated_at
|
|
832
|
+
FROM sessions WHERE id = ?`, id);
|
|
833
|
+
const row = rows[0];
|
|
834
|
+
if (!row)
|
|
835
|
+
return null;
|
|
836
|
+
return {
|
|
837
|
+
id: row.id,
|
|
838
|
+
projectId: row.project_id,
|
|
839
|
+
title: row.title,
|
|
840
|
+
createdAt: row.created_at,
|
|
841
|
+
updatedAt: row.updated_at,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/** Async twin of {@link getMessages} with a caller-supplied bound. */
|
|
845
|
+
async getMessagesTailAsync(sessionId, limit) {
|
|
846
|
+
const rows = await this.db.queryAll(`SELECT id, session_id, role, content, events_jsonl, images_json, credits_used, created_at
|
|
847
|
+
FROM (
|
|
848
|
+
SELECT id, session_id, role, content, events_jsonl, images_json, credits_used, created_at
|
|
849
|
+
FROM messages
|
|
850
|
+
WHERE session_id = ?
|
|
851
|
+
ORDER BY created_at DESC, id DESC
|
|
852
|
+
LIMIT ?
|
|
853
|
+
)
|
|
854
|
+
ORDER BY created_at ASC, id ASC`, sessionId, limit);
|
|
855
|
+
return this.mapMessageRows(rows);
|
|
856
|
+
}
|
|
730
857
|
getMessages(sessionId) {
|
|
731
858
|
return this.mapMessageRows(this.stmtListMessages.all(sessionId));
|
|
732
859
|
}
|
|
@@ -738,9 +865,30 @@ export class SessionStore {
|
|
|
738
865
|
return [];
|
|
739
866
|
return this.mapMessageRows(this.stmtListMessagesBefore.all(sessionId, opts.beforeCreatedAt, opts.beforeCreatedAt, opts.beforeId ?? "\uffff", opts.limit));
|
|
740
867
|
}
|
|
868
|
+
/** Async twin of {@link getMessagesBefore}. */
|
|
869
|
+
async getMessagesBeforeAsync(sessionId, opts) {
|
|
870
|
+
if (opts.beforeCreatedAt === null)
|
|
871
|
+
return [];
|
|
872
|
+
const rows = await this.db.queryAll(`SELECT id, session_id, role, content, events_jsonl, images_json, credits_used, created_at
|
|
873
|
+
FROM (
|
|
874
|
+
SELECT id, session_id, role, content, events_jsonl, images_json, credits_used, created_at
|
|
875
|
+
FROM messages
|
|
876
|
+
WHERE session_id = ?
|
|
877
|
+
AND (created_at < ? OR (created_at = ? AND id < ?))
|
|
878
|
+
ORDER BY created_at DESC, id DESC
|
|
879
|
+
LIMIT ?
|
|
880
|
+
)
|
|
881
|
+
ORDER BY created_at ASC, id ASC`, sessionId, opts.beforeCreatedAt, opts.beforeCreatedAt, opts.beforeId ?? "\uffff", opts.limit);
|
|
882
|
+
return this.mapMessageRows(rows);
|
|
883
|
+
}
|
|
741
884
|
countMessagesBySession(sessionId) {
|
|
742
885
|
return this.stmtCountMessagesBySession.get(sessionId)?.count ?? 0;
|
|
743
886
|
}
|
|
887
|
+
/** Async twin of {@link countMessagesBySession}. */
|
|
888
|
+
async countMessagesBySessionAsync(sessionId) {
|
|
889
|
+
const rows = await this.db.queryAll(`SELECT COUNT(*) AS count FROM messages WHERE session_id = ?`, sessionId);
|
|
890
|
+
return rows[0]?.count ?? 0;
|
|
891
|
+
}
|
|
744
892
|
mapMessageRows(rows) {
|
|
745
893
|
return rows.map((r) => {
|
|
746
894
|
const images = parseImagesJson(r.images_json);
|
|
@@ -952,6 +1100,11 @@ export class SessionStore {
|
|
|
952
1100
|
getSessionActiveAgent(sessionId) {
|
|
953
1101
|
return this.stmtGetSessionActiveAgent.get(sessionId)?.active_agent ?? null;
|
|
954
1102
|
}
|
|
1103
|
+
/** Async twin of {@link getSessionActiveAgent}. */
|
|
1104
|
+
async getSessionActiveAgentAsync(sessionId) {
|
|
1105
|
+
const rows = await this.db.queryAll(`SELECT active_agent FROM sessions WHERE id = ?`, sessionId);
|
|
1106
|
+
return rows[0]?.active_agent ?? null;
|
|
1107
|
+
}
|
|
955
1108
|
setSessionActiveAgent(sessionId, agentName) {
|
|
956
1109
|
this.stmtSetSessionActiveAgent.run(agentName, sessionId);
|
|
957
1110
|
}
|
|
@@ -1035,6 +1188,11 @@ export class SessionStore {
|
|
|
1035
1188
|
const row = this.stmtGetForkCompactSource.get(sessionId);
|
|
1036
1189
|
return row?.fork_compact_source_id ?? null;
|
|
1037
1190
|
}
|
|
1191
|
+
/** Async twin of {@link getForkCompactSource}. */
|
|
1192
|
+
async getForkCompactSourceAsync(sessionId) {
|
|
1193
|
+
const rows = await this.db.queryAll(`SELECT fork_compact_source_id FROM sessions WHERE id = ?`, sessionId);
|
|
1194
|
+
return rows[0]?.fork_compact_source_id ?? null;
|
|
1195
|
+
}
|
|
1038
1196
|
/**
|
|
1039
1197
|
* Clear the fork-compact flag after compaction completes (or fails).
|
|
1040
1198
|
* Idempotent-safe.
|