engrm 0.4.22 → 0.4.23
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 +18 -0
- package/dist/cli.js +308 -24
- package/dist/hooks/elicitation-result.js +223 -15
- package/dist/hooks/post-tool-use.js +257 -15
- package/dist/hooks/pre-compact.js +962 -17
- package/dist/hooks/sentinel.js +223 -15
- package/dist/hooks/session-start.js +1066 -92
- package/dist/hooks/stop.js +361 -33
- package/dist/hooks/user-prompt-submit.js +267 -15
- package/dist/server.js +863 -49
- package/package.json +1 -1
|
@@ -421,7 +421,7 @@ var MIGRATIONS = [
|
|
|
421
421
|
-- Sync outbox (offline-first queue)
|
|
422
422
|
CREATE TABLE sync_outbox (
|
|
423
423
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
424
|
-
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary')),
|
|
424
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
425
425
|
record_id INTEGER NOT NULL,
|
|
426
426
|
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
427
427
|
'pending', 'syncing', 'synced', 'failed'
|
|
@@ -714,6 +714,18 @@ var MIGRATIONS = [
|
|
|
714
714
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
715
715
|
`
|
|
716
716
|
},
|
|
717
|
+
{
|
|
718
|
+
version: 11,
|
|
719
|
+
description: "Add observation provenance from tool and prompt chronology",
|
|
720
|
+
sql: `
|
|
721
|
+
ALTER TABLE observations ADD COLUMN source_tool TEXT;
|
|
722
|
+
ALTER TABLE observations ADD COLUMN source_prompt_number INTEGER;
|
|
723
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_tool
|
|
724
|
+
ON observations(source_tool, created_at_epoch DESC);
|
|
725
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_prompt
|
|
726
|
+
ON observations(session_id, source_prompt_number DESC);
|
|
727
|
+
`
|
|
728
|
+
},
|
|
717
729
|
{
|
|
718
730
|
version: 12,
|
|
719
731
|
description: "Add synced handoff metadata to session summaries",
|
|
@@ -725,15 +737,79 @@ var MIGRATIONS = [
|
|
|
725
737
|
`
|
|
726
738
|
},
|
|
727
739
|
{
|
|
728
|
-
version:
|
|
729
|
-
description: "Add
|
|
740
|
+
version: 13,
|
|
741
|
+
description: "Add current_thread to session summaries",
|
|
730
742
|
sql: `
|
|
731
|
-
ALTER TABLE
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
743
|
+
ALTER TABLE session_summaries ADD COLUMN current_thread TEXT;
|
|
744
|
+
`
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
version: 14,
|
|
748
|
+
description: "Add chat_messages lane for raw conversation recall",
|
|
749
|
+
sql: `
|
|
750
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
751
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
752
|
+
session_id TEXT NOT NULL,
|
|
753
|
+
project_id INTEGER REFERENCES projects(id),
|
|
754
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
755
|
+
content TEXT NOT NULL,
|
|
756
|
+
user_id TEXT NOT NULL,
|
|
757
|
+
device_id TEXT NOT NULL,
|
|
758
|
+
agent TEXT DEFAULT 'claude-code',
|
|
759
|
+
created_at_epoch INTEGER NOT NULL
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_session
|
|
763
|
+
ON chat_messages(session_id, created_at_epoch DESC, id DESC);
|
|
764
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_project
|
|
765
|
+
ON chat_messages(project_id, created_at_epoch DESC, id DESC);
|
|
766
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_created
|
|
767
|
+
ON chat_messages(created_at_epoch DESC, id DESC);
|
|
768
|
+
`
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
version: 15,
|
|
772
|
+
description: "Add remote_source_id for chat message sync deduplication",
|
|
773
|
+
sql: `
|
|
774
|
+
ALTER TABLE chat_messages ADD COLUMN remote_source_id TEXT;
|
|
775
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_messages_remote_source
|
|
776
|
+
ON chat_messages(remote_source_id)
|
|
777
|
+
WHERE remote_source_id IS NOT NULL;
|
|
778
|
+
`
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
version: 16,
|
|
782
|
+
description: "Allow chat_message records in sync_outbox",
|
|
783
|
+
sql: `
|
|
784
|
+
CREATE TABLE sync_outbox_new (
|
|
785
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
786
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
787
|
+
record_id INTEGER NOT NULL,
|
|
788
|
+
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
789
|
+
'pending', 'syncing', 'synced', 'failed'
|
|
790
|
+
)),
|
|
791
|
+
retry_count INTEGER DEFAULT 0,
|
|
792
|
+
max_retries INTEGER DEFAULT 10,
|
|
793
|
+
last_error TEXT,
|
|
794
|
+
created_at_epoch INTEGER NOT NULL,
|
|
795
|
+
synced_at_epoch INTEGER,
|
|
796
|
+
next_retry_epoch INTEGER
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
INSERT INTO sync_outbox_new (
|
|
800
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
801
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
802
|
+
)
|
|
803
|
+
SELECT
|
|
804
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
805
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
806
|
+
FROM sync_outbox;
|
|
807
|
+
|
|
808
|
+
DROP TABLE sync_outbox;
|
|
809
|
+
ALTER TABLE sync_outbox_new RENAME TO sync_outbox;
|
|
810
|
+
|
|
811
|
+
CREATE INDEX idx_outbox_status ON sync_outbox(status, next_retry_epoch);
|
|
812
|
+
CREATE INDEX idx_outbox_record ON sync_outbox(record_type, record_id);
|
|
737
813
|
`
|
|
738
814
|
}
|
|
739
815
|
];
|
|
@@ -793,6 +869,18 @@ function inferLegacySchemaVersion(db) {
|
|
|
793
869
|
if (columnExists(db, "session_summaries", "capture_state") && columnExists(db, "session_summaries", "recent_tool_names") && columnExists(db, "session_summaries", "hot_files") && columnExists(db, "session_summaries", "recent_outcomes")) {
|
|
794
870
|
version = Math.max(version, 12);
|
|
795
871
|
}
|
|
872
|
+
if (columnExists(db, "session_summaries", "current_thread")) {
|
|
873
|
+
version = Math.max(version, 13);
|
|
874
|
+
}
|
|
875
|
+
if (tableExists(db, "chat_messages")) {
|
|
876
|
+
version = Math.max(version, 14);
|
|
877
|
+
}
|
|
878
|
+
if (columnExists(db, "chat_messages", "remote_source_id")) {
|
|
879
|
+
version = Math.max(version, 15);
|
|
880
|
+
}
|
|
881
|
+
if (syncOutboxSupportsChatMessages(db)) {
|
|
882
|
+
version = Math.max(version, 16);
|
|
883
|
+
}
|
|
796
884
|
return version;
|
|
797
885
|
}
|
|
798
886
|
function runMigrations(db) {
|
|
@@ -876,7 +964,8 @@ function ensureSessionSummaryColumns(db) {
|
|
|
876
964
|
"capture_state",
|
|
877
965
|
"recent_tool_names",
|
|
878
966
|
"hot_files",
|
|
879
|
-
"recent_outcomes"
|
|
967
|
+
"recent_outcomes",
|
|
968
|
+
"current_thread"
|
|
880
969
|
];
|
|
881
970
|
for (const column of required) {
|
|
882
971
|
if (columnExists(db, "session_summaries", column))
|
|
@@ -884,10 +973,75 @@ function ensureSessionSummaryColumns(db) {
|
|
|
884
973
|
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
885
974
|
}
|
|
886
975
|
const current = getSchemaVersion(db);
|
|
887
|
-
if (current <
|
|
888
|
-
db.exec("PRAGMA user_version =
|
|
976
|
+
if (current < 13) {
|
|
977
|
+
db.exec("PRAGMA user_version = 13");
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function ensureChatMessageColumns(db) {
|
|
981
|
+
if (!tableExists(db, "chat_messages"))
|
|
982
|
+
return;
|
|
983
|
+
if (!columnExists(db, "chat_messages", "remote_source_id")) {
|
|
984
|
+
db.exec("ALTER TABLE chat_messages ADD COLUMN remote_source_id TEXT");
|
|
985
|
+
}
|
|
986
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_messages_remote_source ON chat_messages(remote_source_id) WHERE remote_source_id IS NOT NULL");
|
|
987
|
+
const current = getSchemaVersion(db);
|
|
988
|
+
if (current < 15) {
|
|
989
|
+
db.exec("PRAGMA user_version = 15");
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
993
|
+
if (syncOutboxSupportsChatMessages(db)) {
|
|
994
|
+
const current = getSchemaVersion(db);
|
|
995
|
+
if (current < 16) {
|
|
996
|
+
db.exec("PRAGMA user_version = 16");
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
db.exec("BEGIN TRANSACTION");
|
|
1001
|
+
try {
|
|
1002
|
+
db.exec(`
|
|
1003
|
+
CREATE TABLE sync_outbox_new (
|
|
1004
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1005
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
1006
|
+
record_id INTEGER NOT NULL,
|
|
1007
|
+
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
1008
|
+
'pending', 'syncing', 'synced', 'failed'
|
|
1009
|
+
)),
|
|
1010
|
+
retry_count INTEGER DEFAULT 0,
|
|
1011
|
+
max_retries INTEGER DEFAULT 10,
|
|
1012
|
+
last_error TEXT,
|
|
1013
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1014
|
+
synced_at_epoch INTEGER,
|
|
1015
|
+
next_retry_epoch INTEGER
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
INSERT INTO sync_outbox_new (
|
|
1019
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
1020
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
1021
|
+
)
|
|
1022
|
+
SELECT
|
|
1023
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
1024
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
1025
|
+
FROM sync_outbox;
|
|
1026
|
+
|
|
1027
|
+
DROP TABLE sync_outbox;
|
|
1028
|
+
ALTER TABLE sync_outbox_new RENAME TO sync_outbox;
|
|
1029
|
+
|
|
1030
|
+
CREATE INDEX idx_outbox_status ON sync_outbox(status, next_retry_epoch);
|
|
1031
|
+
CREATE INDEX idx_outbox_record ON sync_outbox(record_type, record_id);
|
|
1032
|
+
`);
|
|
1033
|
+
db.exec("PRAGMA user_version = 16");
|
|
1034
|
+
db.exec("COMMIT");
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
db.exec("ROLLBACK");
|
|
1037
|
+
throw new Error(`sync_outbox repair failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
889
1038
|
}
|
|
890
1039
|
}
|
|
1040
|
+
function syncOutboxSupportsChatMessages(db) {
|
|
1041
|
+
const row = db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get("sync_outbox");
|
|
1042
|
+
const sql = row?.sql ?? "";
|
|
1043
|
+
return sql.includes("'chat_message'");
|
|
1044
|
+
}
|
|
891
1045
|
function getSchemaVersion(db) {
|
|
892
1046
|
const result = db.query("PRAGMA user_version").get();
|
|
893
1047
|
return result.user_version;
|
|
@@ -1047,6 +1201,8 @@ class MemDatabase {
|
|
|
1047
1201
|
runMigrations(this.db);
|
|
1048
1202
|
ensureObservationTypes(this.db);
|
|
1049
1203
|
ensureSessionSummaryColumns(this.db);
|
|
1204
|
+
ensureChatMessageColumns(this.db);
|
|
1205
|
+
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
1050
1206
|
}
|
|
1051
1207
|
loadVecExtension() {
|
|
1052
1208
|
try {
|
|
@@ -1272,6 +1428,7 @@ class MemDatabase {
|
|
|
1272
1428
|
p.name AS project_name,
|
|
1273
1429
|
ss.request AS request,
|
|
1274
1430
|
ss.completed AS completed,
|
|
1431
|
+
ss.current_thread AS current_thread,
|
|
1275
1432
|
ss.capture_state AS capture_state,
|
|
1276
1433
|
ss.recent_tool_names AS recent_tool_names,
|
|
1277
1434
|
ss.hot_files AS hot_files,
|
|
@@ -1290,6 +1447,7 @@ class MemDatabase {
|
|
|
1290
1447
|
p.name AS project_name,
|
|
1291
1448
|
ss.request AS request,
|
|
1292
1449
|
ss.completed AS completed,
|
|
1450
|
+
ss.current_thread AS current_thread,
|
|
1293
1451
|
ss.capture_state AS capture_state,
|
|
1294
1452
|
ss.recent_tool_names AS recent_tool_names,
|
|
1295
1453
|
ss.hot_files AS hot_files,
|
|
@@ -1380,6 +1538,54 @@ class MemDatabase {
|
|
|
1380
1538
|
ORDER BY created_at_epoch DESC, id DESC
|
|
1381
1539
|
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
1382
1540
|
}
|
|
1541
|
+
insertChatMessage(input) {
|
|
1542
|
+
const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
|
|
1543
|
+
const content = input.content.trim();
|
|
1544
|
+
const result = this.db.query(`INSERT INTO chat_messages (
|
|
1545
|
+
session_id, project_id, role, content, user_id, device_id, agent, created_at_epoch, remote_source_id
|
|
1546
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.session_id, input.project_id, input.role, content, input.user_id, input.device_id, input.agent ?? "claude-code", createdAt, input.remote_source_id ?? null);
|
|
1547
|
+
return this.getChatMessageById(Number(result.lastInsertRowid));
|
|
1548
|
+
}
|
|
1549
|
+
getChatMessageById(id) {
|
|
1550
|
+
return this.db.query("SELECT * FROM chat_messages WHERE id = ?").get(id) ?? null;
|
|
1551
|
+
}
|
|
1552
|
+
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
1553
|
+
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
1554
|
+
}
|
|
1555
|
+
getSessionChatMessages(sessionId, limit = 50) {
|
|
1556
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1557
|
+
WHERE session_id = ?
|
|
1558
|
+
ORDER BY created_at_epoch ASC, id ASC
|
|
1559
|
+
LIMIT ?`).all(sessionId, limit);
|
|
1560
|
+
}
|
|
1561
|
+
getRecentChatMessages(projectId, limit = 20, userId) {
|
|
1562
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
1563
|
+
if (projectId !== null) {
|
|
1564
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1565
|
+
WHERE project_id = ?${visibilityClause}
|
|
1566
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1567
|
+
LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
|
|
1568
|
+
}
|
|
1569
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1570
|
+
WHERE 1 = 1${visibilityClause}
|
|
1571
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1572
|
+
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
1573
|
+
}
|
|
1574
|
+
searchChatMessages(query, projectId, limit = 20, userId) {
|
|
1575
|
+
const needle = `%${query.toLowerCase()}%`;
|
|
1576
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
1577
|
+
if (projectId !== null) {
|
|
1578
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1579
|
+
WHERE project_id = ?
|
|
1580
|
+
AND lower(content) LIKE ?${visibilityClause}
|
|
1581
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1582
|
+
LIMIT ?`).all(projectId, needle, ...userId ? [userId] : [], limit);
|
|
1583
|
+
}
|
|
1584
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1585
|
+
WHERE lower(content) LIKE ?${visibilityClause}
|
|
1586
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1587
|
+
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
1588
|
+
}
|
|
1383
1589
|
addToOutbox(recordType, recordId) {
|
|
1384
1590
|
const now = Math.floor(Date.now() / 1000);
|
|
1385
1591
|
this.db.query(`INSERT INTO sync_outbox (record_type, record_id, created_at_epoch)
|
|
@@ -1468,9 +1674,9 @@ class MemDatabase {
|
|
|
1468
1674
|
};
|
|
1469
1675
|
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1470
1676
|
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1471
|
-
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1677
|
+
current_thread, capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1472
1678
|
)
|
|
1473
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1679
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, summary.current_thread ?? null, summary.capture_state ?? null, summary.recent_tool_names ?? null, summary.hot_files ?? null, summary.recent_outcomes ?? null, now);
|
|
1474
1680
|
const id = Number(result.lastInsertRowid);
|
|
1475
1681
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1476
1682
|
}
|
|
@@ -1486,6 +1692,7 @@ class MemDatabase {
|
|
|
1486
1692
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1487
1693
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1488
1694
|
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1695
|
+
current_thread: summary.current_thread ?? existing.current_thread,
|
|
1489
1696
|
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1490
1697
|
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1491
1698
|
hot_files: summary.hot_files ?? existing.hot_files,
|
|
@@ -1499,12 +1706,13 @@ class MemDatabase {
|
|
|
1499
1706
|
learned = ?,
|
|
1500
1707
|
completed = ?,
|
|
1501
1708
|
next_steps = ?,
|
|
1709
|
+
current_thread = ?,
|
|
1502
1710
|
capture_state = ?,
|
|
1503
1711
|
recent_tool_names = ?,
|
|
1504
1712
|
hot_files = ?,
|
|
1505
1713
|
recent_outcomes = ?,
|
|
1506
1714
|
created_at_epoch = ?
|
|
1507
|
-
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1715
|
+
WHERE session_id = ?`).run(summary.project_id ?? existing.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, normalized.current_thread, normalized.capture_state, normalized.recent_tool_names, normalized.hot_files, normalized.recent_outcomes, now, summary.session_id);
|
|
1508
1716
|
return this.getSessionSummary(summary.session_id);
|
|
1509
1717
|
}
|
|
1510
1718
|
getSessionSummary(sessionId) {
|
|
@@ -1670,11 +1878,13 @@ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
|
1670
1878
|
return acc;
|
|
1671
1879
|
}, new Map).entries()).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 6);
|
|
1672
1880
|
const latestObservationPromptNumber = observations.map((obs) => obs.source_prompt_number).filter((value) => typeof value === "number").sort((a, b) => b - a)[0] ?? null;
|
|
1881
|
+
const currentThread = buildCurrentThread(latestRequest, recentOutcomes, hotFiles, recentToolNames);
|
|
1673
1882
|
return {
|
|
1674
1883
|
prompt_count: prompts.length,
|
|
1675
1884
|
tool_event_count: toolEvents.length,
|
|
1676
1885
|
recent_request_prompts: recentRequestPrompts,
|
|
1677
1886
|
latest_request: latestRequest,
|
|
1887
|
+
current_thread: currentThread,
|
|
1678
1888
|
recent_tool_names: recentToolNames,
|
|
1679
1889
|
recent_tool_commands: recentToolCommands,
|
|
1680
1890
|
capture_state: captureState,
|
|
@@ -1684,6 +1894,37 @@ function buildSessionHandoffMetadata(prompts, toolEvents, observations) {
|
|
|
1684
1894
|
latest_observation_prompt_number: latestObservationPromptNumber
|
|
1685
1895
|
};
|
|
1686
1896
|
}
|
|
1897
|
+
function buildCurrentThread(latestRequest, recentOutcomes, hotFiles, recentToolNames) {
|
|
1898
|
+
const request = compactLine(latestRequest);
|
|
1899
|
+
const outcome = recentOutcomes.map((item) => compactLine(item)).find(Boolean);
|
|
1900
|
+
const file = hotFiles[0] ? compactFileHint(hotFiles[0]) : null;
|
|
1901
|
+
const tools = recentToolNames.slice(0, 2).join("/");
|
|
1902
|
+
if (outcome && file) {
|
|
1903
|
+
return `${outcome} · ${file}${tools ? ` · ${tools}` : ""}`;
|
|
1904
|
+
}
|
|
1905
|
+
if (request && file) {
|
|
1906
|
+
return `${request} · ${file}${tools ? ` · ${tools}` : ""}`;
|
|
1907
|
+
}
|
|
1908
|
+
if (outcome) {
|
|
1909
|
+
return `${outcome}${tools ? ` · ${tools}` : ""}`;
|
|
1910
|
+
}
|
|
1911
|
+
if (request) {
|
|
1912
|
+
return `${request}${tools ? ` · ${tools}` : ""}`;
|
|
1913
|
+
}
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
function compactLine(value) {
|
|
1917
|
+
const trimmed = value?.replace(/\s+/g, " ").trim();
|
|
1918
|
+
if (!trimmed)
|
|
1919
|
+
return null;
|
|
1920
|
+
return trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed;
|
|
1921
|
+
}
|
|
1922
|
+
function compactFileHint(value) {
|
|
1923
|
+
const parts = value.split("/");
|
|
1924
|
+
if (parts.length <= 2)
|
|
1925
|
+
return value;
|
|
1926
|
+
return parts.slice(-2).join("/");
|
|
1927
|
+
}
|
|
1687
1928
|
function parseJsonArray(value) {
|
|
1688
1929
|
if (!value)
|
|
1689
1930
|
return [];
|
|
@@ -1722,6 +1963,16 @@ async function main() {
|
|
|
1722
1963
|
device_id: config.device_id,
|
|
1723
1964
|
agent: "claude-code"
|
|
1724
1965
|
});
|
|
1966
|
+
const chatMessage = db.insertChatMessage({
|
|
1967
|
+
session_id: event.session_id,
|
|
1968
|
+
project_id: project.id,
|
|
1969
|
+
role: "user",
|
|
1970
|
+
content: event.prompt,
|
|
1971
|
+
user_id: config.user_id,
|
|
1972
|
+
device_id: config.device_id,
|
|
1973
|
+
agent: "claude-code"
|
|
1974
|
+
});
|
|
1975
|
+
db.addToOutbox("chat_message", chatMessage.id);
|
|
1725
1976
|
const compactPrompt = event.prompt.replace(/\s+/g, " ").trim();
|
|
1726
1977
|
if (compactPrompt.length >= 8) {
|
|
1727
1978
|
const sessionPrompts = db.getSessionUserPrompts(event.session_id, 20);
|
|
@@ -1737,6 +1988,7 @@ async function main() {
|
|
|
1737
1988
|
learned: null,
|
|
1738
1989
|
completed: null,
|
|
1739
1990
|
next_steps: null,
|
|
1991
|
+
current_thread: handoff.current_thread,
|
|
1740
1992
|
capture_state: handoff.capture_state,
|
|
1741
1993
|
recent_tool_names: JSON.stringify(handoff.recent_tool_names),
|
|
1742
1994
|
hot_files: JSON.stringify(handoff.hot_files),
|