sandstream-kit 1.1.0 → 1.3.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 +101 -92
- package/dist/adapters/expo-eas.js +1 -1
- package/dist/adapters/expo-eas.js.map +1 -1
- package/dist/audit-logging-service.js +1 -1
- package/dist/audit-logging-service.js.map +1 -1
- package/dist/author-verification.js +1 -1
- package/dist/author-verification.js.map +1 -1
- package/dist/check-web-search.js +37 -6
- package/dist/check-web-search.js.map +1 -1
- package/dist/cli-shared.d.ts +4 -0
- package/dist/cli-shared.js +11 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli.js +175 -1189
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +99 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +121 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/env.d.ts +1 -0
- package/dist/commands/env.js +149 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/hooks.d.ts +1 -0
- package/dist/commands/hooks.js +146 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +120 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +1 -0
- package/dist/commands/memory.js +534 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/mcp-server.js +59 -6
- package/dist/mcp-server.js.map +1 -1
- package/dist/memory/amazonq.d.ts +5 -0
- package/dist/memory/amazonq.js +161 -0
- package/dist/memory/amazonq.js.map +1 -0
- package/dist/memory/cline.d.ts +5 -0
- package/dist/memory/cline.js +117 -0
- package/dist/memory/cline.js.map +1 -0
- package/dist/memory/codex.d.ts +5 -0
- package/dist/memory/codex.js +150 -0
- package/dist/memory/codex.js.map +1 -0
- package/dist/memory/continue.d.ts +5 -0
- package/dist/memory/continue.js +116 -0
- package/dist/memory/continue.js.map +1 -0
- package/dist/memory/cursor.d.ts +4 -0
- package/dist/memory/cursor.js +116 -0
- package/dist/memory/cursor.js.map +1 -0
- package/dist/memory/db.d.ts +12 -1
- package/dist/memory/db.js +43 -1
- package/dist/memory/db.js.map +1 -1
- package/dist/memory/gemini.d.ts +5 -0
- package/dist/memory/gemini.js +176 -0
- package/dist/memory/gemini.js.map +1 -0
- package/dist/memory/hook.d.ts +9 -0
- package/dist/memory/hook.js +34 -1
- package/dist/memory/hook.js.map +1 -1
- package/dist/memory/install.js +1 -0
- package/dist/memory/install.js.map +1 -1
- package/dist/memory/merge.d.ts +18 -0
- package/dist/memory/merge.js +109 -0
- package/dist/memory/merge.js.map +1 -0
- package/dist/memory/parser.d.ts +9 -0
- package/dist/memory/parser.js +40 -3
- package/dist/memory/parser.js.map +1 -1
- package/dist/memory/suggest.d.ts +21 -0
- package/dist/memory/suggest.js +36 -0
- package/dist/memory/suggest.js.map +1 -0
- package/dist/onepassword.js +1 -1
- package/dist/onepassword.js.map +1 -1
- package/dist/open.js +8 -2
- package/dist/open.js.map +1 -1
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/service-auth.d.ts +31 -0
- package/dist/service-auth.js +47 -0
- package/dist/service-auth.js.map +1 -0
- package/dist/stack-detector.js +53 -76
- package/dist/stack-detector.js.map +1 -1
- package/dist/status.d.ts +9 -0
- package/dist/status.js +119 -0
- package/dist/status.js.map +1 -0
- package/package.json +9 -4
- package/dist/memory/backup 2.d.ts +0 -6
- package/dist/memory/backup 2.js +0 -80
- package/dist/memory/backup 2.js.map +0 -1
- package/dist/memory/db 2.d.ts +0 -40
- package/dist/memory/db 2.js +0 -233
- package/dist/memory/db 2.js.map +0 -1
- package/dist/memory/hook 2.d.ts +0 -6
- package/dist/memory/hook 2.js +0 -51
- package/dist/memory/hook 2.js.map +0 -1
- package/dist/memory/hook.test 2.d.ts +0 -1
- package/dist/memory/hook.test 2.js +0 -35
- package/dist/memory/hook.test 2.js.map +0 -1
- package/dist/memory/install 2.d.ts +0 -8
- package/dist/memory/install 2.js +0 -72
- package/dist/memory/install 2.js.map +0 -1
- package/dist/memory/install.test 2.d.ts +0 -1
- package/dist/memory/install.test 2.js +0 -59
- package/dist/memory/install.test 2.js.map +0 -1
- package/dist/memory/pal 2.d.ts +0 -47
- package/dist/memory/pal 2.js +0 -154
- package/dist/memory/pal 2.js.map +0 -1
- package/dist/memory/project 2.d.ts +0 -1
- package/dist/memory/project 2.js +0 -24
- package/dist/memory/project 2.js.map +0 -1
- package/dist/memory/scan 2.d.ts +0 -15
- package/dist/memory/scan 2.js +0 -94
- package/dist/memory/scan 2.js.map +0 -1
- package/dist/memory/scan.test 2.d.ts +0 -1
- package/dist/memory/scan.test 2.js +0 -55
- package/dist/memory/scan.test 2.js.map +0 -1
- package/dist/memory/shared 2.d.ts +0 -33
- package/dist/memory/shared 2.js +0 -120
- package/dist/memory/shared 2.js.map +0 -1
- package/dist/memory/threads 2.d.ts +0 -37
- package/dist/memory/threads 2.js +0 -50
- package/dist/memory/threads 2.js.map +0 -1
- package/dist/memory/threads.test 2.d.ts +0 -1
- package/dist/memory/threads.test 2.js +0 -66
- package/dist/memory/threads.test 2.js.map +0 -1
- package/dist/memory/types 2.d.ts +0 -52
- package/dist/memory/types 2.js +0 -5
- package/dist/memory/types 2.js.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kit memory — Cline transcript parser (multi-harness).
|
|
3
|
+
*
|
|
4
|
+
* Cline (VS Code extension `saoudrizwan.claude-dev`) stores each task under
|
|
5
|
+
* globalStorage/saoudrizwan.claude-dev/tasks/<taskId>/, with the conversation in
|
|
6
|
+
* api_conversation_history.json — an array of Anthropic-format messages:
|
|
7
|
+
*
|
|
8
|
+
* [{ role: "user"|"assistant", content: string | [{ type:"text", text }, …] }]
|
|
9
|
+
*
|
|
10
|
+
* (verified against cline/cline + community readers). This is the same message
|
|
11
|
+
* shape kit already parses for Claude Code, so extraction is clean. We index
|
|
12
|
+
* user/assistant text turns, tag harness="cline", synthesize stable uuids.
|
|
13
|
+
* Idempotent, incremental, fail-safe, no model calls.
|
|
14
|
+
*
|
|
15
|
+
* Default path targets stable VS Code; point KIT_CLINE_DIR at the
|
|
16
|
+
* saoudrizwan.claude-dev dir for Cursor/Insiders/VSCodium installs.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { insertMessage, upsertSession, isFileIndexed, markFileIndexed } from "./db.js";
|
|
22
|
+
function vscodeGlobalStorage() {
|
|
23
|
+
const home = homedir();
|
|
24
|
+
switch (process.platform) {
|
|
25
|
+
case "darwin":
|
|
26
|
+
return join(home, "Library", "Application Support", "Code", "User", "globalStorage");
|
|
27
|
+
case "win32":
|
|
28
|
+
return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Code", "User", "globalStorage");
|
|
29
|
+
default:
|
|
30
|
+
return join(home, ".config", "Code", "User", "globalStorage");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getClineTasksDir() {
|
|
34
|
+
const base = process.env.KIT_CLINE_DIR ?? join(vscodeGlobalStorage(), "saoudrizwan.claude-dev");
|
|
35
|
+
return join(base, "tasks");
|
|
36
|
+
}
|
|
37
|
+
/** Flatten Anthropic content (string | block[]) to plain text. */
|
|
38
|
+
function textOf(content) {
|
|
39
|
+
if (typeof content === "string")
|
|
40
|
+
return content;
|
|
41
|
+
if (Array.isArray(content)) {
|
|
42
|
+
return content
|
|
43
|
+
.map((b) => (b?.type === "text" ? (b.text ?? "") : ""))
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join("\n");
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
function indexTask(db, taskId, filepath) {
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(readFileSync(filepath, "utf8"));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { messages: 0 };
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(parsed))
|
|
58
|
+
return { messages: 0 };
|
|
59
|
+
const sessionId = `cline:${taskId}`;
|
|
60
|
+
upsertSession(db, { sessionId, harness: "cline" });
|
|
61
|
+
let messages = 0;
|
|
62
|
+
parsed.forEach((msg, idx) => {
|
|
63
|
+
if (msg?.role !== "user" && msg?.role !== "assistant")
|
|
64
|
+
return;
|
|
65
|
+
const text = textOf(msg.content).trim();
|
|
66
|
+
if (!text)
|
|
67
|
+
return; // tool-only / image-only turns carry no searchable text
|
|
68
|
+
const added = insertMessage(db, {
|
|
69
|
+
uuid: `cline:${taskId}:${idx}`,
|
|
70
|
+
sessionId,
|
|
71
|
+
type: msg.role,
|
|
72
|
+
role: msg.role,
|
|
73
|
+
content: text,
|
|
74
|
+
});
|
|
75
|
+
if (added)
|
|
76
|
+
messages++;
|
|
77
|
+
});
|
|
78
|
+
return { messages };
|
|
79
|
+
}
|
|
80
|
+
/** Walk Cline's tasks dir and index every api_conversation_history.json. Idempotent + incremental. */
|
|
81
|
+
export function indexClineSessions(db) {
|
|
82
|
+
const result = { files: 0, sessions: 0, messages: 0, toolUses: 0, filesSkipped: 0 };
|
|
83
|
+
const tasksDir = getClineTasksDir();
|
|
84
|
+
if (!existsSync(tasksDir))
|
|
85
|
+
return result;
|
|
86
|
+
let taskDirs;
|
|
87
|
+
try {
|
|
88
|
+
taskDirs = readdirSync(tasksDir, { withFileTypes: true });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
for (const entry of taskDirs) {
|
|
94
|
+
if (!entry.isDirectory())
|
|
95
|
+
continue;
|
|
96
|
+
const filepath = join(tasksDir, entry.name, "api_conversation_history.json");
|
|
97
|
+
let st;
|
|
98
|
+
try {
|
|
99
|
+
st = statSync(filepath);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue; // task without a conversation file → skip
|
|
103
|
+
}
|
|
104
|
+
const mtimeMs = Math.floor(st.mtimeMs);
|
|
105
|
+
if (isFileIndexed(db, filepath, mtimeMs, st.size)) {
|
|
106
|
+
result.filesSkipped++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const counts = indexTask(db, entry.name, filepath);
|
|
110
|
+
markFileIndexed(db, filepath, mtimeMs, st.size);
|
|
111
|
+
result.files++;
|
|
112
|
+
result.sessions++;
|
|
113
|
+
result.messages += counts.messages;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=cline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.js","sourceRoot":"","sources":["../../src/memory/cline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGvF,SAAS,mBAAmB;IAC1B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;QACvF,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;QACxG;YACE,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,mBAAmB,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAChG,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC7B,CAAC;AAWD,kEAAkE;AAClE,SAAS,MAAM,CAAC,OAA8C;IAC5D,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aACtD,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,SAAS,CAAC,EAAgB,EAAE,MAAc,EAAE,QAAgB;IACnE,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAEnD,MAAM,SAAS,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACnD,IAAI,QAAQ,GAAG,CAAC,CAAC;IAChB,MAAyB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,GAAG,EAAE,IAAI,KAAK,MAAM,IAAI,GAAG,EAAE,IAAI,KAAK,WAAW;YAAE,OAAO;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,wDAAwD;QAC3E,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,EAAE;YAC9B,IAAI,EAAE,SAAS,MAAM,IAAI,GAAG,EAAE;YAC9B,SAAS;YACT,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,IAAI,KAAK;YAAE,QAAQ,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,kBAAkB,CAAC,EAAgB;IACjD,MAAM,MAAM,GAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IACjG,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,MAAM,CAAC;IAEzC,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,WAAW,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,+BAA+B,CAAC,CAAC;QAC7E,IAAI,EAAE,CAAC;QACP,IAAI,CAAC;YACH,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,0CAA0C;QACtD,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,YAAY,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACnD,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { IndexResult } from "./parser.js";
|
|
3
|
+
export declare function getCodexSessionsDir(): string;
|
|
4
|
+
/** Walk ~/.codex/sessions and index every rollout. Idempotent + incremental (file_index). */
|
|
5
|
+
export declare function indexCodexSessions(db: DatabaseSync): IndexResult;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kit memory — Codex CLI transcript parser (multi-harness).
|
|
3
|
+
*
|
|
4
|
+
* Reads OpenAI Codex rollout logs from ~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl.
|
|
5
|
+
* Each line is { timestamp, type, payload }: a `session_meta` line carries the
|
|
6
|
+
* session id + cwd; `response_item` lines of payload.type "message" carry the actual
|
|
7
|
+
* turns (role + content blocks). We index user/assistant turns (developer/system
|
|
8
|
+
* context is noise), tag them harness="codex", and synthesize a stable per-message
|
|
9
|
+
* uuid so re-indexing is idempotent. RAW + deterministic; no model calls.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join, basename } from "node:path";
|
|
14
|
+
import { insertMessage, upsertSession, isFileIndexed, markFileIndexed } from "./db.js";
|
|
15
|
+
export function getCodexSessionsDir() {
|
|
16
|
+
const base = process.env.KIT_CODEX_DIR ?? join(homedir(), ".codex");
|
|
17
|
+
return join(base, "sessions");
|
|
18
|
+
}
|
|
19
|
+
/** Flatten Codex content blocks (input_text / output_text / text) to plain text. */
|
|
20
|
+
function textOf(content) {
|
|
21
|
+
if (!Array.isArray(content))
|
|
22
|
+
return "";
|
|
23
|
+
return content
|
|
24
|
+
.map((b) => b?.type === "input_text" || b?.type === "output_text" || b?.type === "text"
|
|
25
|
+
? (b.text ?? "")
|
|
26
|
+
: "")
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.join("\n");
|
|
29
|
+
}
|
|
30
|
+
function* walkRollouts(dir) {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const p = join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory())
|
|
41
|
+
yield* walkRollouts(p);
|
|
42
|
+
else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
43
|
+
yield p;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function indexFile(db, filepath) {
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = readFileSync(filepath, "utf8");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { messages: 0 };
|
|
54
|
+
}
|
|
55
|
+
let sessionId = basename(filepath, ".jsonl");
|
|
56
|
+
let cwd;
|
|
57
|
+
let project;
|
|
58
|
+
let messages = 0;
|
|
59
|
+
let idx = 0;
|
|
60
|
+
let firstTs;
|
|
61
|
+
let lastTs;
|
|
62
|
+
for (const line of raw.split("\n")) {
|
|
63
|
+
const t = line.trim();
|
|
64
|
+
if (!t)
|
|
65
|
+
continue;
|
|
66
|
+
let rec;
|
|
67
|
+
try {
|
|
68
|
+
rec = JSON.parse(t);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (rec.type === "session_meta") {
|
|
74
|
+
const p = rec.payload ?? {};
|
|
75
|
+
if (typeof p.id === "string")
|
|
76
|
+
sessionId = p.id;
|
|
77
|
+
if (typeof p.cwd === "string") {
|
|
78
|
+
cwd = p.cwd;
|
|
79
|
+
project = basename(p.cwd);
|
|
80
|
+
}
|
|
81
|
+
upsertSession(db, { sessionId, harness: "codex", project });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (rec.type === "response_item" && rec.payload?.type === "message") {
|
|
85
|
+
const role = String(rec.payload.role ?? "");
|
|
86
|
+
if (role !== "user" && role !== "assistant")
|
|
87
|
+
continue; // skip developer/system noise
|
|
88
|
+
const ts = rec.timestamp;
|
|
89
|
+
if (ts) {
|
|
90
|
+
if (!firstTs)
|
|
91
|
+
firstTs = ts;
|
|
92
|
+
lastTs = ts;
|
|
93
|
+
}
|
|
94
|
+
const added = insertMessage(db, {
|
|
95
|
+
uuid: `codex:${sessionId}:${idx}`, // stable per-session index → idempotent
|
|
96
|
+
sessionId,
|
|
97
|
+
type: role === "assistant" ? "assistant" : "user",
|
|
98
|
+
role,
|
|
99
|
+
content: textOf(rec.payload.content),
|
|
100
|
+
timestamp: ts,
|
|
101
|
+
cwd,
|
|
102
|
+
});
|
|
103
|
+
idx++;
|
|
104
|
+
if (added)
|
|
105
|
+
messages++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
upsertSession(db, {
|
|
109
|
+
sessionId,
|
|
110
|
+
harness: "codex",
|
|
111
|
+
project,
|
|
112
|
+
firstMessageAt: firstTs,
|
|
113
|
+
lastMessageAt: lastTs,
|
|
114
|
+
});
|
|
115
|
+
return { messages };
|
|
116
|
+
}
|
|
117
|
+
/** Walk ~/.codex/sessions and index every rollout. Idempotent + incremental (file_index). */
|
|
118
|
+
export function indexCodexSessions(db) {
|
|
119
|
+
const dir = getCodexSessionsDir();
|
|
120
|
+
const result = {
|
|
121
|
+
files: 0,
|
|
122
|
+
sessions: 0,
|
|
123
|
+
messages: 0,
|
|
124
|
+
toolUses: 0,
|
|
125
|
+
filesSkipped: 0,
|
|
126
|
+
};
|
|
127
|
+
if (!existsSync(dir))
|
|
128
|
+
return result;
|
|
129
|
+
for (const filepath of walkRollouts(dir)) {
|
|
130
|
+
let st;
|
|
131
|
+
try {
|
|
132
|
+
st = statSync(filepath);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const mtimeMs = Math.floor(st.mtimeMs);
|
|
138
|
+
if (isFileIndexed(db, filepath, mtimeMs, st.size)) {
|
|
139
|
+
result.filesSkipped++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const counts = indexFile(db, filepath);
|
|
143
|
+
markFileIndexed(db, filepath, mtimeMs, st.size);
|
|
144
|
+
result.files++;
|
|
145
|
+
result.sessions++;
|
|
146
|
+
result.messages += counts.messages;
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/memory/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { IndexResult } from "./parser.js";
|
|
3
|
+
export declare function getContinueSessionsDir(): string;
|
|
4
|
+
/** Walk ~/.continue/sessions and index every session JSON. Idempotent + incremental. */
|
|
5
|
+
export declare function indexContinueSessions(db: DatabaseSync): IndexResult;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kit memory — Continue.dev transcript parser (multi-harness).
|
|
3
|
+
*
|
|
4
|
+
* Continue stores each chat as a Session JSON at ~/.continue/sessions/<id>.json
|
|
5
|
+
* (an index lives at sessions.json, which we skip). Types verified against the
|
|
6
|
+
* continuedev/continue source (core/index.d.ts):
|
|
7
|
+
*
|
|
8
|
+
* Session { sessionId, title, workspaceDirectory, history: ChatHistoryItem[] }
|
|
9
|
+
* ChatHistoryItem { message: ChatMessage, ... }
|
|
10
|
+
* ChatMessage { role: 'user'|'assistant'|'system'|'thinking'|'tool', content }
|
|
11
|
+
* MessageContent string | ({ type:'text', text } | { type:'imageUrl', ... })[]
|
|
12
|
+
*
|
|
13
|
+
* We index user + assistant turns, tag harness="continue", and — because Session
|
|
14
|
+
* carries workspaceDirectory — set cwd so Continue recall is project-scoped like
|
|
15
|
+
* Claude Code (not global-only). RAW + deterministic; idempotent; no model calls.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join, basename } from "node:path";
|
|
20
|
+
import { insertMessage, upsertSession, isFileIndexed, markFileIndexed } from "./db.js";
|
|
21
|
+
export function getContinueSessionsDir() {
|
|
22
|
+
const base = process.env.KIT_CONTINUE_DIR ?? join(homedir(), ".continue");
|
|
23
|
+
return join(base, "sessions");
|
|
24
|
+
}
|
|
25
|
+
/** Flatten Continue MessageContent (string or part array) to plain text. */
|
|
26
|
+
function contentText(content) {
|
|
27
|
+
if (typeof content === "string")
|
|
28
|
+
return content;
|
|
29
|
+
if (Array.isArray(content)) {
|
|
30
|
+
return content
|
|
31
|
+
.map((p) => (p?.type === "text" ? (p.text ?? "") : ""))
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join("\n");
|
|
34
|
+
}
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
function indexFile(db, filepath) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(readFileSync(filepath, "utf8"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { messages: 0 };
|
|
44
|
+
}
|
|
45
|
+
if (parsed === null || typeof parsed !== "object")
|
|
46
|
+
return { messages: 0 };
|
|
47
|
+
const session = parsed;
|
|
48
|
+
if (!Array.isArray(session.history))
|
|
49
|
+
return { messages: 0 }; // not a Session (e.g. the index)
|
|
50
|
+
const id = session.sessionId ?? basename(filepath, ".json");
|
|
51
|
+
const sessionId = `continue:${id}`;
|
|
52
|
+
const cwd = session.workspaceDirectory || undefined;
|
|
53
|
+
upsertSession(db, {
|
|
54
|
+
sessionId,
|
|
55
|
+
harness: "continue",
|
|
56
|
+
project: cwd ? basename(cwd) : undefined,
|
|
57
|
+
});
|
|
58
|
+
let messages = 0;
|
|
59
|
+
session.history.forEach((item, idx) => {
|
|
60
|
+
const role = item?.message?.role;
|
|
61
|
+
if (role !== "user" && role !== "assistant")
|
|
62
|
+
return; // skip system/thinking/tool
|
|
63
|
+
const text = contentText(item.message?.content);
|
|
64
|
+
if (!text)
|
|
65
|
+
return;
|
|
66
|
+
const added = insertMessage(db, {
|
|
67
|
+
uuid: `continue:${id}:${idx}`,
|
|
68
|
+
sessionId,
|
|
69
|
+
type: role,
|
|
70
|
+
role,
|
|
71
|
+
content: text,
|
|
72
|
+
cwd,
|
|
73
|
+
});
|
|
74
|
+
if (added)
|
|
75
|
+
messages++;
|
|
76
|
+
});
|
|
77
|
+
return { messages };
|
|
78
|
+
}
|
|
79
|
+
/** Walk ~/.continue/sessions and index every session JSON. Idempotent + incremental. */
|
|
80
|
+
export function indexContinueSessions(db) {
|
|
81
|
+
const dir = getContinueSessionsDir();
|
|
82
|
+
const result = { files: 0, sessions: 0, messages: 0, toolUses: 0, filesSkipped: 0 };
|
|
83
|
+
if (!existsSync(dir))
|
|
84
|
+
return result;
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
for (const e of entries) {
|
|
93
|
+
if (!e.isFile() || !e.name.endsWith(".json") || e.name === "sessions.json")
|
|
94
|
+
continue;
|
|
95
|
+
const filepath = join(dir, e.name);
|
|
96
|
+
let st;
|
|
97
|
+
try {
|
|
98
|
+
st = statSync(filepath);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const mtimeMs = Math.floor(st.mtimeMs);
|
|
104
|
+
if (isFileIndexed(db, filepath, mtimeMs, st.size)) {
|
|
105
|
+
result.filesSkipped++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const counts = indexFile(db, filepath);
|
|
109
|
+
markFileIndexed(db, filepath, mtimeMs, st.size);
|
|
110
|
+
result.files++;
|
|
111
|
+
result.sessions++;
|
|
112
|
+
result.messages += counts.messages;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=continue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continue.js","sourceRoot":"","sources":["../../src/memory/continue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kit memory — Cursor transcript parser (multi-harness).
|
|
3
|
+
*
|
|
4
|
+
* Cursor stores chat in a SQLite DB at globalStorage/state.vscdb, table
|
|
5
|
+
* `cursorDiskKV`: one row per "bubble" (message), keyed
|
|
6
|
+
* `bubbleId:<composerId>:<bubbleId>`, plus `composerData:<id>` session metadata.
|
|
7
|
+
* The bubble value is JSON with `type` (1 = user, 2 = assistant) and `text`.
|
|
8
|
+
*
|
|
9
|
+
* Cursor's store is APP-INTERNAL and community-reverse-engineered (not an
|
|
10
|
+
* official schema), so this parser is deliberately DEFENSIVE: it maps only the
|
|
11
|
+
* known fields and, if the table or shape ever differs, indexes nothing rather
|
|
12
|
+
* than guessing — it can never write WRONG data, only less of it. Read-only,
|
|
13
|
+
* idempotent (uuid = the bubble key), fail-safe, no model calls.
|
|
14
|
+
*
|
|
15
|
+
* Limitation: Cursor keys by composerId, not project path — messages carry no
|
|
16
|
+
* cwd and surface in `--global` recall, not project-scoped search.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, statSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { DatabaseSync } from "node:sqlite";
|
|
22
|
+
import { insertMessage, upsertSession, isFileIndexed, markFileIndexed } from "./db.js";
|
|
23
|
+
export function getCursorStateDb() {
|
|
24
|
+
if (process.env.KIT_CURSOR_DB)
|
|
25
|
+
return process.env.KIT_CURSOR_DB;
|
|
26
|
+
const home = homedir();
|
|
27
|
+
const sub = join("Cursor", "User", "globalStorage", "state.vscdb");
|
|
28
|
+
switch (process.platform) {
|
|
29
|
+
case "darwin":
|
|
30
|
+
return join(home, "Library", "Application Support", sub);
|
|
31
|
+
case "win32":
|
|
32
|
+
return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), sub);
|
|
33
|
+
default:
|
|
34
|
+
return join(home, ".config", sub);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** key = "bubbleId:<composerId>:<bubbleId>" → composerId */
|
|
38
|
+
function composerIdFromKey(key) {
|
|
39
|
+
return key.split(":")[1] ?? "unknown";
|
|
40
|
+
}
|
|
41
|
+
export function indexCursorSessions(db) {
|
|
42
|
+
const result = { files: 0, sessions: 0, messages: 0, toolUses: 0, filesSkipped: 0 };
|
|
43
|
+
const dbPath = getCursorStateDb();
|
|
44
|
+
if (!existsSync(dbPath))
|
|
45
|
+
return result;
|
|
46
|
+
let st;
|
|
47
|
+
try {
|
|
48
|
+
st = statSync(dbPath);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
const mtimeMs = Math.floor(st.mtimeMs);
|
|
54
|
+
if (isFileIndexed(db, dbPath, mtimeMs, st.size)) {
|
|
55
|
+
result.filesSkipped++;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
let src;
|
|
59
|
+
try {
|
|
60
|
+
src = new DatabaseSync(dbPath, { readOnly: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return result; // locked / unreadable while Cursor runs → fail-safe, retry next run
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
let rows;
|
|
67
|
+
try {
|
|
68
|
+
rows = src
|
|
69
|
+
.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'")
|
|
70
|
+
.all();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// table absent / schema differs → fail-safe (index nothing, never wrong data)
|
|
74
|
+
markFileIndexed(db, dbPath, mtimeMs, st.size);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
const sessionsSeen = new Set();
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
let bubble;
|
|
80
|
+
try {
|
|
81
|
+
bubble = JSON.parse(row.value);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (bubble.type !== 1 && bubble.type !== 2)
|
|
87
|
+
continue; // only user/assistant turns
|
|
88
|
+
const text = typeof bubble.text === "string" ? bubble.text.trim() : "";
|
|
89
|
+
if (!text)
|
|
90
|
+
continue; // tool-only / empty bubbles carry no searchable text
|
|
91
|
+
const sessionId = `cursor:${composerIdFromKey(row.key)}`;
|
|
92
|
+
if (!sessionsSeen.has(sessionId)) {
|
|
93
|
+
upsertSession(db, { sessionId, harness: "cursor" });
|
|
94
|
+
sessionsSeen.add(sessionId);
|
|
95
|
+
}
|
|
96
|
+
const role = bubble.type === 1 ? "user" : "assistant";
|
|
97
|
+
const added = insertMessage(db, {
|
|
98
|
+
uuid: `cursor:${row.key}`, // stable per bubble → idempotent
|
|
99
|
+
sessionId,
|
|
100
|
+
type: role,
|
|
101
|
+
role,
|
|
102
|
+
content: text,
|
|
103
|
+
});
|
|
104
|
+
if (added)
|
|
105
|
+
result.messages++;
|
|
106
|
+
}
|
|
107
|
+
result.sessions += sessionsSeen.size;
|
|
108
|
+
result.files++;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
src.close();
|
|
112
|
+
}
|
|
113
|
+
markFileIndexed(db, dbPath, mtimeMs, st.size);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/memory/cursor.ts"],"names":[],"mappings":"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"}
|
package/dist/memory/db.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { DatabaseSync } from "node:sqlite";
|
|
16
16
|
import type { MemoryStats, MessageInput, SearchHit, SessionInput, ToolUseInput } from "./types.js";
|
|
17
|
-
export declare const SCHEMA_VERSION =
|
|
17
|
+
export declare const SCHEMA_VERSION = 3;
|
|
18
18
|
export declare function getMemoryDir(): string;
|
|
19
19
|
export declare function getMemoryDbPath(): string;
|
|
20
20
|
/**
|
|
@@ -22,6 +22,10 @@ export declare function getMemoryDbPath(): string;
|
|
|
22
22
|
* an ephemeral in-process DB (tests). Otherwise defaults to ~/.kit/memory.db.
|
|
23
23
|
*/
|
|
24
24
|
export declare function openMemoryDb(path?: string): DatabaseSync;
|
|
25
|
+
/** Has this file already been indexed at exactly this mtime + size? (incremental index) */
|
|
26
|
+
export declare function isFileIndexed(db: DatabaseSync, path: string, mtimeMs: number, size: number): boolean;
|
|
27
|
+
/** Record (or refresh) a file's mtime + size after indexing it. */
|
|
28
|
+
export declare function markFileIndexed(db: DatabaseSync, path: string, mtimeMs: number, size: number): void;
|
|
25
29
|
export declare function upsertSession(db: DatabaseSync, s: SessionInput): void;
|
|
26
30
|
/** Insert a message idempotently (by uuid). Returns true if a new row was added. */
|
|
27
31
|
export declare function insertMessage(db: DatabaseSync, m: MessageInput): boolean;
|
|
@@ -37,4 +41,11 @@ export interface SearchOptions {
|
|
|
37
41
|
* for cross-project ("--global") recall over the personal store.
|
|
38
42
|
*/
|
|
39
43
|
export declare function searchMessages(db: DatabaseSync, query: string, opts?: SearchOptions): SearchHit[];
|
|
44
|
+
/**
|
|
45
|
+
* Most-recent messages by wall-clock time (newest first) — the basis for session
|
|
46
|
+
* recovery (re-injecting "where you left off" after a compaction/resume). Unlike
|
|
47
|
+
* searchMessages this needs no query; pass opts.projectPath to scope to one repo.
|
|
48
|
+
* Skips empty-content rows so the recovery block stays signal, not blank tool turns.
|
|
49
|
+
*/
|
|
50
|
+
export declare function recentMessages(db: DatabaseSync, opts?: SearchOptions): SearchHit[];
|
|
40
51
|
export declare function getStats(db: DatabaseSync): MemoryStats;
|
package/dist/memory/db.js
CHANGED
|
@@ -16,7 +16,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { existsSync, mkdirSync, chmodSync, statSync } from "node:fs";
|
|
19
|
-
export const SCHEMA_VERSION =
|
|
19
|
+
export const SCHEMA_VERSION = 3;
|
|
20
20
|
export function getMemoryDir() {
|
|
21
21
|
return process.env.KIT_MEMORY_DIR ?? join(homedir(), ".kit");
|
|
22
22
|
}
|
|
@@ -89,6 +89,13 @@ CREATE TABLE IF NOT EXISTS pending_actions (
|
|
|
89
89
|
verify_passes INTEGER NOT NULL DEFAULT 0
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
+
CREATE TABLE IF NOT EXISTS file_index (
|
|
93
|
+
path TEXT PRIMARY KEY,
|
|
94
|
+
mtime_ms INTEGER NOT NULL,
|
|
95
|
+
size INTEGER NOT NULL,
|
|
96
|
+
indexed_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
97
|
+
);
|
|
98
|
+
|
|
92
99
|
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
|
93
100
|
CREATE INDEX IF NOT EXISTS idx_messages_type ON messages(type);
|
|
94
101
|
CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp);
|
|
@@ -155,6 +162,18 @@ export function openMemoryDb(path) {
|
|
|
155
162
|
}
|
|
156
163
|
return db;
|
|
157
164
|
}
|
|
165
|
+
/** Has this file already been indexed at exactly this mtime + size? (incremental index) */
|
|
166
|
+
export function isFileIndexed(db, path, mtimeMs, size) {
|
|
167
|
+
return !!db
|
|
168
|
+
.prepare("SELECT 1 FROM file_index WHERE path = ? AND mtime_ms = ? AND size = ?")
|
|
169
|
+
.get(path, mtimeMs, size);
|
|
170
|
+
}
|
|
171
|
+
/** Record (or refresh) a file's mtime + size after indexing it. */
|
|
172
|
+
export function markFileIndexed(db, path, mtimeMs, size) {
|
|
173
|
+
db.prepare(`INSERT INTO file_index (path, mtime_ms, size, indexed_at)
|
|
174
|
+
VALUES (?, ?, ?, datetime('now'))
|
|
175
|
+
ON CONFLICT(path) DO UPDATE SET mtime_ms = excluded.mtime_ms, size = excluded.size, indexed_at = datetime('now')`).run(path, mtimeMs, size);
|
|
176
|
+
}
|
|
158
177
|
export function upsertSession(db, s) {
|
|
159
178
|
db.prepare(`INSERT INTO sessions (session_id, harness, project, first_message_at, last_message_at, is_agent_sidechain)
|
|
160
179
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -206,6 +225,29 @@ export function searchMessages(db, query, opts = {}) {
|
|
|
206
225
|
LIMIT ?`)
|
|
207
226
|
.all(...params);
|
|
208
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Most-recent messages by wall-clock time (newest first) — the basis for session
|
|
230
|
+
* recovery (re-injecting "where you left off" after a compaction/resume). Unlike
|
|
231
|
+
* searchMessages this needs no query; pass opts.projectPath to scope to one repo.
|
|
232
|
+
* Skips empty-content rows so the recovery block stays signal, not blank tool turns.
|
|
233
|
+
*/
|
|
234
|
+
export function recentMessages(db, opts = {}) {
|
|
235
|
+
const limit = opts.limit ?? 10;
|
|
236
|
+
const params = [];
|
|
237
|
+
let where = "content IS NOT NULL AND content != ''";
|
|
238
|
+
if (opts.projectPath) {
|
|
239
|
+
where += " AND (cwd = ? OR cwd LIKE ?)";
|
|
240
|
+
params.push(opts.projectPath, `${opts.projectPath}/%`);
|
|
241
|
+
}
|
|
242
|
+
params.push(limit);
|
|
243
|
+
return db
|
|
244
|
+
.prepare(`SELECT id, uuid, session_id AS sessionId, role, content, timestamp
|
|
245
|
+
FROM messages
|
|
246
|
+
WHERE ${where}
|
|
247
|
+
ORDER BY timestamp DESC, id DESC
|
|
248
|
+
LIMIT ?`)
|
|
249
|
+
.all(...params);
|
|
250
|
+
}
|
|
209
251
|
export function getStats(db) {
|
|
210
252
|
const count = (sql) => {
|
|
211
253
|
const r = db.prepare(sql).get();
|