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
|
@@ -7,11 +7,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
7
7
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
function resolveConfigDir() {
|
|
11
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join(homedir(), ".engrm");
|
|
12
|
+
}
|
|
13
|
+
function resolveSettingsPath() {
|
|
14
|
+
return join(resolveConfigDir(), "settings.json");
|
|
15
|
+
}
|
|
16
|
+
function resolveDbPath() {
|
|
17
|
+
return join(resolveConfigDir(), "engrm.db");
|
|
18
|
+
}
|
|
19
|
+
function resolveAuthBackupPath() {
|
|
20
|
+
return join(resolveConfigDir(), "auth-backup.json");
|
|
21
|
+
}
|
|
13
22
|
function getDbPath() {
|
|
14
|
-
return
|
|
23
|
+
return resolveDbPath();
|
|
15
24
|
}
|
|
16
25
|
function generateDeviceId() {
|
|
17
26
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -34,7 +43,7 @@ function generateDeviceId() {
|
|
|
34
43
|
return `${host}-${suffix}`;
|
|
35
44
|
}
|
|
36
45
|
function createDefaultConfig() {
|
|
37
|
-
|
|
46
|
+
const merged = {
|
|
38
47
|
candengo_url: "",
|
|
39
48
|
candengo_api_key: "",
|
|
40
49
|
site_id: "",
|
|
@@ -89,24 +98,26 @@ function createDefaultConfig() {
|
|
|
89
98
|
},
|
|
90
99
|
tool_profile: "full"
|
|
91
100
|
};
|
|
101
|
+
return merged;
|
|
92
102
|
}
|
|
93
103
|
function loadConfig() {
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
const settingsPath = resolveSettingsPath();
|
|
105
|
+
if (!existsSync(settingsPath)) {
|
|
106
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
96
107
|
}
|
|
97
|
-
const raw = readFileSync(
|
|
108
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
98
109
|
let parsed;
|
|
99
110
|
try {
|
|
100
111
|
parsed = JSON.parse(raw);
|
|
101
112
|
} catch {
|
|
102
|
-
throw new Error(`Invalid JSON in ${
|
|
113
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
103
114
|
}
|
|
104
115
|
if (typeof parsed !== "object" || parsed === null) {
|
|
105
|
-
throw new Error(`Config at ${
|
|
116
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
106
117
|
}
|
|
107
118
|
const config = parsed;
|
|
108
119
|
const defaults = createDefaultConfig();
|
|
109
|
-
|
|
120
|
+
const merged = {
|
|
110
121
|
candengo_url: asString(config["candengo_url"], defaults.candengo_url),
|
|
111
122
|
candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
|
|
112
123
|
site_id: asString(config["site_id"], defaults.site_id),
|
|
@@ -161,16 +172,27 @@ function loadConfig() {
|
|
|
161
172
|
},
|
|
162
173
|
tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
|
|
163
174
|
};
|
|
175
|
+
if (looksLikePlaceholderAuth(merged)) {
|
|
176
|
+
return restoreAuthBackup(merged) ?? merged;
|
|
177
|
+
}
|
|
178
|
+
return merged;
|
|
164
179
|
}
|
|
165
180
|
function saveConfig(config) {
|
|
166
|
-
|
|
167
|
-
|
|
181
|
+
const configDir = resolveConfigDir();
|
|
182
|
+
const settingsPath = resolveSettingsPath();
|
|
183
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
184
|
+
if (!existsSync(configDir)) {
|
|
185
|
+
mkdirSync(configDir, { recursive: true });
|
|
168
186
|
}
|
|
169
|
-
writeFileSync(
|
|
187
|
+
writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
|
|
170
188
|
`, "utf-8");
|
|
189
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
190
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
191
|
+
`, "utf-8");
|
|
192
|
+
}
|
|
171
193
|
}
|
|
172
194
|
function configExists() {
|
|
173
|
-
return existsSync(
|
|
195
|
+
return existsSync(resolveSettingsPath());
|
|
174
196
|
}
|
|
175
197
|
function asString(value, fallback) {
|
|
176
198
|
return typeof value === "string" ? value : fallback;
|
|
@@ -224,6 +246,50 @@ function asTeams(value, fallback) {
|
|
|
224
246
|
return fallback;
|
|
225
247
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
226
248
|
}
|
|
249
|
+
function looksLikePlaceholderAuth(config) {
|
|
250
|
+
const apiKey = config.candengo_api_key.trim();
|
|
251
|
+
const siteId = config.site_id.trim();
|
|
252
|
+
const namespace = config.namespace.trim();
|
|
253
|
+
const email = config.user_email.trim().toLowerCase();
|
|
254
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
255
|
+
return true;
|
|
256
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
257
|
+
return true;
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
function extractAuthBackup(config) {
|
|
261
|
+
return {
|
|
262
|
+
candengo_url: config.candengo_url,
|
|
263
|
+
candengo_api_key: config.candengo_api_key,
|
|
264
|
+
site_id: config.site_id,
|
|
265
|
+
namespace: config.namespace,
|
|
266
|
+
user_id: config.user_id,
|
|
267
|
+
user_email: config.user_email,
|
|
268
|
+
teams: config.teams
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function restoreAuthBackup(config) {
|
|
272
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
273
|
+
if (!existsSync(authBackupPath))
|
|
274
|
+
return null;
|
|
275
|
+
try {
|
|
276
|
+
const raw = readFileSync(authBackupPath, "utf-8");
|
|
277
|
+
const parsed = JSON.parse(raw);
|
|
278
|
+
const restored = {
|
|
279
|
+
...config,
|
|
280
|
+
candengo_url: asString(parsed["candengo_url"], config.candengo_url),
|
|
281
|
+
candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
|
|
282
|
+
site_id: asString(parsed["site_id"], config.site_id),
|
|
283
|
+
namespace: asString(parsed["namespace"], config.namespace),
|
|
284
|
+
user_id: asString(parsed["user_id"], config.user_id),
|
|
285
|
+
user_email: asString(parsed["user_email"], config.user_email),
|
|
286
|
+
teams: asTeams(parsed["teams"], config.teams)
|
|
287
|
+
};
|
|
288
|
+
return looksLikePlaceholderAuth(restored) ? null : restored;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
227
293
|
|
|
228
294
|
// src/storage/migrations.ts
|
|
229
295
|
var MIGRATIONS = [
|
|
@@ -911,6 +977,20 @@ function ensureChatMessageColumns(db) {
|
|
|
911
977
|
db.exec("PRAGMA user_version = 17");
|
|
912
978
|
}
|
|
913
979
|
}
|
|
980
|
+
function ensureObservationVectorTable(db) {
|
|
981
|
+
if (!isVecExtensionLoaded(db))
|
|
982
|
+
return;
|
|
983
|
+
db.exec(`
|
|
984
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
|
|
985
|
+
observation_id INTEGER PRIMARY KEY,
|
|
986
|
+
embedding FLOAT[384]
|
|
987
|
+
);
|
|
988
|
+
`);
|
|
989
|
+
const current = getSchemaVersion(db);
|
|
990
|
+
if (current < 4) {
|
|
991
|
+
db.exec("PRAGMA user_version = 4");
|
|
992
|
+
}
|
|
993
|
+
}
|
|
914
994
|
function ensureChatVectorTable(db) {
|
|
915
995
|
if (!isVecExtensionLoaded(db))
|
|
916
996
|
return;
|
|
@@ -1139,6 +1219,7 @@ class MemDatabase {
|
|
|
1139
1219
|
ensureObservationTypes(this.db);
|
|
1140
1220
|
ensureSessionSummaryColumns(this.db);
|
|
1141
1221
|
ensureChatMessageColumns(this.db);
|
|
1222
|
+
ensureObservationVectorTable(this.db);
|
|
1142
1223
|
ensureChatVectorTable(this.db);
|
|
1143
1224
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1144
1225
|
}
|
|
@@ -4114,14 +4195,18 @@ function getRecentOutcomes(db, projectId, userId, recentSessions) {
|
|
|
4114
4195
|
|
|
4115
4196
|
// src/capture/transcript.ts
|
|
4116
4197
|
import { createHash as createHash3 } from "node:crypto";
|
|
4117
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
|
|
4198
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, statSync, readdirSync } from "node:fs";
|
|
4118
4199
|
import { join as join3 } from "node:path";
|
|
4119
4200
|
import { homedir as homedir2 } from "node:os";
|
|
4120
4201
|
function resolveTranscriptPath(sessionId, cwd, transcriptPath) {
|
|
4121
4202
|
if (transcriptPath)
|
|
4122
4203
|
return transcriptPath;
|
|
4123
4204
|
const encodedCwd = cwd.replace(/\//g, "-");
|
|
4124
|
-
|
|
4205
|
+
const directPath = join3(homedir2(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
|
|
4206
|
+
if (existsSync3(directPath))
|
|
4207
|
+
return directPath;
|
|
4208
|
+
const discovered = findTranscriptPathBySessionId(sessionId);
|
|
4209
|
+
return discovered ?? directPath;
|
|
4125
4210
|
}
|
|
4126
4211
|
function readTranscript(sessionId, cwd, transcriptPath) {
|
|
4127
4212
|
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
@@ -4144,10 +4229,10 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
4144
4229
|
} catch {
|
|
4145
4230
|
continue;
|
|
4146
4231
|
}
|
|
4147
|
-
const role = entry
|
|
4232
|
+
const role = getTranscriptRole(entry);
|
|
4148
4233
|
if (role !== "user" && role !== "assistant")
|
|
4149
4234
|
continue;
|
|
4150
|
-
const content = entry
|
|
4235
|
+
const content = getTranscriptContent(entry);
|
|
4151
4236
|
if (typeof content === "string") {
|
|
4152
4237
|
messages.push({ role, text: content });
|
|
4153
4238
|
continue;
|
|
@@ -4167,6 +4252,66 @@ function readTranscript(sessionId, cwd, transcriptPath) {
|
|
|
4167
4252
|
}
|
|
4168
4253
|
return messages;
|
|
4169
4254
|
}
|
|
4255
|
+
function readTranscriptToolEvents(sessionId, cwd, transcriptPath) {
|
|
4256
|
+
const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
4257
|
+
if (!existsSync3(path))
|
|
4258
|
+
return [];
|
|
4259
|
+
let raw;
|
|
4260
|
+
try {
|
|
4261
|
+
raw = readFileSync3(path, "utf-8");
|
|
4262
|
+
} catch {
|
|
4263
|
+
return [];
|
|
4264
|
+
}
|
|
4265
|
+
const toolEvents = [];
|
|
4266
|
+
const toolEventIndexes = new Map;
|
|
4267
|
+
for (const line of raw.split(`
|
|
4268
|
+
`)) {
|
|
4269
|
+
if (!line.trim())
|
|
4270
|
+
continue;
|
|
4271
|
+
let entry;
|
|
4272
|
+
try {
|
|
4273
|
+
entry = JSON.parse(line);
|
|
4274
|
+
} catch {
|
|
4275
|
+
continue;
|
|
4276
|
+
}
|
|
4277
|
+
const createdAtEpoch = parseTranscriptTimestamp(entry);
|
|
4278
|
+
const content = getTranscriptContent(entry);
|
|
4279
|
+
if (!Array.isArray(content))
|
|
4280
|
+
continue;
|
|
4281
|
+
for (const block of content) {
|
|
4282
|
+
if (!block || typeof block !== "object")
|
|
4283
|
+
continue;
|
|
4284
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
4285
|
+
const preview = extractToolResultPreview(block.content);
|
|
4286
|
+
const index = toolEventIndexes.get(block.tool_use_id);
|
|
4287
|
+
if (preview && index !== undefined) {
|
|
4288
|
+
toolEvents[index] = {
|
|
4289
|
+
...toolEvents[index],
|
|
4290
|
+
tool_response_preview: preview
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
continue;
|
|
4294
|
+
}
|
|
4295
|
+
if (block.type !== "tool_use")
|
|
4296
|
+
continue;
|
|
4297
|
+
const input = block.input && typeof block.input === "object" ? block.input : {};
|
|
4298
|
+
const toolUseId = typeof block.id === "string" ? block.id : null;
|
|
4299
|
+
const nextEvent = {
|
|
4300
|
+
tool_name: typeof block.name === "string" ? block.name : "Unknown",
|
|
4301
|
+
tool_input_json: JSON.stringify(input),
|
|
4302
|
+
tool_response_preview: null,
|
|
4303
|
+
file_path: extractToolFilePath(input),
|
|
4304
|
+
command: typeof input.command === "string" ? input.command : null,
|
|
4305
|
+
created_at_epoch: createdAtEpoch
|
|
4306
|
+
};
|
|
4307
|
+
toolEvents.push(nextEvent);
|
|
4308
|
+
if (toolUseId) {
|
|
4309
|
+
toolEventIndexes.set(toolUseId, toolEvents.length - 1);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
return toolEvents;
|
|
4314
|
+
}
|
|
4170
4315
|
function resolveHistoryPath(historyPath) {
|
|
4171
4316
|
if (historyPath)
|
|
4172
4317
|
return historyPath;
|
|
@@ -4232,9 +4377,22 @@ function readHistoryFallback(sessionId, cwd, opts) {
|
|
|
4232
4377
|
createdAtEpoch: entry.timestamp
|
|
4233
4378
|
})));
|
|
4234
4379
|
}
|
|
4235
|
-
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
4380
|
+
async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath, options = {}) {
|
|
4381
|
+
const embed = options.embed ?? true;
|
|
4236
4382
|
const session = db.getSessionById(sessionId);
|
|
4237
|
-
const
|
|
4383
|
+
const resolvedTranscriptPath = resolveTranscriptPath(sessionId, cwd, transcriptPath);
|
|
4384
|
+
const syncCursorKey = `transcript_sync_cursor:${sessionId}`;
|
|
4385
|
+
if (existsSync3(resolvedTranscriptPath)) {
|
|
4386
|
+
try {
|
|
4387
|
+
const stat = statSync(resolvedTranscriptPath);
|
|
4388
|
+
const cursor = `${stat.size}:${Math.floor(stat.mtimeMs)}`;
|
|
4389
|
+
if (db.getSyncState(syncCursorKey) === cursor) {
|
|
4390
|
+
return { imported: 0, total: 0 };
|
|
4391
|
+
}
|
|
4392
|
+
db.setSyncState(syncCursorKey, cursor);
|
|
4393
|
+
} catch {}
|
|
4394
|
+
}
|
|
4395
|
+
const transcriptMessages = readTranscript(sessionId, cwd, resolvedTranscriptPath).map((message) => ({
|
|
4238
4396
|
...message,
|
|
4239
4397
|
text: message.text.trim()
|
|
4240
4398
|
})).filter((message) => message.text.length > 0);
|
|
@@ -4296,7 +4454,7 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4296
4454
|
created_at_epoch: createdAtEpoch
|
|
4297
4455
|
});
|
|
4298
4456
|
}
|
|
4299
|
-
if (db.vecAvailable) {
|
|
4457
|
+
if (embed && db.vecAvailable) {
|
|
4300
4458
|
const embedding = await embedText(composeChatEmbeddingText(message.text));
|
|
4301
4459
|
if (embedding) {
|
|
4302
4460
|
db.vecChatInsert(row.id, embedding);
|
|
@@ -4306,6 +4464,35 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
|
|
|
4306
4464
|
}
|
|
4307
4465
|
return { imported, total: messages.length };
|
|
4308
4466
|
}
|
|
4467
|
+
function syncTranscriptToolEvents(db, config, sessionId, cwd, transcriptPath) {
|
|
4468
|
+
const session = db.getSessionById(sessionId);
|
|
4469
|
+
if (!session)
|
|
4470
|
+
return { imported: 0, total: 0 };
|
|
4471
|
+
if (db.getSessionToolEvents(sessionId, 1).length > 0) {
|
|
4472
|
+
return { imported: 0, total: 0 };
|
|
4473
|
+
}
|
|
4474
|
+
const toolEvents = readTranscriptToolEvents(sessionId, cwd, transcriptPath);
|
|
4475
|
+
if (toolEvents.length === 0)
|
|
4476
|
+
return { imported: 0, total: 0 };
|
|
4477
|
+
let imported = 0;
|
|
4478
|
+
for (const event of toolEvents) {
|
|
4479
|
+
db.insertToolEvent({
|
|
4480
|
+
session_id: sessionId,
|
|
4481
|
+
project_id: session.project_id,
|
|
4482
|
+
tool_name: event.tool_name,
|
|
4483
|
+
tool_input_json: event.tool_input_json,
|
|
4484
|
+
tool_response_preview: event.tool_response_preview,
|
|
4485
|
+
file_path: event.file_path,
|
|
4486
|
+
command: event.command,
|
|
4487
|
+
user_id: config.user_id,
|
|
4488
|
+
device_id: config.device_id,
|
|
4489
|
+
agent: "claude-code",
|
|
4490
|
+
created_at_epoch: event.created_at_epoch ?? undefined
|
|
4491
|
+
});
|
|
4492
|
+
imported++;
|
|
4493
|
+
}
|
|
4494
|
+
return { imported, total: toolEvents.length };
|
|
4495
|
+
}
|
|
4309
4496
|
function dedupeHistoryMessages(messages) {
|
|
4310
4497
|
const deduped = [];
|
|
4311
4498
|
for (const message of messages) {
|
|
@@ -4323,6 +4510,59 @@ function buildHistorySourceId(sessionId, createdAtEpoch, text) {
|
|
|
4323
4510
|
const digest = createHash3("sha1").update(text).digest("hex").slice(0, 12);
|
|
4324
4511
|
return `history:${sessionId}:${createdAtEpoch}:${digest}`;
|
|
4325
4512
|
}
|
|
4513
|
+
function getTranscriptRole(entry) {
|
|
4514
|
+
return entry.role ?? entry.message?.role ?? entry.type ?? entry.message?.type;
|
|
4515
|
+
}
|
|
4516
|
+
function getTranscriptContent(entry) {
|
|
4517
|
+
return entry.content ?? entry.message?.content;
|
|
4518
|
+
}
|
|
4519
|
+
function parseTranscriptTimestamp(entry) {
|
|
4520
|
+
const raw = entry.timestamp ?? entry.message?.timestamp;
|
|
4521
|
+
if (typeof raw !== "string")
|
|
4522
|
+
return null;
|
|
4523
|
+
const epoch = Date.parse(raw);
|
|
4524
|
+
return Number.isFinite(epoch) ? Math.floor(epoch / 1000) : null;
|
|
4525
|
+
}
|
|
4526
|
+
function extractToolResultPreview(content) {
|
|
4527
|
+
if (typeof content === "string")
|
|
4528
|
+
return content.slice(0, 4000);
|
|
4529
|
+
if (Array.isArray(content)) {
|
|
4530
|
+
const text = content.map((item) => {
|
|
4531
|
+
if (typeof item === "string")
|
|
4532
|
+
return item;
|
|
4533
|
+
if (item && typeof item === "object" && typeof item.text === "string")
|
|
4534
|
+
return item.text;
|
|
4535
|
+
return "";
|
|
4536
|
+
}).filter(Boolean).join(`
|
|
4537
|
+
`);
|
|
4538
|
+
return text ? text.slice(0, 4000) : null;
|
|
4539
|
+
}
|
|
4540
|
+
return null;
|
|
4541
|
+
}
|
|
4542
|
+
function extractToolFilePath(input) {
|
|
4543
|
+
for (const key of ["file_path", "path", "target_file"]) {
|
|
4544
|
+
if (typeof input[key] === "string")
|
|
4545
|
+
return input[key];
|
|
4546
|
+
}
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
function findTranscriptPathBySessionId(sessionId) {
|
|
4550
|
+
const projectsDir = join3(homedir2(), ".claude", "projects");
|
|
4551
|
+
if (!existsSync3(projectsDir))
|
|
4552
|
+
return null;
|
|
4553
|
+
try {
|
|
4554
|
+
for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
4555
|
+
if (!entry.isDirectory())
|
|
4556
|
+
continue;
|
|
4557
|
+
const candidate = join3(projectsDir, entry.name, `${sessionId}.jsonl`);
|
|
4558
|
+
if (existsSync3(candidate))
|
|
4559
|
+
return candidate;
|
|
4560
|
+
}
|
|
4561
|
+
} catch {
|
|
4562
|
+
return null;
|
|
4563
|
+
}
|
|
4564
|
+
return null;
|
|
4565
|
+
}
|
|
4326
4566
|
function truncateTranscript(messages, maxBytes = 50000) {
|
|
4327
4567
|
const lines = [];
|
|
4328
4568
|
for (const msg of messages) {
|
|
@@ -4452,7 +4692,8 @@ async function main() {
|
|
|
4452
4692
|
try {
|
|
4453
4693
|
let importedChat = 0;
|
|
4454
4694
|
if (event.session_id) {
|
|
4455
|
-
|
|
4695
|
+
syncTranscriptToolEvents(db, config, event.session_id, event.cwd);
|
|
4696
|
+
const chatSync = await syncTranscriptChat(db, config, event.session_id, event.cwd, undefined, { embed: false });
|
|
4456
4697
|
importedChat = chatSync.imported;
|
|
4457
4698
|
await upsertRollingHandoff(db, config, {
|
|
4458
4699
|
session_id: event.session_id,
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -83,11 +83,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
83
83
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
84
84
|
import { join } from "node:path";
|
|
85
85
|
import { createHash } from "node:crypto";
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
function resolveConfigDir() {
|
|
87
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join(homedir(), ".engrm");
|
|
88
|
+
}
|
|
89
|
+
function resolveSettingsPath() {
|
|
90
|
+
return join(resolveConfigDir(), "settings.json");
|
|
91
|
+
}
|
|
92
|
+
function resolveDbPath() {
|
|
93
|
+
return join(resolveConfigDir(), "engrm.db");
|
|
94
|
+
}
|
|
95
|
+
function resolveAuthBackupPath() {
|
|
96
|
+
return join(resolveConfigDir(), "auth-backup.json");
|
|
97
|
+
}
|
|
89
98
|
function getDbPath() {
|
|
90
|
-
return
|
|
99
|
+
return resolveDbPath();
|
|
91
100
|
}
|
|
92
101
|
function generateDeviceId() {
|
|
93
102
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -110,7 +119,7 @@ function generateDeviceId() {
|
|
|
110
119
|
return `${host}-${suffix}`;
|
|
111
120
|
}
|
|
112
121
|
function createDefaultConfig() {
|
|
113
|
-
|
|
122
|
+
const merged = {
|
|
114
123
|
candengo_url: "",
|
|
115
124
|
candengo_api_key: "",
|
|
116
125
|
site_id: "",
|
|
@@ -165,24 +174,26 @@ function createDefaultConfig() {
|
|
|
165
174
|
},
|
|
166
175
|
tool_profile: "full"
|
|
167
176
|
};
|
|
177
|
+
return merged;
|
|
168
178
|
}
|
|
169
179
|
function loadConfig() {
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
const settingsPath = resolveSettingsPath();
|
|
181
|
+
if (!existsSync(settingsPath)) {
|
|
182
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
172
183
|
}
|
|
173
|
-
const raw = readFileSync(
|
|
184
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
174
185
|
let parsed;
|
|
175
186
|
try {
|
|
176
187
|
parsed = JSON.parse(raw);
|
|
177
188
|
} catch {
|
|
178
|
-
throw new Error(`Invalid JSON in ${
|
|
189
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
179
190
|
}
|
|
180
191
|
if (typeof parsed !== "object" || parsed === null) {
|
|
181
|
-
throw new Error(`Config at ${
|
|
192
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
182
193
|
}
|
|
183
194
|
const config = parsed;
|
|
184
195
|
const defaults = createDefaultConfig();
|
|
185
|
-
|
|
196
|
+
const merged = {
|
|
186
197
|
candengo_url: asString(config["candengo_url"], defaults.candengo_url),
|
|
187
198
|
candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
|
|
188
199
|
site_id: asString(config["site_id"], defaults.site_id),
|
|
@@ -237,16 +248,27 @@ function loadConfig() {
|
|
|
237
248
|
},
|
|
238
249
|
tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
|
|
239
250
|
};
|
|
251
|
+
if (looksLikePlaceholderAuth(merged)) {
|
|
252
|
+
return restoreAuthBackup(merged) ?? merged;
|
|
253
|
+
}
|
|
254
|
+
return merged;
|
|
240
255
|
}
|
|
241
256
|
function saveConfig(config) {
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
const configDir = resolveConfigDir();
|
|
258
|
+
const settingsPath = resolveSettingsPath();
|
|
259
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
260
|
+
if (!existsSync(configDir)) {
|
|
261
|
+
mkdirSync(configDir, { recursive: true });
|
|
244
262
|
}
|
|
245
|
-
writeFileSync(
|
|
263
|
+
writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
|
|
246
264
|
`, "utf-8");
|
|
265
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
266
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
267
|
+
`, "utf-8");
|
|
268
|
+
}
|
|
247
269
|
}
|
|
248
270
|
function configExists() {
|
|
249
|
-
return existsSync(
|
|
271
|
+
return existsSync(resolveSettingsPath());
|
|
250
272
|
}
|
|
251
273
|
function asString(value, fallback) {
|
|
252
274
|
return typeof value === "string" ? value : fallback;
|
|
@@ -300,6 +322,50 @@ function asTeams(value, fallback) {
|
|
|
300
322
|
return fallback;
|
|
301
323
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
302
324
|
}
|
|
325
|
+
function looksLikePlaceholderAuth(config) {
|
|
326
|
+
const apiKey = config.candengo_api_key.trim();
|
|
327
|
+
const siteId = config.site_id.trim();
|
|
328
|
+
const namespace = config.namespace.trim();
|
|
329
|
+
const email = config.user_email.trim().toLowerCase();
|
|
330
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
331
|
+
return true;
|
|
332
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
333
|
+
return true;
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
function extractAuthBackup(config) {
|
|
337
|
+
return {
|
|
338
|
+
candengo_url: config.candengo_url,
|
|
339
|
+
candengo_api_key: config.candengo_api_key,
|
|
340
|
+
site_id: config.site_id,
|
|
341
|
+
namespace: config.namespace,
|
|
342
|
+
user_id: config.user_id,
|
|
343
|
+
user_email: config.user_email,
|
|
344
|
+
teams: config.teams
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function restoreAuthBackup(config) {
|
|
348
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
349
|
+
if (!existsSync(authBackupPath))
|
|
350
|
+
return null;
|
|
351
|
+
try {
|
|
352
|
+
const raw = readFileSync(authBackupPath, "utf-8");
|
|
353
|
+
const parsed = JSON.parse(raw);
|
|
354
|
+
const restored = {
|
|
355
|
+
...config,
|
|
356
|
+
candengo_url: asString(parsed["candengo_url"], config.candengo_url),
|
|
357
|
+
candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
|
|
358
|
+
site_id: asString(parsed["site_id"], config.site_id),
|
|
359
|
+
namespace: asString(parsed["namespace"], config.namespace),
|
|
360
|
+
user_id: asString(parsed["user_id"], config.user_id),
|
|
361
|
+
user_email: asString(parsed["user_email"], config.user_email),
|
|
362
|
+
teams: asTeams(parsed["teams"], config.teams)
|
|
363
|
+
};
|
|
364
|
+
return looksLikePlaceholderAuth(restored) ? null : restored;
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
303
369
|
|
|
304
370
|
// src/storage/migrations.ts
|
|
305
371
|
var MIGRATIONS = [
|
|
@@ -987,6 +1053,20 @@ function ensureChatMessageColumns(db) {
|
|
|
987
1053
|
db.exec("PRAGMA user_version = 17");
|
|
988
1054
|
}
|
|
989
1055
|
}
|
|
1056
|
+
function ensureObservationVectorTable(db) {
|
|
1057
|
+
if (!isVecExtensionLoaded(db))
|
|
1058
|
+
return;
|
|
1059
|
+
db.exec(`
|
|
1060
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
|
|
1061
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1062
|
+
embedding FLOAT[384]
|
|
1063
|
+
);
|
|
1064
|
+
`);
|
|
1065
|
+
const current = getSchemaVersion(db);
|
|
1066
|
+
if (current < 4) {
|
|
1067
|
+
db.exec("PRAGMA user_version = 4");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
990
1070
|
function ensureChatVectorTable(db) {
|
|
991
1071
|
if (!isVecExtensionLoaded(db))
|
|
992
1072
|
return;
|
|
@@ -1215,6 +1295,7 @@ class MemDatabase {
|
|
|
1215
1295
|
ensureObservationTypes(this.db);
|
|
1216
1296
|
ensureSessionSummaryColumns(this.db);
|
|
1217
1297
|
ensureChatMessageColumns(this.db);
|
|
1298
|
+
ensureObservationVectorTable(this.db);
|
|
1218
1299
|
ensureChatVectorTable(this.db);
|
|
1219
1300
|
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1220
1301
|
}
|