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
package/README.md
CHANGED
|
@@ -221,6 +221,9 @@ The MCP server exposes tools that supported agents can call directly:
|
|
|
221
221
|
| `recent_tools` | Inspect captured raw tool chronology |
|
|
222
222
|
| `recent_sessions` | List recent local sessions to inspect further |
|
|
223
223
|
| `session_story` | Show prompts, tools, observations, and summary for one session |
|
|
224
|
+
| `create_handoff` | Save an explicit syncable handoff so you can resume work on another device |
|
|
225
|
+
| `recent_handoffs` | List recent saved handoffs for the current project or workspace |
|
|
226
|
+
| `load_handoff` | Open a saved handoff as a resume point for a new session |
|
|
224
227
|
| `plugin_catalog` | Inspect Engrm plugin manifests for memory-aware integrations |
|
|
225
228
|
| `save_plugin_memory` | Save reduced plugin output with stable Engrm provenance |
|
|
226
229
|
| `capture_git_diff` | Reduce a git diff into a durable memory object and save it |
|
|
@@ -273,6 +276,21 @@ These tools are intentionally small:
|
|
|
273
276
|
- reduced durable memory output
|
|
274
277
|
- visible in Engrm's local inspection tools so we can judge tool value honestly
|
|
275
278
|
|
|
279
|
+
### Explicit Handoffs
|
|
280
|
+
|
|
281
|
+
For long-running work across devices, Engrm now has an explicit handoff flow:
|
|
282
|
+
|
|
283
|
+
- `create_handoff`
|
|
284
|
+
- snapshot the active thread into a syncable handoff message
|
|
285
|
+
- `recent_handoffs`
|
|
286
|
+
- list the latest saved handoffs
|
|
287
|
+
- `load_handoff`
|
|
288
|
+
- reopen a saved handoff as a clear resume point in a new session
|
|
289
|
+
|
|
290
|
+
This is the deliberate version of multi-device continuity: useful when you want to move from laptop to home machine without waiting for an end-of-session summary.
|
|
291
|
+
|
|
292
|
+
The separate chat lane is still kept distinct from durable observations, but it can now sync too, so recent user/assistant conversation is recoverable on another machine without polluting the main memory feed.
|
|
293
|
+
|
|
276
294
|
### Local Memory Inspection
|
|
277
295
|
|
|
278
296
|
For local testing, Engrm now exposes a small inspection set that lets you see
|
package/dist/cli.js
CHANGED
|
@@ -305,7 +305,7 @@ var MIGRATIONS = [
|
|
|
305
305
|
-- Sync outbox (offline-first queue)
|
|
306
306
|
CREATE TABLE sync_outbox (
|
|
307
307
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
308
|
-
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary')),
|
|
308
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
309
309
|
record_id INTEGER NOT NULL,
|
|
310
310
|
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
311
311
|
'pending', 'syncing', 'synced', 'failed'
|
|
@@ -598,6 +598,18 @@ var MIGRATIONS = [
|
|
|
598
598
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
599
599
|
`
|
|
600
600
|
},
|
|
601
|
+
{
|
|
602
|
+
version: 11,
|
|
603
|
+
description: "Add observation provenance from tool and prompt chronology",
|
|
604
|
+
sql: `
|
|
605
|
+
ALTER TABLE observations ADD COLUMN source_tool TEXT;
|
|
606
|
+
ALTER TABLE observations ADD COLUMN source_prompt_number INTEGER;
|
|
607
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_tool
|
|
608
|
+
ON observations(source_tool, created_at_epoch DESC);
|
|
609
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_prompt
|
|
610
|
+
ON observations(session_id, source_prompt_number DESC);
|
|
611
|
+
`
|
|
612
|
+
},
|
|
601
613
|
{
|
|
602
614
|
version: 12,
|
|
603
615
|
description: "Add synced handoff metadata to session summaries",
|
|
@@ -609,15 +621,79 @@ var MIGRATIONS = [
|
|
|
609
621
|
`
|
|
610
622
|
},
|
|
611
623
|
{
|
|
612
|
-
version:
|
|
613
|
-
description: "Add
|
|
624
|
+
version: 13,
|
|
625
|
+
description: "Add current_thread to session summaries",
|
|
614
626
|
sql: `
|
|
615
|
-
ALTER TABLE
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
627
|
+
ALTER TABLE session_summaries ADD COLUMN current_thread TEXT;
|
|
628
|
+
`
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
version: 14,
|
|
632
|
+
description: "Add chat_messages lane for raw conversation recall",
|
|
633
|
+
sql: `
|
|
634
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
635
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
636
|
+
session_id TEXT NOT NULL,
|
|
637
|
+
project_id INTEGER REFERENCES projects(id),
|
|
638
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
639
|
+
content TEXT NOT NULL,
|
|
640
|
+
user_id TEXT NOT NULL,
|
|
641
|
+
device_id TEXT NOT NULL,
|
|
642
|
+
agent TEXT DEFAULT 'claude-code',
|
|
643
|
+
created_at_epoch INTEGER NOT NULL
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_session
|
|
647
|
+
ON chat_messages(session_id, created_at_epoch DESC, id DESC);
|
|
648
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_project
|
|
649
|
+
ON chat_messages(project_id, created_at_epoch DESC, id DESC);
|
|
650
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_created
|
|
651
|
+
ON chat_messages(created_at_epoch DESC, id DESC);
|
|
652
|
+
`
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
version: 15,
|
|
656
|
+
description: "Add remote_source_id for chat message sync deduplication",
|
|
657
|
+
sql: `
|
|
658
|
+
ALTER TABLE chat_messages ADD COLUMN remote_source_id TEXT;
|
|
659
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_messages_remote_source
|
|
660
|
+
ON chat_messages(remote_source_id)
|
|
661
|
+
WHERE remote_source_id IS NOT NULL;
|
|
662
|
+
`
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
version: 16,
|
|
666
|
+
description: "Allow chat_message records in sync_outbox",
|
|
667
|
+
sql: `
|
|
668
|
+
CREATE TABLE sync_outbox_new (
|
|
669
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
670
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
671
|
+
record_id INTEGER NOT NULL,
|
|
672
|
+
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
673
|
+
'pending', 'syncing', 'synced', 'failed'
|
|
674
|
+
)),
|
|
675
|
+
retry_count INTEGER DEFAULT 0,
|
|
676
|
+
max_retries INTEGER DEFAULT 10,
|
|
677
|
+
last_error TEXT,
|
|
678
|
+
created_at_epoch INTEGER NOT NULL,
|
|
679
|
+
synced_at_epoch INTEGER,
|
|
680
|
+
next_retry_epoch INTEGER
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
INSERT INTO sync_outbox_new (
|
|
684
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
685
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
686
|
+
)
|
|
687
|
+
SELECT
|
|
688
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
689
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
690
|
+
FROM sync_outbox;
|
|
691
|
+
|
|
692
|
+
DROP TABLE sync_outbox;
|
|
693
|
+
ALTER TABLE sync_outbox_new RENAME TO sync_outbox;
|
|
694
|
+
|
|
695
|
+
CREATE INDEX idx_outbox_status ON sync_outbox(status, next_retry_epoch);
|
|
696
|
+
CREATE INDEX idx_outbox_record ON sync_outbox(record_type, record_id);
|
|
621
697
|
`
|
|
622
698
|
}
|
|
623
699
|
];
|
|
@@ -677,6 +753,18 @@ function inferLegacySchemaVersion(db) {
|
|
|
677
753
|
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")) {
|
|
678
754
|
version = Math.max(version, 12);
|
|
679
755
|
}
|
|
756
|
+
if (columnExists(db, "session_summaries", "current_thread")) {
|
|
757
|
+
version = Math.max(version, 13);
|
|
758
|
+
}
|
|
759
|
+
if (tableExists(db, "chat_messages")) {
|
|
760
|
+
version = Math.max(version, 14);
|
|
761
|
+
}
|
|
762
|
+
if (columnExists(db, "chat_messages", "remote_source_id")) {
|
|
763
|
+
version = Math.max(version, 15);
|
|
764
|
+
}
|
|
765
|
+
if (syncOutboxSupportsChatMessages(db)) {
|
|
766
|
+
version = Math.max(version, 16);
|
|
767
|
+
}
|
|
680
768
|
return version;
|
|
681
769
|
}
|
|
682
770
|
function runMigrations(db) {
|
|
@@ -760,7 +848,8 @@ function ensureSessionSummaryColumns(db) {
|
|
|
760
848
|
"capture_state",
|
|
761
849
|
"recent_tool_names",
|
|
762
850
|
"hot_files",
|
|
763
|
-
"recent_outcomes"
|
|
851
|
+
"recent_outcomes",
|
|
852
|
+
"current_thread"
|
|
764
853
|
];
|
|
765
854
|
for (const column of required) {
|
|
766
855
|
if (columnExists(db, "session_summaries", column))
|
|
@@ -768,10 +857,75 @@ function ensureSessionSummaryColumns(db) {
|
|
|
768
857
|
db.exec(`ALTER TABLE session_summaries ADD COLUMN ${column} TEXT`);
|
|
769
858
|
}
|
|
770
859
|
const current = getSchemaVersion(db);
|
|
771
|
-
if (current <
|
|
772
|
-
db.exec("PRAGMA user_version =
|
|
860
|
+
if (current < 13) {
|
|
861
|
+
db.exec("PRAGMA user_version = 13");
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function ensureChatMessageColumns(db) {
|
|
865
|
+
if (!tableExists(db, "chat_messages"))
|
|
866
|
+
return;
|
|
867
|
+
if (!columnExists(db, "chat_messages", "remote_source_id")) {
|
|
868
|
+
db.exec("ALTER TABLE chat_messages ADD COLUMN remote_source_id TEXT");
|
|
869
|
+
}
|
|
870
|
+
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");
|
|
871
|
+
const current = getSchemaVersion(db);
|
|
872
|
+
if (current < 15) {
|
|
873
|
+
db.exec("PRAGMA user_version = 15");
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function ensureSyncOutboxSupportsChatMessages(db) {
|
|
877
|
+
if (syncOutboxSupportsChatMessages(db)) {
|
|
878
|
+
const current = getSchemaVersion(db);
|
|
879
|
+
if (current < 16) {
|
|
880
|
+
db.exec("PRAGMA user_version = 16");
|
|
881
|
+
}
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
db.exec("BEGIN TRANSACTION");
|
|
885
|
+
try {
|
|
886
|
+
db.exec(`
|
|
887
|
+
CREATE TABLE sync_outbox_new (
|
|
888
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
889
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
890
|
+
record_id INTEGER NOT NULL,
|
|
891
|
+
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
892
|
+
'pending', 'syncing', 'synced', 'failed'
|
|
893
|
+
)),
|
|
894
|
+
retry_count INTEGER DEFAULT 0,
|
|
895
|
+
max_retries INTEGER DEFAULT 10,
|
|
896
|
+
last_error TEXT,
|
|
897
|
+
created_at_epoch INTEGER NOT NULL,
|
|
898
|
+
synced_at_epoch INTEGER,
|
|
899
|
+
next_retry_epoch INTEGER
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
INSERT INTO sync_outbox_new (
|
|
903
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
904
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
905
|
+
)
|
|
906
|
+
SELECT
|
|
907
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
908
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
909
|
+
FROM sync_outbox;
|
|
910
|
+
|
|
911
|
+
DROP TABLE sync_outbox;
|
|
912
|
+
ALTER TABLE sync_outbox_new RENAME TO sync_outbox;
|
|
913
|
+
|
|
914
|
+
CREATE INDEX idx_outbox_status ON sync_outbox(status, next_retry_epoch);
|
|
915
|
+
CREATE INDEX idx_outbox_record ON sync_outbox(record_type, record_id);
|
|
916
|
+
`);
|
|
917
|
+
db.exec("PRAGMA user_version = 16");
|
|
918
|
+
db.exec("COMMIT");
|
|
919
|
+
} catch (error) {
|
|
920
|
+
db.exec("ROLLBACK");
|
|
921
|
+
throw new Error(`sync_outbox repair failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
773
922
|
}
|
|
774
923
|
}
|
|
924
|
+
function syncOutboxSupportsChatMessages(db) {
|
|
925
|
+
const row = db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get("sync_outbox");
|
|
926
|
+
const sql = row?.sql ?? "";
|
|
927
|
+
return sql.includes("'chat_message'");
|
|
928
|
+
}
|
|
775
929
|
function getSchemaVersion(db) {
|
|
776
930
|
const result = db.query("PRAGMA user_version").get();
|
|
777
931
|
return result.user_version;
|
|
@@ -931,6 +1085,8 @@ class MemDatabase {
|
|
|
931
1085
|
runMigrations(this.db);
|
|
932
1086
|
ensureObservationTypes(this.db);
|
|
933
1087
|
ensureSessionSummaryColumns(this.db);
|
|
1088
|
+
ensureChatMessageColumns(this.db);
|
|
1089
|
+
ensureSyncOutboxSupportsChatMessages(this.db);
|
|
934
1090
|
}
|
|
935
1091
|
loadVecExtension() {
|
|
936
1092
|
try {
|
|
@@ -1156,6 +1312,7 @@ class MemDatabase {
|
|
|
1156
1312
|
p.name AS project_name,
|
|
1157
1313
|
ss.request AS request,
|
|
1158
1314
|
ss.completed AS completed,
|
|
1315
|
+
ss.current_thread AS current_thread,
|
|
1159
1316
|
ss.capture_state AS capture_state,
|
|
1160
1317
|
ss.recent_tool_names AS recent_tool_names,
|
|
1161
1318
|
ss.hot_files AS hot_files,
|
|
@@ -1174,6 +1331,7 @@ class MemDatabase {
|
|
|
1174
1331
|
p.name AS project_name,
|
|
1175
1332
|
ss.request AS request,
|
|
1176
1333
|
ss.completed AS completed,
|
|
1334
|
+
ss.current_thread AS current_thread,
|
|
1177
1335
|
ss.capture_state AS capture_state,
|
|
1178
1336
|
ss.recent_tool_names AS recent_tool_names,
|
|
1179
1337
|
ss.hot_files AS hot_files,
|
|
@@ -1264,6 +1422,54 @@ class MemDatabase {
|
|
|
1264
1422
|
ORDER BY created_at_epoch DESC, id DESC
|
|
1265
1423
|
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
1266
1424
|
}
|
|
1425
|
+
insertChatMessage(input) {
|
|
1426
|
+
const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
|
|
1427
|
+
const content = input.content.trim();
|
|
1428
|
+
const result = this.db.query(`INSERT INTO chat_messages (
|
|
1429
|
+
session_id, project_id, role, content, user_id, device_id, agent, created_at_epoch, remote_source_id
|
|
1430
|
+
) 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);
|
|
1431
|
+
return this.getChatMessageById(Number(result.lastInsertRowid));
|
|
1432
|
+
}
|
|
1433
|
+
getChatMessageById(id) {
|
|
1434
|
+
return this.db.query("SELECT * FROM chat_messages WHERE id = ?").get(id) ?? null;
|
|
1435
|
+
}
|
|
1436
|
+
getChatMessageByRemoteSourceId(remoteSourceId) {
|
|
1437
|
+
return this.db.query("SELECT * FROM chat_messages WHERE remote_source_id = ?").get(remoteSourceId) ?? null;
|
|
1438
|
+
}
|
|
1439
|
+
getSessionChatMessages(sessionId, limit = 50) {
|
|
1440
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1441
|
+
WHERE session_id = ?
|
|
1442
|
+
ORDER BY created_at_epoch ASC, id ASC
|
|
1443
|
+
LIMIT ?`).all(sessionId, limit);
|
|
1444
|
+
}
|
|
1445
|
+
getRecentChatMessages(projectId, limit = 20, userId) {
|
|
1446
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
1447
|
+
if (projectId !== null) {
|
|
1448
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1449
|
+
WHERE project_id = ?${visibilityClause}
|
|
1450
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1451
|
+
LIMIT ?`).all(projectId, ...userId ? [userId] : [], limit);
|
|
1452
|
+
}
|
|
1453
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1454
|
+
WHERE 1 = 1${visibilityClause}
|
|
1455
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1456
|
+
LIMIT ?`).all(...userId ? [userId] : [], limit);
|
|
1457
|
+
}
|
|
1458
|
+
searchChatMessages(query, projectId, limit = 20, userId) {
|
|
1459
|
+
const needle = `%${query.toLowerCase()}%`;
|
|
1460
|
+
const visibilityClause = userId ? " AND user_id = ?" : "";
|
|
1461
|
+
if (projectId !== null) {
|
|
1462
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1463
|
+
WHERE project_id = ?
|
|
1464
|
+
AND lower(content) LIKE ?${visibilityClause}
|
|
1465
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1466
|
+
LIMIT ?`).all(projectId, needle, ...userId ? [userId] : [], limit);
|
|
1467
|
+
}
|
|
1468
|
+
return this.db.query(`SELECT * FROM chat_messages
|
|
1469
|
+
WHERE lower(content) LIKE ?${visibilityClause}
|
|
1470
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
1471
|
+
LIMIT ?`).all(needle, ...userId ? [userId] : [], limit);
|
|
1472
|
+
}
|
|
1267
1473
|
addToOutbox(recordType, recordId) {
|
|
1268
1474
|
const now = Math.floor(Date.now() / 1000);
|
|
1269
1475
|
this.db.query(`INSERT INTO sync_outbox (record_type, record_id, created_at_epoch)
|
|
@@ -1352,9 +1558,9 @@ class MemDatabase {
|
|
|
1352
1558
|
};
|
|
1353
1559
|
const result = this.db.query(`INSERT INTO session_summaries (
|
|
1354
1560
|
session_id, project_id, user_id, request, investigated, learned, completed, next_steps,
|
|
1355
|
-
capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1561
|
+
current_thread, capture_state, recent_tool_names, hot_files, recent_outcomes, created_at_epoch
|
|
1356
1562
|
)
|
|
1357
|
-
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);
|
|
1563
|
+
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);
|
|
1358
1564
|
const id = Number(result.lastInsertRowid);
|
|
1359
1565
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1360
1566
|
}
|
|
@@ -1370,6 +1576,7 @@ class MemDatabase {
|
|
|
1370
1576
|
learned: normalizeSummarySection(summary.learned ?? existing.learned),
|
|
1371
1577
|
completed: normalizeSummarySection(summary.completed ?? existing.completed),
|
|
1372
1578
|
next_steps: normalizeSummarySection(summary.next_steps ?? existing.next_steps),
|
|
1579
|
+
current_thread: summary.current_thread ?? existing.current_thread,
|
|
1373
1580
|
capture_state: summary.capture_state ?? existing.capture_state,
|
|
1374
1581
|
recent_tool_names: summary.recent_tool_names ?? existing.recent_tool_names,
|
|
1375
1582
|
hot_files: summary.hot_files ?? existing.hot_files,
|
|
@@ -1383,12 +1590,13 @@ class MemDatabase {
|
|
|
1383
1590
|
learned = ?,
|
|
1384
1591
|
completed = ?,
|
|
1385
1592
|
next_steps = ?,
|
|
1593
|
+
current_thread = ?,
|
|
1386
1594
|
capture_state = ?,
|
|
1387
1595
|
recent_tool_names = ?,
|
|
1388
1596
|
hot_files = ?,
|
|
1389
1597
|
recent_outcomes = ?,
|
|
1390
1598
|
created_at_epoch = ?
|
|
1391
|
-
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);
|
|
1599
|
+
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);
|
|
1392
1600
|
return this.getSessionSummary(summary.session_id);
|
|
1393
1601
|
}
|
|
1394
1602
|
getSessionSummary(sessionId) {
|
|
@@ -1600,7 +1808,7 @@ var MIGRATIONS2 = [
|
|
|
1600
1808
|
-- Sync outbox (offline-first queue)
|
|
1601
1809
|
CREATE TABLE sync_outbox (
|
|
1602
1810
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1603
|
-
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary')),
|
|
1811
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
1604
1812
|
record_id INTEGER NOT NULL,
|
|
1605
1813
|
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
1606
1814
|
'pending', 'syncing', 'synced', 'failed'
|
|
@@ -1893,6 +2101,18 @@ var MIGRATIONS2 = [
|
|
|
1893
2101
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
1894
2102
|
`
|
|
1895
2103
|
},
|
|
2104
|
+
{
|
|
2105
|
+
version: 11,
|
|
2106
|
+
description: "Add observation provenance from tool and prompt chronology",
|
|
2107
|
+
sql: `
|
|
2108
|
+
ALTER TABLE observations ADD COLUMN source_tool TEXT;
|
|
2109
|
+
ALTER TABLE observations ADD COLUMN source_prompt_number INTEGER;
|
|
2110
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_tool
|
|
2111
|
+
ON observations(source_tool, created_at_epoch DESC);
|
|
2112
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_prompt
|
|
2113
|
+
ON observations(session_id, source_prompt_number DESC);
|
|
2114
|
+
`
|
|
2115
|
+
},
|
|
1896
2116
|
{
|
|
1897
2117
|
version: 12,
|
|
1898
2118
|
description: "Add synced handoff metadata to session summaries",
|
|
@@ -1904,15 +2124,79 @@ var MIGRATIONS2 = [
|
|
|
1904
2124
|
`
|
|
1905
2125
|
},
|
|
1906
2126
|
{
|
|
1907
|
-
version:
|
|
1908
|
-
description: "Add
|
|
2127
|
+
version: 13,
|
|
2128
|
+
description: "Add current_thread to session summaries",
|
|
1909
2129
|
sql: `
|
|
1910
|
-
ALTER TABLE
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2130
|
+
ALTER TABLE session_summaries ADD COLUMN current_thread TEXT;
|
|
2131
|
+
`
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
version: 14,
|
|
2135
|
+
description: "Add chat_messages lane for raw conversation recall",
|
|
2136
|
+
sql: `
|
|
2137
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
2138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2139
|
+
session_id TEXT NOT NULL,
|
|
2140
|
+
project_id INTEGER REFERENCES projects(id),
|
|
2141
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
2142
|
+
content TEXT NOT NULL,
|
|
2143
|
+
user_id TEXT NOT NULL,
|
|
2144
|
+
device_id TEXT NOT NULL,
|
|
2145
|
+
agent TEXT DEFAULT 'claude-code',
|
|
2146
|
+
created_at_epoch INTEGER NOT NULL
|
|
2147
|
+
);
|
|
2148
|
+
|
|
2149
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_session
|
|
2150
|
+
ON chat_messages(session_id, created_at_epoch DESC, id DESC);
|
|
2151
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_project
|
|
2152
|
+
ON chat_messages(project_id, created_at_epoch DESC, id DESC);
|
|
2153
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_created
|
|
2154
|
+
ON chat_messages(created_at_epoch DESC, id DESC);
|
|
2155
|
+
`
|
|
2156
|
+
},
|
|
2157
|
+
{
|
|
2158
|
+
version: 15,
|
|
2159
|
+
description: "Add remote_source_id for chat message sync deduplication",
|
|
2160
|
+
sql: `
|
|
2161
|
+
ALTER TABLE chat_messages ADD COLUMN remote_source_id TEXT;
|
|
2162
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_messages_remote_source
|
|
2163
|
+
ON chat_messages(remote_source_id)
|
|
2164
|
+
WHERE remote_source_id IS NOT NULL;
|
|
2165
|
+
`
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
version: 16,
|
|
2169
|
+
description: "Allow chat_message records in sync_outbox",
|
|
2170
|
+
sql: `
|
|
2171
|
+
CREATE TABLE sync_outbox_new (
|
|
2172
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2173
|
+
record_type TEXT NOT NULL CHECK (record_type IN ('observation', 'summary', 'chat_message')),
|
|
2174
|
+
record_id INTEGER NOT NULL,
|
|
2175
|
+
status TEXT DEFAULT 'pending' CHECK (status IN (
|
|
2176
|
+
'pending', 'syncing', 'synced', 'failed'
|
|
2177
|
+
)),
|
|
2178
|
+
retry_count INTEGER DEFAULT 0,
|
|
2179
|
+
max_retries INTEGER DEFAULT 10,
|
|
2180
|
+
last_error TEXT,
|
|
2181
|
+
created_at_epoch INTEGER NOT NULL,
|
|
2182
|
+
synced_at_epoch INTEGER,
|
|
2183
|
+
next_retry_epoch INTEGER
|
|
2184
|
+
);
|
|
2185
|
+
|
|
2186
|
+
INSERT INTO sync_outbox_new (
|
|
2187
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
2188
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
2189
|
+
)
|
|
2190
|
+
SELECT
|
|
2191
|
+
id, record_type, record_id, status, retry_count, max_retries,
|
|
2192
|
+
last_error, created_at_epoch, synced_at_epoch, next_retry_epoch
|
|
2193
|
+
FROM sync_outbox;
|
|
2194
|
+
|
|
2195
|
+
DROP TABLE sync_outbox;
|
|
2196
|
+
ALTER TABLE sync_outbox_new RENAME TO sync_outbox;
|
|
2197
|
+
|
|
2198
|
+
CREATE INDEX idx_outbox_status ON sync_outbox(status, next_retry_epoch);
|
|
2199
|
+
CREATE INDEX idx_outbox_record ON sync_outbox(record_type, record_id);
|
|
1916
2200
|
`
|
|
1917
2201
|
}
|
|
1918
2202
|
];
|