@tznthou/ccrecall 0.4.8 → 0.5.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/README.md +27 -51
- package/README_ZH.md +27 -52
- package/dist/api/routes.d.ts +0 -10
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +4 -354
- package/dist/api/routes.js.map +1 -1
- package/dist/api/server.js +3 -3
- package/dist/api/server.js.map +1 -1
- package/dist/core/database.d.ts +14 -49
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +104 -225
- package/dist/core/database.js.map +1 -1
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +0 -1
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/maintenance-coordinator.d.ts +3 -9
- package/dist/core/maintenance-coordinator.d.ts.map +1 -1
- package/dist/core/maintenance-coordinator.js +4 -24
- package/dist/core/maintenance-coordinator.js.map +1 -1
- package/dist/core/summarizer.d.ts.map +1 -1
- package/dist/core/summarizer.js +0 -22
- package/dist/core/summarizer.js.map +1 -1
- package/dist/core/types.d.ts +0 -87
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/journal.d.ts +0 -11
- package/dist/cli/journal.d.ts.map +0 -1
- package/dist/cli/journal.js +0 -111
- package/dist/cli/journal.js.map +0 -1
- package/dist/core/lint.d.ts +0 -17
- package/dist/core/lint.d.ts.map +0 -1
- package/dist/core/lint.js +0 -51
- package/dist/core/lint.js.map +0 -1
- package/dist/core/outcome-extractor.d.ts +0 -6
- package/dist/core/outcome-extractor.d.ts.map +0 -1
- package/dist/core/outcome-extractor.js +0 -39
- package/dist/core/outcome-extractor.js.map +0 -1
- package/dist/core/outcome-scorer.d.ts +0 -8
- package/dist/core/outcome-scorer.d.ts.map +0 -1
- package/dist/core/outcome-scorer.js +0 -103
- package/dist/core/outcome-scorer.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAiB,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"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"}
|
package/dist/core/database.js
CHANGED
|
@@ -4,10 +4,21 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { scrubErrorMessage } from './log-safe.js';
|
|
7
|
+
/** 64-bit signed hash for message_uuids (v24 dual-hash schema). First 8 bytes
|
|
8
|
+
* of SHA-256 read as a signed BigInt — exactly the SQLite INTEGER range, so
|
|
9
|
+
* uuid_hash doubles as the rowid (zero autoindex). Collision odds at ~400k
|
|
10
|
+
* rows ≈ 4e-9; a collision merely makes one replayed message skip indexing.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ Reading these values back REQUIRES `.safeIntegers(true)` on the statement:
|
|
13
|
+
* better-sqlite3 otherwise returns a lossy JS number for |v| > 2^53 and any
|
|
14
|
+
* Map<bigint, …> reverse lookup silently misses (dedup would silently die). */
|
|
15
|
+
export function hash64(s) {
|
|
16
|
+
return createHash('sha256').update(s).digest().readBigInt64BE(0);
|
|
17
|
+
}
|
|
7
18
|
/** sessions 資料表的完整欄位 SELECT 子句(getSessions / getSessionById 共用) */
|
|
8
19
|
const SESSION_SELECT_COLUMNS = `id, project_id, title, message_count, started_at, ended_at, archived,
|
|
9
20
|
summary_text, intent_text, outcome_status, duration_seconds, active_duration_seconds, summary_version,
|
|
10
|
-
tags, files_touched, tools_used, total_input_tokens, total_output_tokens
|
|
21
|
+
tags, files_touched, tools_used, total_input_tokens, total_output_tokens`;
|
|
11
22
|
function mapSessionRow(r) {
|
|
12
23
|
return {
|
|
13
24
|
id: r.id,
|
|
@@ -28,7 +39,6 @@ function mapSessionRow(r) {
|
|
|
28
39
|
toolsUsed: r.tools_used,
|
|
29
40
|
totalInputTokens: r.total_input_tokens,
|
|
30
41
|
totalOutputTokens: r.total_output_tokens,
|
|
31
|
-
harvestText: r.harvest_text,
|
|
32
42
|
};
|
|
33
43
|
}
|
|
34
44
|
function mapMemoryRow(r) {
|
|
@@ -51,42 +61,6 @@ function mapTopicRow(r) {
|
|
|
51
61
|
lastTouched: r.last_touched,
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
|
-
function mapCheckpointRow(r) {
|
|
55
|
-
return {
|
|
56
|
-
id: r.id,
|
|
57
|
-
sessionId: r.session_id,
|
|
58
|
-
projectId: r.project_id,
|
|
59
|
-
snapshotText: r.snapshot_text,
|
|
60
|
-
createdAt: r.created_at,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function mapJournalRow(r) {
|
|
64
|
-
return {
|
|
65
|
-
id: r.id,
|
|
66
|
-
sessionId: r.session_id,
|
|
67
|
-
messageId: r.message_id,
|
|
68
|
-
content: r.content,
|
|
69
|
-
contentHash: r.content_hash,
|
|
70
|
-
score: r.score,
|
|
71
|
-
reasonsJson: r.reasons_json,
|
|
72
|
-
status: r.status,
|
|
73
|
-
expiresAt: r.expires_at,
|
|
74
|
-
promotedMemoryId: r.promoted_memory_id,
|
|
75
|
-
projectId: r.project_id,
|
|
76
|
-
createdAt: r.created_at,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function mapJournalPreviewRow(r) {
|
|
80
|
-
return {
|
|
81
|
-
id: r.id,
|
|
82
|
-
sessionId: r.session_id,
|
|
83
|
-
score: r.score,
|
|
84
|
-
reasonsJson: r.reasons_json,
|
|
85
|
-
contentPreview: r.content_preview,
|
|
86
|
-
projectId: r.project_id,
|
|
87
|
-
createdAt: r.created_at,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
64
|
/** 重建 message_content / message_archive 表,修正 FK 指向 messages */
|
|
91
65
|
function rebuildSideTables(db) {
|
|
92
66
|
db.exec(`
|
|
@@ -655,6 +629,48 @@ const migrations = [
|
|
|
655
629
|
`);
|
|
656
630
|
},
|
|
657
631
|
},
|
|
632
|
+
{
|
|
633
|
+
version: 24,
|
|
634
|
+
description: 'knife-field (v0.5.0): drop session_journal/session_checkpoints/sessions.harvest_text; rebuild message_uuids as dual 64-bit hash (73.6MB → ~14MB)',
|
|
635
|
+
up: (db) => {
|
|
636
|
+
// Dead-pipeline tables — the journal DLQ recorded 0 promotions in its
|
|
637
|
+
// entire history (2026-06-10 audit) and /session/checkpoint had no caller.
|
|
638
|
+
db.exec(`
|
|
639
|
+
DROP TABLE IF EXISTS session_journal;
|
|
640
|
+
DROP TABLE IF EXISTS session_checkpoints;
|
|
641
|
+
`);
|
|
642
|
+
const sessionCols = db.prepare('PRAGMA table_info(sessions)').all();
|
|
643
|
+
if (sessionCols.some(c => c.name === 'harvest_text')) {
|
|
644
|
+
db.exec('ALTER TABLE sessions DROP COLUMN harvest_text;');
|
|
645
|
+
}
|
|
646
|
+
// message_uuids: TEXT uuid PK (36 chars ×3 b-trees) → dual 64-bit hash.
|
|
647
|
+
// uuid_hash INTEGER PRIMARY KEY is the rowid alias — zero autoindex.
|
|
648
|
+
// Skip when already dual-hash (idempotency; also covers a repaired DB
|
|
649
|
+
// where initSchema recreated the table in its final shape).
|
|
650
|
+
const uuidCols = db.prepare('PRAGMA table_info(message_uuids)').all();
|
|
651
|
+
if (uuidCols.some(c => c.name === 'uuid_hash'))
|
|
652
|
+
return;
|
|
653
|
+
db.exec(`
|
|
654
|
+
CREATE TABLE message_uuids_new (
|
|
655
|
+
uuid_hash INTEGER PRIMARY KEY,
|
|
656
|
+
session_hash INTEGER NOT NULL
|
|
657
|
+
);
|
|
658
|
+
`);
|
|
659
|
+
// JS-side rehash: hash64 lives here, not in SQLite. ~400k rows over the
|
|
660
|
+
// sync API completes in seconds. INSERT OR IGNORE keeps first-writer-wins
|
|
661
|
+
// on the astronomically unlikely 64-bit collision.
|
|
662
|
+
const insert = db.prepare('INSERT OR IGNORE INTO message_uuids_new (uuid_hash, session_hash) VALUES (?, ?)');
|
|
663
|
+
const rows = db.prepare('SELECT uuid, session_id FROM message_uuids').all();
|
|
664
|
+
for (const r of rows) {
|
|
665
|
+
insert.run(hash64(r.uuid), hash64(r.session_id));
|
|
666
|
+
}
|
|
667
|
+
db.exec(`
|
|
668
|
+
DROP TABLE message_uuids;
|
|
669
|
+
ALTER TABLE message_uuids_new RENAME TO message_uuids;
|
|
670
|
+
CREATE INDEX idx_message_uuids_session ON message_uuids(session_hash);
|
|
671
|
+
`);
|
|
672
|
+
},
|
|
673
|
+
},
|
|
658
674
|
];
|
|
659
675
|
export class Database {
|
|
660
676
|
db;
|
|
@@ -872,14 +888,24 @@ export class Database {
|
|
|
872
888
|
}
|
|
873
889
|
}
|
|
874
890
|
else {
|
|
875
|
-
// v20+ baseline — message_uuids carries the UUID dedup set.
|
|
891
|
+
// v20+ baseline — message_uuids carries the UUID dedup set. DDL is the
|
|
892
|
+
// v24 dual-hash shape: on a v20–v23 DB the table already exists so this
|
|
893
|
+
// is a no-op (v24 migration handles the rebuild); it only materialises
|
|
894
|
+
// when repairing a DB whose table went missing, and then the v24
|
|
895
|
+
// migration's shape check skips the rebuild.
|
|
876
896
|
this.db.exec(`
|
|
877
897
|
CREATE TABLE IF NOT EXISTS message_uuids (
|
|
878
|
-
|
|
879
|
-
|
|
898
|
+
uuid_hash INTEGER PRIMARY KEY,
|
|
899
|
+
session_hash INTEGER NOT NULL
|
|
880
900
|
);
|
|
881
|
-
CREATE INDEX IF NOT EXISTS idx_message_uuids_session ON message_uuids(session_id);
|
|
882
901
|
`);
|
|
902
|
+
// Index only when the table is dual-hash: a v20–v23 table awaiting the
|
|
903
|
+
// v24 rebuild has no session_hash column, and indexing it here would
|
|
904
|
+
// throw. The rebuild recreates the index itself.
|
|
905
|
+
const uuidCols = this.db.prepare('PRAGMA table_info(message_uuids)').all();
|
|
906
|
+
if (uuidCols.some(c => c.name === 'session_hash')) {
|
|
907
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_message_uuids_session ON message_uuids(session_hash);');
|
|
908
|
+
}
|
|
883
909
|
}
|
|
884
910
|
}
|
|
885
911
|
/** 依序執行尚未套用的 migrations */
|
|
@@ -956,10 +982,14 @@ export class Database {
|
|
|
956
982
|
return row?.file_mtime ?? null;
|
|
957
983
|
}
|
|
958
984
|
getSessions(projectId) {
|
|
985
|
+
// instr(id,'/') mirrors getLastSession's registry-timing guard (v0.4.8):
|
|
986
|
+
// a subagent row can momentarily lack its subagent_sessions registration,
|
|
987
|
+
// and composite "<parent>/agent-…" ids are never main sessions.
|
|
959
988
|
const rows = this.db.prepare(`SELECT ${SESSION_SELECT_COLUMNS}
|
|
960
989
|
FROM sessions
|
|
961
990
|
WHERE project_id = ?
|
|
962
991
|
AND id ${Database.EXCLUDE_SUBAGENTS}
|
|
992
|
+
AND instr(id, '/') = 0
|
|
963
993
|
ORDER BY started_at DESC`).all(projectId);
|
|
964
994
|
return rows.map(mapSessionRow);
|
|
965
995
|
}
|
|
@@ -1046,11 +1076,12 @@ export class Database {
|
|
|
1046
1076
|
deleteSubagentSessions(parentSessionId) {
|
|
1047
1077
|
this.db.prepare('DELETE FROM subagent_sessions WHERE parent_session_id = ?').run(parentSessionId);
|
|
1048
1078
|
}
|
|
1049
|
-
/** 刪除單一 subagent session
|
|
1079
|
+
/** 刪除單一 subagent session。v24: message_uuids 無 FK CASCADE,手動清除。 */
|
|
1050
1080
|
deleteSubagentSession(subagentId) {
|
|
1051
1081
|
const doDelete = this.db.transaction(() => {
|
|
1052
1082
|
this.db.prepare('DELETE FROM session_files WHERE session_id = ?').run(subagentId);
|
|
1053
1083
|
this.db.prepare('DELETE FROM sessions WHERE id = ?').run(subagentId);
|
|
1084
|
+
this.db.prepare('DELETE FROM message_uuids WHERE session_hash = ?').run(hash64(subagentId));
|
|
1054
1085
|
this.db.prepare('DELETE FROM subagent_sessions WHERE id = ?').run(subagentId);
|
|
1055
1086
|
});
|
|
1056
1087
|
doDelete();
|
|
@@ -1076,15 +1107,27 @@ export class Database {
|
|
|
1076
1107
|
return map;
|
|
1077
1108
|
}
|
|
1078
1109
|
// ── UUID dedup helper ──
|
|
1079
|
-
/** 查詢 DB 中已存在的 uuid(用於跨 session 去重 resumed session replay
|
|
1110
|
+
/** 查詢 DB 中已存在的 uuid(用於跨 session 去重 resumed session replay)。
|
|
1111
|
+
* v24: message_uuids 只存 64-bit hash,查詢以 hash 比對、以 Map 反查還原
|
|
1112
|
+
* 原 uuid — 簽名與回傳不變,indexer 端零改動。 */
|
|
1080
1113
|
getExistingUuids(uuids, excludeSessionId) {
|
|
1081
1114
|
const result = new Set();
|
|
1115
|
+
const excludeHash = hash64(excludeSessionId);
|
|
1082
1116
|
for (let i = 0; i < uuids.length; i += 500) {
|
|
1083
1117
|
const chunk = uuids.slice(i, i + 500);
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1118
|
+
const byHash = new Map();
|
|
1119
|
+
for (const u of chunk)
|
|
1120
|
+
byHash.set(hash64(u), u);
|
|
1121
|
+
const hashes = [...byHash.keys()];
|
|
1122
|
+
const placeholders = hashes.map(() => '?').join(',');
|
|
1123
|
+
// safeIntegers is load-bearing: without it uuid_hash values beyond 2^53
|
|
1124
|
+
// come back as lossy numbers and the Map lookup misses every row.
|
|
1125
|
+
const rows = this.db.prepare(`SELECT uuid_hash FROM message_uuids WHERE session_hash != ? AND uuid_hash IN (${placeholders})`).safeIntegers(true).all(excludeHash, ...hashes);
|
|
1126
|
+
for (const r of rows) {
|
|
1127
|
+
const uuid = byHash.get(r.uuid_hash);
|
|
1128
|
+
if (uuid !== undefined)
|
|
1129
|
+
result.add(uuid);
|
|
1130
|
+
}
|
|
1088
1131
|
}
|
|
1089
1132
|
return result;
|
|
1090
1133
|
}
|
|
@@ -1098,6 +1141,10 @@ export class Database {
|
|
|
1098
1141
|
}
|
|
1099
1142
|
this.db.prepare('DELETE FROM session_files WHERE session_id = ?').run(params.sessionId);
|
|
1100
1143
|
this.db.prepare('DELETE FROM sessions WHERE id = ?').run(params.sessionId);
|
|
1144
|
+
// v24: message_uuids no longer has an FK to sessions (hash columns can't
|
|
1145
|
+
// reference TEXT ids) — clear this session's registrations manually so a
|
|
1146
|
+
// reindex re-registers from scratch instead of stacking stale hashes.
|
|
1147
|
+
this.db.prepare('DELETE FROM message_uuids WHERE session_hash = ?').run(hash64(params.sessionId));
|
|
1101
1148
|
this.upsertProject(params.projectId, params.projectDisplayName);
|
|
1102
1149
|
// 計算 token 彙總
|
|
1103
1150
|
let totalInput = 0;
|
|
@@ -1111,9 +1158,9 @@ export class Database {
|
|
|
1111
1158
|
const insertResult = this.db.prepare(`
|
|
1112
1159
|
INSERT INTO sessions (id, project_id, title, message_count, file_path, file_size, file_mtime, started_at, ended_at,
|
|
1113
1160
|
summary_text, intent_text, outcome_status, outcome_signals, duration_seconds, active_duration_seconds, summary_version,
|
|
1114
|
-
tags, files_touched, tools_used, total_input_tokens, total_output_tokens
|
|
1115
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
1116
|
-
`).run(params.sessionId, params.projectId, params.title, params.messageCount, params.filePath, params.fileSize, params.fileMtime, params.startedAt, params.endedAt, params.summaryText ?? null, params.intentText ?? null, params.outcomeStatus ?? null, params.outcomeSignals ?? null, params.durationSeconds ?? null, params.activeDurationSeconds ?? null, params.summaryVersion ?? null, params.tags ?? null, params.filesTouched ?? null, params.toolsUsed ?? null, totalInput || null, totalOutput || null
|
|
1161
|
+
tags, files_touched, tools_used, total_input_tokens, total_output_tokens)
|
|
1162
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1163
|
+
`).run(params.sessionId, params.projectId, params.title, params.messageCount, params.filePath, params.fileSize, params.fileMtime, params.startedAt, params.endedAt, params.summaryText ?? null, params.intentText ?? null, params.outcomeStatus ?? null, params.outcomeSignals ?? null, params.durationSeconds ?? null, params.activeDurationSeconds ?? null, params.summaryVersion ?? null, params.tags ?? null, params.filesTouched ?? null, params.toolsUsed ?? null, totalInput || null, totalOutput || null);
|
|
1117
1164
|
// 新增 sessions_fts 條目(用 INSERT 回傳的 rowid 避免多餘查詢)
|
|
1118
1165
|
this.db.prepare('INSERT INTO sessions_fts(rowid, title, tags, files_touched, summary_text, intent_text) VALUES (?, ?, ?, ?, ?, ?)').run(insertResult.lastInsertRowid, params.title ?? '', params.tags ?? '', params.filesTouched ?? '', params.summaryText ?? '', params.intentText ?? '');
|
|
1119
1166
|
// 寫入 session_files
|
|
@@ -1126,12 +1173,14 @@ export class Database {
|
|
|
1126
1173
|
insertFile.run(params.sessionId, f.filePath, f.operation, f.count, f.firstSeenSeq, f.lastSeenSeq);
|
|
1127
1174
|
}
|
|
1128
1175
|
}
|
|
1129
|
-
// message_uuids: 寫入 uuid
|
|
1130
|
-
//
|
|
1131
|
-
|
|
1176
|
+
// message_uuids: 寫入 uuid 登記表(雙 hash),供跨 session replay dedup 查詢。
|
|
1177
|
+
// 舊登記已在本 transaction 開頭手動 DELETE。INSERT OR IGNORE 保持「先寫者
|
|
1178
|
+
// 擁有 uuid」語義(uuid_hash 為 PK;他 session 已登記或本 session 重複皆略過)。
|
|
1179
|
+
const insertUuid = this.db.prepare('INSERT OR IGNORE INTO message_uuids (uuid_hash, session_hash) VALUES (?, ?)');
|
|
1180
|
+
const sessionHash = hash64(params.sessionId);
|
|
1132
1181
|
for (const m of params.messages) {
|
|
1133
1182
|
if (m.uuid != null) {
|
|
1134
|
-
insertUuid.run(m.uuid,
|
|
1183
|
+
insertUuid.run(hash64(m.uuid), sessionHash);
|
|
1135
1184
|
}
|
|
1136
1185
|
}
|
|
1137
1186
|
});
|
|
@@ -1395,101 +1444,6 @@ export class Database {
|
|
|
1395
1444
|
`).run(input.sessionId, input.messageId, input.content, input.type, input.confidence ?? 0.8, projectId, key);
|
|
1396
1445
|
return Number(info.lastInsertRowid);
|
|
1397
1446
|
}
|
|
1398
|
-
// ─────────────────────────────────────────────────────────────
|
|
1399
|
-
// session_journal DAO (issue #21 P1)
|
|
1400
|
-
// 低信任 harvest 候選的寫入入口; manual recall_save 仍走 saveMemory()。
|
|
1401
|
-
//
|
|
1402
|
-
// Trust boundary: queryMemories / getMemoriesByTopics 故意只查 memories table,
|
|
1403
|
-
// 不 union session_journal — unpromoted journal entries 是 low-trust,不該污染
|
|
1404
|
-
// recall 結果。promote 路徑 (C4) 把 journal entry 搬到 memories 後才會被 recall
|
|
1405
|
-
// 看到。若未來修改 query 邏輯需動到 journal,務必保持這條 invariant。
|
|
1406
|
-
// ─────────────────────────────────────────────────────────────
|
|
1407
|
-
/** 寫入 journal 條目。content_hash 內部以 SHA-256 計算; 同 hash 第二次 INSERT
|
|
1408
|
-
* 會被 UNIQUE INDEX 擋下並回傳 0 (idempotency)。
|
|
1409
|
-
*
|
|
1410
|
-
* 注意: better-sqlite3 的 INSERT OR IGNORE 在 IGNORE'd 時, lastInsertRowid
|
|
1411
|
-
* 不會歸零 (保留前次成功 insert 的 rowid)。所以用 info.changes 判 dedup。 */
|
|
1412
|
-
saveJournalEntry(input) {
|
|
1413
|
-
const hash = createHash('sha256').update(input.content).digest('hex');
|
|
1414
|
-
const info = this.db.prepare(`
|
|
1415
|
-
INSERT OR IGNORE INTO session_journal
|
|
1416
|
-
(session_id, message_id, content, content_hash, score, reasons_json, project_id)
|
|
1417
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1418
|
-
`).run(input.sessionId ?? null, input.messageId ?? null, input.content, hash, input.score, input.reasonsJson ?? null, input.projectId ?? null);
|
|
1419
|
-
return info.changes === 0 ? 0 : Number(info.lastInsertRowid);
|
|
1420
|
-
}
|
|
1421
|
-
/** 給 /health endpoint 用; promotion path manual-only,user 看 pending 數決定是否 invoke。 */
|
|
1422
|
-
getJournalPendingCount() {
|
|
1423
|
-
const row = this.db.prepare("SELECT COUNT(*) AS c FROM session_journal WHERE status = 'pending'").get();
|
|
1424
|
-
return row.c;
|
|
1425
|
-
}
|
|
1426
|
-
getJournalEntry(id) {
|
|
1427
|
-
const row = this.db.prepare(`
|
|
1428
|
-
SELECT id, session_id, message_id, content, content_hash, score,
|
|
1429
|
-
reasons_json, status, expires_at, promoted_memory_id,
|
|
1430
|
-
project_id, created_at
|
|
1431
|
-
FROM session_journal WHERE id = ?
|
|
1432
|
-
`).get(id);
|
|
1433
|
-
return row ? mapJournalRow(row) : null;
|
|
1434
|
-
}
|
|
1435
|
-
/** 升級 journal entry 為 promoted; caller 已透過 saveMemory 寫入 memories table
|
|
1436
|
-
* 並拿到 memoryId,本方法只更新 journal 的 status + 回填 promoted_memory_id。
|
|
1437
|
-
* 回 false 表示 entry 不存在或已不在 'pending' 狀態 (idempotent)。 */
|
|
1438
|
-
promoteJournalEntry(id, memoryId) {
|
|
1439
|
-
const info = this.db.prepare(`
|
|
1440
|
-
UPDATE session_journal
|
|
1441
|
-
SET status = 'promoted', promoted_memory_id = ?
|
|
1442
|
-
WHERE id = ? AND status = 'pending'
|
|
1443
|
-
`).run(memoryId, id);
|
|
1444
|
-
return info.changes > 0;
|
|
1445
|
-
}
|
|
1446
|
-
/** List pending journal entries (newest first) with content preview for
|
|
1447
|
-
* surfacing on the manual promotion path. content 截 200 chars 避免 response
|
|
1448
|
-
* blob 過大; caller 想看完整內容可走 promote 看 memories 或直接 sqlite3。 */
|
|
1449
|
-
getPendingJournalEntries(limit) {
|
|
1450
|
-
const cappedLimit = Math.min(Math.max(limit, 1), 100);
|
|
1451
|
-
const rows = this.db.prepare(`
|
|
1452
|
-
SELECT id, session_id, score, reasons_json,
|
|
1453
|
-
substr(content, 1, 200) AS content_preview,
|
|
1454
|
-
project_id, created_at
|
|
1455
|
-
FROM session_journal
|
|
1456
|
-
WHERE status = 'pending'
|
|
1457
|
-
ORDER BY created_at DESC, id DESC
|
|
1458
|
-
LIMIT ?
|
|
1459
|
-
`).all(cappedLimit);
|
|
1460
|
-
return rows.map(mapJournalPreviewRow);
|
|
1461
|
-
}
|
|
1462
|
-
/** 標記 entry 為 rejected, expires_at 設為 now+7d (decay sweep 才真的刪除)。
|
|
1463
|
-
* caller 傳 expiresAt ISO string; 回 false 表示 entry 不存在或非 pending。 */
|
|
1464
|
-
rejectJournalEntry(id, expiresAt) {
|
|
1465
|
-
const info = this.db.prepare(`
|
|
1466
|
-
UPDATE session_journal
|
|
1467
|
-
SET status = 'rejected', expires_at = ?
|
|
1468
|
-
WHERE id = ? AND status = 'pending'
|
|
1469
|
-
`).run(expiresAt, id);
|
|
1470
|
-
return info.changes > 0;
|
|
1471
|
-
}
|
|
1472
|
-
/** Decay sweep (P1 step 7):
|
|
1473
|
-
* - rejected entries past expires_at → DELETE
|
|
1474
|
-
* - pending entries older than 30 days → DELETE
|
|
1475
|
-
* promoted entries 留著作 audit trail (有 promoted_memory_id 指 memories.id)。
|
|
1476
|
-
* manual memories 在 memories table,本方法完全不碰。 */
|
|
1477
|
-
sweepJournal(now = new Date()) {
|
|
1478
|
-
const nowIso = now.toISOString();
|
|
1479
|
-
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
1480
|
-
const rejectedInfo = this.db.prepare(`
|
|
1481
|
-
DELETE FROM session_journal
|
|
1482
|
-
WHERE status = 'rejected' AND expires_at IS NOT NULL AND expires_at < ?
|
|
1483
|
-
`).run(nowIso);
|
|
1484
|
-
const pendingInfo = this.db.prepare(`
|
|
1485
|
-
DELETE FROM session_journal
|
|
1486
|
-
WHERE status = 'pending' AND created_at < ?
|
|
1487
|
-
`).run(thirtyDaysAgo);
|
|
1488
|
-
return {
|
|
1489
|
-
rejectedDeleted: rejectedInfo.changes,
|
|
1490
|
-
pendingDeleted: pendingInfo.changes,
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
1447
|
queryMemories(query, limit, projectId) {
|
|
1494
1448
|
const rawQuery = query;
|
|
1495
1449
|
const q = Database.fts5QuoteIfNeeded(query);
|
|
@@ -1910,23 +1864,6 @@ export class Database {
|
|
|
1910
1864
|
});
|
|
1911
1865
|
run();
|
|
1912
1866
|
}
|
|
1913
|
-
static TOPIC_ORDER_BY = {
|
|
1914
|
-
mention: 'mention_count DESC, last_touched DESC',
|
|
1915
|
-
recent: 'last_touched DESC',
|
|
1916
|
-
stale: 'last_touched ASC',
|
|
1917
|
-
};
|
|
1918
|
-
getKnowledgeMap(projectId, opts = {}) {
|
|
1919
|
-
const { limit = 50, sortBy = 'mention' } = opts;
|
|
1920
|
-
const cappedLimit = Math.min(limit, 500);
|
|
1921
|
-
const rows = this.db.prepare(`
|
|
1922
|
-
SELECT topic_key, project_id, mention_count, last_touched
|
|
1923
|
-
FROM knowledge_map
|
|
1924
|
-
WHERE project_id = ?
|
|
1925
|
-
ORDER BY ${Database.TOPIC_ORDER_BY[sortBy]}
|
|
1926
|
-
LIMIT ?
|
|
1927
|
-
`).all(projectId, cappedLimit);
|
|
1928
|
-
return rows.map(mapTopicRow);
|
|
1929
|
-
}
|
|
1930
1867
|
getMemoriesByTopics(projectId, topicKeys, limit) {
|
|
1931
1868
|
if (topicKeys.length === 0)
|
|
1932
1869
|
return [];
|
|
@@ -1963,63 +1900,5 @@ export class Database {
|
|
|
1963
1900
|
`).get(topicKey, projectId);
|
|
1964
1901
|
return row ? mapTopicRow(row) : null;
|
|
1965
1902
|
}
|
|
1966
|
-
/** 共現 topics(與目標共享 session 或 memory),按共現次數排序。
|
|
1967
|
-
project_id 透過 JOIN sessions 決定,避免 memory_topics 過時的 project_id 汙染。 */
|
|
1968
|
-
getRelatedTopics(topicKey, projectId, limit) {
|
|
1969
|
-
const cappedLimit = Math.min(limit, 50);
|
|
1970
|
-
const rows = this.db.prepare(`
|
|
1971
|
-
SELECT k, COUNT(*) AS c FROM (
|
|
1972
|
-
SELECT st2.topic_key AS k
|
|
1973
|
-
FROM session_topics st1
|
|
1974
|
-
JOIN session_topics st2 ON st1.session_id = st2.session_id AND st2.topic_key != st1.topic_key
|
|
1975
|
-
JOIN sessions s ON s.id = st1.session_id
|
|
1976
|
-
WHERE st1.topic_key = ? AND s.project_id = ? AND s.id ${Database.EXCLUDE_SUBAGENTS}
|
|
1977
|
-
UNION ALL
|
|
1978
|
-
SELECT mt2.topic_key AS k
|
|
1979
|
-
FROM memory_topics mt1
|
|
1980
|
-
JOIN memory_topics mt2 ON mt1.memory_id = mt2.memory_id AND mt2.topic_key != mt1.topic_key
|
|
1981
|
-
JOIN memories m ON m.id = mt1.memory_id
|
|
1982
|
-
JOIN sessions s ON s.id = m.session_id
|
|
1983
|
-
WHERE mt1.topic_key = ? AND s.project_id = ? AND s.id ${Database.EXCLUDE_SUBAGENTS}
|
|
1984
|
-
)
|
|
1985
|
-
GROUP BY k ORDER BY c DESC, k ASC LIMIT ?
|
|
1986
|
-
`).all(topicKey, projectId, topicKey, projectId, cappedLimit);
|
|
1987
|
-
return rows.map(r => r.k);
|
|
1988
|
-
}
|
|
1989
|
-
getKnowledgeMapCounts(projectId) {
|
|
1990
|
-
const topics = this.db.prepare('SELECT COUNT(*) AS c FROM knowledge_map WHERE project_id = ?').get(projectId);
|
|
1991
|
-
const mems = this.db.prepare(`
|
|
1992
|
-
SELECT COUNT(*) AS c FROM memories m
|
|
1993
|
-
JOIN sessions s ON s.id = m.session_id
|
|
1994
|
-
WHERE s.project_id = ? AND s.id ${Database.EXCLUDE_SUBAGENTS}
|
|
1995
|
-
`).get(projectId);
|
|
1996
|
-
const sess = this.db.prepare(`
|
|
1997
|
-
SELECT COUNT(*) AS c FROM sessions WHERE project_id = ? AND id ${Database.EXCLUDE_SUBAGENTS}
|
|
1998
|
-
`).get(projectId);
|
|
1999
|
-
return { totalTopics: topics.c, totalMemories: mems.c, totalSessions: sess.c };
|
|
2000
|
-
}
|
|
2001
|
-
// ── Session Checkpoints (Phase 3d) ──
|
|
2002
|
-
saveCheckpoint(sessionId, projectId, snapshotText) {
|
|
2003
|
-
const info = this.db.prepare(`
|
|
2004
|
-
INSERT INTO session_checkpoints (session_id, project_id, snapshot_text)
|
|
2005
|
-
VALUES (?, ?, ?)
|
|
2006
|
-
`).run(sessionId, projectId, snapshotText);
|
|
2007
|
-
return Number(info.lastInsertRowid);
|
|
2008
|
-
}
|
|
2009
|
-
getCheckpointById(id) {
|
|
2010
|
-
const row = this.db.prepare(`
|
|
2011
|
-
SELECT id, session_id, project_id, snapshot_text, created_at
|
|
2012
|
-
FROM session_checkpoints WHERE id = ?
|
|
2013
|
-
`).get(id);
|
|
2014
|
-
return row ? mapCheckpointRow(row) : null;
|
|
2015
|
-
}
|
|
2016
|
-
getCheckpointsBySessionId(sessionId) {
|
|
2017
|
-
const rows = this.db.prepare(`
|
|
2018
|
-
SELECT id, session_id, project_id, snapshot_text, created_at
|
|
2019
|
-
FROM session_checkpoints WHERE session_id = ?
|
|
2020
|
-
ORDER BY created_at DESC, id DESC
|
|
2021
|
-
`).all(sessionId);
|
|
2022
|
-
return rows.map(mapCheckpointRow);
|
|
2023
|
-
}
|
|
2024
1903
|
}
|
|
2025
1904
|
//# sourceMappingURL=database.js.map
|