engrm 0.3.4 → 0.4.0
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 +631 -11
- package/dist/hooks/elicitation-result.js +32 -4
- package/dist/hooks/post-tool-use.js +287 -4
- package/dist/hooks/pre-compact.js +55 -10
- package/dist/hooks/sentinel.js +32 -4
- package/dist/hooks/session-start.js +291 -11
- package/dist/hooks/stop.js +852 -4
- package/dist/server.js +102 -10
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -13554,9 +13554,9 @@ function date4(params) {
|
|
|
13554
13554
|
config(en_default());
|
|
13555
13555
|
// src/config.ts
|
|
13556
13556
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13557
|
-
import { homedir, hostname as hostname3 } from "node:os";
|
|
13557
|
+
import { homedir, hostname as hostname3, networkInterfaces } from "node:os";
|
|
13558
13558
|
import { join } from "node:path";
|
|
13559
|
-
import {
|
|
13559
|
+
import { createHash } from "node:crypto";
|
|
13560
13560
|
var CONFIG_DIR = join(homedir(), ".engrm");
|
|
13561
13561
|
var SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
|
|
13562
13562
|
var DB_PATH = join(CONFIG_DIR, "engrm.db");
|
|
@@ -13565,7 +13565,22 @@ function getDbPath() {
|
|
|
13565
13565
|
}
|
|
13566
13566
|
function generateDeviceId() {
|
|
13567
13567
|
const host = hostname3().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
13568
|
-
|
|
13568
|
+
let mac3 = "";
|
|
13569
|
+
const ifaces = networkInterfaces();
|
|
13570
|
+
for (const entries of Object.values(ifaces)) {
|
|
13571
|
+
if (!entries)
|
|
13572
|
+
continue;
|
|
13573
|
+
for (const entry of entries) {
|
|
13574
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
13575
|
+
mac3 = entry.mac;
|
|
13576
|
+
break;
|
|
13577
|
+
}
|
|
13578
|
+
}
|
|
13579
|
+
if (mac3)
|
|
13580
|
+
break;
|
|
13581
|
+
}
|
|
13582
|
+
const material = `${host}:${mac3 || "no-mac"}`;
|
|
13583
|
+
const suffix = createHash("sha256").update(material).digest("hex").slice(0, 8);
|
|
13569
13584
|
return `${host}-${suffix}`;
|
|
13570
13585
|
}
|
|
13571
13586
|
function createDefaultConfig() {
|
|
@@ -13607,7 +13622,10 @@ function createDefaultConfig() {
|
|
|
13607
13622
|
observer: {
|
|
13608
13623
|
enabled: true,
|
|
13609
13624
|
mode: "per_event",
|
|
13610
|
-
model: "
|
|
13625
|
+
model: "sonnet"
|
|
13626
|
+
},
|
|
13627
|
+
transcript_analysis: {
|
|
13628
|
+
enabled: false
|
|
13611
13629
|
}
|
|
13612
13630
|
};
|
|
13613
13631
|
}
|
|
@@ -13666,9 +13684,19 @@ function loadConfig() {
|
|
|
13666
13684
|
enabled: asBool(config2["observer"]?.["enabled"], defaults.observer.enabled),
|
|
13667
13685
|
mode: asObserverMode(config2["observer"]?.["mode"], defaults.observer.mode),
|
|
13668
13686
|
model: asString(config2["observer"]?.["model"], defaults.observer.model)
|
|
13687
|
+
},
|
|
13688
|
+
transcript_analysis: {
|
|
13689
|
+
enabled: asBool(config2["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
13669
13690
|
}
|
|
13670
13691
|
};
|
|
13671
13692
|
}
|
|
13693
|
+
function saveConfig(config2) {
|
|
13694
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
13695
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
13696
|
+
}
|
|
13697
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(config2, null, 2) + `
|
|
13698
|
+
`, "utf-8");
|
|
13699
|
+
}
|
|
13672
13700
|
function configExists() {
|
|
13673
13701
|
return existsSync(SETTINGS_PATH);
|
|
13674
13702
|
}
|
|
@@ -15443,7 +15471,7 @@ function estimateTokens(text) {
|
|
|
15443
15471
|
}
|
|
15444
15472
|
function buildSessionContext(db, cwd, options = {}) {
|
|
15445
15473
|
const opts = typeof options === "number" ? { maxCount: options } : options;
|
|
15446
|
-
const tokenBudget = opts.tokenBudget ??
|
|
15474
|
+
const tokenBudget = opts.tokenBudget ?? 3000;
|
|
15447
15475
|
const maxCount = opts.maxCount;
|
|
15448
15476
|
const detected = detectProject(cwd);
|
|
15449
15477
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
@@ -15465,6 +15493,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15465
15493
|
AND superseded_by IS NULL
|
|
15466
15494
|
ORDER BY quality DESC, created_at_epoch DESC
|
|
15467
15495
|
LIMIT ?`).all(project.id, MAX_PINNED);
|
|
15496
|
+
const MAX_RECENT = 5;
|
|
15497
|
+
const recent = db.db.query(`SELECT * FROM observations
|
|
15498
|
+
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
15499
|
+
AND superseded_by IS NULL
|
|
15500
|
+
ORDER BY created_at_epoch DESC
|
|
15501
|
+
LIMIT ?`).all(project.id, MAX_RECENT);
|
|
15468
15502
|
const candidateLimit = maxCount ?? 50;
|
|
15469
15503
|
const candidates = db.db.query(`SELECT * FROM observations
|
|
15470
15504
|
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
@@ -15492,6 +15526,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15492
15526
|
});
|
|
15493
15527
|
}
|
|
15494
15528
|
const seenIds = new Set(pinned.map((o) => o.id));
|
|
15529
|
+
const dedupedRecent = recent.filter((o) => {
|
|
15530
|
+
if (seenIds.has(o.id))
|
|
15531
|
+
return false;
|
|
15532
|
+
seenIds.add(o.id);
|
|
15533
|
+
return true;
|
|
15534
|
+
});
|
|
15495
15535
|
const deduped = candidates.filter((o) => !seenIds.has(o.id));
|
|
15496
15536
|
for (const obs of crossProjectCandidates) {
|
|
15497
15537
|
if (!seenIds.has(obs.id)) {
|
|
@@ -15508,8 +15548,8 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15508
15548
|
return scoreB - scoreA;
|
|
15509
15549
|
});
|
|
15510
15550
|
if (maxCount !== undefined) {
|
|
15511
|
-
const remaining = Math.max(0, maxCount - pinned.length);
|
|
15512
|
-
const all = [...pinned, ...sorted.slice(0, remaining)];
|
|
15551
|
+
const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
|
|
15552
|
+
const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
|
|
15513
15553
|
return {
|
|
15514
15554
|
project_name: project.name,
|
|
15515
15555
|
canonical_id: project.canonical_id,
|
|
@@ -15525,6 +15565,11 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15525
15565
|
remainingBudget -= cost;
|
|
15526
15566
|
selected.push(obs);
|
|
15527
15567
|
}
|
|
15568
|
+
for (const obs of dedupedRecent) {
|
|
15569
|
+
const cost = estimateObservationTokens(obs, selected.length);
|
|
15570
|
+
remainingBudget -= cost;
|
|
15571
|
+
selected.push(obs);
|
|
15572
|
+
}
|
|
15528
15573
|
for (const obs of sorted) {
|
|
15529
15574
|
const cost = estimateObservationTokens(obs, selected.length);
|
|
15530
15575
|
if (remainingBudget - cost < 0 && selected.length > 0)
|
|
@@ -15532,7 +15577,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15532
15577
|
remainingBudget -= cost;
|
|
15533
15578
|
selected.push(obs);
|
|
15534
15579
|
}
|
|
15535
|
-
const summaries = db.getRecentSummaries(project.id,
|
|
15580
|
+
const summaries = db.getRecentSummaries(project.id, 5);
|
|
15536
15581
|
let securityFindings = [];
|
|
15537
15582
|
try {
|
|
15538
15583
|
const weekAgo = Math.floor(Date.now() / 1000) - 7 * 86400;
|
|
@@ -15552,7 +15597,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
15552
15597
|
};
|
|
15553
15598
|
}
|
|
15554
15599
|
function estimateObservationTokens(obs, index) {
|
|
15555
|
-
const DETAILED_THRESHOLD =
|
|
15600
|
+
const DETAILED_THRESHOLD = 5;
|
|
15556
15601
|
const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);
|
|
15557
15602
|
if (index >= DETAILED_THRESHOLD) {
|
|
15558
15603
|
return titleCost;
|
|
@@ -15564,7 +15609,7 @@ function formatContextForInjection(context) {
|
|
|
15564
15609
|
if (context.observations.length === 0) {
|
|
15565
15610
|
return `Project: ${context.project_name} (no prior observations)`;
|
|
15566
15611
|
}
|
|
15567
|
-
const DETAILED_COUNT =
|
|
15612
|
+
const DETAILED_COUNT = 5;
|
|
15568
15613
|
const lines = [
|
|
15569
15614
|
`## Project Memory: ${context.project_name}`,
|
|
15570
15615
|
`${context.session_count} relevant observation(s) from prior sessions:`,
|
|
@@ -15944,6 +15989,13 @@ class VectorClient {
|
|
|
15944
15989
|
async sendTelemetry(beacon) {
|
|
15945
15990
|
await this.request("POST", "/v1/mem/telemetry", beacon);
|
|
15946
15991
|
}
|
|
15992
|
+
async fetchSettings() {
|
|
15993
|
+
try {
|
|
15994
|
+
return await this.request("GET", "/v1/mem/user-settings");
|
|
15995
|
+
} catch {
|
|
15996
|
+
return null;
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15947
15999
|
async health() {
|
|
15948
16000
|
try {
|
|
15949
16001
|
await this.request("GET", "/health");
|
|
@@ -16043,6 +16095,7 @@ function buildVectorDocument(obs, config2, project) {
|
|
|
16043
16095
|
project_name: project.name,
|
|
16044
16096
|
user_id: obs.user_id,
|
|
16045
16097
|
device_id: obs.device_id,
|
|
16098
|
+
device_name: __require("node:os").hostname(),
|
|
16046
16099
|
agent: obs.agent,
|
|
16047
16100
|
title: obs.title,
|
|
16048
16101
|
narrative: obs.narrative,
|
|
@@ -16259,6 +16312,44 @@ function extractNarrative(content) {
|
|
|
16259
16312
|
`).trim();
|
|
16260
16313
|
return narrative.length > 0 ? narrative : null;
|
|
16261
16314
|
}
|
|
16315
|
+
async function pullSettings(client, config2) {
|
|
16316
|
+
try {
|
|
16317
|
+
const settings = await client.fetchSettings();
|
|
16318
|
+
if (!settings)
|
|
16319
|
+
return false;
|
|
16320
|
+
let changed = false;
|
|
16321
|
+
if (settings.transcript_analysis !== undefined) {
|
|
16322
|
+
const ta = settings.transcript_analysis;
|
|
16323
|
+
if (typeof ta === "object" && ta !== null) {
|
|
16324
|
+
const taObj = ta;
|
|
16325
|
+
if (taObj.enabled !== undefined && taObj.enabled !== config2.transcript_analysis.enabled) {
|
|
16326
|
+
config2.transcript_analysis.enabled = !!taObj.enabled;
|
|
16327
|
+
changed = true;
|
|
16328
|
+
}
|
|
16329
|
+
}
|
|
16330
|
+
}
|
|
16331
|
+
if (settings.observer !== undefined) {
|
|
16332
|
+
const obs = settings.observer;
|
|
16333
|
+
if (typeof obs === "object" && obs !== null) {
|
|
16334
|
+
const obsObj = obs;
|
|
16335
|
+
if (obsObj.enabled !== undefined && obsObj.enabled !== config2.observer.enabled) {
|
|
16336
|
+
config2.observer.enabled = !!obsObj.enabled;
|
|
16337
|
+
changed = true;
|
|
16338
|
+
}
|
|
16339
|
+
if (obsObj.model !== undefined && typeof obsObj.model === "string" && obsObj.model !== config2.observer.model) {
|
|
16340
|
+
config2.observer.model = obsObj.model;
|
|
16341
|
+
changed = true;
|
|
16342
|
+
}
|
|
16343
|
+
}
|
|
16344
|
+
}
|
|
16345
|
+
if (changed) {
|
|
16346
|
+
saveConfig(config2);
|
|
16347
|
+
}
|
|
16348
|
+
return changed;
|
|
16349
|
+
} catch {
|
|
16350
|
+
return false;
|
|
16351
|
+
}
|
|
16352
|
+
}
|
|
16262
16353
|
|
|
16263
16354
|
// src/sync/engine.ts
|
|
16264
16355
|
var DEFAULT_PULL_INTERVAL = 60000;
|
|
@@ -16323,6 +16414,7 @@ class SyncEngine {
|
|
|
16323
16414
|
this._pulling = true;
|
|
16324
16415
|
try {
|
|
16325
16416
|
await pullFromVector(this.db, this.client, this.config);
|
|
16417
|
+
await pullSettings(this.client, this.config);
|
|
16326
16418
|
} finally {
|
|
16327
16419
|
this._pulling = false;
|
|
16328
16420
|
}
|