engrm 0.4.44 → 0.4.46
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 +396 -118
- package/dist/hooks/elicitation-result.js +81 -15
- package/dist/hooks/post-tool-use.js +250 -23
- package/dist/hooks/pre-compact.js +249 -23
- package/dist/hooks/sentinel.js +81 -15
- package/dist/hooks/session-start.js +105 -17
- package/dist/hooks/stop.js +311 -27
- package/dist/hooks/user-prompt-submit.js +81 -1521
- package/dist/server.js +193 -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 = [
|
|
@@ -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) + `
|
|
394
|
+
`, "utf-8");
|
|
395
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
396
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
376
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 = [
|
|
@@ -3826,14 +3892,18 @@ function parseJsonArray(value) {
|
|
|
3826
3892
|
|
|
3827
3893
|
// src/capture/transcript.ts
|
|
3828
3894
|
import { createHash as createHash3 } from "node:crypto";
|
|
3829
|
-
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
3895
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, statSync, readdirSync } from "node:fs";
|
|
3830
3896
|
import { join as join4 } from "node:path";
|
|
3831
3897
|
import { homedir as homedir3 } from "node:os";
|
|
3832
3898
|
function resolveTranscriptPath(sessionId, cwd, transcriptPath) {
|
|
3833
3899
|
if (transcriptPath)
|
|
3834
3900
|
return transcriptPath;
|
|
3835
3901
|
const encodedCwd = cwd.replace(/\//g, "-");
|
|
3836
|
-
|
|
3902
|
+
const directPath = join4(homedir3(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
|
|
3903
|
+
if (existsSync4(directPath))
|
|
3904
|
+
return directPath;
|
|
3905
|
+
const discovered = findTranscriptPathBySessionId(sessionId);
|
|
3906
|
+
return discovered ?? directPath;
|
|
3837
3907
|
}
|
|
3838
3908
|
function readTranscript(sessionId, cwd, transcriptPath) {
|
|
3839
3909
|
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
@@ -3856,10 +3926,10 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
3856
3926
|
} catch {
|
|
3857
3927
|
continue;
|
|
3858
3928
|
}
|
|
3859
|
-
const role = entry
|
|
3929
|
+
const role = getTranscriptRole(entry);
|
|
3860
3930
|
if (role !== "user" && role !== "assistant")
|
|
3861
3931
|
continue;
|
|
3862
|
-
const content = entry
|
|
3932
|
+
const content = getTranscriptContent(entry);
|
|
3863
3933
|
if (typeof content === "string") {
|
|
3864
3934
|
messages.push({ role, text: content });
|
|
3865
3935
|
continue;
|
|
@@ -3879,6 +3949,66 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
3879
3949
|
}
|
|
3880
3950
|
return messages;
|
|
3881
3951
|
}
|
|
3952
|
+
function readTranscriptToolEvents(sessionId, cwd, transcriptPath) {
|
|
3953
|
+
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
3954
|
+
if (!existsSync4(path))
|
|
3955
|
+
return [];
|
|
3956
|
+
let raw;
|
|
3957
|
+
try {
|
|
3958
|
+
raw = readFileSync4(path, "utf-8");
|
|
3959
|
+
} catch {
|
|
3960
|
+
return [];
|
|
3961
|
+
}
|
|
3962
|
+
const toolEvents = [];
|
|
3963
|
+
const toolEventIndexes = new Map;
|
|
3964
|
+
for (const line of raw.split(`
|
|
3965
|
+
`)) {
|
|
3966
|
+
if (!line.trim())
|
|
3967
|
+
continue;
|
|
3968
|
+
let entry;
|
|
3969
|
+
try {
|
|
3970
|
+
entry = JSON.parse(line);
|
|
3971
|
+
} catch {
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
const createdAtEpoch = parseTranscriptTimestamp(entry);
|
|
3975
|
+
const content = getTranscriptContent(entry);
|
|
3976
|
+
if (!Array.isArray(content))
|
|
3977
|
+
continue;
|
|
3978
|
+
for (const block of content) {
|
|
3979
|
+
if (!block || typeof block !== "object")
|
|
3980
|
+
continue;
|
|
3981
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
3982
|
+
const preview = extractToolResultPreview(block.content);
|
|
3983
|
+
const index = toolEventIndexes.get(block.tool_use_id);
|
|
3984
|
+
if (preview && index !== undefined) {
|
|
3985
|
+
toolEvents[index] = {
|
|
3986
|
+
...toolEvents[index],
|
|
3987
|
+
tool_response_preview: preview
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3992
|
+
if (block.type !== "tool_use")
|
|
3993
|
+
continue;
|
|
3994
|
+
const input = block.input && typeof block.input === "object" ? block.input : {};
|
|
3995
|
+
const toolUseId = typeof block.id === "string" ? block.id : null;
|
|
3996
|
+
const nextEvent = {
|
|
3997
|
+
tool_name: typeof block.name === "string" ? block.name : "Unknown",
|
|
3998
|
+
tool_input_json: JSON.stringify(input),
|
|
3999
|
+
tool_response_preview: null,
|
|
4000
|
+
file_path: extractToolFilePath(input),
|
|
4001
|
+
command: typeof input.command === "string" ? input.command : null,
|
|
4002
|
+
created_at_epoch: createdAtEpoch
|
|
4003
|
+
};
|
|
4004
|
+
toolEvents.push(nextEvent);
|
|
4005
|
+
if (toolUseId) {
|
|
4006
|
+
toolEventIndexes.set(toolUseId, toolEvents.length - 1);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
return toolEvents;
|
|
4011
|
+
}
|
|
3882
4012
|
function resolveHistoryPath(historyPath) {
|
|
3883
4013
|
if (historyPath)
|
|
3884
4014
|
return historyPath;
|
|
@@ -3944,9 +4074,22 @@ function readHistoryFallback(sessionId, cwd, opts) {
|
|
|
3944
4074
|
createdAtEpoch: entry.timestamp
|
|
3945
4075
|
})));
|
|
3946
4076
|
}
|
|
3947
|
-
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
4077
|
+
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath, options = {}) {
|
|
4078
|
+
const embed = options.embed ?? true;
|
|
3948
4079
|
const session = db.getSessionById(sessionId);
|
|
3949
|
-
const
|
|
4080
|
+
const resolvedTranscriptPath = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
4081
|
+
const syncCursorKey = `transcript_sync_cursor:${sessionId}`;
|
|
4082
|
+
if (existsSync4(resolvedTranscriptPath)) {
|
|
4083
|
+
try {
|
|
4084
|
+
const stat = statSync(resolvedTranscriptPath);
|
|
4085
|
+
const cursor = `${stat.size}:${Math.floor(stat.mtimeMs)}`;
|
|
4086
|
+
if (db.getSyncState(syncCursorKey) === cursor) {
|
|
4087
|
+
return { imported: 0, total: 0 };
|
|
4088
|
+
}
|
|
4089
|
+
db.setSyncState(syncCursorKey, cursor);
|
|
4090
|
+
} catch {}
|
|
4091
|
+
}
|
|
4092
|
+
const transcriptMessages = readTranscript(sessionId, cwd, resolvedTranscriptPath).map((message) => ({
|
|
3950
4093
|
...message,
|
|
3951
4094
|
text: message.text.trim()
|
|
3952
4095
|
})).filter((message) => message.text.length > 0);
|
|
@@ -4008,7 +4151,7 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4008
4151
|
created_at_epoch: createdAtEpoch
|
|
4009
4152
|
});
|
|
4010
4153
|
}
|
|
4011
|
-
if (db.vecAvailable) {
|
|
4154
|
+
if (embed && db.vecAvailable) {
|
|
4012
4155
|
const embedding = await embedText(composeChatEmbeddingText(message.text));
|
|
4013
4156
|
if (embedding) {
|
|
4014
4157
|
db.vecChatInsert(row.id, embedding);
|
|
@@ -4018,6 +4161,35 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4018
4161
|
}
|
|
4019
4162
|
return { imported, total: messages.length };
|
|
4020
4163
|
}
|
|
4164
|
+
function syncTranscriptToolEvents(db, config, sessionId, cwd, transcriptPath) {
|
|
4165
|
+
const session = db.getSessionById(sessionId);
|
|
4166
|
+
if (!session)
|
|
4167
|
+
return { imported: 0, total: 0 };
|
|
4168
|
+
if (db.getSessionToolEvents(sessionId, 1).length > 0) {
|
|
4169
|
+
return { imported: 0, total: 0 };
|
|
4170
|
+
}
|
|
4171
|
+
const toolEvents = readTranscriptToolEvents(sessionId, cwd, transcriptPath);
|
|
4172
|
+
if (toolEvents.length === 0)
|
|
4173
|
+
return { imported: 0, total: 0 };
|
|
4174
|
+
let imported = 0;
|
|
4175
|
+
for (const event of toolEvents) {
|
|
4176
|
+
db.insertToolEvent({
|
|
4177
|
+
session_id: sessionId,
|
|
4178
|
+
project_id: session.project_id,
|
|
4179
|
+
tool_name: event.tool_name,
|
|
4180
|
+
tool_input_json: event.tool_input_json,
|
|
4181
|
+
tool_response_preview: event.tool_response_preview,
|
|
4182
|
+
file_path: event.file_path,
|
|
4183
|
+
command: event.command,
|
|
4184
|
+
user_id: config.user_id,
|
|
4185
|
+
device_id: config.device_id,
|
|
4186
|
+
agent: "claude-code",
|
|
4187
|
+
created_at_epoch: event.created_at_epoch ?? undefined
|
|
4188
|
+
});
|
|
4189
|
+
imported++;
|
|
4190
|
+
}
|
|
4191
|
+
return { imported, total: toolEvents.length };
|
|
4192
|
+
}
|
|
4021
4193
|
function dedupeHistoryMessages(messages) {
|
|
4022
4194
|
const deduped = [];
|
|
4023
4195
|
for (const message of messages) {
|
|
@@ -4035,6 +4207,59 @@ function buildHistorySourceId(sessionId, createdAtEpoch, text) {
|
|
|
4035
4207
|
const digest = createHash3("sha1").update(text).digest("hex").slice(0, 12);
|
|
4036
4208
|
return `history:${sessionId}:${createdAtEpoch}:${digest}`;
|
|
4037
4209
|
}
|
|
4210
|
+
function getTranscriptRole(entry) {
|
|
4211
|
+
return entry.role ?? entry.message?.role ?? entry.type ?? entry.message?.type;
|
|
4212
|
+
}
|
|
4213
|
+
function getTranscriptContent(entry) {
|
|
4214
|
+
return entry.content ?? entry.message?.content;
|
|
4215
|
+
}
|
|
4216
|
+
function parseTranscriptTimestamp(entry) {
|
|
4217
|
+
const raw = entry.timestamp ?? entry.message?.timestamp;
|
|
4218
|
+
if (typeof raw !== "string")
|
|
4219
|
+
return null;
|
|
4220
|
+
const epoch = Date.parse(raw);
|
|
4221
|
+
return Number.isFinite(epoch) ? Math.floor(epoch / 1000) : null;
|
|
4222
|
+
}
|
|
4223
|
+
function extractToolResultPreview(content) {
|
|
4224
|
+
if (typeof content === "string")
|
|
4225
|
+
return content.slice(0, 4000);
|
|
4226
|
+
if (Array.isArray(content)) {
|
|
4227
|
+
const text = content.map((item) => {
|
|
4228
|
+
if (typeof item === "string")
|
|
4229
|
+
return item;
|
|
4230
|
+
if (item && typeof item === "object" && typeof item.text === "string")
|
|
4231
|
+
return item.text;
|
|
4232
|
+
return "";
|
|
4233
|
+
}).filter(Boolean).join(`
|
|
4234
|
+
`);
|
|
4235
|
+
return text ? text.slice(0, 4000) : null;
|
|
4236
|
+
}
|
|
4237
|
+
return null;
|
|
4238
|
+
}
|
|
4239
|
+
function extractToolFilePath(input) {
|
|
4240
|
+
for (const key of ["file_path", "path", "target_file"]) {
|
|
4241
|
+
if (typeof input[key] === "string")
|
|
4242
|
+
return input[key];
|
|
4243
|
+
}
|
|
4244
|
+
return null;
|
|
4245
|
+
}
|
|
4246
|
+
function findTranscriptPathBySessionId(sessionId) {
|
|
4247
|
+
const projectsDir = join4(homedir3(), ".claude", "projects");
|
|
4248
|
+
if (!existsSync4(projectsDir))
|
|
4249
|
+
return null;
|
|
4250
|
+
try {
|
|
4251
|
+
for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
4252
|
+
if (!entry.isDirectory())
|
|
4253
|
+
continue;
|
|
4254
|
+
const candidate = join4(projectsDir, entry.name, `${sessionId}.jsonl`);
|
|
4255
|
+
if (existsSync4(candidate))
|
|
4256
|
+
return candidate;
|
|
4257
|
+
}
|
|
4258
|
+
} catch {
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
4261
|
+
return null;
|
|
4262
|
+
}
|
|
4038
4263
|
function truncateTranscript(messages, maxBytes = 50000) {
|
|
4039
4264
|
const lines = [];
|
|
4040
4265
|
for (const msg of messages) {
|
|
@@ -4588,7 +4813,9 @@ async function main() {
|
|
|
4588
4813
|
try {
|
|
4589
4814
|
if (event.session_id) {
|
|
4590
4815
|
persistRawToolChronology(db, event, config.user_id, config.device_id);
|
|
4591
|
-
await syncTranscriptChat(db, config, event.session_id, event.cwd
|
|
4816
|
+
await syncTranscriptChat(db, config, event.session_id, event.cwd, undefined, {
|
|
4817
|
+
embed: false
|
|
4818
|
+
});
|
|
4592
4819
|
}
|
|
4593
4820
|
const textToScan = extractScanText(event);
|
|
4594
4821
|
if (textToScan) {
|