engrm 0.4.26 → 0.4.28
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 +7 -2
- package/dist/cli.js +84 -0
- package/dist/hooks/elicitation-result.js +73 -0
- package/dist/hooks/post-tool-use.js +89 -2
- package/dist/hooks/pre-compact.js +89 -2
- package/dist/hooks/sentinel.js +70 -0
- package/dist/hooks/session-start.js +92 -2
- package/dist/hooks/stop.js +96 -3
- package/dist/hooks/user-prompt-submit.js +154 -61
- package/dist/server.js +357 -59
- package/package.json +1 -1
package/dist/hooks/sentinel.js
CHANGED
|
@@ -756,6 +756,17 @@ var MIGRATIONS = [
|
|
|
756
756
|
ON chat_messages(session_id, transcript_index)
|
|
757
757
|
WHERE transcript_index IS NOT NULL;
|
|
758
758
|
`
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
version: 18,
|
|
762
|
+
description: "Add sqlite-vec semantic search for chat recall",
|
|
763
|
+
condition: (db) => isVecExtensionLoaded(db),
|
|
764
|
+
sql: `
|
|
765
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
766
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
767
|
+
embedding FLOAT[384]
|
|
768
|
+
);
|
|
769
|
+
`
|
|
759
770
|
}
|
|
760
771
|
];
|
|
761
772
|
function isVecExtensionLoaded(db) {
|
|
@@ -829,6 +840,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
829
840
|
if (columnExists(db, "chat_messages", "source_kind") && columnExists(db, "chat_messages", "transcript_index")) {
|
|
830
841
|
version = Math.max(version, 17);
|
|
831
842
|
}
|
|
843
|
+
if (tableExists(db, "vec_chat_messages")) {
|
|
844
|
+
version = Math.max(version, 18);
|
|
845
|
+
}
|
|
832
846
|
return version;
|
|
833
847
|
}
|
|
834
848
|
function runMigrations(db) {
|
|
@@ -945,6 +959,20 @@ function ensureChatMessageColumns(db) {
|
|
|
945
959
|
db.exec("PRAGMA user_version = 17");
|
|
946
960
|
}
|
|
947
961
|
}
|
|
962
|
+
function ensureChatVectorTable(db) {
|
|
963
|
+
if (!isVecExtensionLoaded(db))
|
|
964
|
+
return;
|
|
965
|
+
db.exec(`
|
|
966
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
967
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
968
|
+
embedding FLOAT[384]
|
|
969
|
+
);
|
|
970
|
+
`);
|
|
971
|
+
const current = getSchemaVersion(db);
|
|
972
|
+
if (current < 18) {
|
|
973
|
+
db.exec("PRAGMA user_version = 18");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
948
976
|
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
949
977
|
if (syncOutboxSupportsChatMessages(db)) {
|
|
950
978
|
const current = getSchemaVersion(db);
|
|
@@ -1158,6 +1186,7 @@ class MemDatabase {
|
|
|
1158
1186
|
ensureObservationTypes(this.db);
|
|
1159
1187
|
ensureSessionSummaryColumns(this.db);
|
|
1160
1188
|
ensureChatMessageColumns(this.db);
|
|
1189
|
+
ensureChatVectorTable(this.db);
|
|
1161
1190
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1162
1191
|
}
|
|
1163
1192
|
loadVecExtension() {
|
|
@@ -1524,6 +1553,14 @@ class MemDatabase {
|
|
|
1524
1553
|
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
1525
1554
|
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
1526
1555
|
}
|
|
1556
|
+
getChatMessagesByIds(ids) {
|
|
1557
|
+
if (ids.length === 0)
|
|
1558
|
+
return [];
|
|
1559
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1560
|
+
const rows = this.db.query(`SELECT * FROM chat_messages WHERE id IN (${placeholders})`).all(...ids);
|
|
1561
|
+
const order = new Map(ids.map((id, index) => [id, index]));
|
|
1562
|
+
return rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
1563
|
+
}
|
|
1527
1564
|
getSessionChatMessages(sessionId, limit = 50) {
|
|
1528
1565
|
return this.db.query(`SELECT * FROM chat_messages
|
|
1529
1566
|
WHERE session_id = ?
|
|
@@ -1600,6 +1637,39 @@ class MemDatabase {
|
|
|
1600
1637
|
ORDER BY created_at_epoch DESC, id DESC
|
|
1601
1638
|
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
1602
1639
|
}
|
|
1640
|
+
vecChatInsert(chatMessageId, embedding) {
|
|
1641
|
+
if (!this.vecAvailable)
|
|
1642
|
+
return;
|
|
1643
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(chatMessageId, new Uint8Array(embedding.buffer));
|
|
1644
|
+
}
|
|
1645
|
+
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1646
|
+
if (!this.vecAvailable)
|
|
1647
|
+
return [];
|
|
1648
|
+
const embeddingBlob = new Uint8Array(queryEmbedding.buffer);
|
|
1649
|
+
const visibilityClause = userId ? " AND c.user_id = ?" : "";
|
|
1650
|
+
const transcriptPreference = `
|
|
1651
|
+
AND (
|
|
1652
|
+
c.source_kind = 'transcript'
|
|
1653
|
+
OR NOT EXISTS (
|
|
1654
|
+
SELECT 1 FROM chat_messages t2
|
|
1655
|
+
WHERE t2.session_id = c.session_id
|
|
1656
|
+
AND t2.source_kind = 'transcript'
|
|
1657
|
+
)
|
|
1658
|
+
)`;
|
|
1659
|
+
if (projectId !== null) {
|
|
1660
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
1661
|
+
FROM vec_chat_messages v
|
|
1662
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
1663
|
+
WHERE v.embedding MATCH ?
|
|
1664
|
+
AND k = ?
|
|
1665
|
+
AND c.project_id = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, projectId, ...userId ? [userId] : []);
|
|
1666
|
+
}
|
|
1667
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
1668
|
+
FROM vec_chat_messages v
|
|
1669
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
1670
|
+
WHERE v.embedding MATCH ?
|
|
1671
|
+
AND k = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, ...userId ? [userId] : []);
|
|
1672
|
+
}
|
|
1603
1673
|
getTranscriptChatMessage(sessionId, transcriptIndex) {
|
|
1604
1674
|
return this.db.query("SELECT * FROM chat_messages WHERE session_id = ? AND transcript_index = ?").get(sessionId, transcriptIndex) ?? null;
|
|
1605
1675
|
}
|
|
@@ -494,6 +494,8 @@ function getSessionStory(db, input) {
|
|
|
494
494
|
summary,
|
|
495
495
|
prompts,
|
|
496
496
|
chat_messages: chatMessages,
|
|
497
|
+
chat_source_summary: summarizeChatSources(chatMessages),
|
|
498
|
+
chat_coverage_state: chatMessages.some((message) => message.source_kind === "transcript") ? "transcript-backed" : chatMessages.length > 0 ? "hook-only" : "none",
|
|
497
499
|
tool_events: toolEvents,
|
|
498
500
|
observations,
|
|
499
501
|
handoffs,
|
|
@@ -591,6 +593,12 @@ function collectProvenanceSummary(observations) {
|
|
|
591
593
|
}
|
|
592
594
|
return Array.from(counts.entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
593
595
|
}
|
|
596
|
+
function summarizeChatSources(messages) {
|
|
597
|
+
return messages.reduce((summary, message) => {
|
|
598
|
+
summary[message.source_kind] += 1;
|
|
599
|
+
return summary;
|
|
600
|
+
}, { transcript: 0, hook: 0 });
|
|
601
|
+
}
|
|
594
602
|
|
|
595
603
|
// src/tools/save.ts
|
|
596
604
|
import { relative, isAbsolute } from "node:path";
|
|
@@ -919,6 +927,9 @@ function composeEmbeddingText(obs) {
|
|
|
919
927
|
|
|
920
928
|
`);
|
|
921
929
|
}
|
|
930
|
+
function composeChatEmbeddingText(text) {
|
|
931
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 2000);
|
|
932
|
+
}
|
|
922
933
|
async function getPipeline() {
|
|
923
934
|
if (_pipeline)
|
|
924
935
|
return _pipeline;
|
|
@@ -3049,7 +3060,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync
|
|
|
3049
3060
|
import { join as join3 } from "node:path";
|
|
3050
3061
|
import { homedir } from "node:os";
|
|
3051
3062
|
var STATE_PATH = join3(homedir(), ".engrm", "config-fingerprint.json");
|
|
3052
|
-
var CLIENT_VERSION = "0.4.
|
|
3063
|
+
var CLIENT_VERSION = "0.4.28";
|
|
3053
3064
|
function hashFile(filePath) {
|
|
3054
3065
|
try {
|
|
3055
3066
|
if (!existsSync3(filePath))
|
|
@@ -4244,6 +4255,17 @@ var MIGRATIONS = [
|
|
|
4244
4255
|
ON chat_messages(session_id, transcript_index)
|
|
4245
4256
|
WHERE transcript_index IS NOT NULL;
|
|
4246
4257
|
`
|
|
4258
|
+
},
|
|
4259
|
+
{
|
|
4260
|
+
version: 18,
|
|
4261
|
+
description: "Add sqlite-vec semantic search for chat recall",
|
|
4262
|
+
condition: (db) => isVecExtensionLoaded(db),
|
|
4263
|
+
sql: `
|
|
4264
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
4265
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
4266
|
+
embedding FLOAT[384]
|
|
4267
|
+
);
|
|
4268
|
+
`
|
|
4247
4269
|
}
|
|
4248
4270
|
];
|
|
4249
4271
|
function isVecExtensionLoaded(db) {
|
|
@@ -4317,6 +4339,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
4317
4339
|
if (columnExists(db, "chat_messages", "source_kind") && columnExists(db, "chat_messages", "transcript_index")) {
|
|
4318
4340
|
version = Math.max(version, 17);
|
|
4319
4341
|
}
|
|
4342
|
+
if (tableExists(db, "vec_chat_messages")) {
|
|
4343
|
+
version = Math.max(version, 18);
|
|
4344
|
+
}
|
|
4320
4345
|
return version;
|
|
4321
4346
|
}
|
|
4322
4347
|
function runMigrations(db) {
|
|
@@ -4433,6 +4458,20 @@ function ensureChatMessageColumns(db) {
|
|
|
4433
4458
|
db.exec("PRAGMA user_version = 17");
|
|
4434
4459
|
}
|
|
4435
4460
|
}
|
|
4461
|
+
function ensureChatVectorTable(db) {
|
|
4462
|
+
if (!isVecExtensionLoaded(db))
|
|
4463
|
+
return;
|
|
4464
|
+
db.exec(`
|
|
4465
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
4466
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
4467
|
+
embedding FLOAT[384]
|
|
4468
|
+
);
|
|
4469
|
+
`);
|
|
4470
|
+
const current = getSchemaVersion(db);
|
|
4471
|
+
if (current < 18) {
|
|
4472
|
+
db.exec("PRAGMA user_version = 18");
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4436
4475
|
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
4437
4476
|
if (syncOutboxSupportsChatMessages(db)) {
|
|
4438
4477
|
const current = getSchemaVersion(db);
|
|
@@ -4566,6 +4605,7 @@ class MemDatabase {
|
|
|
4566
4605
|
ensureObservationTypes(this.db);
|
|
4567
4606
|
ensureSessionSummaryColumns(this.db);
|
|
4568
4607
|
ensureChatMessageColumns(this.db);
|
|
4608
|
+
ensureChatVectorTable(this.db);
|
|
4569
4609
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
4570
4610
|
}
|
|
4571
4611
|
loadVecExtension() {
|
|
@@ -4932,6 +4972,14 @@ class MemDatabase {
|
|
|
4932
4972
|
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
4933
4973
|
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
4934
4974
|
}
|
|
4975
|
+
getChatMessagesByIds(ids) {
|
|
4976
|
+
if (ids.length === 0)
|
|
4977
|
+
return [];
|
|
4978
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
4979
|
+
const rows = this.db.query(`SELECT * FROM chat_messages WHERE id IN (${placeholders})`).all(...ids);
|
|
4980
|
+
const order = new Map(ids.map((id, index) => [id, index]));
|
|
4981
|
+
return rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
4982
|
+
}
|
|
4935
4983
|
getSessionChatMessages(sessionId, limit = 50) {
|
|
4936
4984
|
return this.db.query(`SELECT * FROM chat_messages
|
|
4937
4985
|
WHERE session_id = ?
|
|
@@ -5008,6 +5056,39 @@ class MemDatabase {
|
|
|
5008
5056
|
ORDER BY created_at_epoch DESC, id DESC
|
|
5009
5057
|
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
5010
5058
|
}
|
|
5059
|
+
vecChatInsert(chatMessageId, embedding) {
|
|
5060
|
+
if (!this.vecAvailable)
|
|
5061
|
+
return;
|
|
5062
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(chatMessageId, new Uint8Array(embedding.buffer));
|
|
5063
|
+
}
|
|
5064
|
+
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
5065
|
+
if (!this.vecAvailable)
|
|
5066
|
+
return [];
|
|
5067
|
+
const embeddingBlob = new Uint8Array(queryEmbedding.buffer);
|
|
5068
|
+
const visibilityClause = userId ? " AND c.user_id = ?" : "";
|
|
5069
|
+
const transcriptPreference = `
|
|
5070
|
+
AND (
|
|
5071
|
+
c.source_kind = 'transcript'
|
|
5072
|
+
OR NOT EXISTS (
|
|
5073
|
+
SELECT 1 FROM chat_messages t2
|
|
5074
|
+
WHERE t2.session_id = c.session_id
|
|
5075
|
+
AND t2.source_kind = 'transcript'
|
|
5076
|
+
)
|
|
5077
|
+
)`;
|
|
5078
|
+
if (projectId !== null) {
|
|
5079
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
5080
|
+
FROM vec_chat_messages v
|
|
5081
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
5082
|
+
WHERE v.embedding MATCH ?
|
|
5083
|
+
AND k = ?
|
|
5084
|
+
AND c.project_id = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, projectId, ...userId ? [userId] : []);
|
|
5085
|
+
}
|
|
5086
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
5087
|
+
FROM vec_chat_messages v
|
|
5088
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
5089
|
+
WHERE v.embedding MATCH ?
|
|
5090
|
+
AND k = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, ...userId ? [userId] : []);
|
|
5091
|
+
}
|
|
5011
5092
|
getTranscriptChatMessage(sessionId, transcriptIndex) {
|
|
5012
5093
|
return this.db.query("SELECT * FROM chat_messages WHERE session_id = ? AND transcript_index = ?").get(sessionId, transcriptIndex) ?? null;
|
|
5013
5094
|
}
|
|
@@ -5661,6 +5742,9 @@ function formatInspectHints(context, visibleObservationIds = []) {
|
|
|
5661
5742
|
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentToolEvents?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0) {
|
|
5662
5743
|
hints.push("activity_feed");
|
|
5663
5744
|
}
|
|
5745
|
+
if ((context.recentPrompts?.length ?? 0) > 0 || (context.recentChatMessages?.length ?? 0) > 0 || context.observations.length > 0) {
|
|
5746
|
+
hints.push("search_recall");
|
|
5747
|
+
}
|
|
5664
5748
|
if (context.observations.length > 0) {
|
|
5665
5749
|
hints.push("memory_console");
|
|
5666
5750
|
}
|
|
@@ -5671,10 +5755,12 @@ function formatInspectHints(context, visibleObservationIds = []) {
|
|
|
5671
5755
|
if ((context.recentChatMessages?.length ?? 0) > 0) {
|
|
5672
5756
|
hints.push("recent_chat");
|
|
5673
5757
|
}
|
|
5758
|
+
if (hasHookOnlyRecentChat(context)) {
|
|
5759
|
+
hints.push("refresh_chat_recall");
|
|
5760
|
+
}
|
|
5674
5761
|
if (continuityState !== "fresh") {
|
|
5675
5762
|
hints.push("recent_chat");
|
|
5676
5763
|
hints.push("recent_handoffs");
|
|
5677
|
-
hints.push("refresh_chat_recall");
|
|
5678
5764
|
}
|
|
5679
5765
|
const unique = Array.from(new Set(hints)).slice(0, 4);
|
|
5680
5766
|
if (unique.length === 0)
|
|
@@ -6144,6 +6230,10 @@ function hasFreshContinuitySignal(context) {
|
|
|
6144
6230
|
function getStartupContinuityState(context) {
|
|
6145
6231
|
return classifyContinuityState(context.recentPrompts?.length ?? 0, context.recentToolEvents?.length ?? 0, context.recentHandoffs?.length ?? 0, context.recentChatMessages?.length ?? 0, context.recentSessions ?? [], context.recentOutcomes?.length ?? 0);
|
|
6146
6232
|
}
|
|
6233
|
+
function hasHookOnlyRecentChat(context) {
|
|
6234
|
+
const recentChat = context.recentChatMessages ?? [];
|
|
6235
|
+
return recentChat.length > 0 && !recentChat.some((message) => message.source_kind === "transcript");
|
|
6236
|
+
}
|
|
6147
6237
|
function observationAgeDays3(obs) {
|
|
6148
6238
|
const createdAt = new Date(obs.created_at).getTime();
|
|
6149
6239
|
if (!Number.isFinite(createdAt))
|
package/dist/hooks/stop.js
CHANGED
|
@@ -993,6 +993,17 @@ var MIGRATIONS = [
|
|
|
993
993
|
ON chat_messages(session_id, transcript_index)
|
|
994
994
|
WHERE transcript_index IS NOT NULL;
|
|
995
995
|
`
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
version: 18,
|
|
999
|
+
description: "Add sqlite-vec semantic search for chat recall",
|
|
1000
|
+
condition: (db) => isVecExtensionLoaded(db),
|
|
1001
|
+
sql: `
|
|
1002
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
1003
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
1004
|
+
embedding FLOAT[384]
|
|
1005
|
+
);
|
|
1006
|
+
`
|
|
996
1007
|
}
|
|
997
1008
|
];
|
|
998
1009
|
function isVecExtensionLoaded(db) {
|
|
@@ -1066,6 +1077,9 @@ function inferLegacySchemaVersion(db) {
|
|
|
1066
1077
|
if (columnExists(db, "chat_messages", "source_kind") && columnExists(db, "chat_messages", "transcript_index")) {
|
|
1067
1078
|
version = Math.max(version, 17);
|
|
1068
1079
|
}
|
|
1080
|
+
if (tableExists(db, "vec_chat_messages")) {
|
|
1081
|
+
version = Math.max(version, 18);
|
|
1082
|
+
}
|
|
1069
1083
|
return version;
|
|
1070
1084
|
}
|
|
1071
1085
|
function runMigrations(db) {
|
|
@@ -1182,6 +1196,20 @@ function ensureChatMessageColumns(db) {
|
|
|
1182
1196
|
db.exec("PRAGMA user_version = 17");
|
|
1183
1197
|
}
|
|
1184
1198
|
}
|
|
1199
|
+
function ensureChatVectorTable(db) {
|
|
1200
|
+
if (!isVecExtensionLoaded(db))
|
|
1201
|
+
return;
|
|
1202
|
+
db.exec(`
|
|
1203
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chat_messages USING vec0(
|
|
1204
|
+
chat_message_id INTEGER PRIMARY KEY,
|
|
1205
|
+
embedding FLOAT[384]
|
|
1206
|
+
);
|
|
1207
|
+
`);
|
|
1208
|
+
const current = getSchemaVersion(db);
|
|
1209
|
+
if (current < 18) {
|
|
1210
|
+
db.exec("PRAGMA user_version = 18");
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1185
1213
|
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
1186
1214
|
if (syncOutboxSupportsChatMessages(db)) {
|
|
1187
1215
|
const current = getSchemaVersion(db);
|
|
@@ -1315,6 +1343,7 @@ class MemDatabase {
|
|
|
1315
1343
|
ensureObservationTypes(this.db);
|
|
1316
1344
|
ensureSessionSummaryColumns(this.db);
|
|
1317
1345
|
ensureChatMessageColumns(this.db);
|
|
1346
|
+
ensureChatVectorTable(this.db);
|
|
1318
1347
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1319
1348
|
}
|
|
1320
1349
|
loadVecExtension() {
|
|
@@ -1681,6 +1710,14 @@ class MemDatabase {
|
|
|
1681
1710
|
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
1682
1711
|
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
1683
1712
|
}
|
|
1713
|
+
getChatMessagesByIds(ids) {
|
|
1714
|
+
if (ids.length === 0)
|
|
1715
|
+
return [];
|
|
1716
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1717
|
+
const rows = this.db.query(`SELECT * FROM chat_messages WHERE id IN (${placeholders})`).all(...ids);
|
|
1718
|
+
const order = new Map(ids.map((id, index) => [id, index]));
|
|
1719
|
+
return rows.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
1720
|
+
}
|
|
1684
1721
|
getSessionChatMessages(sessionId, limit = 50) {
|
|
1685
1722
|
return this.db.query(`SELECT * FROM chat_messages
|
|
1686
1723
|
WHERE session_id = ?
|
|
@@ -1757,6 +1794,39 @@ class MemDatabase {
|
|
|
1757
1794
|
ORDER BY created_at_epoch DESC, id DESC
|
|
1758
1795
|
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
1759
1796
|
}
|
|
1797
|
+
vecChatInsert(chatMessageId, embedding) {
|
|
1798
|
+
if (!this.vecAvailable)
|
|
1799
|
+
return;
|
|
1800
|
+
this.db.query("INSERT OR REPLACE INTO vec_chat_messages (chat_message_id, embedding) VALUES (?, ?)").run(chatMessageId, new Uint8Array(embedding.buffer));
|
|
1801
|
+
}
|
|
1802
|
+
searchChatVec(queryEmbedding, projectId, limit = 20, userId) {
|
|
1803
|
+
if (!this.vecAvailable)
|
|
1804
|
+
return [];
|
|
1805
|
+
const embeddingBlob = new Uint8Array(queryEmbedding.buffer);
|
|
1806
|
+
const visibilityClause = userId ? " AND c.user_id = ?" : "";
|
|
1807
|
+
const transcriptPreference = `
|
|
1808
|
+
AND (
|
|
1809
|
+
c.source_kind = 'transcript'
|
|
1810
|
+
OR NOT EXISTS (
|
|
1811
|
+
SELECT 1 FROM chat_messages t2
|
|
1812
|
+
WHERE t2.session_id = c.session_id
|
|
1813
|
+
AND t2.source_kind = 'transcript'
|
|
1814
|
+
)
|
|
1815
|
+
)`;
|
|
1816
|
+
if (projectId !== null) {
|
|
1817
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
1818
|
+
FROM vec_chat_messages v
|
|
1819
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
1820
|
+
WHERE v.embedding MATCH ?
|
|
1821
|
+
AND k = ?
|
|
1822
|
+
AND c.project_id = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, projectId, ...userId ? [userId] : []);
|
|
1823
|
+
}
|
|
1824
|
+
return this.db.query(`SELECT v.chat_message_id, v.distance
|
|
1825
|
+
FROM vec_chat_messages v
|
|
1826
|
+
JOIN chat_messages c ON c.id = v.chat_message_id
|
|
1827
|
+
WHERE v.embedding MATCH ?
|
|
1828
|
+
AND k = ?` + visibilityClause + transcriptPreference).all(embeddingBlob, limit, ...userId ? [userId] : []);
|
|
1829
|
+
}
|
|
1760
1830
|
getTranscriptChatMessage(sessionId, transcriptIndex) {
|
|
1761
1831
|
return this.db.query("SELECT * FROM chat_messages WHERE session_id = ? AND transcript_index = ?").get(sessionId, transcriptIndex) ?? null;
|
|
1762
1832
|
}
|
|
@@ -2719,6 +2789,9 @@ function composeEmbeddingText(obs) {
|
|
|
2719
2789
|
|
|
2720
2790
|
`);
|
|
2721
2791
|
}
|
|
2792
|
+
function composeChatEmbeddingText(text) {
|
|
2793
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 2000);
|
|
2794
|
+
}
|
|
2722
2795
|
async function getPipeline() {
|
|
2723
2796
|
if (_pipeline)
|
|
2724
2797
|
return _pipeline;
|
|
@@ -3009,7 +3082,7 @@ function buildBeacon(db, config, sessionId, metrics) {
|
|
|
3009
3082
|
sentinel_used: valueSignals.security_findings_count > 0,
|
|
3010
3083
|
risk_score: riskScore,
|
|
3011
3084
|
stacks_detected: stacks,
|
|
3012
|
-
client_version: "0.4.
|
|
3085
|
+
client_version: "0.4.28",
|
|
3013
3086
|
context_observations_injected: metrics?.contextObsInjected ?? 0,
|
|
3014
3087
|
context_total_available: metrics?.contextTotalAvailable ?? 0,
|
|
3015
3088
|
recall_attempts: metrics?.recallAttempts ?? 0,
|
|
@@ -3910,7 +3983,7 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
3910
3983
|
}
|
|
3911
3984
|
return messages;
|
|
3912
3985
|
}
|
|
3913
|
-
function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
3986
|
+
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
3914
3987
|
const messages = readTranscript(sessionId, cwd, transcriptPath).map((message) => ({
|
|
3915
3988
|
...message,
|
|
3916
3989
|
text: message.text.trim()
|
|
@@ -3940,6 +4013,12 @@ function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
3940
4013
|
transcript_index: transcriptIndex
|
|
3941
4014
|
});
|
|
3942
4015
|
db.addToOutbox("chat_message", row.id);
|
|
4016
|
+
if (db.vecAvailable) {
|
|
4017
|
+
const embedding = await embedText(composeChatEmbeddingText(message.text));
|
|
4018
|
+
if (embedding) {
|
|
4019
|
+
db.vecChatInsert(row.id, embedding);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
3943
4022
|
imported++;
|
|
3944
4023
|
}
|
|
3945
4024
|
return { imported, total: messages.length };
|
|
@@ -4040,6 +4119,8 @@ function getSessionStory(db, input) {
|
|
|
4040
4119
|
summary,
|
|
4041
4120
|
prompts,
|
|
4042
4121
|
chat_messages: chatMessages,
|
|
4122
|
+
chat_source_summary: summarizeChatSources(chatMessages),
|
|
4123
|
+
chat_coverage_state: chatMessages.some((message) => message.source_kind === "transcript") ? "transcript-backed" : chatMessages.length > 0 ? "hook-only" : "none",
|
|
4043
4124
|
tool_events: toolEvents,
|
|
4044
4125
|
observations,
|
|
4045
4126
|
handoffs,
|
|
@@ -4137,6 +4218,12 @@ function collectProvenanceSummary(observations) {
|
|
|
4137
4218
|
}
|
|
4138
4219
|
return Array.from(counts.entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
4139
4220
|
}
|
|
4221
|
+
function summarizeChatSources(messages) {
|
|
4222
|
+
return messages.reduce((summary, message) => {
|
|
4223
|
+
summary[message.source_kind] += 1;
|
|
4224
|
+
return summary;
|
|
4225
|
+
}, { transcript: 0, hook: 0 });
|
|
4226
|
+
}
|
|
4140
4227
|
|
|
4141
4228
|
// src/tools/handoffs.ts
|
|
4142
4229
|
async function upsertRollingHandoff(db, config, input) {
|
|
@@ -4477,7 +4564,7 @@ async function main() {
|
|
|
4477
4564
|
try {
|
|
4478
4565
|
if (event.session_id) {
|
|
4479
4566
|
db.completeSession(event.session_id);
|
|
4480
|
-
syncTranscriptChat(db, config, event.session_id, event.cwd, event.transcript_path);
|
|
4567
|
+
await syncTranscriptChat(db, config, event.session_id, event.cwd, event.transcript_path);
|
|
4481
4568
|
if (event.last_assistant_message) {
|
|
4482
4569
|
try {
|
|
4483
4570
|
const detected = detectProject(event.cwd);
|
|
@@ -4500,6 +4587,12 @@ async function main() {
|
|
|
4500
4587
|
source_kind: "hook"
|
|
4501
4588
|
});
|
|
4502
4589
|
db.addToOutbox("chat_message", chatMessage.id);
|
|
4590
|
+
if (db.vecAvailable) {
|
|
4591
|
+
const chatEmbedding = await embedText(composeChatEmbeddingText(event.last_assistant_message));
|
|
4592
|
+
if (chatEmbedding) {
|
|
4593
|
+
db.vecChatInsert(chatMessage.id, chatEmbedding);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4503
4596
|
}
|
|
4504
4597
|
createAssistantCheckpoint(db, event.session_id, event.cwd, event.last_assistant_message);
|
|
4505
4598
|
} catch {}
|