engrm 0.4.7 → 0.4.8
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 +42 -1
- package/dist/cli.js +404 -54
- package/dist/hooks/elicitation-result.js +166 -0
- package/dist/hooks/post-tool-use.js +204 -0
- package/dist/hooks/pre-compact.js +262 -4
- package/dist/hooks/sentinel.js +166 -0
- package/dist/hooks/session-start.js +337 -6
- package/dist/hooks/stop.js +200 -3
- package/dist/hooks/user-prompt-submit.js +1387 -0
- package/dist/server.js +1562 -57
- package/package.json +1 -1
|
@@ -457,12 +457,20 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
457
457
|
if (maxCount !== undefined) {
|
|
458
458
|
const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
|
|
459
459
|
const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
|
|
460
|
+
const recentPrompts2 = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
461
|
+
const recentToolEvents2 = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
462
|
+
const recentSessions2 = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
463
|
+
const projectTypeCounts2 = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
460
464
|
return {
|
|
461
465
|
project_name: projectName,
|
|
462
466
|
canonical_id: canonicalId,
|
|
463
467
|
observations: all.map(toContextObservation),
|
|
464
468
|
session_count: all.length,
|
|
465
|
-
total_active: totalActive
|
|
469
|
+
total_active: totalActive,
|
|
470
|
+
recentPrompts: recentPrompts2.length > 0 ? recentPrompts2 : undefined,
|
|
471
|
+
recentToolEvents: recentToolEvents2.length > 0 ? recentToolEvents2 : undefined,
|
|
472
|
+
recentSessions: recentSessions2.length > 0 ? recentSessions2 : undefined,
|
|
473
|
+
projectTypeCounts: projectTypeCounts2
|
|
466
474
|
};
|
|
467
475
|
}
|
|
468
476
|
let remainingBudget = tokenBudget - 30;
|
|
@@ -485,6 +493,10 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
485
493
|
selected.push(obs);
|
|
486
494
|
}
|
|
487
495
|
const summaries = isNewProject ? [] : db.getRecentSummaries(projectId, 5);
|
|
496
|
+
const recentPrompts = db.getRecentUserPrompts(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
497
|
+
const recentToolEvents = db.getRecentToolEvents(isNewProject ? null : projectId, isNewProject ? 8 : 6, opts.userId);
|
|
498
|
+
const recentSessions = isNewProject ? [] : db.getRecentSessions(projectId, 5, opts.userId);
|
|
499
|
+
const projectTypeCounts = isNewProject ? undefined : getProjectTypeCounts(db, projectId, opts.userId);
|
|
488
500
|
let securityFindings = [];
|
|
489
501
|
if (!isNewProject) {
|
|
490
502
|
try {
|
|
@@ -538,7 +550,11 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
538
550
|
summaries: summaries.length > 0 ? summaries : undefined,
|
|
539
551
|
securityFindings: securityFindings.length > 0 ? securityFindings : undefined,
|
|
540
552
|
recentProjects,
|
|
541
|
-
staleDecisions
|
|
553
|
+
staleDecisions,
|
|
554
|
+
recentPrompts: recentPrompts.length > 0 ? recentPrompts : undefined,
|
|
555
|
+
recentToolEvents: recentToolEvents.length > 0 ? recentToolEvents : undefined,
|
|
556
|
+
recentSessions: recentSessions.length > 0 ? recentSessions : undefined,
|
|
557
|
+
projectTypeCounts
|
|
542
558
|
};
|
|
543
559
|
}
|
|
544
560
|
function estimateObservationTokens(obs, index) {
|
|
@@ -551,7 +567,7 @@ function estimateObservationTokens(obs, index) {
|
|
|
551
567
|
return titleCost + estimateTokens(detailText);
|
|
552
568
|
}
|
|
553
569
|
function formatContextForInjection(context) {
|
|
554
|
-
if (context.observations.length === 0) {
|
|
570
|
+
if (context.observations.length === 0 && (!context.recentPrompts || context.recentPrompts.length === 0) && (!context.recentToolEvents || context.recentToolEvents.length === 0) && (!context.recentSessions || context.recentSessions.length === 0) && (!context.projectTypeCounts || Object.keys(context.projectTypeCounts).length === 0)) {
|
|
555
571
|
return `Project: ${context.project_name} (no prior observations)`;
|
|
556
572
|
}
|
|
557
573
|
const DETAILED_COUNT = 5;
|
|
@@ -574,11 +590,43 @@ function formatContextForInjection(context) {
|
|
|
574
590
|
lines.push(`${context.session_count} relevant observation(s) from prior sessions:`);
|
|
575
591
|
lines.push("");
|
|
576
592
|
}
|
|
593
|
+
if (context.recentPrompts && context.recentPrompts.length > 0) {
|
|
594
|
+
lines.push("## Recent Requests");
|
|
595
|
+
for (const prompt of context.recentPrompts.slice(0, 5)) {
|
|
596
|
+
const label = prompt.prompt_number > 0 ? `#${prompt.prompt_number}` : new Date(prompt.created_at_epoch * 1000).toISOString().split("T")[0];
|
|
597
|
+
lines.push(`- ${label}: ${truncateText(prompt.prompt.replace(/\s+/g, " "), 160)}`);
|
|
598
|
+
}
|
|
599
|
+
lines.push("");
|
|
600
|
+
}
|
|
601
|
+
if (context.recentToolEvents && context.recentToolEvents.length > 0) {
|
|
602
|
+
lines.push("## Recent Tools");
|
|
603
|
+
for (const tool of context.recentToolEvents.slice(0, 5)) {
|
|
604
|
+
lines.push(`- ${tool.tool_name}: ${formatToolEventDetail(tool)}`);
|
|
605
|
+
}
|
|
606
|
+
lines.push("");
|
|
607
|
+
}
|
|
608
|
+
if (context.recentSessions && context.recentSessions.length > 0) {
|
|
609
|
+
lines.push("## Recent Sessions");
|
|
610
|
+
for (const session of context.recentSessions.slice(0, 4)) {
|
|
611
|
+
const summary = session.request ?? session.completed ?? "(no summary)";
|
|
612
|
+
lines.push(`- ${session.session_id}: ${truncateText(summary.replace(/\s+/g, " "), 140)} ` + `(prompts ${session.prompt_count}, tools ${session.tool_event_count}, obs ${session.observation_count})`);
|
|
613
|
+
}
|
|
614
|
+
lines.push("");
|
|
615
|
+
}
|
|
616
|
+
if (context.projectTypeCounts && Object.keys(context.projectTypeCounts).length > 0) {
|
|
617
|
+
const topTypes = Object.entries(context.projectTypeCounts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 5).map(([type, count]) => `${type} ${count}`).join(" · ");
|
|
618
|
+
if (topTypes) {
|
|
619
|
+
lines.push(`## Project Signals`);
|
|
620
|
+
lines.push(`Top memory types: ${topTypes}`);
|
|
621
|
+
lines.push("");
|
|
622
|
+
}
|
|
623
|
+
}
|
|
577
624
|
for (let i = 0;i < context.observations.length; i++) {
|
|
578
625
|
const obs = context.observations[i];
|
|
579
626
|
const date = obs.created_at.split("T")[0];
|
|
580
627
|
const fromLabel = obs.source_project ? ` [from: ${obs.source_project}]` : "";
|
|
581
|
-
|
|
628
|
+
const fileLabel = formatObservationFiles(obs);
|
|
629
|
+
lines.push(`- **#${obs.id} [${obs.type}]** ${obs.title} (${date}, q=${obs.quality.toFixed(1)})${fromLabel}${fileLabel}`);
|
|
582
630
|
if (i < DETAILED_COUNT) {
|
|
583
631
|
const detail = formatObservationDetailFromContext(obs);
|
|
584
632
|
if (detail) {
|
|
@@ -715,11 +763,55 @@ function toContextObservation(obs) {
|
|
|
715
763
|
title: obs.title,
|
|
716
764
|
narrative: obs.narrative,
|
|
717
765
|
facts: obs.facts,
|
|
766
|
+
files_read: obs.files_read,
|
|
767
|
+
files_modified: obs.files_modified,
|
|
718
768
|
quality: obs.quality,
|
|
719
769
|
created_at: obs.created_at,
|
|
720
770
|
...obs._source_project ? { source_project: obs._source_project } : {}
|
|
721
771
|
};
|
|
722
772
|
}
|
|
773
|
+
function formatObservationFiles(obs) {
|
|
774
|
+
const modified = parseJsonStringArray(obs.files_modified);
|
|
775
|
+
if (modified.length > 0) {
|
|
776
|
+
return ` · files: ${truncateText(modified.slice(0, 2).join(", "), 60)}`;
|
|
777
|
+
}
|
|
778
|
+
const read = parseJsonStringArray(obs.files_read);
|
|
779
|
+
if (read.length > 0) {
|
|
780
|
+
return ` · read: ${truncateText(read.slice(0, 2).join(", "), 60)}`;
|
|
781
|
+
}
|
|
782
|
+
return "";
|
|
783
|
+
}
|
|
784
|
+
function parseJsonStringArray(value) {
|
|
785
|
+
if (!value)
|
|
786
|
+
return [];
|
|
787
|
+
try {
|
|
788
|
+
const parsed = JSON.parse(value);
|
|
789
|
+
if (!Array.isArray(parsed))
|
|
790
|
+
return [];
|
|
791
|
+
return parsed.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
792
|
+
} catch {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function formatToolEventDetail(tool) {
|
|
797
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
798
|
+
return truncateText(detail || "recent tool execution", 160);
|
|
799
|
+
}
|
|
800
|
+
function getProjectTypeCounts(db, projectId, userId) {
|
|
801
|
+
const visibilityClause = userId ? " AND (sensitivity != 'personal' OR user_id = ?)" : "";
|
|
802
|
+
const rows = db.db.query(`SELECT type, COUNT(*) as count
|
|
803
|
+
FROM observations
|
|
804
|
+
WHERE project_id = ?
|
|
805
|
+
AND lifecycle IN ('active', 'aging', 'pinned')
|
|
806
|
+
AND superseded_by IS NULL
|
|
807
|
+
${visibilityClause}
|
|
808
|
+
GROUP BY type`).all(projectId, ...userId ? [userId] : []);
|
|
809
|
+
const counts = {};
|
|
810
|
+
for (const row of rows) {
|
|
811
|
+
counts[row.type] = row.count;
|
|
812
|
+
}
|
|
813
|
+
return counts;
|
|
814
|
+
}
|
|
723
815
|
|
|
724
816
|
// src/telemetry/stack-detect.ts
|
|
725
817
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -1168,6 +1260,21 @@ function asTeams(value, fallback) {
|
|
|
1168
1260
|
}
|
|
1169
1261
|
|
|
1170
1262
|
// src/sync/auth.ts
|
|
1263
|
+
var LEGACY_PUBLIC_HOSTS = new Set(["www.candengo.com", "candengo.com"]);
|
|
1264
|
+
function normalizeBaseUrl(url) {
|
|
1265
|
+
const trimmed = url.trim();
|
|
1266
|
+
if (!trimmed)
|
|
1267
|
+
return trimmed;
|
|
1268
|
+
try {
|
|
1269
|
+
const parsed = new URL(trimmed);
|
|
1270
|
+
if (LEGACY_PUBLIC_HOSTS.has(parsed.hostname)) {
|
|
1271
|
+
parsed.hostname = "engrm.dev";
|
|
1272
|
+
}
|
|
1273
|
+
return parsed.toString().replace(/\/$/, "");
|
|
1274
|
+
} catch {
|
|
1275
|
+
return trimmed.replace(/\/$/, "");
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1171
1278
|
function getApiKey(config) {
|
|
1172
1279
|
const envKey = process.env.ENGRM_TOKEN;
|
|
1173
1280
|
if (envKey && envKey.startsWith("cvk_"))
|
|
@@ -1179,7 +1286,7 @@ function getApiKey(config) {
|
|
|
1179
1286
|
}
|
|
1180
1287
|
function getBaseUrl(config) {
|
|
1181
1288
|
if (config.candengo_url && config.candengo_url.length > 0) {
|
|
1182
|
-
return config.candengo_url;
|
|
1289
|
+
return normalizeBaseUrl(config.candengo_url);
|
|
1183
1290
|
}
|
|
1184
1291
|
return null;
|
|
1185
1292
|
}
|
|
@@ -1809,6 +1916,64 @@ var MIGRATIONS = [
|
|
|
1809
1916
|
);
|
|
1810
1917
|
INSERT INTO observations_fts(observations_fts) VALUES('rebuild');
|
|
1811
1918
|
`
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
version: 9,
|
|
1922
|
+
description: "Add first-class user prompt capture",
|
|
1923
|
+
sql: `
|
|
1924
|
+
CREATE TABLE IF NOT EXISTS user_prompts (
|
|
1925
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1926
|
+
session_id TEXT NOT NULL,
|
|
1927
|
+
project_id INTEGER REFERENCES projects(id),
|
|
1928
|
+
prompt_number INTEGER NOT NULL,
|
|
1929
|
+
prompt TEXT NOT NULL,
|
|
1930
|
+
prompt_hash TEXT NOT NULL,
|
|
1931
|
+
cwd TEXT,
|
|
1932
|
+
user_id TEXT NOT NULL,
|
|
1933
|
+
device_id TEXT NOT NULL,
|
|
1934
|
+
agent TEXT DEFAULT 'claude-code',
|
|
1935
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1936
|
+
UNIQUE(session_id, prompt_number)
|
|
1937
|
+
);
|
|
1938
|
+
|
|
1939
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_session
|
|
1940
|
+
ON user_prompts(session_id, prompt_number DESC);
|
|
1941
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_project
|
|
1942
|
+
ON user_prompts(project_id, created_at_epoch DESC);
|
|
1943
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_created
|
|
1944
|
+
ON user_prompts(created_at_epoch DESC);
|
|
1945
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_hash
|
|
1946
|
+
ON user_prompts(prompt_hash);
|
|
1947
|
+
`
|
|
1948
|
+
},
|
|
1949
|
+
{
|
|
1950
|
+
version: 10,
|
|
1951
|
+
description: "Add first-class tool event chronology",
|
|
1952
|
+
sql: `
|
|
1953
|
+
CREATE TABLE IF NOT EXISTS tool_events (
|
|
1954
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1955
|
+
session_id TEXT NOT NULL,
|
|
1956
|
+
project_id INTEGER REFERENCES projects(id),
|
|
1957
|
+
tool_name TEXT NOT NULL,
|
|
1958
|
+
tool_input_json TEXT,
|
|
1959
|
+
tool_response_preview TEXT,
|
|
1960
|
+
file_path TEXT,
|
|
1961
|
+
command TEXT,
|
|
1962
|
+
user_id TEXT NOT NULL,
|
|
1963
|
+
device_id TEXT NOT NULL,
|
|
1964
|
+
agent TEXT DEFAULT 'claude-code',
|
|
1965
|
+
created_at_epoch INTEGER NOT NULL
|
|
1966
|
+
);
|
|
1967
|
+
|
|
1968
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_session
|
|
1969
|
+
ON tool_events(session_id, created_at_epoch DESC, id DESC);
|
|
1970
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_project
|
|
1971
|
+
ON tool_events(project_id, created_at_epoch DESC, id DESC);
|
|
1972
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_tool_name
|
|
1973
|
+
ON tool_events(tool_name, created_at_epoch DESC);
|
|
1974
|
+
CREATE INDEX IF NOT EXISTS idx_tool_events_created
|
|
1975
|
+
ON tool_events(created_at_epoch DESC, id DESC);
|
|
1976
|
+
`
|
|
1812
1977
|
}
|
|
1813
1978
|
];
|
|
1814
1979
|
function isVecExtensionLoaded(db) {
|
|
@@ -1893,6 +2058,7 @@ function ensureObservationTypes(db) {
|
|
|
1893
2058
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
1894
2059
|
|
|
1895
2060
|
// src/storage/sqlite.ts
|
|
2061
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1896
2062
|
var IS_BUN = typeof globalThis.Bun !== "undefined";
|
|
1897
2063
|
function openDatabase(dbPath) {
|
|
1898
2064
|
if (IS_BUN) {
|
|
@@ -2156,6 +2322,110 @@ class MemDatabase {
|
|
|
2156
2322
|
const now = Math.floor(Date.now() / 1000);
|
|
2157
2323
|
this.db.query("UPDATE sessions SET status = 'completed', completed_at_epoch = ? WHERE session_id = ?").run(now, sessionId);
|
|
2158
2324
|
}
|
|
2325
|
+
getSessionById(sessionId) {
|
|
2326
|
+
return this.db.query("SELECT * FROM sessions WHERE session_id = ?").get(sessionId) ?? null;
|
|
2327
|
+
}
|
|
2328
|
+
getRecentSessions(projectId, limit = 10, userId) {
|
|
2329
|
+
const visibilityClause = userId ? " AND s.user_id = ?" : "";
|
|
2330
|
+
if (projectId !== null) {
|
|
2331
|
+
return this.db.query(`SELECT
|
|
2332
|
+
s.*,
|
|
2333
|
+
p.name AS project_name,
|
|
2334
|
+
ss.request AS request,
|
|
2335
|
+
ss.completed AS completed,
|
|
2336
|
+
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
2337
|
+
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
2338
|
+
FROM sessions s
|
|
2339
|
+
LEFT JOIN projects p ON p.id = s.project_id
|
|
2340
|
+
LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
|
|
2341
|
+
WHERE s.project_id = ?${visibilityClause}
|
|
2342
|
+
ORDER BY COALESCE(s.completed_at_epoch, s.started_at_epoch, 0) DESC, s.id DESC
|
|
2343
|
+
LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
|
|
2344
|
+
}
|
|
2345
|
+
return this.db.query(`SELECT
|
|
2346
|
+
s.*,
|
|
2347
|
+
p.name AS project_name,
|
|
2348
|
+
ss.request AS request,
|
|
2349
|
+
ss.completed AS completed,
|
|
2350
|
+
(SELECT COUNT(*) FROM user_prompts up WHERE up.session_id = s.session_id) AS prompt_count,
|
|
2351
|
+
(SELECT COUNT(*) FROM tool_events te WHERE te.session_id = s.session_id) AS tool_event_count
|
|
2352
|
+
FROM sessions s
|
|
2353
|
+
LEFT JOIN projects p ON p.id = s.project_id
|
|
2354
|
+
LEFT JOIN session_summaries ss ON ss.session_id = s.session_id
|
|
2355
|
+
WHERE 1 = 1${visibilityClause}
|
|
2356
|
+
ORDER BY COALESCE(s.completed_at_epoch, s.started_at_epoch, 0) DESC, s.id DESC
|
|
2357
|
+
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
2358
|
+
}
|
|
2359
|
+
insertUserPrompt(input) {
|
|
2360
|
+
const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
|
|
2361
|
+
const normalizedPrompt = input.prompt.trim();
|
|
2362
|
+
const promptHash = hashPrompt(normalizedPrompt);
|
|
2363
|
+
const latest = this.db.query(`SELECT * FROM user_prompts
|
|
2364
|
+
WHERE session_id = ?
|
|
2365
|
+
ORDER BY prompt_number DESC
|
|
2366
|
+
LIMIT 1`).get(input.session_id);
|
|
2367
|
+
if (latest && latest.prompt_hash === promptHash) {
|
|
2368
|
+
return latest;
|
|
2369
|
+
}
|
|
2370
|
+
const promptNumber = (latest?.prompt_number ?? 0) + 1;
|
|
2371
|
+
const result = this.db.query(`INSERT INTO user_prompts (
|
|
2372
|
+
session_id, project_id, prompt_number, prompt, prompt_hash, cwd,
|
|
2373
|
+
user_id, device_id, agent, created_at_epoch
|
|
2374
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.session_id, input.project_id, promptNumber, normalizedPrompt, promptHash, input.cwd ?? null, input.user_id, input.device_id, input.agent ?? "claude-code", createdAt);
|
|
2375
|
+
return this.getUserPromptById(Number(result.lastInsertRowid));
|
|
2376
|
+
}
|
|
2377
|
+
getUserPromptById(id) {
|
|
2378
|
+
return this.db.query("SELECT * FROM user_prompts WHERE id = ?").get(id) ?? null;
|
|
2379
|
+
}
|
|
2380
|
+
getRecentUserPrompts(projectId, limit = 10, userId) {
|
|
2381
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
2382
|
+
if (projectId !== null) {
|
|
2383
|
+
return this.db.query(`SELECT * FROM user_prompts
|
|
2384
|
+
WHERE project_id = ?${visibilityClause}
|
|
2385
|
+
ORDER BY created_at_epoch DESC, prompt_number DESC
|
|
2386
|
+
LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
|
|
2387
|
+
}
|
|
2388
|
+
return this.db.query(`SELECT * FROM user_prompts
|
|
2389
|
+
WHERE 1 = 1${visibilityClause}
|
|
2390
|
+
ORDER BY created_at_epoch DESC, prompt_number DESC
|
|
2391
|
+
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
2392
|
+
}
|
|
2393
|
+
getSessionUserPrompts(sessionId, limit = 20) {
|
|
2394
|
+
return this.db.query(`SELECT * FROM user_prompts
|
|
2395
|
+
WHERE session_id = ?
|
|
2396
|
+
ORDER BY prompt_number ASC
|
|
2397
|
+
LIMIT ?`).all(sessionId, limit);
|
|
2398
|
+
}
|
|
2399
|
+
insertToolEvent(input) {
|
|
2400
|
+
const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
|
|
2401
|
+
const result = this.db.query(`INSERT INTO tool_events (
|
|
2402
|
+
session_id, project_id, tool_name, tool_input_json, tool_response_preview,
|
|
2403
|
+
file_path, command, user_id, device_id, agent, created_at_epoch
|
|
2404
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.session_id, input.project_id, input.tool_name, input.tool_input_json ?? null, input.tool_response_preview ?? null, input.file_path ?? null, input.command ?? null, input.user_id, input.device_id, input.agent ?? "claude-code", createdAt);
|
|
2405
|
+
return this.getToolEventById(Number(result.lastInsertRowid));
|
|
2406
|
+
}
|
|
2407
|
+
getToolEventById(id) {
|
|
2408
|
+
return this.db.query("SELECT * FROM tool_events WHERE id = ?").get(id) ?? null;
|
|
2409
|
+
}
|
|
2410
|
+
getSessionToolEvents(sessionId, limit = 20) {
|
|
2411
|
+
return this.db.query(`SELECT * FROM tool_events
|
|
2412
|
+
WHERE session_id = ?
|
|
2413
|
+
ORDER BY created_at_epoch ASC, id ASC
|
|
2414
|
+
LIMIT ?`).all(sessionId, limit);
|
|
2415
|
+
}
|
|
2416
|
+
getRecentToolEvents(projectId, limit = 20, userId) {
|
|
2417
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
2418
|
+
if (projectId !== null) {
|
|
2419
|
+
return this.db.query(`SELECT * FROM tool_events
|
|
2420
|
+
WHERE project_id = ?${visibilityClause}
|
|
2421
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
2422
|
+
LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
|
|
2423
|
+
}
|
|
2424
|
+
return this.db.query(`SELECT * FROM tool_events
|
|
2425
|
+
WHERE 1 = 1${visibilityClause}
|
|
2426
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
2427
|
+
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
2428
|
+
}
|
|
2159
2429
|
addToOutbox(recordType, recordId) {
|
|
2160
2430
|
const now = Math.floor(Date.now() / 1000);
|
|
2161
2431
|
this.db.query(`INSERT INTO sync_outbox (record_type, record_id, created_at_epoch)
|
|
@@ -2326,6 +2596,9 @@ class MemDatabase {
|
|
|
2326
2596
|
this.db.query("INSERT OR REPLACE INTO packs_installed (name, installed_at, observation_count) VALUES (?, ?, ?)").run(name, now, observationCount);
|
|
2327
2597
|
}
|
|
2328
2598
|
}
|
|
2599
|
+
function hashPrompt(prompt) {
|
|
2600
|
+
return createHash3("sha256").update(prompt).digest("hex");
|
|
2601
|
+
}
|
|
2329
2602
|
|
|
2330
2603
|
// src/hooks/common.ts
|
|
2331
2604
|
var c = {
|
|
@@ -2491,6 +2764,15 @@ function formatSplashScreen(data) {
|
|
|
2491
2764
|
if (data.available > 0) {
|
|
2492
2765
|
statParts.push(`${c2.dim}${data.available.toLocaleString()} searchable${c2.reset}`);
|
|
2493
2766
|
}
|
|
2767
|
+
if (data.context.recentSessions && data.context.recentSessions.length > 0) {
|
|
2768
|
+
statParts.push(`${c2.white}${data.context.recentSessions.length} sessions${c2.reset}`);
|
|
2769
|
+
}
|
|
2770
|
+
if (data.context.recentPrompts && data.context.recentPrompts.length > 0) {
|
|
2771
|
+
statParts.push(`${c2.magenta}${data.context.recentPrompts.length} requests${c2.reset}`);
|
|
2772
|
+
}
|
|
2773
|
+
if (data.context.recentToolEvents && data.context.recentToolEvents.length > 0) {
|
|
2774
|
+
statParts.push(`${c2.yellow}${data.context.recentToolEvents.length} tools${c2.reset}`);
|
|
2775
|
+
}
|
|
2494
2776
|
if (data.synced > 0) {
|
|
2495
2777
|
statParts.push(`${c2.cyan}${data.synced} synced${c2.reset}`);
|
|
2496
2778
|
}
|
|
@@ -2523,9 +2805,13 @@ function formatVisibleStartupBrief(context) {
|
|
|
2523
2805
|
const lines = [];
|
|
2524
2806
|
const latest = pickBestSummary(context);
|
|
2525
2807
|
const observationFallbacks = buildObservationFallbacks(context);
|
|
2808
|
+
const promptFallback = buildPromptFallback(context);
|
|
2809
|
+
const toolFallbacks = buildToolFallbacks(context);
|
|
2810
|
+
const sessionFallbacks = buildSessionFallbacks(context);
|
|
2811
|
+
const projectSignals = buildProjectSignalLine(context);
|
|
2526
2812
|
if (latest) {
|
|
2527
2813
|
const sections = [
|
|
2528
|
-
["Request", chooseRequest(latest.request, observationFallbacks.request), 1],
|
|
2814
|
+
["Request", chooseRequest(latest.request, promptFallback ?? observationFallbacks.request), 1],
|
|
2529
2815
|
["Investigated", chooseSection(latest.investigated, observationFallbacks.investigated, "Investigated"), 2],
|
|
2530
2816
|
["Learned", latest.learned, 2],
|
|
2531
2817
|
["Completed", chooseSection(latest.completed, observationFallbacks.completed, "Completed"), 2],
|
|
@@ -2540,6 +2826,25 @@ function formatVisibleStartupBrief(context) {
|
|
|
2540
2826
|
}
|
|
2541
2827
|
}
|
|
2542
2828
|
}
|
|
2829
|
+
} else if (promptFallback) {
|
|
2830
|
+
lines.push(`${c2.cyan}Request:${c2.reset}`);
|
|
2831
|
+
lines.push(` - ${truncateInline(promptFallback, 140)}`);
|
|
2832
|
+
if (toolFallbacks.length > 0) {
|
|
2833
|
+
lines.push(`${c2.cyan}Recent Tools:${c2.reset}`);
|
|
2834
|
+
for (const item of toolFallbacks) {
|
|
2835
|
+
lines.push(` - ${truncateInline(item, 140)}`);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
if (sessionFallbacks.length > 0) {
|
|
2840
|
+
lines.push(`${c2.cyan}Recent Sessions:${c2.reset}`);
|
|
2841
|
+
for (const item of sessionFallbacks) {
|
|
2842
|
+
lines.push(` - ${truncateInline(item, 140)}`);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
if (projectSignals) {
|
|
2846
|
+
lines.push(`${c2.cyan}Project Signals:${c2.reset}`);
|
|
2847
|
+
lines.push(` - ${truncateInline(projectSignals, 140)}`);
|
|
2543
2848
|
}
|
|
2544
2849
|
const stale = pickRelevantStaleDecision(context, latest);
|
|
2545
2850
|
if (stale) {
|
|
@@ -2553,6 +2858,32 @@ function formatVisibleStartupBrief(context) {
|
|
|
2553
2858
|
}
|
|
2554
2859
|
return lines.slice(0, 10);
|
|
2555
2860
|
}
|
|
2861
|
+
function buildPromptFallback(context) {
|
|
2862
|
+
const latest = context.recentPrompts?.[0];
|
|
2863
|
+
if (!latest?.prompt)
|
|
2864
|
+
return null;
|
|
2865
|
+
return latest.prompt.replace(/\s+/g, " ").trim();
|
|
2866
|
+
}
|
|
2867
|
+
function buildToolFallbacks(context) {
|
|
2868
|
+
return (context.recentToolEvents ?? []).slice(0, 3).map((tool) => {
|
|
2869
|
+
const detail = tool.file_path ?? tool.command ?? tool.tool_response_preview ?? "";
|
|
2870
|
+
return `${tool.tool_name}: ${detail}`.trim();
|
|
2871
|
+
}).filter((item) => item.length > 0);
|
|
2872
|
+
}
|
|
2873
|
+
function buildSessionFallbacks(context) {
|
|
2874
|
+
return (context.recentSessions ?? []).slice(0, 2).map((session) => {
|
|
2875
|
+
const summary = session.request ?? session.completed ?? "";
|
|
2876
|
+
if (!summary)
|
|
2877
|
+
return "";
|
|
2878
|
+
return `${session.session_id}: ${summary}`;
|
|
2879
|
+
}).filter((item) => item.length > 0);
|
|
2880
|
+
}
|
|
2881
|
+
function buildProjectSignalLine(context) {
|
|
2882
|
+
if (!context.projectTypeCounts)
|
|
2883
|
+
return null;
|
|
2884
|
+
const top = Object.entries(context.projectTypeCounts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 4).map(([type, count]) => `${type} ${count}`).join("; ");
|
|
2885
|
+
return top || null;
|
|
2886
|
+
}
|
|
2556
2887
|
function toSplashLines(value, maxItems) {
|
|
2557
2888
|
if (!value)
|
|
2558
2889
|
return [];
|