@loreai/core 0.17.1 → 0.19.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/dist/bun/agents-file.d.ts +4 -0
- package/dist/bun/agents-file.d.ts.map +1 -1
- package/dist/bun/config.d.ts +2 -0
- package/dist/bun/config.d.ts.map +1 -1
- package/dist/bun/curator.d.ts +45 -0
- package/dist/bun/curator.d.ts.map +1 -1
- package/dist/bun/data-dir.d.ts +18 -0
- package/dist/bun/data-dir.d.ts.map +1 -0
- package/dist/bun/db.d.ts +85 -0
- package/dist/bun/db.d.ts.map +1 -1
- package/dist/bun/distillation.d.ts +2 -13
- package/dist/bun/distillation.d.ts.map +1 -1
- package/dist/bun/embedding-vendor.d.ts +22 -38
- package/dist/bun/embedding-vendor.d.ts.map +1 -1
- package/dist/bun/embedding-worker-types.d.ts +17 -12
- package/dist/bun/embedding-worker-types.d.ts.map +1 -1
- package/dist/bun/embedding-worker.d.ts +9 -2
- package/dist/bun/embedding-worker.d.ts.map +1 -1
- package/dist/bun/embedding-worker.js +38864 -33
- package/dist/bun/embedding-worker.js.map +4 -4
- package/dist/bun/embedding.d.ts +35 -23
- package/dist/bun/embedding.d.ts.map +1 -1
- package/dist/bun/gradient.d.ts +17 -1
- package/dist/bun/gradient.d.ts.map +1 -1
- package/dist/bun/import/detect.d.ts +14 -0
- package/dist/bun/import/detect.d.ts.map +1 -0
- package/dist/bun/import/extract.d.ts +43 -0
- package/dist/bun/import/extract.d.ts.map +1 -0
- package/dist/bun/import/history.d.ts +40 -0
- package/dist/bun/import/history.d.ts.map +1 -0
- package/dist/bun/import/index.d.ts +17 -0
- package/dist/bun/import/index.d.ts.map +1 -0
- package/dist/bun/import/providers/aider.d.ts +2 -0
- package/dist/bun/import/providers/aider.d.ts.map +1 -0
- package/dist/bun/import/providers/claude-code.d.ts +2 -0
- package/dist/bun/import/providers/claude-code.d.ts.map +1 -0
- package/dist/bun/import/providers/cline.d.ts +2 -0
- package/dist/bun/import/providers/cline.d.ts.map +1 -0
- package/dist/bun/import/providers/codex.d.ts +2 -0
- package/dist/bun/import/providers/codex.d.ts.map +1 -0
- package/dist/bun/import/providers/continue.d.ts +2 -0
- package/dist/bun/import/providers/continue.d.ts.map +1 -0
- package/dist/bun/import/providers/index.d.ts +19 -0
- package/dist/bun/import/providers/index.d.ts.map +1 -0
- package/dist/bun/import/providers/opencode.d.ts +2 -0
- package/dist/bun/import/providers/opencode.d.ts.map +1 -0
- package/dist/bun/import/providers/pi.d.ts +2 -0
- package/dist/bun/import/providers/pi.d.ts.map +1 -0
- package/dist/bun/import/types.d.ts +82 -0
- package/dist/bun/import/types.d.ts.map +1 -0
- package/dist/bun/index.d.ts +5 -2
- package/dist/bun/index.d.ts.map +1 -1
- package/dist/bun/index.js +3150 -439
- package/dist/bun/index.js.map +4 -4
- package/dist/bun/instruction-detect.d.ts +66 -0
- package/dist/bun/instruction-detect.d.ts.map +1 -0
- package/dist/bun/log.d.ts +9 -0
- package/dist/bun/log.d.ts.map +1 -1
- package/dist/bun/ltm.d.ts +139 -5
- package/dist/bun/ltm.d.ts.map +1 -1
- package/dist/bun/pattern-extract.d.ts +7 -0
- package/dist/bun/pattern-extract.d.ts.map +1 -1
- package/dist/bun/prompt.d.ts +1 -1
- package/dist/bun/prompt.d.ts.map +1 -1
- package/dist/bun/recall.d.ts.map +1 -1
- package/dist/bun/search.d.ts +5 -3
- package/dist/bun/search.d.ts.map +1 -1
- package/dist/bun/session-limiter.d.ts +26 -0
- package/dist/bun/session-limiter.d.ts.map +1 -0
- package/dist/bun/temporal.d.ts +2 -0
- package/dist/bun/temporal.d.ts.map +1 -1
- package/dist/bun/types.d.ts +1 -1
- package/dist/node/agents-file.d.ts +4 -0
- package/dist/node/agents-file.d.ts.map +1 -1
- package/dist/node/config.d.ts +2 -0
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/curator.d.ts +45 -0
- package/dist/node/curator.d.ts.map +1 -1
- package/dist/node/data-dir.d.ts +18 -0
- package/dist/node/data-dir.d.ts.map +1 -0
- package/dist/node/db.d.ts +85 -0
- package/dist/node/db.d.ts.map +1 -1
- package/dist/node/distillation.d.ts +2 -13
- package/dist/node/distillation.d.ts.map +1 -1
- package/dist/node/embedding-vendor.d.ts +22 -38
- package/dist/node/embedding-vendor.d.ts.map +1 -1
- package/dist/node/embedding-worker-types.d.ts +17 -12
- package/dist/node/embedding-worker-types.d.ts.map +1 -1
- package/dist/node/embedding-worker.d.ts +9 -2
- package/dist/node/embedding-worker.d.ts.map +1 -1
- package/dist/node/embedding-worker.js +38864 -33
- package/dist/node/embedding-worker.js.map +4 -4
- package/dist/node/embedding.d.ts +35 -23
- package/dist/node/embedding.d.ts.map +1 -1
- package/dist/node/gradient.d.ts +17 -1
- package/dist/node/gradient.d.ts.map +1 -1
- package/dist/node/import/detect.d.ts +14 -0
- package/dist/node/import/detect.d.ts.map +1 -0
- package/dist/node/import/extract.d.ts +43 -0
- package/dist/node/import/extract.d.ts.map +1 -0
- package/dist/node/import/history.d.ts +40 -0
- package/dist/node/import/history.d.ts.map +1 -0
- package/dist/node/import/index.d.ts +17 -0
- package/dist/node/import/index.d.ts.map +1 -0
- package/dist/node/import/providers/aider.d.ts +2 -0
- package/dist/node/import/providers/aider.d.ts.map +1 -0
- package/dist/node/import/providers/claude-code.d.ts +2 -0
- package/dist/node/import/providers/claude-code.d.ts.map +1 -0
- package/dist/node/import/providers/cline.d.ts +2 -0
- package/dist/node/import/providers/cline.d.ts.map +1 -0
- package/dist/node/import/providers/codex.d.ts +2 -0
- package/dist/node/import/providers/codex.d.ts.map +1 -0
- package/dist/node/import/providers/continue.d.ts +2 -0
- package/dist/node/import/providers/continue.d.ts.map +1 -0
- package/dist/node/import/providers/index.d.ts +19 -0
- package/dist/node/import/providers/index.d.ts.map +1 -0
- package/dist/node/import/providers/opencode.d.ts +2 -0
- package/dist/node/import/providers/opencode.d.ts.map +1 -0
- package/dist/node/import/providers/pi.d.ts +2 -0
- package/dist/node/import/providers/pi.d.ts.map +1 -0
- package/dist/node/import/types.d.ts +82 -0
- package/dist/node/import/types.d.ts.map +1 -0
- package/dist/node/index.d.ts +5 -2
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +3150 -439
- package/dist/node/index.js.map +4 -4
- package/dist/node/instruction-detect.d.ts +66 -0
- package/dist/node/instruction-detect.d.ts.map +1 -0
- package/dist/node/log.d.ts +9 -0
- package/dist/node/log.d.ts.map +1 -1
- package/dist/node/ltm.d.ts +139 -5
- package/dist/node/ltm.d.ts.map +1 -1
- package/dist/node/pattern-extract.d.ts +7 -0
- package/dist/node/pattern-extract.d.ts.map +1 -1
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.d.ts.map +1 -1
- package/dist/node/recall.d.ts.map +1 -1
- package/dist/node/search.d.ts +5 -3
- package/dist/node/search.d.ts.map +1 -1
- package/dist/node/session-limiter.d.ts +26 -0
- package/dist/node/session-limiter.d.ts.map +1 -0
- package/dist/node/temporal.d.ts +2 -0
- package/dist/node/temporal.d.ts.map +1 -1
- package/dist/node/types.d.ts +1 -1
- package/dist/types/agents-file.d.ts +4 -0
- package/dist/types/agents-file.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/curator.d.ts +45 -0
- package/dist/types/curator.d.ts.map +1 -1
- package/dist/types/data-dir.d.ts +18 -0
- package/dist/types/data-dir.d.ts.map +1 -0
- package/dist/types/db.d.ts +85 -0
- package/dist/types/db.d.ts.map +1 -1
- package/dist/types/distillation.d.ts +2 -13
- package/dist/types/distillation.d.ts.map +1 -1
- package/dist/types/embedding-vendor.d.ts +22 -38
- package/dist/types/embedding-vendor.d.ts.map +1 -1
- package/dist/types/embedding-worker-types.d.ts +17 -12
- package/dist/types/embedding-worker-types.d.ts.map +1 -1
- package/dist/types/embedding-worker.d.ts +9 -2
- package/dist/types/embedding-worker.d.ts.map +1 -1
- package/dist/types/embedding.d.ts +35 -23
- package/dist/types/embedding.d.ts.map +1 -1
- package/dist/types/gradient.d.ts +17 -1
- package/dist/types/gradient.d.ts.map +1 -1
- package/dist/types/import/detect.d.ts +14 -0
- package/dist/types/import/detect.d.ts.map +1 -0
- package/dist/types/import/extract.d.ts +43 -0
- package/dist/types/import/extract.d.ts.map +1 -0
- package/dist/types/import/history.d.ts +40 -0
- package/dist/types/import/history.d.ts.map +1 -0
- package/dist/types/import/index.d.ts +17 -0
- package/dist/types/import/index.d.ts.map +1 -0
- package/dist/types/import/providers/aider.d.ts +2 -0
- package/dist/types/import/providers/aider.d.ts.map +1 -0
- package/dist/types/import/providers/claude-code.d.ts +2 -0
- package/dist/types/import/providers/claude-code.d.ts.map +1 -0
- package/dist/types/import/providers/cline.d.ts +2 -0
- package/dist/types/import/providers/cline.d.ts.map +1 -0
- package/dist/types/import/providers/codex.d.ts +2 -0
- package/dist/types/import/providers/codex.d.ts.map +1 -0
- package/dist/types/import/providers/continue.d.ts +2 -0
- package/dist/types/import/providers/continue.d.ts.map +1 -0
- package/dist/types/import/providers/index.d.ts +19 -0
- package/dist/types/import/providers/index.d.ts.map +1 -0
- package/dist/types/import/providers/opencode.d.ts +2 -0
- package/dist/types/import/providers/opencode.d.ts.map +1 -0
- package/dist/types/import/providers/pi.d.ts +2 -0
- package/dist/types/import/providers/pi.d.ts.map +1 -0
- package/dist/types/import/types.d.ts +82 -0
- package/dist/types/import/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/instruction-detect.d.ts +66 -0
- package/dist/types/instruction-detect.d.ts.map +1 -0
- package/dist/types/log.d.ts +9 -0
- package/dist/types/log.d.ts.map +1 -1
- package/dist/types/ltm.d.ts +139 -5
- package/dist/types/ltm.d.ts.map +1 -1
- package/dist/types/pattern-extract.d.ts +7 -0
- package/dist/types/pattern-extract.d.ts.map +1 -1
- package/dist/types/prompt.d.ts +1 -1
- package/dist/types/prompt.d.ts.map +1 -1
- package/dist/types/recall.d.ts.map +1 -1
- package/dist/types/search.d.ts +5 -3
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/session-limiter.d.ts +26 -0
- package/dist/types/session-limiter.d.ts.map +1 -0
- package/dist/types/temporal.d.ts +2 -0
- package/dist/types/temporal.d.ts.map +1 -1
- package/dist/types/types.d.ts +1 -1
- package/package.json +3 -4
- package/src/agents-file.ts +41 -13
- package/src/config.ts +31 -18
- package/src/curator.ts +163 -75
- package/src/data-dir.ts +76 -0
- package/src/db.ts +457 -11
- package/src/distillation.ts +65 -16
- package/src/embedding-vendor.ts +23 -40
- package/src/embedding-worker-types.ts +19 -11
- package/src/embedding-worker.ts +111 -47
- package/src/embedding.ts +224 -174
- package/src/gradient.ts +192 -75
- package/src/import/detect.ts +37 -0
- package/src/import/extract.ts +137 -0
- package/src/import/history.ts +99 -0
- package/src/import/index.ts +45 -0
- package/src/import/providers/aider.ts +207 -0
- package/src/import/providers/claude-code.ts +339 -0
- package/src/import/providers/cline.ts +324 -0
- package/src/import/providers/codex.ts +369 -0
- package/src/import/providers/continue.ts +304 -0
- package/src/import/providers/index.ts +32 -0
- package/src/import/providers/opencode.ts +272 -0
- package/src/import/providers/pi.ts +332 -0
- package/src/import/types.ts +91 -0
- package/src/index.ts +13 -0
- package/src/instruction-detect.ts +275 -0
- package/src/log.ts +91 -3
- package/src/ltm.ts +789 -41
- package/src/pattern-extract.ts +41 -0
- package/src/prompt.ts +7 -1
- package/src/recall.ts +43 -5
- package/src/search.ts +7 -5
- package/src/session-limiter.ts +47 -0
- package/src/temporal.ts +18 -6
- package/src/types.ts +1 -1
package/src/data-dir.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared data-directory path resolution with one-time migration from the
|
|
3
|
+
* legacy `opencode-lore` directory name to `lore`.
|
|
4
|
+
*
|
|
5
|
+
* Both `db.ts` and `log.ts` need the data directory path. This module
|
|
6
|
+
* provides a single source of truth so the path logic is not duplicated.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, renameSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
|
|
13
|
+
const OLD_DIR_NAME = "opencode-lore";
|
|
14
|
+
const NEW_DIR_NAME = "lore";
|
|
15
|
+
|
|
16
|
+
let migrationAttempted = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compute the XDG-compliant base directory for lore data.
|
|
20
|
+
* Respects `$XDG_DATA_HOME`, defaults to `~/.local/share`.
|
|
21
|
+
*/
|
|
22
|
+
function baseDir(): string {
|
|
23
|
+
return process.env.XDG_DATA_HOME || join(homedir(), ".local", "share");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Attempt a one-time migration of the legacy data directory.
|
|
28
|
+
*
|
|
29
|
+
* - Old exists, new does not → atomic `renameSync` (same filesystem).
|
|
30
|
+
* - Both exist → keep new (user already migrated or has fresh data).
|
|
31
|
+
* - Neither exists → no-op; callers create the dir via `mkdirSync`.
|
|
32
|
+
*
|
|
33
|
+
* Runs at most once per process. Errors are swallowed — migration
|
|
34
|
+
* failure is not fatal because callers create the directory anyway.
|
|
35
|
+
*/
|
|
36
|
+
function migrateDataDir(): void {
|
|
37
|
+
if (migrationAttempted) return;
|
|
38
|
+
migrationAttempted = true;
|
|
39
|
+
|
|
40
|
+
// Tests use LORE_DB_PATH pointed at a temp dir; never touch the real
|
|
41
|
+
// data directory.
|
|
42
|
+
if (process.env.NODE_ENV === "test") return;
|
|
43
|
+
|
|
44
|
+
const base = baseDir();
|
|
45
|
+
const oldDir = join(base, OLD_DIR_NAME);
|
|
46
|
+
const newDir = join(base, NEW_DIR_NAME);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(oldDir) && !existsSync(newDir)) {
|
|
50
|
+
renameSync(oldDir, newDir);
|
|
51
|
+
// Can't use the lore logger here (circular dep), so use stderr.
|
|
52
|
+
console.error(`[lore] migrated data directory: ${oldDir} → ${newDir}`);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Permission error, cross-device rename, concurrent process already
|
|
56
|
+
// renamed it, etc. Not fatal — dataDir() returns the new path and
|
|
57
|
+
// callers create the directory if it doesn't exist yet.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Return the resolved data directory path (`~/.local/share/lore` by
|
|
63
|
+
* default), running the legacy-directory migration on the first call.
|
|
64
|
+
*
|
|
65
|
+
* **Callers are responsible for creating the directory** — this function
|
|
66
|
+
* does not call `mkdirSync`.
|
|
67
|
+
*/
|
|
68
|
+
export function dataDir(): string {
|
|
69
|
+
migrateDataDir();
|
|
70
|
+
return join(baseDir(), NEW_DIR_NAME);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @internal Visible for testing only — resets the one-shot guard. */
|
|
74
|
+
export function _resetMigrationFlag(): void {
|
|
75
|
+
migrationAttempted = false;
|
|
76
|
+
}
|
package/src/db.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Database } from "#db/driver";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { mkdirSync } from "fs";
|
|
4
|
-
import { homedir } from "os";
|
|
5
4
|
import { getGitRemote } from "./git";
|
|
5
|
+
import { dataDir } from "./data-dir";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Extract the repository name from a normalized git remote URL.
|
|
@@ -453,14 +453,119 @@ const MIGRATIONS: string[] = [
|
|
|
453
453
|
ALTER TABLE session_state ADD COLUMN ttl_hits INTEGER NOT NULL DEFAULT 0;
|
|
454
454
|
ALTER TABLE session_state ADD COLUMN batch_savings REAL NOT NULL DEFAULT 0;
|
|
455
455
|
`,
|
|
456
|
+
`
|
|
457
|
+
-- Version 19: Import history for conversation import idempotency.
|
|
458
|
+
-- Tracks which external agent sessions have been imported to prevent
|
|
459
|
+
-- re-importing unchanged sources and to record user-declined imports.
|
|
460
|
+
CREATE TABLE IF NOT EXISTS import_history (
|
|
461
|
+
id TEXT PRIMARY KEY,
|
|
462
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
463
|
+
agent_name TEXT NOT NULL,
|
|
464
|
+
source_id TEXT NOT NULL,
|
|
465
|
+
source_hash TEXT NOT NULL,
|
|
466
|
+
entries_created INTEGER NOT NULL DEFAULT 0,
|
|
467
|
+
entries_updated INTEGER NOT NULL DEFAULT 0,
|
|
468
|
+
imported_at INTEGER NOT NULL,
|
|
469
|
+
UNIQUE(project_id, agent_name, source_id)
|
|
470
|
+
);
|
|
471
|
+
CREATE INDEX IF NOT EXISTS idx_import_history_project ON import_history(project_id);
|
|
472
|
+
`,
|
|
473
|
+
`
|
|
474
|
+
-- Version 20: Purge worker boilerplate from temporal messages.
|
|
475
|
+
-- Legacy gateway/plugin worker calls (distillation observer, curator,
|
|
476
|
+
-- consolidation, reflector, eval) stored their full system prompts
|
|
477
|
+
-- (containing entire conversation transcripts, up to 1.6MB each) as
|
|
478
|
+
-- temporal messages. These pollute FTS search results by matching
|
|
479
|
+
-- virtually any domain keyword. Safe to delete: their actual output
|
|
480
|
+
-- (distillations, knowledge entries) is stored in dedicated tables.
|
|
481
|
+
DELETE FROM temporal_messages WHERE content LIKE '%You are a memory observer.%'
|
|
482
|
+
OR content LIKE '%You are a long-term memory curator.%'
|
|
483
|
+
OR content LIKE '%You are a long-term memory curator performing a consolidation pass.%'
|
|
484
|
+
OR content LIKE '%You are a memory reflector.%'
|
|
485
|
+
OR content LIKE '%You are evaluating distillation quality.%';
|
|
486
|
+
`,
|
|
487
|
+
`
|
|
488
|
+
-- Version 21: Persist avoided compaction data from live sessions.
|
|
489
|
+
-- Historical estimates previously re-simulated avoided compactions from
|
|
490
|
+
-- temporal message token estimates (chars/3), missing system prompt and
|
|
491
|
+
-- tool definition overhead. Persisting the live session's real shadow
|
|
492
|
+
-- context tracking (from actual API-reported total input tokens) gives
|
|
493
|
+
-- accurate post-restart historical estimates.
|
|
494
|
+
ALTER TABLE session_state ADD COLUMN avoided_compactions INTEGER NOT NULL DEFAULT 0;
|
|
495
|
+
ALTER TABLE session_state ADD COLUMN avoided_compaction_cost REAL NOT NULL DEFAULT 0;
|
|
496
|
+
`,
|
|
497
|
+
`
|
|
498
|
+
-- Version 22: Track when conversation import was last offered/run.
|
|
499
|
+
-- NULL means import has never been offered for this project.
|
|
500
|
+
-- Used by auto-import to avoid re-prompting, and by explicit
|
|
501
|
+
-- \`lore import\` for incremental imports (only newer conversations).
|
|
502
|
+
ALTER TABLE projects ADD COLUMN last_import_at INTEGER;
|
|
503
|
+
|
|
504
|
+
-- Backfill: migrate legacy __declined__ sentinel rows so existing
|
|
505
|
+
-- users who previously declined are not re-prompted after upgrading.
|
|
506
|
+
UPDATE projects SET last_import_at = (
|
|
507
|
+
SELECT ih.imported_at FROM import_history ih
|
|
508
|
+
WHERE ih.project_id = projects.id
|
|
509
|
+
AND ih.source_id = '__declined__'
|
|
510
|
+
LIMIT 1
|
|
511
|
+
)
|
|
512
|
+
WHERE EXISTS (
|
|
513
|
+
SELECT 1 FROM import_history ih
|
|
514
|
+
WHERE ih.project_id = projects.id
|
|
515
|
+
AND ih.source_id = '__declined__'
|
|
516
|
+
);
|
|
517
|
+
`,
|
|
518
|
+
`
|
|
519
|
+
-- Version 23: Persist volatile session tracking state across restarts.
|
|
520
|
+
-- Previously these were in-memory only, causing duplicate processing,
|
|
521
|
+
-- false compaction detection, and expensive prompt cache busts on restart.
|
|
522
|
+
ALTER TABLE session_state ADD COLUMN last_curated_at INTEGER NOT NULL DEFAULT 0;
|
|
523
|
+
ALTER TABLE session_state ADD COLUMN message_count INTEGER NOT NULL DEFAULT 0;
|
|
524
|
+
ALTER TABLE session_state ADD COLUMN turns_since_curation INTEGER NOT NULL DEFAULT 0;
|
|
525
|
+
ALTER TABLE session_state ADD COLUMN ltm_cache_text TEXT;
|
|
526
|
+
ALTER TABLE session_state ADD COLUMN ltm_cache_tokens INTEGER;
|
|
527
|
+
ALTER TABLE session_state ADD COLUMN ltm_pin_text TEXT;
|
|
528
|
+
ALTER TABLE session_state ADD COLUMN ltm_pin_tokens INTEGER;
|
|
529
|
+
ALTER TABLE session_state ADD COLUMN consecutive_text_only_turns INTEGER NOT NULL DEFAULT 0;
|
|
530
|
+
`,
|
|
531
|
+
`
|
|
532
|
+
-- Version 24: Persist remaining volatile session state across restarts.
|
|
533
|
+
-- Session identity (Tier 1/2/3 session correlation)
|
|
534
|
+
ALTER TABLE session_state ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
|
|
535
|
+
ALTER TABLE session_state ADD COLUMN header_session_id TEXT;
|
|
536
|
+
ALTER TABLE session_state ADD COLUMN header_name TEXT;
|
|
537
|
+
-- Cache warming state
|
|
538
|
+
ALTER TABLE session_state ADD COLUMN resolved_conversation_ttl TEXT NOT NULL DEFAULT '5m';
|
|
539
|
+
ALTER TABLE session_state ADD COLUMN warmup_state TEXT;
|
|
540
|
+
-- Gradient calibration state (survives restarts to avoid uncalibrated busts)
|
|
541
|
+
ALTER TABLE session_state ADD COLUMN dynamic_context_cap REAL NOT NULL DEFAULT 0;
|
|
542
|
+
ALTER TABLE session_state ADD COLUMN bust_rate_ema REAL NOT NULL DEFAULT -1;
|
|
543
|
+
ALTER TABLE session_state ADD COLUMN inter_bust_interval_ema REAL NOT NULL DEFAULT -1;
|
|
544
|
+
ALTER TABLE session_state ADD COLUMN last_layer INTEGER NOT NULL DEFAULT 0;
|
|
545
|
+
ALTER TABLE session_state ADD COLUMN last_known_input INTEGER NOT NULL DEFAULT 0;
|
|
546
|
+
ALTER TABLE session_state ADD COLUMN last_turn_at INTEGER NOT NULL DEFAULT 0;
|
|
547
|
+
ALTER TABLE session_state ADD COLUMN last_bust_at INTEGER NOT NULL DEFAULT 0;
|
|
548
|
+
`,
|
|
549
|
+
`
|
|
550
|
+
-- Version 25: Adaptive dedup threshold — store accept/reject feedback
|
|
551
|
+
-- on embedding-based duplicate pairs for per-project threshold calibration.
|
|
552
|
+
-- Titles stored instead of FK IDs because entries are deleted during dedup;
|
|
553
|
+
-- the similarity float is the actual calibration input.
|
|
554
|
+
CREATE TABLE IF NOT EXISTS dedup_feedback (
|
|
555
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
556
|
+
project_id TEXT,
|
|
557
|
+
entry_a_title TEXT NOT NULL,
|
|
558
|
+
entry_b_title TEXT NOT NULL,
|
|
559
|
+
similarity REAL NOT NULL,
|
|
560
|
+
accepted INTEGER NOT NULL,
|
|
561
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
562
|
+
created_at INTEGER NOT NULL
|
|
563
|
+
);
|
|
564
|
+
CREATE INDEX IF NOT EXISTS idx_dedup_feedback_project
|
|
565
|
+
ON dedup_feedback(project_id);
|
|
566
|
+
`,
|
|
456
567
|
];
|
|
457
568
|
|
|
458
|
-
function dataDir() {
|
|
459
|
-
const xdg = process.env.XDG_DATA_HOME;
|
|
460
|
-
const base = xdg || join(homedir(), ".local", "share");
|
|
461
|
-
return join(base, "opencode-lore");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
569
|
/** Return the resolved path of the SQLite database file. */
|
|
465
570
|
export function dbPath(): string {
|
|
466
571
|
const envPath = process.env.LORE_DB_PATH;
|
|
@@ -709,6 +814,21 @@ export function close() {
|
|
|
709
814
|
* git_remote was not yet populated (pre-v14 rows), it is backfilled lazily.
|
|
710
815
|
*/
|
|
711
816
|
export function ensureProject(path: string, name?: string): string {
|
|
817
|
+
// Guard: reject synthetic test paths when targeting the production DB.
|
|
818
|
+
// Test paths like "/test/ltm/project" are absolute paths that don't exist
|
|
819
|
+
// on any real filesystem — they're only valid in test suites running against
|
|
820
|
+
// a temp DB (LORE_DB_PATH set by test preload). If we see such a path
|
|
821
|
+
// without LORE_DB_PATH being set, a test is likely hitting the production DB.
|
|
822
|
+
// Note: LORE_DB_PATH unset is used as a proxy for "production DB". This
|
|
823
|
+
// wouldn't catch the unlikely case of someone explicitly setting LORE_DB_PATH
|
|
824
|
+
// to the default production path, but that's not a realistic scenario.
|
|
825
|
+
if (!process.env.LORE_DB_PATH && /^\/test\//.test(path)) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
`Refusing to create project with test path "${path}" in the production DB. ` +
|
|
828
|
+
`Set LORE_DB_PATH to a temp path, or run tests via \`bun test\` from the repo root.`,
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
712
832
|
// 1. Exact path match (fast path)
|
|
713
833
|
const existing = db()
|
|
714
834
|
.query("SELECT id, git_remote FROM projects WHERE path = ?")
|
|
@@ -807,6 +927,33 @@ export function isFirstRun(): boolean {
|
|
|
807
927
|
return row.count === 0;
|
|
808
928
|
}
|
|
809
929
|
|
|
930
|
+
// ---------------------------------------------------------------------------
|
|
931
|
+
// Conversation import tracking
|
|
932
|
+
// ---------------------------------------------------------------------------
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Get the timestamp of the last conversation import offer/run for a project.
|
|
936
|
+
* Returns null if import has never been offered for this project.
|
|
937
|
+
*/
|
|
938
|
+
export function getLastImportAt(projectPath: string): number | null {
|
|
939
|
+
const id = ensureProject(projectPath);
|
|
940
|
+
const row = db()
|
|
941
|
+
.query("SELECT last_import_at FROM projects WHERE id = ?")
|
|
942
|
+
.get(id) as { last_import_at: number | null } | null;
|
|
943
|
+
return row?.last_import_at ?? null;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Record that conversation import was offered/run for a project.
|
|
948
|
+
* Prevents auto-import from re-prompting, and enables incremental imports.
|
|
949
|
+
*/
|
|
950
|
+
export function setLastImportAt(projectPath: string, timestamp: number): void {
|
|
951
|
+
const id = ensureProject(projectPath);
|
|
952
|
+
db()
|
|
953
|
+
.query("UPDATE projects SET last_import_at = ? WHERE id = ?")
|
|
954
|
+
.run(timestamp, id);
|
|
955
|
+
}
|
|
956
|
+
|
|
810
957
|
// ---------------------------------------------------------------------------
|
|
811
958
|
// Persistent session state (error recovery)
|
|
812
959
|
// ---------------------------------------------------------------------------
|
|
@@ -851,6 +998,8 @@ export type SessionCostSnapshot = {
|
|
|
851
998
|
ttlSavings: number;
|
|
852
999
|
ttlHits: number;
|
|
853
1000
|
batchSavings: number;
|
|
1001
|
+
avoidedCompactions: number;
|
|
1002
|
+
avoidedCompactionCost: number;
|
|
854
1003
|
};
|
|
855
1004
|
|
|
856
1005
|
/**
|
|
@@ -863,8 +1012,9 @@ export function saveSessionCosts(sessionID: string, costs: SessionCostSnapshot):
|
|
|
863
1012
|
`INSERT INTO session_state (session_id, force_min_layer, updated_at,
|
|
864
1013
|
conversation_cost, worker_cost, conversation_turns,
|
|
865
1014
|
cache_read_tokens, cache_write_tokens,
|
|
866
|
-
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings
|
|
867
|
-
|
|
1015
|
+
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings,
|
|
1016
|
+
avoided_compactions, avoided_compaction_cost)
|
|
1017
|
+
VALUES (?, COALESCE((SELECT force_min_layer FROM session_state WHERE session_id = ?), 0), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
868
1018
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
869
1019
|
conversation_cost = excluded.conversation_cost,
|
|
870
1020
|
worker_cost = excluded.worker_cost,
|
|
@@ -876,6 +1026,8 @@ export function saveSessionCosts(sessionID: string, costs: SessionCostSnapshot):
|
|
|
876
1026
|
ttl_savings = excluded.ttl_savings,
|
|
877
1027
|
ttl_hits = excluded.ttl_hits,
|
|
878
1028
|
batch_savings = excluded.batch_savings,
|
|
1029
|
+
avoided_compactions = excluded.avoided_compactions,
|
|
1030
|
+
avoided_compaction_cost = excluded.avoided_compaction_cost,
|
|
879
1031
|
updated_at = excluded.updated_at`,
|
|
880
1032
|
)
|
|
881
1033
|
.run(
|
|
@@ -883,6 +1035,7 @@ export function saveSessionCosts(sessionID: string, costs: SessionCostSnapshot):
|
|
|
883
1035
|
costs.conversationCost, costs.workerCost, costs.conversationTurns,
|
|
884
1036
|
costs.cacheReadTokens, costs.cacheWriteTokens,
|
|
885
1037
|
costs.warmupSavings, costs.warmupHits, costs.ttlSavings, costs.ttlHits, costs.batchSavings,
|
|
1038
|
+
costs.avoidedCompactions, costs.avoidedCompactionCost,
|
|
886
1039
|
);
|
|
887
1040
|
}
|
|
888
1041
|
|
|
@@ -895,7 +1048,8 @@ export function loadSessionCosts(sessionID: string): SessionCostSnapshot | null
|
|
|
895
1048
|
.query(
|
|
896
1049
|
`SELECT conversation_cost, worker_cost, conversation_turns,
|
|
897
1050
|
cache_read_tokens, cache_write_tokens,
|
|
898
|
-
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings
|
|
1051
|
+
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings,
|
|
1052
|
+
avoided_compactions, avoided_compaction_cost
|
|
899
1053
|
FROM session_state WHERE session_id = ?`,
|
|
900
1054
|
)
|
|
901
1055
|
.get(sessionID) as {
|
|
@@ -909,6 +1063,8 @@ export function loadSessionCosts(sessionID: string): SessionCostSnapshot | null
|
|
|
909
1063
|
ttl_savings: number;
|
|
910
1064
|
ttl_hits: number;
|
|
911
1065
|
batch_savings: number;
|
|
1066
|
+
avoided_compactions: number;
|
|
1067
|
+
avoided_compaction_cost: number;
|
|
912
1068
|
} | null;
|
|
913
1069
|
if (!row) return null;
|
|
914
1070
|
return {
|
|
@@ -922,6 +1078,8 @@ export function loadSessionCosts(sessionID: string): SessionCostSnapshot | null
|
|
|
922
1078
|
ttlSavings: row.ttl_savings,
|
|
923
1079
|
ttlHits: row.ttl_hits,
|
|
924
1080
|
batchSavings: row.batch_savings,
|
|
1081
|
+
avoidedCompactions: row.avoided_compactions,
|
|
1082
|
+
avoidedCompactionCost: row.avoided_compaction_cost,
|
|
925
1083
|
};
|
|
926
1084
|
}
|
|
927
1085
|
|
|
@@ -934,7 +1092,8 @@ export function loadAllSessionCosts(): Map<string, SessionCostSnapshot> {
|
|
|
934
1092
|
.query(
|
|
935
1093
|
`SELECT session_id, conversation_cost, worker_cost, conversation_turns,
|
|
936
1094
|
cache_read_tokens, cache_write_tokens,
|
|
937
|
-
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings
|
|
1095
|
+
warmup_savings, warmup_hits, ttl_savings, ttl_hits, batch_savings,
|
|
1096
|
+
avoided_compactions, avoided_compaction_cost
|
|
938
1097
|
FROM session_state
|
|
939
1098
|
WHERE conversation_turns > 0 OR warmup_savings > 0 OR ttl_savings > 0 OR batch_savings > 0`,
|
|
940
1099
|
)
|
|
@@ -950,6 +1109,8 @@ export function loadAllSessionCosts(): Map<string, SessionCostSnapshot> {
|
|
|
950
1109
|
ttl_savings: number;
|
|
951
1110
|
ttl_hits: number;
|
|
952
1111
|
batch_savings: number;
|
|
1112
|
+
avoided_compactions: number;
|
|
1113
|
+
avoided_compaction_cost: number;
|
|
953
1114
|
}>;
|
|
954
1115
|
const result = new Map<string, SessionCostSnapshot>();
|
|
955
1116
|
for (const row of rows) {
|
|
@@ -964,11 +1125,296 @@ export function loadAllSessionCosts(): Map<string, SessionCostSnapshot> {
|
|
|
964
1125
|
ttlSavings: row.ttl_savings,
|
|
965
1126
|
ttlHits: row.ttl_hits,
|
|
966
1127
|
batchSavings: row.batch_savings,
|
|
1128
|
+
avoidedCompactions: row.avoided_compactions,
|
|
1129
|
+
avoidedCompactionCost: row.avoided_compaction_cost,
|
|
967
1130
|
});
|
|
968
1131
|
}
|
|
969
1132
|
return result;
|
|
970
1133
|
}
|
|
971
1134
|
|
|
1135
|
+
// ---------------------------------------------------------------------------
|
|
1136
|
+
// Session tracking state (session_state table, v23 columns)
|
|
1137
|
+
// ---------------------------------------------------------------------------
|
|
1138
|
+
|
|
1139
|
+
/** Fields that can be persisted for session tracking state. */
|
|
1140
|
+
export type SessionTrackingState = {
|
|
1141
|
+
lastCuratedAt?: number;
|
|
1142
|
+
messageCount?: number;
|
|
1143
|
+
turnsSinceCuration?: number;
|
|
1144
|
+
consecutiveTextOnlyTurns?: number;
|
|
1145
|
+
ltmCacheText?: string | null;
|
|
1146
|
+
ltmCacheTokens?: number | null;
|
|
1147
|
+
ltmPinText?: string | null;
|
|
1148
|
+
ltmPinTokens?: number | null;
|
|
1149
|
+
// v24: session identity
|
|
1150
|
+
fingerprint?: string;
|
|
1151
|
+
headerSessionId?: string | null;
|
|
1152
|
+
headerName?: string | null;
|
|
1153
|
+
// v24: cache warming
|
|
1154
|
+
resolvedConversationTTL?: string;
|
|
1155
|
+
warmupState?: string | null; // JSON blob
|
|
1156
|
+
// v24: gradient calibration
|
|
1157
|
+
dynamicContextCap?: number;
|
|
1158
|
+
bustRateEMA?: number;
|
|
1159
|
+
interBustIntervalEMA?: number;
|
|
1160
|
+
lastLayer?: number;
|
|
1161
|
+
lastKnownInput?: number;
|
|
1162
|
+
lastTurnAt?: number;
|
|
1163
|
+
lastBustAt?: number;
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Persist session tracking state. Ensures the row exists, then updates
|
|
1168
|
+
* only the fields that are explicitly provided (not undefined).
|
|
1169
|
+
*/
|
|
1170
|
+
export function saveSessionTracking(sessionID: string, state: SessionTrackingState): void {
|
|
1171
|
+
const now = Date.now();
|
|
1172
|
+
|
|
1173
|
+
// Ensure row exists (no-op if it already does)
|
|
1174
|
+
db()
|
|
1175
|
+
.query(
|
|
1176
|
+
"INSERT OR IGNORE INTO session_state (session_id, force_min_layer, updated_at) VALUES (?, 0, ?)",
|
|
1177
|
+
)
|
|
1178
|
+
.run(sessionID, now);
|
|
1179
|
+
|
|
1180
|
+
// Build SET clauses for only the provided fields
|
|
1181
|
+
const sets: string[] = ["updated_at = ?"];
|
|
1182
|
+
const vals: (string | number | null)[] = [now];
|
|
1183
|
+
|
|
1184
|
+
if (state.lastCuratedAt !== undefined) {
|
|
1185
|
+
sets.push("last_curated_at = ?");
|
|
1186
|
+
vals.push(state.lastCuratedAt);
|
|
1187
|
+
}
|
|
1188
|
+
if (state.messageCount !== undefined) {
|
|
1189
|
+
sets.push("message_count = ?");
|
|
1190
|
+
vals.push(state.messageCount);
|
|
1191
|
+
}
|
|
1192
|
+
if (state.turnsSinceCuration !== undefined) {
|
|
1193
|
+
sets.push("turns_since_curation = ?");
|
|
1194
|
+
vals.push(state.turnsSinceCuration);
|
|
1195
|
+
}
|
|
1196
|
+
if (state.consecutiveTextOnlyTurns !== undefined) {
|
|
1197
|
+
sets.push("consecutive_text_only_turns = ?");
|
|
1198
|
+
vals.push(state.consecutiveTextOnlyTurns);
|
|
1199
|
+
}
|
|
1200
|
+
if (state.ltmCacheText !== undefined) {
|
|
1201
|
+
sets.push("ltm_cache_text = ?");
|
|
1202
|
+
vals.push(state.ltmCacheText);
|
|
1203
|
+
}
|
|
1204
|
+
if (state.ltmCacheTokens !== undefined) {
|
|
1205
|
+
sets.push("ltm_cache_tokens = ?");
|
|
1206
|
+
vals.push(state.ltmCacheTokens);
|
|
1207
|
+
}
|
|
1208
|
+
if (state.ltmPinText !== undefined) {
|
|
1209
|
+
sets.push("ltm_pin_text = ?");
|
|
1210
|
+
vals.push(state.ltmPinText);
|
|
1211
|
+
}
|
|
1212
|
+
if (state.ltmPinTokens !== undefined) {
|
|
1213
|
+
sets.push("ltm_pin_tokens = ?");
|
|
1214
|
+
vals.push(state.ltmPinTokens);
|
|
1215
|
+
}
|
|
1216
|
+
// v24: session identity
|
|
1217
|
+
if (state.fingerprint !== undefined) {
|
|
1218
|
+
sets.push("fingerprint = ?");
|
|
1219
|
+
vals.push(state.fingerprint);
|
|
1220
|
+
}
|
|
1221
|
+
if (state.headerSessionId !== undefined) {
|
|
1222
|
+
sets.push("header_session_id = ?");
|
|
1223
|
+
vals.push(state.headerSessionId);
|
|
1224
|
+
}
|
|
1225
|
+
if (state.headerName !== undefined) {
|
|
1226
|
+
sets.push("header_name = ?");
|
|
1227
|
+
vals.push(state.headerName);
|
|
1228
|
+
}
|
|
1229
|
+
// v24: cache warming
|
|
1230
|
+
if (state.resolvedConversationTTL !== undefined) {
|
|
1231
|
+
sets.push("resolved_conversation_ttl = ?");
|
|
1232
|
+
vals.push(state.resolvedConversationTTL);
|
|
1233
|
+
}
|
|
1234
|
+
if (state.warmupState !== undefined) {
|
|
1235
|
+
sets.push("warmup_state = ?");
|
|
1236
|
+
vals.push(state.warmupState);
|
|
1237
|
+
}
|
|
1238
|
+
// v24: gradient calibration
|
|
1239
|
+
if (state.dynamicContextCap !== undefined) {
|
|
1240
|
+
sets.push("dynamic_context_cap = ?");
|
|
1241
|
+
vals.push(state.dynamicContextCap);
|
|
1242
|
+
}
|
|
1243
|
+
if (state.bustRateEMA !== undefined) {
|
|
1244
|
+
sets.push("bust_rate_ema = ?");
|
|
1245
|
+
vals.push(state.bustRateEMA);
|
|
1246
|
+
}
|
|
1247
|
+
if (state.interBustIntervalEMA !== undefined) {
|
|
1248
|
+
sets.push("inter_bust_interval_ema = ?");
|
|
1249
|
+
vals.push(state.interBustIntervalEMA);
|
|
1250
|
+
}
|
|
1251
|
+
if (state.lastLayer !== undefined) {
|
|
1252
|
+
sets.push("last_layer = ?");
|
|
1253
|
+
vals.push(state.lastLayer);
|
|
1254
|
+
}
|
|
1255
|
+
if (state.lastKnownInput !== undefined) {
|
|
1256
|
+
sets.push("last_known_input = ?");
|
|
1257
|
+
vals.push(state.lastKnownInput);
|
|
1258
|
+
}
|
|
1259
|
+
if (state.lastTurnAt !== undefined) {
|
|
1260
|
+
sets.push("last_turn_at = ?");
|
|
1261
|
+
vals.push(state.lastTurnAt);
|
|
1262
|
+
}
|
|
1263
|
+
if (state.lastBustAt !== undefined) {
|
|
1264
|
+
sets.push("last_bust_at = ?");
|
|
1265
|
+
vals.push(state.lastBustAt);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Update only the specified columns
|
|
1269
|
+
db()
|
|
1270
|
+
.query(
|
|
1271
|
+
"UPDATE session_state SET " + sets.join(", ") + " WHERE session_id = ?",
|
|
1272
|
+
)
|
|
1273
|
+
.run(...vals, sessionID);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/** Loaded session tracking state. */
|
|
1277
|
+
export type LoadedSessionTracking = {
|
|
1278
|
+
lastCuratedAt: number;
|
|
1279
|
+
messageCount: number;
|
|
1280
|
+
turnsSinceCuration: number;
|
|
1281
|
+
consecutiveTextOnlyTurns: number;
|
|
1282
|
+
ltmCacheText: string | null;
|
|
1283
|
+
ltmCacheTokens: number | null;
|
|
1284
|
+
ltmPinText: string | null;
|
|
1285
|
+
ltmPinTokens: number | null;
|
|
1286
|
+
// v24: session identity
|
|
1287
|
+
fingerprint: string;
|
|
1288
|
+
headerSessionId: string | null;
|
|
1289
|
+
headerName: string | null;
|
|
1290
|
+
// v24: cache warming
|
|
1291
|
+
resolvedConversationTTL: string;
|
|
1292
|
+
warmupState: string | null;
|
|
1293
|
+
// v24: gradient calibration
|
|
1294
|
+
dynamicContextCap: number;
|
|
1295
|
+
bustRateEMA: number;
|
|
1296
|
+
interBustIntervalEMA: number;
|
|
1297
|
+
lastLayer: number;
|
|
1298
|
+
lastKnownInput: number;
|
|
1299
|
+
lastTurnAt: number;
|
|
1300
|
+
lastBustAt: number;
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Load persisted session tracking state. Returns null if no row exists.
|
|
1305
|
+
*/
|
|
1306
|
+
export function loadSessionTracking(sessionID: string): LoadedSessionTracking | null {
|
|
1307
|
+
const row = db()
|
|
1308
|
+
.query(
|
|
1309
|
+
`SELECT last_curated_at, message_count, turns_since_curation,
|
|
1310
|
+
consecutive_text_only_turns,
|
|
1311
|
+
ltm_cache_text, ltm_cache_tokens, ltm_pin_text, ltm_pin_tokens,
|
|
1312
|
+
fingerprint, header_session_id, header_name,
|
|
1313
|
+
resolved_conversation_ttl, warmup_state,
|
|
1314
|
+
dynamic_context_cap, bust_rate_ema, inter_bust_interval_ema,
|
|
1315
|
+
last_layer, last_known_input, last_turn_at, last_bust_at
|
|
1316
|
+
FROM session_state WHERE session_id = ?`,
|
|
1317
|
+
)
|
|
1318
|
+
.get(sessionID) as {
|
|
1319
|
+
last_curated_at: number;
|
|
1320
|
+
message_count: number;
|
|
1321
|
+
turns_since_curation: number;
|
|
1322
|
+
consecutive_text_only_turns: number;
|
|
1323
|
+
ltm_cache_text: string | null;
|
|
1324
|
+
ltm_cache_tokens: number | null;
|
|
1325
|
+
ltm_pin_text: string | null;
|
|
1326
|
+
ltm_pin_tokens: number | null;
|
|
1327
|
+
fingerprint: string;
|
|
1328
|
+
header_session_id: string | null;
|
|
1329
|
+
header_name: string | null;
|
|
1330
|
+
resolved_conversation_ttl: string;
|
|
1331
|
+
warmup_state: string | null;
|
|
1332
|
+
dynamic_context_cap: number;
|
|
1333
|
+
bust_rate_ema: number;
|
|
1334
|
+
inter_bust_interval_ema: number;
|
|
1335
|
+
last_layer: number;
|
|
1336
|
+
last_known_input: number;
|
|
1337
|
+
last_turn_at: number;
|
|
1338
|
+
last_bust_at: number;
|
|
1339
|
+
} | null;
|
|
1340
|
+
if (!row) return null;
|
|
1341
|
+
return {
|
|
1342
|
+
lastCuratedAt: row.last_curated_at,
|
|
1343
|
+
messageCount: row.message_count,
|
|
1344
|
+
turnsSinceCuration: row.turns_since_curation,
|
|
1345
|
+
consecutiveTextOnlyTurns: row.consecutive_text_only_turns,
|
|
1346
|
+
ltmCacheText: row.ltm_cache_text,
|
|
1347
|
+
ltmCacheTokens: row.ltm_cache_tokens,
|
|
1348
|
+
ltmPinText: row.ltm_pin_text,
|
|
1349
|
+
ltmPinTokens: row.ltm_pin_tokens,
|
|
1350
|
+
fingerprint: row.fingerprint,
|
|
1351
|
+
headerSessionId: row.header_session_id,
|
|
1352
|
+
headerName: row.header_name,
|
|
1353
|
+
resolvedConversationTTL: row.resolved_conversation_ttl,
|
|
1354
|
+
warmupState: row.warmup_state,
|
|
1355
|
+
dynamicContextCap: row.dynamic_context_cap,
|
|
1356
|
+
bustRateEMA: row.bust_rate_ema,
|
|
1357
|
+
interBustIntervalEMA: row.inter_bust_interval_ema,
|
|
1358
|
+
lastLayer: row.last_layer,
|
|
1359
|
+
lastKnownInput: row.last_known_input,
|
|
1360
|
+
lastTurnAt: row.last_turn_at,
|
|
1361
|
+
lastBustAt: row.last_bust_at,
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Load all persisted header → session ID mappings from the session_state table.
|
|
1367
|
+
*
|
|
1368
|
+
* Used on gateway startup (in initIfNeeded) to pre-populate the in-memory
|
|
1369
|
+
* headerSessionIndex so Tier 1 session identification works immediately
|
|
1370
|
+
* after a process restart — without this, the first post-restart request
|
|
1371
|
+
* with a known session header would generate a new session ID and orphan
|
|
1372
|
+
* the old session's persisted state.
|
|
1373
|
+
*/
|
|
1374
|
+
export function loadHeaderSessionIndex(): Array<{
|
|
1375
|
+
sessionId: string;
|
|
1376
|
+
headerSessionId: string;
|
|
1377
|
+
headerName: string;
|
|
1378
|
+
}> {
|
|
1379
|
+
const rows = db()
|
|
1380
|
+
.query(
|
|
1381
|
+
`SELECT session_id, header_session_id, header_name
|
|
1382
|
+
FROM session_state
|
|
1383
|
+
WHERE header_session_id IS NOT NULL AND header_name IS NOT NULL`,
|
|
1384
|
+
)
|
|
1385
|
+
.all() as Array<{
|
|
1386
|
+
session_id: string;
|
|
1387
|
+
header_session_id: string;
|
|
1388
|
+
header_name: string;
|
|
1389
|
+
}>;
|
|
1390
|
+
return rows.map((row) => ({
|
|
1391
|
+
sessionId: row.session_id,
|
|
1392
|
+
headerSessionId: row.header_session_id,
|
|
1393
|
+
headerName: row.header_name,
|
|
1394
|
+
}));
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// ---------------------------------------------------------------------------
|
|
1398
|
+
// Key-value store (kv_meta table)
|
|
1399
|
+
// ---------------------------------------------------------------------------
|
|
1400
|
+
|
|
1401
|
+
/** Get a kv_meta value by key. Returns null if not found. */
|
|
1402
|
+
export function getKV(key: string): string | null {
|
|
1403
|
+
const row = db()
|
|
1404
|
+
.query("SELECT value FROM kv_meta WHERE key = ?")
|
|
1405
|
+
.get(key) as { value: string } | null;
|
|
1406
|
+
return row?.value ?? null;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/** Set a kv_meta value (upsert). */
|
|
1410
|
+
export function setKV(key: string, value: string): void {
|
|
1411
|
+
db()
|
|
1412
|
+
.query(
|
|
1413
|
+
"INSERT INTO kv_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?",
|
|
1414
|
+
)
|
|
1415
|
+
.run(key, value, value);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
972
1418
|
// ---------------------------------------------------------------------------
|
|
973
1419
|
// Installation metadata (metadata table)
|
|
974
1420
|
// ---------------------------------------------------------------------------
|