elepha 0.1.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/LICENSE.md +375 -0
- package/README.md +135 -0
- package/bin/elepha.js +2 -0
- package/dist/adapters/base.js +600 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude-code.js +260 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.js +499 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/discriminators.js +39 -0
- package/dist/adapters/discriminators.js.map +1 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/internal-command.js +3 -0
- package/dist/adapters/internal-command.js.map +1 -0
- package/dist/cli/backup-wizard.js +97 -0
- package/dist/cli/backup-wizard.js.map +1 -0
- package/dist/cli/capture-service.js +80 -0
- package/dist/cli/capture-service.js.map +1 -0
- package/dist/cli/commands/backfills.js +336 -0
- package/dist/cli/commands/backfills.js.map +1 -0
- package/dist/cli/commands/backup.js +234 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.js +66 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/consent.js +237 -0
- package/dist/cli/commands/consent.js.map +1 -0
- package/dist/cli/commands/daemon-control.js +76 -0
- package/dist/cli/commands/daemon-control.js.map +1 -0
- package/dist/cli/commands/doctor.js +31 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/hook.js +24 -0
- package/dist/cli/commands/hook.js.map +1 -0
- package/dist/cli/commands/import.js +611 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/inspect.js +54 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/install.js +49 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/internal.js +16 -0
- package/dist/cli/commands/internal.js.map +1 -0
- package/dist/cli/commands/mcp.js +12 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/projects.js +26 -0
- package/dist/cli/commands/projects.js.map +1 -0
- package/dist/cli/commands/purge.js +216 -0
- package/dist/cli/commands/purge.js.map +1 -0
- package/dist/cli/commands/reingest.js +80 -0
- package/dist/cli/commands/reingest.js.map +1 -0
- package/dist/cli/commands/rekey.js +58 -0
- package/dist/cli/commands/rekey.js.map +1 -0
- package/dist/cli/commands/restore.js +376 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/rollup.js +130 -0
- package/dist/cli/commands/rollup.js.map +1 -0
- package/dist/cli/commands/sanitize.js +72 -0
- package/dist/cli/commands/sanitize.js.map +1 -0
- package/dist/cli/commands/segment.js +153 -0
- package/dist/cli/commands/segment.js.map +1 -0
- package/dist/cli/commands/self-update.js +26 -0
- package/dist/cli/commands/self-update.js.map +1 -0
- package/dist/cli/commands/start.js +53 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stats.js +42 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/status.js +73 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/uninstall.js +23 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/config-wizard.js +104 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/destructive-op.js +32 -0
- package/dist/cli/destructive-op.js.map +1 -0
- package/dist/cli/import-wizard.js +89 -0
- package/dist/cli/import-wizard.js.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-wizard.js +80 -0
- package/dist/cli/init-wizard.js.map +1 -0
- package/dist/cli/init.js +159 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launcher-probe.js +71 -0
- package/dist/cli/launcher-probe.js.map +1 -0
- package/dist/cli/project-path.js +16 -0
- package/dist/cli/project-path.js.map +1 -0
- package/dist/cli/purge-wizard.js +200 -0
- package/dist/cli/purge-wizard.js.map +1 -0
- package/dist/cli/restore-wizard.js +101 -0
- package/dist/cli/restore-wizard.js.map +1 -0
- package/dist/cli/shared.js +128 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/status.js +22 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/wordmark.js +15 -0
- package/dist/cli/wordmark.js.map +1 -0
- package/dist/config/constants.js +115 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/env.js +168 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/memory-config.js +63 -0
- package/dist/config/memory-config.js.map +1 -0
- package/dist/config/paths.js +343 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/settings.js +132 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/version.js +12 -0
- package/dist/config/version.js.map +1 -0
- package/dist/daemon/failure-window.js +38 -0
- package/dist/daemon/failure-window.js.map +1 -0
- package/dist/daemon/heartbeat.js +40 -0
- package/dist/daemon/heartbeat.js.map +1 -0
- package/dist/daemon/index.js +873 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/log-rotation.js +27 -0
- package/dist/daemon/log-rotation.js.map +1 -0
- package/dist/daemon/readability-guard.js +89 -0
- package/dist/daemon/readability-guard.js.map +1 -0
- package/dist/daemon/rollup-service.js +239 -0
- package/dist/daemon/rollup-service.js.map +1 -0
- package/dist/daemon/update-check.js +87 -0
- package/dist/daemon/update-check.js.map +1 -0
- package/dist/daemon/work-queue.js +28 -0
- package/dist/daemon/work-queue.js.map +1 -0
- package/dist/discovery/session-projects.js +242 -0
- package/dist/discovery/session-projects.js.map +1 -0
- package/dist/hooks/codex-trust.js +80 -0
- package/dist/hooks/codex-trust.js.map +1 -0
- package/dist/hooks/common.js +81 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/hook-log.js +38 -0
- package/dist/hooks/hook-log.js.map +1 -0
- package/dist/hooks/installer.js +200 -0
- package/dist/hooks/installer.js.map +1 -0
- package/dist/hooks/session-start.js +321 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/user-prompt-submit.js +248 -0
- package/dist/hooks/user-prompt-submit.js.map +1 -0
- package/dist/install/binary.js +72 -0
- package/dist/install/binary.js.map +1 -0
- package/dist/install/config-file.js +119 -0
- package/dist/install/config-file.js.map +1 -0
- package/dist/install/daemon-health.js +48 -0
- package/dist/install/daemon-health.js.map +1 -0
- package/dist/install/daemon-service.js +309 -0
- package/dist/install/daemon-service.js.map +1 -0
- package/dist/install/doctor.js +180 -0
- package/dist/install/doctor.js.map +1 -0
- package/dist/install/health-checks.js +90 -0
- package/dist/install/health-checks.js.map +1 -0
- package/dist/install/installer.js +411 -0
- package/dist/install/installer.js.map +1 -0
- package/dist/install/launchd-backend.js +261 -0
- package/dist/install/launchd-backend.js.map +1 -0
- package/dist/install/launcher.js +224 -0
- package/dist/install/launcher.js.map +1 -0
- package/dist/install/markers.js +14 -0
- package/dist/install/markers.js.map +1 -0
- package/dist/install/platform.js +29 -0
- package/dist/install/platform.js.map +1 -0
- package/dist/install/present-tools.js +16 -0
- package/dist/install/present-tools.js.map +1 -0
- package/dist/install/self-update.js +120 -0
- package/dist/install/self-update.js.map +1 -0
- package/dist/install/service-backend.js +36 -0
- package/dist/install/service-backend.js.map +1 -0
- package/dist/install/service-manifest.js +36 -0
- package/dist/install/service-manifest.js.map +1 -0
- package/dist/install/status.js +58 -0
- package/dist/install/status.js.map +1 -0
- package/dist/install/systemd-backend.js +225 -0
- package/dist/install/systemd-backend.js.map +1 -0
- package/dist/markers.js +5 -0
- package/dist/markers.js.map +1 -0
- package/dist/mcp/installer.js +131 -0
- package/dist/mcp/installer.js.map +1 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-context.js +3 -0
- package/dist/mcp/tools/get-context.js.map +1 -0
- package/dist/mcp/tools/list-projects.js +3 -0
- package/dist/mcp/tools/list-projects.js.map +1 -0
- package/dist/mcp/tools/save-memory.js +3 -0
- package/dist/mcp/tools/save-memory.js.map +1 -0
- package/dist/mcp/tools.js +288 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rendering/raw-turn-renderer.js +85 -0
- package/dist/rendering/raw-turn-renderer.js.map +1 -0
- package/dist/security/file-permissions.js +15 -0
- package/dist/security/file-permissions.js.map +1 -0
- package/dist/security/provider-transcript.js +74 -0
- package/dist/security/provider-transcript.js.map +1 -0
- package/dist/security/sanitize.js +210 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/self-ingestion.js +47 -0
- package/dist/security/self-ingestion.js.map +1 -0
- package/dist/security/sentinel.js +19 -0
- package/dist/security/sentinel.js.map +1 -0
- package/dist/security/subprocess-allowlist.js +421 -0
- package/dist/security/subprocess-allowlist.js.map +1 -0
- package/dist/serving/instructions.js +33 -0
- package/dist/serving/instructions.js.map +1 -0
- package/dist/serving/lexical-recall.js +249 -0
- package/dist/serving/lexical-recall.js.map +1 -0
- package/dist/serving/session-reader.js +302 -0
- package/dist/serving/session-reader.js.map +1 -0
- package/dist/storage/backfill-runner.js +34 -0
- package/dist/storage/backfill-runner.js.map +1 -0
- package/dist/storage/backup.js +46 -0
- package/dist/storage/backup.js.map +1 -0
- package/dist/storage/candidate-validator.js +77 -0
- package/dist/storage/candidate-validator.js.map +1 -0
- package/dist/storage/consent-store.js +198 -0
- package/dist/storage/consent-store.js.map +1 -0
- package/dist/storage/custom-title-backfill.js +68 -0
- package/dist/storage/custom-title-backfill.js.map +1 -0
- package/dist/storage/db.js +312 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/external-agent-import-purge.js +182 -0
- package/dist/storage/external-agent-import-purge.js.map +1 -0
- package/dist/storage/first-prompt-search-backfill.js +76 -0
- package/dist/storage/first-prompt-search-backfill.js.map +1 -0
- package/dist/storage/first-prompt-search.js +7 -0
- package/dist/storage/first-prompt-search.js.map +1 -0
- package/dist/storage/injection-store.js +37 -0
- package/dist/storage/injection-store.js.map +1 -0
- package/dist/storage/memory-store.js +428 -0
- package/dist/storage/memory-store.js.map +1 -0
- package/dist/storage/project-resolver.js +294 -0
- package/dist/storage/project-resolver.js.map +1 -0
- package/dist/storage/project-store.js +104 -0
- package/dist/storage/project-store.js.map +1 -0
- package/dist/storage/rendered-chars-backfill.js +85 -0
- package/dist/storage/rendered-chars-backfill.js.map +1 -0
- package/dist/storage/resegmentation.js +582 -0
- package/dist/storage/resegmentation.js.map +1 -0
- package/dist/storage/rollup-store.js +246 -0
- package/dist/storage/rollup-store.js.map +1 -0
- package/dist/storage/root-commit-backfill.js +32 -0
- package/dist/storage/root-commit-backfill.js.map +1 -0
- package/dist/storage/sanitize-backfill.js +184 -0
- package/dist/storage/sanitize-backfill.js.map +1 -0
- package/dist/storage/segmentation.js +87 -0
- package/dist/storage/segmentation.js.map +1 -0
- package/dist/storage/session-fields-backfill.js +196 -0
- package/dist/storage/session-fields-backfill.js.map +1 -0
- package/dist/storage/session-read-model.js +130 -0
- package/dist/storage/session-read-model.js.map +1 -0
- package/dist/storage/session-store.js +200 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/session-title-backfill.js +75 -0
- package/dist/storage/session-title-backfill.js.map +1 -0
- package/dist/storage/session-title.js +71 -0
- package/dist/storage/session-title.js.map +1 -0
- package/dist/storage/shown-session-list-store.js +33 -0
- package/dist/storage/shown-session-list-store.js.map +1 -0
- package/dist/storage/stats.js +30 -0
- package/dist/storage/stats.js.map +1 -0
- package/dist/storage/turn-store.js +202 -0
- package/dist/storage/turn-store.js.map +1 -0
- package/dist/storage/ulid.js +17 -0
- package/dist/storage/ulid.js.map +1 -0
- package/dist/summarizer/anthropic-call.js +76 -0
- package/dist/summarizer/anthropic-call.js.map +1 -0
- package/dist/summarizer/call-log.js +120 -0
- package/dist/summarizer/call-log.js.map +1 -0
- package/dist/summarizer/haiku-provider.js +136 -0
- package/dist/summarizer/haiku-provider.js.map +1 -0
- package/dist/summarizer/pricing.js +8 -0
- package/dist/summarizer/pricing.js.map +1 -0
- package/dist/summarizer/prompt-cache.js +6 -0
- package/dist/summarizer/prompt-cache.js.map +1 -0
- package/dist/summarizer/prompt.js +47 -0
- package/dist/summarizer/prompt.js.map +1 -0
- package/dist/summarizer/provider-config.js +25 -0
- package/dist/summarizer/provider-config.js.map +1 -0
- package/dist/summarizer/rollup-prompt.js +225 -0
- package/dist/summarizer/rollup-prompt.js.map +1 -0
- package/dist/summarizer/rollup-provider.js +224 -0
- package/dist/summarizer/rollup-provider.js.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/error.js +5 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/fs.js +97 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/relative-time.js +12 -0
- package/dist/util/relative-time.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// SQLite connection + schema management. Single local DB file, zero config.
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { elephaHome } from '../config/paths.js';
|
|
6
|
+
import { hardenDir, hardenFile } from '../security/file-permissions.js';
|
|
7
|
+
import { CONSENT_GRANDFATHERED_AT_KEY, canonicalizeConsentRoots, grandfatherConsentRoots } from './consent-store.js';
|
|
8
|
+
export function defaultDbPath() {
|
|
9
|
+
const override = process.env.ELEPHA_DB_PATH?.trim();
|
|
10
|
+
return override ? path.resolve(override) : path.join(elephaHome(), 'elepha.db');
|
|
11
|
+
}
|
|
12
|
+
const SCHEMA = `
|
|
13
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
14
|
+
id INTEGER PRIMARY KEY,
|
|
15
|
+
path TEXT NOT NULL UNIQUE,
|
|
16
|
+
display_name TEXT,
|
|
17
|
+
git_root TEXT,
|
|
18
|
+
git_remote TEXT,
|
|
19
|
+
git_root_commit TEXT,
|
|
20
|
+
first_seen_at TEXT NOT NULL,
|
|
21
|
+
last_seen_at TEXT NOT NULL
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
25
|
+
id INTEGER PRIMARY KEY,
|
|
26
|
+
tool TEXT NOT NULL CHECK (tool IN ('claude-code','codex')),
|
|
27
|
+
native_id TEXT NOT NULL,
|
|
28
|
+
segment_index INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
30
|
+
source_path TEXT NOT NULL,
|
|
31
|
+
cursor TEXT,
|
|
32
|
+
started_at TEXT NOT NULL,
|
|
33
|
+
last_ingested_at TEXT NOT NULL,
|
|
34
|
+
surface TEXT CHECK (surface IN ('cli','desktop')),
|
|
35
|
+
git_branch TEXT,
|
|
36
|
+
kind TEXT CHECK (kind IN ('main','subagent','fork','adjudicator')),
|
|
37
|
+
last_turn_at TEXT,
|
|
38
|
+
trailing_branch TEXT,
|
|
39
|
+
trailing_files TEXT NOT NULL DEFAULT '[]',
|
|
40
|
+
rendered_chars INTEGER DEFAULT 0,
|
|
41
|
+
rendered_turns INTEGER DEFAULT 0,
|
|
42
|
+
title TEXT,
|
|
43
|
+
custom_title TEXT,
|
|
44
|
+
first_prompt_search TEXT,
|
|
45
|
+
git_commit_count INTEGER,
|
|
46
|
+
UNIQUE (tool, native_id, segment_index)
|
|
47
|
+
);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
51
|
+
id INTEGER PRIMARY KEY,
|
|
52
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
53
|
+
session_id INTEGER NOT NULL REFERENCES sessions(id),
|
|
54
|
+
turn_index INTEGER NOT NULL,
|
|
55
|
+
tool TEXT NOT NULL,
|
|
56
|
+
turn_started_at TEXT NOT NULL,
|
|
57
|
+
decisions TEXT NOT NULL,
|
|
58
|
+
files_touched TEXT NOT NULL,
|
|
59
|
+
pending_items TEXT NOT NULL,
|
|
60
|
+
superseded_at TEXT,
|
|
61
|
+
created_at TEXT NOT NULL,
|
|
62
|
+
summarizer_status TEXT NOT NULL DEFAULT 'unknown',
|
|
63
|
+
reingested_at TEXT,
|
|
64
|
+
has_external_content INTEGER NOT NULL DEFAULT 0 CHECK (has_external_content IN (0,1)),
|
|
65
|
+
UNIQUE (session_id, turn_index)
|
|
66
|
+
);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project_time ON memories(project_id, turn_started_at);
|
|
68
|
+
|
|
69
|
+
-- Session-level rollups. Additive: turn rows in memories are never deleted
|
|
70
|
+
-- after rollup, and a rollup can always be rebuilt from them.
|
|
71
|
+
--
|
|
72
|
+
-- rollup_state is the crux. "Closed" is a heuristic (file idle), never a
|
|
73
|
+
-- guarantee: Codex Desktop reopens a session days later and appends to the
|
|
74
|
+
-- original rollout file. So final -> live -> final -> live must be safe to
|
|
75
|
+
-- repeat any number of times, and re-processing the same turns must not
|
|
76
|
+
-- double-count. rolled_up_through_turn_index is the high-water mark that makes
|
|
77
|
+
-- the incremental merge idempotent - it advances in the SAME transaction as
|
|
78
|
+
-- the content it accounts for.
|
|
79
|
+
CREATE TABLE IF NOT EXISTS session_rollups (
|
|
80
|
+
session_id INTEGER PRIMARY KEY REFERENCES sessions(id),
|
|
81
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
82
|
+
tool TEXT NOT NULL,
|
|
83
|
+
title TEXT NOT NULL,
|
|
84
|
+
summary TEXT NOT NULL,
|
|
85
|
+
decisions TEXT NOT NULL, -- JSON [{what, why}]
|
|
86
|
+
pending_items TEXT NOT NULL, -- JSON string[]
|
|
87
|
+
files_touched TEXT NOT NULL, -- JSON string[], case-insensitively deduped
|
|
88
|
+
turn_count INTEGER NOT NULL,
|
|
89
|
+
started_at TEXT NOT NULL,
|
|
90
|
+
ended_at TEXT NOT NULL,
|
|
91
|
+
kind TEXT NOT NULL, -- 'primary' | 'subagent'
|
|
92
|
+
parent_session_id INTEGER REFERENCES sessions(id),
|
|
93
|
+
summarizer_status TEXT NOT NULL,
|
|
94
|
+
rollup_state TEXT NOT NULL, -- 'live' | 'final'
|
|
95
|
+
rolled_up_through_turn_index INTEGER NOT NULL,
|
|
96
|
+
computed_at TEXT NOT NULL,
|
|
97
|
+
rollup_version INTEGER NOT NULL
|
|
98
|
+
);
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_rollups_project ON session_rollups(project_id, ended_at);
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_rollups_parent ON session_rollups(parent_session_id);
|
|
101
|
+
|
|
102
|
+
CREATE TABLE IF NOT EXISTS consent_roots (
|
|
103
|
+
id INTEGER PRIMARY KEY,
|
|
104
|
+
ulid TEXT NOT NULL UNIQUE,
|
|
105
|
+
path TEXT NOT NULL UNIQUE,
|
|
106
|
+
state TEXT NOT NULL CHECK (state IN ('approved', 'denied', 'pending')),
|
|
107
|
+
decided_at TEXT NOT NULL,
|
|
108
|
+
source TEXT NOT NULL CHECK (source IN ('discovery', 'cli', 'grandfathered')),
|
|
109
|
+
nudged_at TEXT
|
|
110
|
+
);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_consent_roots_state ON consent_roots(state);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
114
|
+
key TEXT PRIMARY KEY,
|
|
115
|
+
value TEXT NOT NULL
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
-- Security Rule 4 quote-back guard. One row per successfully recorded injection.
|
|
119
|
+
CREATE TABLE IF NOT EXISTS injections (
|
|
120
|
+
id INTEGER PRIMARY KEY,
|
|
121
|
+
tool TEXT NOT NULL,
|
|
122
|
+
native_session_id TEXT NOT NULL,
|
|
123
|
+
injected_at TEXT NOT NULL,
|
|
124
|
+
injection_id TEXT NOT NULL, -- ULID embedded in the sentinel
|
|
125
|
+
body_hash TEXT NOT NULL, -- sha256 of the normalized body
|
|
126
|
+
body TEXT NOT NULL, -- needed for near-verbatim comparison
|
|
127
|
+
UNIQUE (tool, native_session_id, body_hash)
|
|
128
|
+
);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_injections_session ON injections(tool, native_session_id);
|
|
130
|
+
|
|
131
|
+
-- The ordered session ids behind elepha:select:<n>. One row is the complete
|
|
132
|
+
-- last list shown to one native chat, including an intentionally empty list.
|
|
133
|
+
CREATE TABLE IF NOT EXISTS shown_session_lists (
|
|
134
|
+
tool TEXT NOT NULL CHECK (tool IN ('claude-code','codex')),
|
|
135
|
+
native_session_id TEXT NOT NULL,
|
|
136
|
+
session_ids TEXT NOT NULL,
|
|
137
|
+
PRIMARY KEY (tool, native_session_id)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
-- A purge is absolute for a transcript. A later daemon sweep must not
|
|
141
|
+
-- recreate any segment from a file whose history was deliberately removed.
|
|
142
|
+
CREATE TABLE IF NOT EXISTS purged_transcripts (
|
|
143
|
+
tool TEXT NOT NULL,
|
|
144
|
+
native_id TEXT NOT NULL,
|
|
145
|
+
purged_at TEXT NOT NULL,
|
|
146
|
+
PRIMARY KEY (tool, native_id)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
-- Capture-off sessions observed under an explicit denial stay excluded even
|
|
150
|
+
-- if that root is later approved. This stores identity only; transcript
|
|
151
|
+
-- content remains solely in the provider's on-disk file.
|
|
152
|
+
CREATE TABLE IF NOT EXISTS incognito_transcripts (
|
|
153
|
+
tool TEXT NOT NULL,
|
|
154
|
+
native_id TEXT NOT NULL,
|
|
155
|
+
tombstoned_at TEXT NOT NULL,
|
|
156
|
+
PRIMARY KEY (tool, native_id)
|
|
157
|
+
);
|
|
158
|
+
`;
|
|
159
|
+
/**
|
|
160
|
+
* No migration framework exists yet (single-table-additive product, pre-1.0).
|
|
161
|
+
* Columns added after the initial CREATE TABLE need an idempotent ALTER TABLE
|
|
162
|
+
* here, guarded by a PRAGMA table_info check, since ALTER TABLE ADD COLUMN
|
|
163
|
+
* has no IF NOT EXISTS form and CREATE TABLE IF NOT EXISTS is a no-op against
|
|
164
|
+
* an already-existing table.
|
|
165
|
+
*/
|
|
166
|
+
function migrate(db) {
|
|
167
|
+
migrateSessionsTable(db);
|
|
168
|
+
const projectColumns = db.pragma('table_info(projects)').map((c) => c.name);
|
|
169
|
+
if (!projectColumns.includes('git_root_commit')) {
|
|
170
|
+
db.exec('ALTER TABLE projects ADD COLUMN git_root_commit TEXT');
|
|
171
|
+
}
|
|
172
|
+
const consentColumns = db.pragma('table_info(consent_roots)').map((c) => c.name);
|
|
173
|
+
if (!consentColumns.includes('nudged_at')) {
|
|
174
|
+
db.exec('ALTER TABLE consent_roots ADD COLUMN nudged_at TEXT');
|
|
175
|
+
}
|
|
176
|
+
db.prepare(`INSERT OR IGNORE INTO meta (key, value)
|
|
177
|
+
SELECT @key, @value
|
|
178
|
+
WHERE EXISTS (SELECT 1 FROM consent_roots)
|
|
179
|
+
AND NOT EXISTS (SELECT 1 FROM meta WHERE key = @key)`).run({ key: CONSENT_GRANDFATHERED_AT_KEY, value: new Date().toISOString() });
|
|
180
|
+
// The gap comparison needs the prior closed turn's timestamp.
|
|
181
|
+
// Keep it on the session row with the rest of the trailing window: using
|
|
182
|
+
// last_ingested_at would compare wall-clock daemon activity, not transcript
|
|
183
|
+
// time, and querying memories here would make every turn scan its history.
|
|
184
|
+
const sessionColumns = db.pragma('table_info(sessions)').map((c) => c.name);
|
|
185
|
+
if (!sessionColumns.includes('last_turn_at')) {
|
|
186
|
+
db.exec('ALTER TABLE sessions ADD COLUMN last_turn_at TEXT');
|
|
187
|
+
}
|
|
188
|
+
if (!sessionColumns.includes('rendered_chars')) {
|
|
189
|
+
// Existing rows need a transcript backfill. NULL means that transcript
|
|
190
|
+
// is unavailable, while new rows receive the SCHEMA default of zero.
|
|
191
|
+
db.exec('ALTER TABLE sessions ADD COLUMN rendered_chars INTEGER');
|
|
192
|
+
}
|
|
193
|
+
if (!sessionColumns.includes('rendered_turns')) {
|
|
194
|
+
// Existing rows need the same renderer-derived backfill as
|
|
195
|
+
// rendered_chars. NULL means the transcript was unavailable.
|
|
196
|
+
db.exec('ALTER TABLE sessions ADD COLUMN rendered_turns INTEGER');
|
|
197
|
+
}
|
|
198
|
+
if (!sessionColumns.includes('title')) {
|
|
199
|
+
db.exec('ALTER TABLE sessions ADD COLUMN title TEXT');
|
|
200
|
+
}
|
|
201
|
+
if (!sessionColumns.includes('custom_title')) {
|
|
202
|
+
db.exec('ALTER TABLE sessions ADD COLUMN custom_title TEXT');
|
|
203
|
+
}
|
|
204
|
+
if (!sessionColumns.includes('first_prompt_search')) {
|
|
205
|
+
// Historical rows stay visibly unindexed until the explicit transcript
|
|
206
|
+
// backfill can derive their first stored user prompt.
|
|
207
|
+
db.exec('ALTER TABLE sessions ADD COLUMN first_prompt_search TEXT');
|
|
208
|
+
}
|
|
209
|
+
// This is the count at segment open. Historical rows deliberately remain
|
|
210
|
+
// unknown: recording today's count would manufacture stale-gate evidence.
|
|
211
|
+
if (!sessionColumns.includes('git_commit_count')) {
|
|
212
|
+
db.exec('ALTER TABLE sessions ADD COLUMN git_commit_count INTEGER');
|
|
213
|
+
}
|
|
214
|
+
const columns = db.pragma('table_info(memories)').map((c) => c.name);
|
|
215
|
+
if (!columns.includes('summarizer_status')) {
|
|
216
|
+
db.exec(`ALTER TABLE memories ADD COLUMN summarizer_status TEXT NOT NULL DEFAULT 'unknown'`);
|
|
217
|
+
}
|
|
218
|
+
if (!columns.includes('reingested_at')) {
|
|
219
|
+
db.exec('ALTER TABLE memories ADD COLUMN reingested_at TEXT');
|
|
220
|
+
}
|
|
221
|
+
if (!columns.includes('has_external_content')) {
|
|
222
|
+
db.exec('ALTER TABLE memories ADD COLUMN has_external_content INTEGER NOT NULL DEFAULT 0 CHECK (has_external_content IN (0,1))');
|
|
223
|
+
}
|
|
224
|
+
// Substantiveness is computed at read time. Older databases retain this
|
|
225
|
+
// inert NOT NULL column until their next open; fresh databases never get it.
|
|
226
|
+
const rollupColumns = db.pragma('table_info(session_rollups)').map((c) => c.name);
|
|
227
|
+
if (rollupColumns.includes('substantive')) {
|
|
228
|
+
db.exec('ALTER TABLE session_rollups DROP COLUMN substantive');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Sessions are unique by tool, native id, and segment index, with
|
|
233
|
+
* surface/git_branch/kind/trailing_branch/trailing_files. SQLite has no
|
|
234
|
+
* ALTER TABLE ... DROP/ADD CONSTRAINT, so a constraint change needs a full
|
|
235
|
+
* table rebuild - the destructive-operations rule applies even though this
|
|
236
|
+
* runs unconditionally at openDb() time, not behind a CLI --apply flag,
|
|
237
|
+
* because it is purely additive to the row set (same ids, same row count)
|
|
238
|
+
* and gated by foreign_key_check before commit.
|
|
239
|
+
*/
|
|
240
|
+
function migrateSessionsTable(db) {
|
|
241
|
+
const columns = db.pragma('table_info(sessions)').map((c) => c.name);
|
|
242
|
+
if (columns.includes('segment_index')) {
|
|
243
|
+
if (!columns.includes('git_commit_count')) {
|
|
244
|
+
db.exec('ALTER TABLE sessions ADD COLUMN git_commit_count INTEGER');
|
|
245
|
+
}
|
|
246
|
+
return; // already migrated (or a fresh DB that got the final SCHEMA directly)
|
|
247
|
+
}
|
|
248
|
+
db.pragma('foreign_keys = OFF');
|
|
249
|
+
const rebuild = db.transaction(() => {
|
|
250
|
+
db.exec(`
|
|
251
|
+
CREATE TABLE sessions_new (
|
|
252
|
+
id INTEGER PRIMARY KEY,
|
|
253
|
+
tool TEXT NOT NULL CHECK (tool IN ('claude-code','codex')),
|
|
254
|
+
native_id TEXT NOT NULL,
|
|
255
|
+
segment_index INTEGER NOT NULL DEFAULT 0,
|
|
256
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
257
|
+
source_path TEXT NOT NULL,
|
|
258
|
+
cursor TEXT,
|
|
259
|
+
started_at TEXT NOT NULL,
|
|
260
|
+
last_ingested_at TEXT NOT NULL,
|
|
261
|
+
surface TEXT CHECK (surface IN ('cli','desktop')),
|
|
262
|
+
git_branch TEXT,
|
|
263
|
+
kind TEXT CHECK (kind IN ('main','subagent','fork','adjudicator')),
|
|
264
|
+
last_turn_at TEXT,
|
|
265
|
+
trailing_branch TEXT,
|
|
266
|
+
trailing_files TEXT NOT NULL DEFAULT '[]',
|
|
267
|
+
UNIQUE (tool, native_id, segment_index)
|
|
268
|
+
);
|
|
269
|
+
`);
|
|
270
|
+
db.exec(`
|
|
271
|
+
INSERT INTO sessions_new (id, tool, native_id, segment_index, project_id, source_path, cursor, started_at, last_ingested_at, trailing_files)
|
|
272
|
+
SELECT id, tool, native_id, 0, project_id, source_path, cursor, started_at, last_ingested_at, '[]'
|
|
273
|
+
FROM sessions;
|
|
274
|
+
`);
|
|
275
|
+
db.exec('DROP TABLE sessions;');
|
|
276
|
+
db.exec('ALTER TABLE sessions_new RENAME TO sessions;');
|
|
277
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);');
|
|
278
|
+
});
|
|
279
|
+
rebuild();
|
|
280
|
+
const violations = db.pragma('foreign_key_check');
|
|
281
|
+
if (Array.isArray(violations) && violations.length > 0) {
|
|
282
|
+
throw new Error(`sessions table rebuild left ${violations.length} foreign-key violation(s): ${JSON.stringify(violations)}`);
|
|
283
|
+
}
|
|
284
|
+
db.pragma('foreign_keys = ON');
|
|
285
|
+
// Preserve rebuilt rows and add the nullable historical baseline only
|
|
286
|
+
// after the constraint migration is done.
|
|
287
|
+
db.exec('ALTER TABLE sessions ADD COLUMN git_commit_count INTEGER');
|
|
288
|
+
}
|
|
289
|
+
export function openDb(dbPath = defaultDbPath()) {
|
|
290
|
+
if (dbPath !== ':memory:') {
|
|
291
|
+
mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
292
|
+
hardenDir(path.dirname(dbPath));
|
|
293
|
+
}
|
|
294
|
+
const db = new Database(dbPath);
|
|
295
|
+
db.pragma('journal_mode = WAL');
|
|
296
|
+
db.pragma('foreign_keys = ON');
|
|
297
|
+
db.exec(SCHEMA);
|
|
298
|
+
migrate(db);
|
|
299
|
+
grandfatherConsentRoots(db);
|
|
300
|
+
canonicalizeConsentRoots(db);
|
|
301
|
+
if (dbPath !== ':memory:') {
|
|
302
|
+
// WAL mode creates -wal/-shm sidecars as a side effect of the pragma
|
|
303
|
+
// above; harden every file that actually exists on disk, not just
|
|
304
|
+
// the main DB file - a leaked WAL still holds deleted content
|
|
305
|
+
// (the purge path checkpoints WAL before deletion).
|
|
306
|
+
hardenFile(dbPath);
|
|
307
|
+
hardenFile(`${dbPath}-wal`);
|
|
308
|
+
hardenFile(`${dbPath}-shm`);
|
|
309
|
+
}
|
|
310
|
+
return db;
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Purges Codex rollouts created by OpenAI's "Import from another agent".
|
|
2
|
+
// Identification is structural and source-backed: every stored Codex
|
|
3
|
+
// source_path is re-read through CodexAdapter.classifySession(), which checks
|
|
4
|
+
// event_msg.payload.turn_id for the external-import-turn- prefix.
|
|
5
|
+
import { isReadableProviderSource } from '../config/paths.js';
|
|
6
|
+
import { errorMessage } from '../util/error.js';
|
|
7
|
+
function count(db, table) {
|
|
8
|
+
return db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count;
|
|
9
|
+
}
|
|
10
|
+
function storeCounts(db) {
|
|
11
|
+
return {
|
|
12
|
+
projects: count(db, 'projects'),
|
|
13
|
+
sessions: count(db, 'sessions'),
|
|
14
|
+
memories: count(db, 'memories'),
|
|
15
|
+
rollups: count(db, 'session_rollups'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function placeholders(values) {
|
|
19
|
+
return values.map(() => '?').join(',');
|
|
20
|
+
}
|
|
21
|
+
function affectedRollupCount(db, sessionIds) {
|
|
22
|
+
if (sessionIds.length === 0) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
const marks = placeholders(sessionIds);
|
|
26
|
+
return db
|
|
27
|
+
.prepare(`SELECT COUNT(*) AS count FROM session_rollups WHERE session_id IN (${marks}) OR parent_session_id IN (${marks})`)
|
|
28
|
+
.get(...sessionIds, ...sessionIds).count;
|
|
29
|
+
}
|
|
30
|
+
function emptiedProjects(db, sessionIds, projectIds) {
|
|
31
|
+
if (sessionIds.length === 0 || projectIds.length === 0) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const sessionMarks = placeholders(sessionIds);
|
|
35
|
+
const projectMarks = placeholders(projectIds);
|
|
36
|
+
return db
|
|
37
|
+
.prepare(`SELECT p.id, p.path
|
|
38
|
+
FROM projects p
|
|
39
|
+
WHERE p.id IN (${projectMarks})
|
|
40
|
+
AND NOT EXISTS (
|
|
41
|
+
SELECT 1 FROM sessions s
|
|
42
|
+
WHERE s.project_id = p.id AND s.id NOT IN (${sessionMarks})
|
|
43
|
+
)
|
|
44
|
+
AND NOT EXISTS (
|
|
45
|
+
SELECT 1 FROM memories m
|
|
46
|
+
WHERE m.project_id = p.id AND m.session_id NOT IN (${sessionMarks})
|
|
47
|
+
)
|
|
48
|
+
ORDER BY p.id`)
|
|
49
|
+
.all(...projectIds, ...sessionIds, ...sessionIds);
|
|
50
|
+
}
|
|
51
|
+
/** Read-only preview. Every currently referenced Codex transcript is re-read from disk. */
|
|
52
|
+
export async function planExternalAgentImportPurge(db, adapter) {
|
|
53
|
+
const sourcePaths = db.prepare("SELECT DISTINCT source_path FROM sessions WHERE tool = 'codex' ORDER BY source_path").all().map((row) => row.source_path);
|
|
54
|
+
const importedSourcePaths = [];
|
|
55
|
+
const issues = [];
|
|
56
|
+
for (const sourcePath of sourcePaths) {
|
|
57
|
+
if (!isReadableProviderSource('codex', sourcePath)) {
|
|
58
|
+
issues.push({ sourcePath, reason: 'source transcript is missing' });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const classification = await adapter.classifySession(sourcePath);
|
|
63
|
+
if (classification.exclusion === 'external-agent-import') {
|
|
64
|
+
importedSourcePaths.push(sourcePath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
issues.push({ sourcePath, reason: errorMessage(error) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const rows = importedSourcePaths.length === 0
|
|
72
|
+
? []
|
|
73
|
+
: db
|
|
74
|
+
.prepare(`SELECT s.id, s.native_id, s.segment_index, s.project_id, p.path AS project_path, s.source_path
|
|
75
|
+
FROM sessions s
|
|
76
|
+
JOIN projects p ON p.id = s.project_id
|
|
77
|
+
WHERE s.tool = 'codex' AND s.source_path IN (${placeholders(importedSourcePaths)})
|
|
78
|
+
ORDER BY s.id`)
|
|
79
|
+
.all(...importedSourcePaths);
|
|
80
|
+
const countMemories = db.prepare('SELECT COUNT(*) AS count FROM memories WHERE session_id = ?');
|
|
81
|
+
const sessions = rows.map((row) => ({
|
|
82
|
+
id: row.id,
|
|
83
|
+
nativeId: row.native_id,
|
|
84
|
+
segmentIndex: row.segment_index,
|
|
85
|
+
projectId: row.project_id,
|
|
86
|
+
projectPath: row.project_path,
|
|
87
|
+
sourcePath: row.source_path,
|
|
88
|
+
memoryRows: countMemories.get(row.id).count,
|
|
89
|
+
}));
|
|
90
|
+
const sessionIds = sessions.map((session) => session.id);
|
|
91
|
+
const projectIds = [...new Set(sessions.map((session) => session.projectId))];
|
|
92
|
+
const memoryRowsAffected = sessions.reduce((sum, session) => sum + session.memoryRows, 0);
|
|
93
|
+
const rollupsAffected = affectedRollupCount(db, sessionIds);
|
|
94
|
+
const emptiedProjectsAfterPurge = emptiedProjects(db, sessionIds, projectIds);
|
|
95
|
+
const before = storeCounts(db);
|
|
96
|
+
return {
|
|
97
|
+
sourcePathsScanned: sourcePaths.length,
|
|
98
|
+
importedSourcePaths,
|
|
99
|
+
sessions,
|
|
100
|
+
memoryRowsAffected,
|
|
101
|
+
rollupsAffected,
|
|
102
|
+
emptiedProjects: emptiedProjectsAfterPurge,
|
|
103
|
+
issues,
|
|
104
|
+
before,
|
|
105
|
+
resulting: {
|
|
106
|
+
projects: before.projects - emptiedProjectsAfterPurge.length,
|
|
107
|
+
sessions: before.sessions - sessions.length,
|
|
108
|
+
memories: before.memories - memoryRowsAffected,
|
|
109
|
+
rollups: before.rollups - rollupsAffected,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function assertPlanStillMatches(db, plan) {
|
|
114
|
+
const sessionIds = plan.sessions.map((session) => session.id);
|
|
115
|
+
const currentSessionIds = plan.importedSourcePaths.length === 0
|
|
116
|
+
? []
|
|
117
|
+
: db
|
|
118
|
+
.prepare(`SELECT id FROM sessions
|
|
119
|
+
WHERE tool = 'codex' AND source_path IN (${placeholders(plan.importedSourcePaths)})
|
|
120
|
+
ORDER BY id`)
|
|
121
|
+
.all(...plan.importedSourcePaths).map((row) => row.id);
|
|
122
|
+
if (currentSessionIds.join(',') !== sessionIds.join(',')) {
|
|
123
|
+
throw new Error('session rows changed after preview');
|
|
124
|
+
}
|
|
125
|
+
const memoryRows = sessionIds.length === 0
|
|
126
|
+
? 0
|
|
127
|
+
: db
|
|
128
|
+
.prepare(`SELECT COUNT(*) AS count FROM memories WHERE session_id IN (${placeholders(sessionIds)})`)
|
|
129
|
+
.get(...sessionIds).count;
|
|
130
|
+
if (memoryRows !== plan.memoryRowsAffected) {
|
|
131
|
+
throw new Error('memory rows changed after preview');
|
|
132
|
+
}
|
|
133
|
+
if (affectedRollupCount(db, sessionIds) !== plan.rollupsAffected) {
|
|
134
|
+
throw new Error('rollups changed after preview');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Applies exactly the previewed row ids in one transaction. */
|
|
138
|
+
export function applyExternalAgentImportPurge(db, plan) {
|
|
139
|
+
if (plan.issues.length > 0) {
|
|
140
|
+
throw new Error(`cannot apply with ${plan.issues.length} unreadable or missing source transcript(s)`);
|
|
141
|
+
}
|
|
142
|
+
const sessionIds = plan.sessions.map((session) => session.id);
|
|
143
|
+
if (sessionIds.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const marks = placeholders(sessionIds);
|
|
147
|
+
const apply = db.transaction(() => {
|
|
148
|
+
assertPlanStillMatches(db, plan);
|
|
149
|
+
db.prepare(`DELETE FROM session_rollups WHERE session_id IN (${marks}) OR parent_session_id IN (${marks})`).run(...sessionIds, ...sessionIds);
|
|
150
|
+
db.prepare(`DELETE FROM memories WHERE session_id IN (${marks})`).run(...sessionIds);
|
|
151
|
+
db.prepare(`DELETE FROM sessions WHERE id IN (${marks})`).run(...sessionIds);
|
|
152
|
+
if (plan.emptiedProjects.length > 0) {
|
|
153
|
+
const projectIds = plan.emptiedProjects.map((project) => project.id);
|
|
154
|
+
db.prepare(`DELETE FROM projects WHERE id IN (${placeholders(projectIds)})`).run(...projectIds);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
apply();
|
|
158
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
159
|
+
}
|
|
160
|
+
/** Re-reads remaining Codex source paths and validates counts plus foreign keys. */
|
|
161
|
+
export async function verifyExternalAgentImportPurge(db, adapter, plan) {
|
|
162
|
+
const errors = [];
|
|
163
|
+
const counts = storeCounts(db);
|
|
164
|
+
for (const key of ['projects', 'sessions', 'memories', 'rollups']) {
|
|
165
|
+
if (counts[key] !== plan.resulting[key]) {
|
|
166
|
+
errors.push(`${key} count is ${counts[key]}, expected ${plan.resulting[key]}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const violations = db.pragma('foreign_key_check');
|
|
170
|
+
if (violations.length > 0) {
|
|
171
|
+
errors.push(`foreign_key_check returned ${violations.length} violation(s)`);
|
|
172
|
+
}
|
|
173
|
+
const remaining = await planExternalAgentImportPurge(db, adapter);
|
|
174
|
+
if (remaining.sessions.length > 0) {
|
|
175
|
+
errors.push(`${remaining.sessions.length} session row(s) still reference external-agent import turns`);
|
|
176
|
+
}
|
|
177
|
+
if (remaining.issues.length > 0) {
|
|
178
|
+
errors.push(`${remaining.issues.length} remaining Codex source transcript(s) could not be verified`);
|
|
179
|
+
}
|
|
180
|
+
return { ok: errors.length === 0, errors, counts };
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=external-agent-import-purge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-agent-import-purge.js","sourceRoot":"","sources":["../../src/storage/external-agent-import-purge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Re-derives the bounded body-search document for each stored segment from
|
|
2
|
+
// its first retained turn. It never changes turns, boundaries, or rollups.
|
|
3
|
+
import { isReadableProviderSource } from '../config/paths.js';
|
|
4
|
+
import { applyBackfill, planBackfill } from './backfill-runner.js';
|
|
5
|
+
import { firstPromptSearch } from './first-prompt-search.js';
|
|
6
|
+
async function deriveFirstPrompt(session, adapter, firstStoredIndex) {
|
|
7
|
+
if (!isReadableProviderSource(session.tool, session.source_path)) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
for await (const turn of adapter.parseTurns(session.source_path, undefined, { closeTrailingOnIdle: true })) {
|
|
12
|
+
if (turn.turnIndex === firstStoredIndex) {
|
|
13
|
+
return firstPromptSearch(turn.userMessage);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const deriver = {
|
|
23
|
+
load(db) {
|
|
24
|
+
const sessions = db
|
|
25
|
+
.prepare('SELECT id, tool, native_id, source_path, first_prompt_search FROM sessions ORDER BY id')
|
|
26
|
+
.all();
|
|
27
|
+
const firstIndexes = db
|
|
28
|
+
.prepare('SELECT session_id, MIN(turn_index) AS turn_index FROM memories GROUP BY session_id')
|
|
29
|
+
.all();
|
|
30
|
+
return { sessions, state: new Map(firstIndexes.map((row) => [row.session_id, row.turn_index])) };
|
|
31
|
+
},
|
|
32
|
+
async derive({ adapters, session, state }) {
|
|
33
|
+
const firstStoredIndex = state.get(session.id);
|
|
34
|
+
if (firstStoredIndex === undefined) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const after = await deriveFirstPrompt(session, adapters[session.tool], firstStoredIndex);
|
|
38
|
+
if (after === undefined) {
|
|
39
|
+
return {
|
|
40
|
+
sessionId: session.id,
|
|
41
|
+
nativeId: session.native_id,
|
|
42
|
+
tool: session.tool,
|
|
43
|
+
sourcePath: session.source_path,
|
|
44
|
+
before: session.first_prompt_search,
|
|
45
|
+
after: session.first_prompt_search,
|
|
46
|
+
transcriptMissing: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (session.first_prompt_search === after) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
sessionId: session.id,
|
|
54
|
+
nativeId: session.native_id,
|
|
55
|
+
tool: session.tool,
|
|
56
|
+
sourcePath: session.source_path,
|
|
57
|
+
before: session.first_prompt_search,
|
|
58
|
+
after,
|
|
59
|
+
transcriptMissing: false,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
shouldWrite: (change) => !change.transcriptMissing,
|
|
63
|
+
write(db, change) {
|
|
64
|
+
db.prepare('UPDATE sessions SET first_prompt_search = ? WHERE id = ?').run(change.after, change.sessionId);
|
|
65
|
+
return {};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** Shows first-prompt search changes without writing. */
|
|
69
|
+
export async function planFirstPromptSearchBackfill(db, adapters) {
|
|
70
|
+
return planBackfill(db, adapters, deriver);
|
|
71
|
+
}
|
|
72
|
+
/** Applies only readable sessions' derived first-prompt search documents in one transaction. */
|
|
73
|
+
export async function applyFirstPromptSearchBackfill(db, adapters) {
|
|
74
|
+
return applyBackfill(db, adapters, deriver);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=first-prompt-search-backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-prompt-search-backfill.js","sourceRoot":"","sources":["../../src/storage/first-prompt-search-backfill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FIRST_PROMPT_SEARCH_CAP } from '../config/constants.js';
|
|
2
|
+
import { stripShellSyntax } from '../security/sanitize.js';
|
|
3
|
+
/** Stores a bounded inert search document while preserving the prompt's leading ask. */
|
|
4
|
+
export function firstPromptSearch(userMessage) {
|
|
5
|
+
return stripShellSyntax(userMessage).slice(0, FIRST_PROMPT_SEARCH_CAP);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=first-prompt-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-prompt-search.js","sourceRoot":"","sources":["../../src/storage/first-prompt-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACjD,OAAO,gBAAgB,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { normalizeForNearVerbatim } from '../security/self-ingestion.js';
|
|
3
|
+
export class InjectionStore {
|
|
4
|
+
stmts;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.stmts = {
|
|
7
|
+
insertInjection: db.prepare(`INSERT OR IGNORE INTO injections (tool, native_session_id, injected_at, injection_id, body_hash, body)
|
|
8
|
+
VALUES (@tool, @native_session_id, @injected_at, @injection_id, @body_hash, @body)`),
|
|
9
|
+
injectionsForSession: db.prepare(`SELECT * FROM injections
|
|
10
|
+
WHERE tool = ? AND native_session_id = ? AND injected_at <= ?
|
|
11
|
+
ORDER BY injected_at ASC, id ASC`),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Stores a future injection's exact body once; normalized-hash uniqueness makes repeats safe. */
|
|
15
|
+
recordInjection(input) {
|
|
16
|
+
const bodyHash = createHash('sha256').update(normalizeForNearVerbatim(input.body)).digest('hex');
|
|
17
|
+
const result = this.stmts.insertInjection.run({
|
|
18
|
+
tool: input.tool,
|
|
19
|
+
native_session_id: input.nativeSessionId,
|
|
20
|
+
injected_at: input.injectedAt,
|
|
21
|
+
injection_id: input.injectionId,
|
|
22
|
+
body_hash: bodyHash,
|
|
23
|
+
body: input.body,
|
|
24
|
+
});
|
|
25
|
+
if (result.changes > 0) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// INSERT OR IGNORE is not proof: SQLite can ignore for a different
|
|
29
|
+
// constraint or a damaged statement. A repeated body is durable only
|
|
30
|
+
// when the exact scoped row is observable before stdout is written.
|
|
31
|
+
return this.injectionsForSession(input.tool, input.nativeSessionId, input.injectedAt).some((row) => row.body_hash === bodyHash && row.body === input.body);
|
|
32
|
+
}
|
|
33
|
+
injectionsForSession(tool, nativeSessionId, atOrBefore) {
|
|
34
|
+
return this.stmts.injectionsForSession.all(tool, nativeSessionId, atOrBefore);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=injection-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection-store.js","sourceRoot":"","sources":["../../src/storage/injection-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAqBzE,MAAM,OAAO,cAAc;IACN,KAAK,CAAkE;IAExF,YAAY,EAAY;QACpB,IAAI,CAAC,KAAK,GAAG;YACT,eAAe,EAAE,EAAE,CAAC,OAAO,CACvB;oGACoF,CACvF;YACD,oBAAoB,EAAE,EAAE,CAAC,OAAO,CAC5B;;kDAEkC,CACrC;SACJ,CAAC;IACN,CAAC;IAED,kGAAkG;IAClG,eAAe,CAAC,KAA2B;QACvC,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC;YAC1C,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,iBAAiB,EAAE,KAAK,CAAC,eAAe;YACxC,WAAW,EAAE,KAAK,CAAC,UAAU;YAC7B,YAAY,EAAE,KAAK,CAAC,WAAW;YAC/B,SAAS,EAAE,QAAQ;YACnB,IAAI,EAAE,KAAK,CAAC,IAAI;SACnB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,mEAAmE;QACnE,qEAAqE;QACrE,oEAAoE;QACpE,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CACtF,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CACjE,CAAC;IACN,CAAC;IAED,oBAAoB,CAAC,IAAc,EAAE,eAAuB,EAAE,UAAkB;QAC5E,OAAO,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAmB,CAAC;IACpG,CAAC;CACJ"}
|