engrm 0.4.45 → 0.4.47
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/cli.js +411 -118
- package/dist/hooks/elicitation-result.js +96 -15
- package/dist/hooks/post-tool-use.js +265 -23
- package/dist/hooks/pre-compact.js +264 -23
- package/dist/hooks/sentinel.js +96 -15
- package/dist/hooks/session-start.js +120 -17
- package/dist/hooks/stop.js +326 -27
- package/dist/hooks/user-prompt-submit.js +96 -15
- package/dist/server.js +208 -34
- package/package.json +1 -1
|
@@ -909,11 +909,20 @@ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, wr
|
|
|
909
909
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
910
910
|
import { join as join2 } from "node:path";
|
|
911
911
|
import { createHash } from "node:crypto";
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
912
|
+
function resolveConfigDir() {
|
|
913
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join2(homedir(), ".engrm");
|
|
914
|
+
}
|
|
915
|
+
function resolveSettingsPath() {
|
|
916
|
+
return join2(resolveConfigDir(), "settings.json");
|
|
917
|
+
}
|
|
918
|
+
function resolveDbPath() {
|
|
919
|
+
return join2(resolveConfigDir(), "engrm.db");
|
|
920
|
+
}
|
|
921
|
+
function resolveAuthBackupPath() {
|
|
922
|
+
return join2(resolveConfigDir(), "auth-backup.json");
|
|
923
|
+
}
|
|
915
924
|
function getDbPath() {
|
|
916
|
-
return
|
|
925
|
+
return resolveDbPath();
|
|
917
926
|
}
|
|
918
927
|
function generateDeviceId() {
|
|
919
928
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -936,7 +945,7 @@ function generateDeviceId() {
|
|
|
936
945
|
return `${host}-${suffix}`;
|
|
937
946
|
}
|
|
938
947
|
function createDefaultConfig() {
|
|
939
|
-
|
|
948
|
+
const merged = {
|
|
940
949
|
candengo_url: "",
|
|
941
950
|
candengo_api_key: "",
|
|
942
951
|
site_id: "",
|
|
@@ -991,24 +1000,26 @@ function createDefaultConfig() {
|
|
|
991
1000
|
},
|
|
992
1001
|
tool_profile: "full"
|
|
993
1002
|
};
|
|
1003
|
+
return merged;
|
|
994
1004
|
}
|
|
995
1005
|
function loadConfig() {
|
|
996
|
-
|
|
997
|
-
|
|
1006
|
+
const settingsPath = resolveSettingsPath();
|
|
1007
|
+
if (!existsSync2(settingsPath)) {
|
|
1008
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
998
1009
|
}
|
|
999
|
-
const raw = readFileSync2(
|
|
1010
|
+
const raw = readFileSync2(settingsPath, "utf-8");
|
|
1000
1011
|
let parsed;
|
|
1001
1012
|
try {
|
|
1002
1013
|
parsed = JSON.parse(raw);
|
|
1003
1014
|
} catch {
|
|
1004
|
-
throw new Error(`Invalid JSON in ${
|
|
1015
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
1005
1016
|
}
|
|
1006
1017
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1007
|
-
throw new Error(`Config at ${
|
|
1018
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
1008
1019
|
}
|
|
1009
1020
|
const config = parsed;
|
|
1010
1021
|
const defaults = createDefaultConfig();
|
|
1011
|
-
|
|
1022
|
+
const merged = {
|
|
1012
1023
|
candengo_url: asString(config["candengo_url"], defaults.candengo_url),
|
|
1013
1024
|
candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
|
|
1014
1025
|
site_id: asString(config["site_id"], defaults.site_id),
|
|
@@ -1063,16 +1074,27 @@ function loadConfig() {
|
|
|
1063
1074
|
},
|
|
1064
1075
|
tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
|
|
1065
1076
|
};
|
|
1077
|
+
if (looksLikePlaceholderAuth(merged)) {
|
|
1078
|
+
return restoreAuthBackup(merged) ?? merged;
|
|
1079
|
+
}
|
|
1080
|
+
return merged;
|
|
1066
1081
|
}
|
|
1067
1082
|
function saveConfig(config) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1083
|
+
const configDir = resolveConfigDir();
|
|
1084
|
+
const settingsPath = resolveSettingsPath();
|
|
1085
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
1086
|
+
if (!existsSync2(configDir)) {
|
|
1087
|
+
mkdirSync(configDir, { recursive: true });
|
|
1070
1088
|
}
|
|
1071
|
-
writeFileSync(
|
|
1089
|
+
writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
|
|
1072
1090
|
`, "utf-8");
|
|
1091
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
1092
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
1093
|
+
`, "utf-8");
|
|
1094
|
+
}
|
|
1073
1095
|
}
|
|
1074
1096
|
function configExists() {
|
|
1075
|
-
return existsSync2(
|
|
1097
|
+
return existsSync2(resolveSettingsPath());
|
|
1076
1098
|
}
|
|
1077
1099
|
function asString(value, fallback) {
|
|
1078
1100
|
return typeof value === "string" ? value : fallback;
|
|
@@ -1126,6 +1148,50 @@ function asTeams(value, fallback) {
|
|
|
1126
1148
|
return fallback;
|
|
1127
1149
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
1128
1150
|
}
|
|
1151
|
+
function looksLikePlaceholderAuth(config) {
|
|
1152
|
+
const apiKey = config.candengo_api_key.trim();
|
|
1153
|
+
const siteId = config.site_id.trim();
|
|
1154
|
+
const namespace = config.namespace.trim();
|
|
1155
|
+
const email = config.user_email.trim().toLowerCase();
|
|
1156
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
1157
|
+
return true;
|
|
1158
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
1159
|
+
return true;
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
function extractAuthBackup(config) {
|
|
1163
|
+
return {
|
|
1164
|
+
candengo_url: config.candengo_url,
|
|
1165
|
+
candengo_api_key: config.candengo_api_key,
|
|
1166
|
+
site_id: config.site_id,
|
|
1167
|
+
namespace: config.namespace,
|
|
1168
|
+
user_id: config.user_id,
|
|
1169
|
+
user_email: config.user_email,
|
|
1170
|
+
teams: config.teams
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function restoreAuthBackup(config) {
|
|
1174
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
1175
|
+
if (!existsSync2(authBackupPath))
|
|
1176
|
+
return null;
|
|
1177
|
+
try {
|
|
1178
|
+
const raw = readFileSync2(authBackupPath, "utf-8");
|
|
1179
|
+
const parsed = JSON.parse(raw);
|
|
1180
|
+
const restored = {
|
|
1181
|
+
...config,
|
|
1182
|
+
candengo_url: asString(parsed["candengo_url"], config.candengo_url),
|
|
1183
|
+
candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
|
|
1184
|
+
site_id: asString(parsed["site_id"], config.site_id),
|
|
1185
|
+
namespace: asString(parsed["namespace"], config.namespace),
|
|
1186
|
+
user_id: asString(parsed["user_id"], config.user_id),
|
|
1187
|
+
user_email: asString(parsed["user_email"], config.user_email),
|
|
1188
|
+
teams: asTeams(parsed["teams"], config.teams)
|
|
1189
|
+
};
|
|
1190
|
+
return looksLikePlaceholderAuth(restored) ? null : restored;
|
|
1191
|
+
} catch {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1129
1195
|
|
|
1130
1196
|
// src/storage/migrations.ts
|
|
1131
1197
|
var MIGRATIONS = [
|
|
@@ -1813,6 +1879,20 @@ function ensureChatMessageColumns(db) {
|
|
|
1813
1879
|
db.exec("PRAGMA user_version = 17");
|
|
1814
1880
|
}
|
|
1815
1881
|
}
|
|
1882
|
+
function ensureObservationVectorTable(db) {
|
|
1883
|
+
if (!isVecExtensionLoaded(db))
|
|
1884
|
+
return;
|
|
1885
|
+
db.exec(`
|
|
1886
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
|
|
1887
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1888
|
+
embedding FLOAT[384]
|
|
1889
|
+
);
|
|
1890
|
+
`);
|
|
1891
|
+
const current = getSchemaVersion(db);
|
|
1892
|
+
if (current < 4) {
|
|
1893
|
+
db.exec("PRAGMA user_version = 4");
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1816
1896
|
function ensureChatVectorTable(db) {
|
|
1817
1897
|
if (!isVecExtensionLoaded(db))
|
|
1818
1898
|
return;
|
|
@@ -2041,6 +2121,7 @@ class MemDatabase {
|
|
|
2041
2121
|
ensureObservationTypes(this.db);
|
|
2042
2122
|
ensureSessionSummaryColumns(this.db);
|
|
2043
2123
|
ensureChatMessageColumns(this.db);
|
|
2124
|
+
ensureObservationVectorTable(this.db);
|
|
2044
2125
|
ensureChatVectorTable(this.db);
|
|
2045
2126
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
2046
2127
|
}
|
|
@@ -213,11 +213,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
213
213
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
214
214
|
import { join } from "node:path";
|
|
215
215
|
import { createHash } from "node:crypto";
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
function resolveConfigDir() {
|
|
217
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join(homedir(), ".engrm");
|
|
218
|
+
}
|
|
219
|
+
function resolveSettingsPath() {
|
|
220
|
+
return join(resolveConfigDir(), "settings.json");
|
|
221
|
+
}
|
|
222
|
+
function resolveDbPath() {
|
|
223
|
+
return join(resolveConfigDir(), "engrm.db");
|
|
224
|
+
}
|
|
225
|
+
function resolveAuthBackupPath() {
|
|
226
|
+
return join(resolveConfigDir(), "auth-backup.json");
|
|
227
|
+
}
|
|
219
228
|
function getDbPath() {
|
|
220
|
-
return
|
|
229
|
+
return resolveDbPath();
|
|
221
230
|
}
|
|
222
231
|
function generateDeviceId() {
|
|
223
232
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -240,7 +249,7 @@ function generateDeviceId() {
|
|
|
240
249
|
return `${host}-${suffix}`;
|
|
241
250
|
}
|
|
242
251
|
function createDefaultConfig() {
|
|
243
|
-
|
|
252
|
+
const merged = {
|
|
244
253
|
candengo_url: "",
|
|
245
254
|
candengo_api_key: "",
|
|
246
255
|
site_id: "",
|
|
@@ -295,24 +304,26 @@ function createDefaultConfig() {
|
|
|
295
304
|
},
|
|
296
305
|
tool_profile: "full"
|
|
297
306
|
};
|
|
307
|
+
return merged;
|
|
298
308
|
}
|
|
299
309
|
function loadConfig() {
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
const settingsPath = resolveSettingsPath();
|
|
311
|
+
if (!existsSync(settingsPath)) {
|
|
312
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
302
313
|
}
|
|
303
|
-
const raw = readFileSync(
|
|
314
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
304
315
|
let parsed;
|
|
305
316
|
try {
|
|
306
317
|
parsed = JSON.parse(raw);
|
|
307
318
|
} catch {
|
|
308
|
-
throw new Error(`Invalid JSON in ${
|
|
319
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
309
320
|
}
|
|
310
321
|
if (typeof parsed !== "object" || parsed === null) {
|
|
311
|
-
throw new Error(`Config at ${
|
|
322
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
312
323
|
}
|
|
313
324
|
const config = parsed;
|
|
314
325
|
const defaults = createDefaultConfig();
|
|
315
|
-
|
|
326
|
+
const merged = {
|
|
316
327
|
candengo_url: asString(config["candengo_url"], defaults.candengo_url),
|
|
317
328
|
candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
|
|
318
329
|
site_id: asString(config["site_id"], defaults.site_id),
|
|
@@ -367,16 +378,27 @@ function loadConfig() {
|
|
|
367
378
|
},
|
|
368
379
|
tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
|
|
369
380
|
};
|
|
381
|
+
if (looksLikePlaceholderAuth(merged)) {
|
|
382
|
+
return restoreAuthBackup(merged) ?? merged;
|
|
383
|
+
}
|
|
384
|
+
return merged;
|
|
370
385
|
}
|
|
371
386
|
function saveConfig(config) {
|
|
372
|
-
|
|
373
|
-
|
|
387
|
+
const configDir = resolveConfigDir();
|
|
388
|
+
const settingsPath = resolveSettingsPath();
|
|
389
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
390
|
+
if (!existsSync(configDir)) {
|
|
391
|
+
mkdirSync(configDir, { recursive: true });
|
|
374
392
|
}
|
|
375
|
-
writeFileSync(
|
|
393
|
+
writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
|
|
376
394
|
`, "utf-8");
|
|
395
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
396
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
397
|
+
`, "utf-8");
|
|
398
|
+
}
|
|
377
399
|
}
|
|
378
400
|
function configExists() {
|
|
379
|
-
return existsSync(
|
|
401
|
+
return existsSync(resolveSettingsPath());
|
|
380
402
|
}
|
|
381
403
|
function asString(value, fallback) {
|
|
382
404
|
return typeof value === "string" ? value : fallback;
|
|
@@ -430,6 +452,50 @@ function asTeams(value, fallback) {
|
|
|
430
452
|
return fallback;
|
|
431
453
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
432
454
|
}
|
|
455
|
+
function looksLikePlaceholderAuth(config) {
|
|
456
|
+
const apiKey = config.candengo_api_key.trim();
|
|
457
|
+
const siteId = config.site_id.trim();
|
|
458
|
+
const namespace = config.namespace.trim();
|
|
459
|
+
const email = config.user_email.trim().toLowerCase();
|
|
460
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
461
|
+
return true;
|
|
462
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
463
|
+
return true;
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
function extractAuthBackup(config) {
|
|
467
|
+
return {
|
|
468
|
+
candengo_url: config.candengo_url,
|
|
469
|
+
candengo_api_key: config.candengo_api_key,
|
|
470
|
+
site_id: config.site_id,
|
|
471
|
+
namespace: config.namespace,
|
|
472
|
+
user_id: config.user_id,
|
|
473
|
+
user_email: config.user_email,
|
|
474
|
+
teams: config.teams
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function restoreAuthBackup(config) {
|
|
478
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
479
|
+
if (!existsSync(authBackupPath))
|
|
480
|
+
return null;
|
|
481
|
+
try {
|
|
482
|
+
const raw = readFileSync(authBackupPath, "utf-8");
|
|
483
|
+
const parsed = JSON.parse(raw);
|
|
484
|
+
const restored = {
|
|
485
|
+
...config,
|
|
486
|
+
candengo_url: asString(parsed["candengo_url"], config.candengo_url),
|
|
487
|
+
candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
|
|
488
|
+
site_id: asString(parsed["site_id"], config.site_id),
|
|
489
|
+
namespace: asString(parsed["namespace"], config.namespace),
|
|
490
|
+
user_id: asString(parsed["user_id"], config.user_id),
|
|
491
|
+
user_email: asString(parsed["user_email"], config.user_email),
|
|
492
|
+
teams: asTeams(parsed["teams"], config.teams)
|
|
493
|
+
};
|
|
494
|
+
return looksLikePlaceholderAuth(restored) ? null : restored;
|
|
495
|
+
} catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
433
499
|
|
|
434
500
|
// src/storage/migrations.ts
|
|
435
501
|
var MIGRATIONS = [
|
|
@@ -1117,6 +1183,20 @@ function ensureChatMessageColumns(db) {
|
|
|
1117
1183
|
db.exec("PRAGMA user_version = 17");
|
|
1118
1184
|
}
|
|
1119
1185
|
}
|
|
1186
|
+
function ensureObservationVectorTable(db) {
|
|
1187
|
+
if (!isVecExtensionLoaded(db))
|
|
1188
|
+
return;
|
|
1189
|
+
db.exec(`
|
|
1190
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
|
|
1191
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1192
|
+
embedding FLOAT[384]
|
|
1193
|
+
);
|
|
1194
|
+
`);
|
|
1195
|
+
const current = getSchemaVersion(db);
|
|
1196
|
+
if (current < 4) {
|
|
1197
|
+
db.exec("PRAGMA user_version = 4");
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1120
1200
|
function ensureChatVectorTable(db) {
|
|
1121
1201
|
if (!isVecExtensionLoaded(db))
|
|
1122
1202
|
return;
|
|
@@ -1345,6 +1425,7 @@ class MemDatabase {
|
|
|
1345
1425
|
ensureObservationTypes(this.db);
|
|
1346
1426
|
ensureSessionSummaryColumns(this.db);
|
|
1347
1427
|
ensureChatMessageColumns(this.db);
|
|
1428
|
+
ensureObservationVectorTable(this.db);
|
|
1348
1429
|
ensureChatVectorTable(this.db);
|
|
1349
1430
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1350
1431
|
}
|
|
@@ -3826,14 +3907,18 @@ function parseJsonArray(value) {
|
|
|
3826
3907
|
|
|
3827
3908
|
// src/capture/transcript.ts
|
|
3828
3909
|
import { createHash as createHash3 } from "node:crypto";
|
|
3829
|
-
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
3910
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, statSync, readdirSync } from "node:fs";
|
|
3830
3911
|
import { join as join4 } from "node:path";
|
|
3831
3912
|
import { homedir as homedir3 } from "node:os";
|
|
3832
3913
|
function resolveTranscriptPath(sessionId, cwd, transcriptPath) {
|
|
3833
3914
|
if (transcriptPath)
|
|
3834
3915
|
return transcriptPath;
|
|
3835
3916
|
const encodedCwd = cwd.replace(/\//g, "-");
|
|
3836
|
-
|
|
3917
|
+
const directPath = join4(homedir3(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
|
|
3918
|
+
if (existsSync4(directPath))
|
|
3919
|
+
return directPath;
|
|
3920
|
+
const discovered = findTranscriptPathBySessionId(sessionId);
|
|
3921
|
+
return discovered ?? directPath;
|
|
3837
3922
|
}
|
|
3838
3923
|
function readTranscript(sessionId, cwd, transcriptPath) {
|
|
3839
3924
|
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
@@ -3856,10 +3941,10 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
3856
3941
|
} catch {
|
|
3857
3942
|
continue;
|
|
3858
3943
|
}
|
|
3859
|
-
const role = entry
|
|
3944
|
+
const role = getTranscriptRole(entry);
|
|
3860
3945
|
if (role !== "user" && role !== "assistant")
|
|
3861
3946
|
continue;
|
|
3862
|
-
const content = entry
|
|
3947
|
+
const content = getTranscriptContent(entry);
|
|
3863
3948
|
if (typeof content === "string") {
|
|
3864
3949
|
messages.push({ role, text: content });
|
|
3865
3950
|
continue;
|
|
@@ -3879,6 +3964,66 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
3879
3964
|
}
|
|
3880
3965
|
return messages;
|
|
3881
3966
|
}
|
|
3967
|
+
function readTranscriptToolEvents(sessionId, cwd, transcriptPath) {
|
|
3968
|
+
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
3969
|
+
if (!existsSync4(path))
|
|
3970
|
+
return [];
|
|
3971
|
+
let raw;
|
|
3972
|
+
try {
|
|
3973
|
+
raw = readFileSync4(path, "utf-8");
|
|
3974
|
+
} catch {
|
|
3975
|
+
return [];
|
|
3976
|
+
}
|
|
3977
|
+
const toolEvents = [];
|
|
3978
|
+
const toolEventIndexes = new Map;
|
|
3979
|
+
for (const line of raw.split(`
|
|
3980
|
+
`)) {
|
|
3981
|
+
if (!line.trim())
|
|
3982
|
+
continue;
|
|
3983
|
+
let entry;
|
|
3984
|
+
try {
|
|
3985
|
+
entry = JSON.parse(line);
|
|
3986
|
+
} catch {
|
|
3987
|
+
continue;
|
|
3988
|
+
}
|
|
3989
|
+
const createdAtEpoch = parseTranscriptTimestamp(entry);
|
|
3990
|
+
const content = getTranscriptContent(entry);
|
|
3991
|
+
if (!Array.isArray(content))
|
|
3992
|
+
continue;
|
|
3993
|
+
for (const block of content) {
|
|
3994
|
+
if (!block || typeof block !== "object")
|
|
3995
|
+
continue;
|
|
3996
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
3997
|
+
const preview = extractToolResultPreview(block.content);
|
|
3998
|
+
const index = toolEventIndexes.get(block.tool_use_id);
|
|
3999
|
+
if (preview && index !== undefined) {
|
|
4000
|
+
toolEvents[index] = {
|
|
4001
|
+
...toolEvents[index],
|
|
4002
|
+
tool_response_preview: preview
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
continue;
|
|
4006
|
+
}
|
|
4007
|
+
if (block.type !== "tool_use")
|
|
4008
|
+
continue;
|
|
4009
|
+
const input = block.input && typeof block.input === "object" ? block.input : {};
|
|
4010
|
+
const toolUseId = typeof block.id === "string" ? block.id : null;
|
|
4011
|
+
const nextEvent = {
|
|
4012
|
+
tool_name: typeof block.name === "string" ? block.name : "Unknown",
|
|
4013
|
+
tool_input_json: JSON.stringify(input),
|
|
4014
|
+
tool_response_preview: null,
|
|
4015
|
+
file_path: extractToolFilePath(input),
|
|
4016
|
+
command: typeof input.command === "string" ? input.command : null,
|
|
4017
|
+
created_at_epoch: createdAtEpoch
|
|
4018
|
+
};
|
|
4019
|
+
toolEvents.push(nextEvent);
|
|
4020
|
+
if (toolUseId) {
|
|
4021
|
+
toolEventIndexes.set(toolUseId, toolEvents.length - 1);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
return toolEvents;
|
|
4026
|
+
}
|
|
3882
4027
|
function resolveHistoryPath(historyPath) {
|
|
3883
4028
|
if (historyPath)
|
|
3884
4029
|
return historyPath;
|
|
@@ -3944,9 +4089,22 @@ function readHistoryFallback(sessionId, cwd, opts) {
|
|
|
3944
4089
|
createdAtEpoch: entry.timestamp
|
|
3945
4090
|
})));
|
|
3946
4091
|
}
|
|
3947
|
-
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
4092
|
+
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath, options = {}) {
|
|
4093
|
+
const embed = options.embed ?? true;
|
|
3948
4094
|
const session = db.getSessionById(sessionId);
|
|
3949
|
-
const
|
|
4095
|
+
const resolvedTranscriptPath = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
4096
|
+
const syncCursorKey = `transcript_sync_cursor:${sessionId}`;
|
|
4097
|
+
if (existsSync4(resolvedTranscriptPath)) {
|
|
4098
|
+
try {
|
|
4099
|
+
const stat = statSync(resolvedTranscriptPath);
|
|
4100
|
+
const cursor = `${stat.size}:${Math.floor(stat.mtimeMs)}`;
|
|
4101
|
+
if (db.getSyncState(syncCursorKey) === cursor) {
|
|
4102
|
+
return { imported: 0, total: 0 };
|
|
4103
|
+
}
|
|
4104
|
+
db.setSyncState(syncCursorKey, cursor);
|
|
4105
|
+
} catch {}
|
|
4106
|
+
}
|
|
4107
|
+
const transcriptMessages = readTranscript(sessionId, cwd, resolvedTranscriptPath).map((message) => ({
|
|
3950
4108
|
...message,
|
|
3951
4109
|
text: message.text.trim()
|
|
3952
4110
|
})).filter((message) => message.text.length > 0);
|
|
@@ -4008,7 +4166,7 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4008
4166
|
created_at_epoch: createdAtEpoch
|
|
4009
4167
|
});
|
|
4010
4168
|
}
|
|
4011
|
-
if (db.vecAvailable) {
|
|
4169
|
+
if (embed && db.vecAvailable) {
|
|
4012
4170
|
const embedding = await embedText(composeChatEmbeddingText(message.text));
|
|
4013
4171
|
if (embedding) {
|
|
4014
4172
|
db.vecChatInsert(row.id, embedding);
|
|
@@ -4018,6 +4176,35 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4018
4176
|
}
|
|
4019
4177
|
return { imported, total: messages.length };
|
|
4020
4178
|
}
|
|
4179
|
+
function syncTranscriptToolEvents(db, config, sessionId, cwd, transcriptPath) {
|
|
4180
|
+
const session = db.getSessionById(sessionId);
|
|
4181
|
+
if (!session)
|
|
4182
|
+
return { imported: 0, total: 0 };
|
|
4183
|
+
if (db.getSessionToolEvents(sessionId, 1).length > 0) {
|
|
4184
|
+
return { imported: 0, total: 0 };
|
|
4185
|
+
}
|
|
4186
|
+
const toolEvents = readTranscriptToolEvents(sessionId, cwd, transcriptPath);
|
|
4187
|
+
if (toolEvents.length === 0)
|
|
4188
|
+
return { imported: 0, total: 0 };
|
|
4189
|
+
let imported = 0;
|
|
4190
|
+
for (const event of toolEvents) {
|
|
4191
|
+
db.insertToolEvent({
|
|
4192
|
+
session_id: sessionId,
|
|
4193
|
+
project_id: session.project_id,
|
|
4194
|
+
tool_name: event.tool_name,
|
|
4195
|
+
tool_input_json: event.tool_input_json,
|
|
4196
|
+
tool_response_preview: event.tool_response_preview,
|
|
4197
|
+
file_path: event.file_path,
|
|
4198
|
+
command: event.command,
|
|
4199
|
+
user_id: config.user_id,
|
|
4200
|
+
device_id: config.device_id,
|
|
4201
|
+
agent: "claude-code",
|
|
4202
|
+
created_at_epoch: event.created_at_epoch ?? undefined
|
|
4203
|
+
});
|
|
4204
|
+
imported++;
|
|
4205
|
+
}
|
|
4206
|
+
return { imported, total: toolEvents.length };
|
|
4207
|
+
}
|
|
4021
4208
|
function dedupeHistoryMessages(messages) {
|
|
4022
4209
|
const deduped = [];
|
|
4023
4210
|
for (const message of messages) {
|
|
@@ -4035,6 +4222,59 @@ function buildHistorySourceId(sessionId, createdAtEpoch, text) {
|
|
|
4035
4222
|
const digest = createHash3("sha1").update(text).digest("hex").slice(0, 12);
|
|
4036
4223
|
return `history:${sessionId}:${createdAtEpoch}:${digest}`;
|
|
4037
4224
|
}
|
|
4225
|
+
function getTranscriptRole(entry) {
|
|
4226
|
+
return entry.role ?? entry.message?.role ?? entry.type ?? entry.message?.type;
|
|
4227
|
+
}
|
|
4228
|
+
function getTranscriptContent(entry) {
|
|
4229
|
+
return entry.content ?? entry.message?.content;
|
|
4230
|
+
}
|
|
4231
|
+
function parseTranscriptTimestamp(entry) {
|
|
4232
|
+
const raw = entry.timestamp ?? entry.message?.timestamp;
|
|
4233
|
+
if (typeof raw !== "string")
|
|
4234
|
+
return null;
|
|
4235
|
+
const epoch = Date.parse(raw);
|
|
4236
|
+
return Number.isFinite(epoch) ? Math.floor(epoch / 1000) : null;
|
|
4237
|
+
}
|
|
4238
|
+
function extractToolResultPreview(content) {
|
|
4239
|
+
if (typeof content === "string")
|
|
4240
|
+
return content.slice(0, 4000);
|
|
4241
|
+
if (Array.isArray(content)) {
|
|
4242
|
+
const text = content.map((item) => {
|
|
4243
|
+
if (typeof item === "string")
|
|
4244
|
+
return item;
|
|
4245
|
+
if (item && typeof item === "object" && typeof item.text === "string")
|
|
4246
|
+
return item.text;
|
|
4247
|
+
return "";
|
|
4248
|
+
}).filter(Boolean).join(`
|
|
4249
|
+
`);
|
|
4250
|
+
return text ? text.slice(0, 4000) : null;
|
|
4251
|
+
}
|
|
4252
|
+
return null;
|
|
4253
|
+
}
|
|
4254
|
+
function extractToolFilePath(input) {
|
|
4255
|
+
for (const key of ["file_path", "path", "target_file"]) {
|
|
4256
|
+
if (typeof input[key] === "string")
|
|
4257
|
+
return input[key];
|
|
4258
|
+
}
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
4261
|
+
function findTranscriptPathBySessionId(sessionId) {
|
|
4262
|
+
const projectsDir = join4(homedir3(), ".claude", "projects");
|
|
4263
|
+
if (!existsSync4(projectsDir))
|
|
4264
|
+
return null;
|
|
4265
|
+
try {
|
|
4266
|
+
for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
4267
|
+
if (!entry.isDirectory())
|
|
4268
|
+
continue;
|
|
4269
|
+
const candidate = join4(projectsDir, entry.name, `${sessionId}.jsonl`);
|
|
4270
|
+
if (existsSync4(candidate))
|
|
4271
|
+
return candidate;
|
|
4272
|
+
}
|
|
4273
|
+
} catch {
|
|
4274
|
+
return null;
|
|
4275
|
+
}
|
|
4276
|
+
return null;
|
|
4277
|
+
}
|
|
4038
4278
|
function truncateTranscript(messages, maxBytes = 50000) {
|
|
4039
4279
|
const lines = [];
|
|
4040
4280
|
for (const msg of messages) {
|
|
@@ -4588,7 +4828,9 @@ async function main() {
|
|
|
4588
4828
|
try {
|
|
4589
4829
|
if (event.session_id) {
|
|
4590
4830
|
persistRawToolChronology(db, event, config.user_id, config.device_id);
|
|
4591
|
-
await syncTranscriptChat(db, config, event.session_id, event.cwd
|
|
4831
|
+
await syncTranscriptChat(db, config, event.session_id, event.cwd, undefined, {
|
|
4832
|
+
embed: false
|
|
4833
|
+
});
|
|
4592
4834
|
}
|
|
4593
4835
|
const textToScan = extractScanText(event);
|
|
4594
4836
|
if (textToScan) {
|