f9447f8e5f
CI / typecheck · build · test · bundle-size (push) Failing after 1s
CI / clean-room dependency closure smoke (push) Failing after 1s
CI / server-runtime e2e (docker · pg + valkey) (push) Failing after 2s
Deploy Install Scripts / deploy (push) Failing after 2s
Windows / build (push) Has been cancelled
770 lines
75 KiB
JavaScript
770 lines
75 KiB
JavaScript
var __IMPORT_META_URL__ = require("node:url").pathToFileURL(__filename).href;
|
|
"use strict";var k=Object.defineProperty;var oe=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var ae=Object.prototype.hasOwnProperty;var de=(d,e)=>{for(var s in e)k(d,s,{get:e[s],enumerable:!0})},_e=(d,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ie(e))!ae.call(d,r)&&r!==s&&k(d,r,{get:()=>e[r],enumerable:!(t=oe(e,r))||t.enumerable});return d};var pe=d=>_e(k({},"__esModule",{value:!0}),d);var Ae={};de(Ae,{SessionStore:()=>j});module.exports=pe(Ae);var W=require("bun:sqlite"),x=require("fs");var l=require("path"),P=require("os"),D=require("fs"),q=require("url");var ue=null;function Ee(d){return(ue??process.stderr.write.bind(process.stderr))(d)}function M(d){Ee(d)}var De=process.platform==="win32";function me(d){return d.replace(/^\uFEFF/,"")}function F(d){return JSON.parse(me(d))}function ce(){return typeof __dirname<"u"?__dirname:(0,l.dirname)((0,q.fileURLToPath)(__IMPORT_META_URL__))}var Fe=ce();function le(){if(process.env.CLAUDE_MEM_DATA_DIR)return process.env.CLAUDE_MEM_DATA_DIR;let d=(0,l.join)((0,P.homedir)(),".claude-mem"),e=(0,l.join)(d,"settings.json");try{if((0,D.existsSync)(e)){let s=F((0,D.readFileSync)(e,"utf-8")),t=s.env??s;if(t.CLAUDE_MEM_DATA_DIR)return t.CLAUDE_MEM_DATA_DIR}}catch{}return d}var I=le(),Te=process.env.CLAUDE_CONFIG_DIR||(0,l.join)((0,P.homedir)(),".claude"),we=(0,l.join)(Te,"plugins","marketplaces","thedotmack"),be=(0,l.join)(I,"logs"),xe=(0,l.join)(I,"settings.json"),V=(0,l.join)(I,"claude-mem.db"),Se=(0,l.join)(I,"observer-sessions"),X=(0,l.basename)(Se);function Y(d){(0,D.mkdirSync)(d,{recursive:!0})}var U={dataDir:()=>I,workerPid:()=>(0,l.join)(I,"worker.pid"),serverPid:()=>(0,l.join)(I,".server-beta.pid"),serverPort:()=>(0,l.join)(I,".server-beta.port"),serverRuntime:()=>(0,l.join)(I,".server-beta.runtime.json"),settings:()=>(0,l.join)(I,"settings.json"),database:()=>(0,l.join)(I,"claude-mem.db"),chroma:()=>(0,l.join)(I,"chroma"),combinedCerts:()=>(0,l.join)(I,"combined_certs.pem"),transcriptsConfig:()=>(0,l.join)(I,"transcript-watch.json"),transcriptsState:()=>(0,l.join)(I,"transcript-watch-state.json"),cloudSyncState:()=>(0,l.join)(I,"cloud-sync-state.json"),corpora:()=>(0,l.join)(I,"corpora"),supervisorRegistry:()=>(0,l.join)(I,"supervisor.json"),envFile:()=>(0,l.join)(I,".env"),logsDir:()=>be};var C=require("fs"),K=require("path");var B=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(B||{}),G=null,$=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let e=U.logsDir();(0,C.existsSync)(e)||(0,C.mkdirSync)(e,{recursive:!0});let s=new Date().toISOString().split("T")[0];this.logFilePath=(0,K.join)(e,`claude-mem-${s}.log`)}catch(e){console.error("[LOGGER] Failed to initialize log file:",e instanceof Error?e.message:String(e)),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let e=U.settings();if((0,C.existsSync)(e)){let s=(0,C.readFileSync)(e,"utf-8"),r=(F(s).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=B[r]??1}else this.level=1}catch(e){console.error("[LOGGER] Failed to load log level from settings:",e instanceof Error?e.message:String(e)),this.level=1}return this.level}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.getLevel()===0?`${e.message}
|
|
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;let t=s;if(typeof s=="string")try{t=JSON.parse(s)}catch{t=s}if(e==="Bash"&&t.command)return`${e}(${t.command})`;if(t.file_path)return`${e}(${t.file_path})`;if(t.notebook_path)return`${e}(${t.notebook_path})`;if(e==="Glob"&&t.pattern)return`${e}(${t.pattern})`;if(e==="Grep"&&t.pattern)return`${e}(${t.pattern})`;if(t.url)return`${e}(${t.url})`;if(t.query)return`${e}(${t.query})`;if(e==="Task"){if(t.subagent_type)return`${e}(${t.subagent_type})`;if(t.description)return`${e}(${t.description})`}return e==="Skill"&&t.skill?`${e}(${t.skill})`:e==="LSP"&&t.operation?`${e}(${t.operation})`:e}formatTimestamp(e){let s=e.getFullYear(),t=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0"),n=String(e.getHours()).padStart(2,"0"),o=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0"),p=String(e.getMilliseconds()).padStart(3,"0");return`${s}-${t}-${r} ${n}:${o}:${i}.${p}`}log(e,s,t,r,n){if(e<this.getLevel())return;this.ensureLogFileInitialized();let o=this.formatTimestamp(new Date),i=B[e].padEnd(5),p=s.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let E="";if(n!=null)if(n instanceof Error)E=this.getLevel()===0?`
|
|
${n.message}
|
|
${n.stack}`:` ${n.message}`;else if(this.getLevel()===0&&typeof n=="object")try{E=`
|
|
`+JSON.stringify(n,null,2)}catch{E=" "+this.formatData(n)}else E=" "+this.formatData(n);let h="";if(r){let{sessionId:N,memorySessionId:O,correlationId:R,...m}=r;Object.keys(m).length>0&&(h=` {${Object.entries(m).map(([f,v])=>`${f}=${v}`).join(", ")}}`)}let T=`[${o}] [${i}] [${p}] ${_}${t}${h}${E}`;if(this.logFilePath)try{(0,C.appendFileSync)(this.logFilePath,T+`
|
|
`,"utf8")}catch(N){let O=N instanceof Error?N:new Error(String(N));M(`[LOGGER] Failed to write to log file: ${O.message}
|
|
${O.stack??""}
|
|
`)}else M(T+`
|
|
`)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}setErrorSink(e){G=e}error(e,s,t,r){this.log(3,e,s,t,r),this.routeErrorToSink(s,t,r)}routeErrorToSink(e,s,t){try{if(!G||!(t instanceof Error))return;G(t)}catch{}}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}},a=new $;var Q=require("crypto");function J(d,e,s){return(0,Q.createHash)("sha256").update([d||"",e||"",s||""].join("\0")).digest("hex").slice(0,16)}var u="claude";function Re(d){return d.trim().toLowerCase().replace(/\s+/g,"-")}function L(d){if(!d)return u;let e=Re(d);return e?e==="transcript"||e.includes("codex")?"codex":e.includes("cursor")?"cursor":e.includes("claude")?"claude":e:u}function z(d){let e=["claude","codex","cursor"];return[...d].sort((s,t)=>{let r=e.indexOf(s),n=e.indexOf(t);return r!==-1||n!==-1?r===-1?1:n===-1?-1:r-n:s.localeCompare(t)})}function Z(d,e,s,t,r){let n=Date.now()-t,o=r!==void 0?"up.session_db_id = ?":"up.content_session_id = ?",i=r??e;return d.prepare(`
|
|
SELECT
|
|
up.*,
|
|
s.memory_session_id,
|
|
s.project,
|
|
COALESCE(s.platform_source, '${u}') as platform_source
|
|
FROM user_prompts up
|
|
JOIN sdk_sessions s ON up.session_db_id = s.id
|
|
WHERE ${o}
|
|
AND up.prompt_text = ?
|
|
AND up.created_at_epoch >= ?
|
|
ORDER BY up.created_at_epoch DESC
|
|
LIMIT 1
|
|
`).get(i,s,n)??void 0}var te=["private","claude-mem-context","system_instruction","system-instruction","persisted-output","system-reminder"],ee=new RegExp(`<(${te.join("|")})\\b[^>]*>[\\s\\S]*?</\\1>`,"g");var se=100;function Ne(d){let e=Object.fromEntries(te.map(r=>[r,0]));ee.lastIndex=0;let s=0,t=d.replace(ee,(r,n)=>(e[n]=(e[n]??0)+1,s+=1,""));return s>se&&a.warn("SYSTEM","tag count exceeds limit",void 0,{tagCount:s,maxAllowed:se,contentLength:d.length}),{stripped:t.trim(),counts:e}}function re(d){return Ne(d).stripped}var Ie=["task-notification"],Ve=new RegExp(`^\\s*<(${Ie.join("|")})\\b[^>]*>(?:(?!<\\1\\b|</\\1\\b)[\\s\\S])*</\\1>\\s*$`),Ye=256*1024;var H=4e3;function w(d){let e=d.trim(),t=re(d).trim()||e;return t.length<=H?t:(a.debug("DB","Truncated stored prompt text to the configured cap",{originalLength:t.length,storedLength:H}),`${t.slice(0,H-1)}\u2026`)}var he=require("bun:sqlite");var ge=5e3,Oe=4194304;function fe(d){return d.prepare(`
|
|
SELECT name
|
|
FROM sqlite_master
|
|
WHERE type = 'table'
|
|
AND name NOT LIKE 'sqlite_%'
|
|
LIMIT 1
|
|
`).get()!=null}function y(d,e,s){try{d.run(e)}catch(t){let r=t instanceof Error?t:new Error(String(t));throw a.warn("DB",`Failed to apply SQLite pragma ${s}`,{sql:e},r),t}}function ne(d,e={}){let{enableWal:s=!0,enableIncrementalAutoVacuum:t=!0}=e;y(d,`PRAGMA busy_timeout = ${ge}`,"busy_timeout"),y(d,"PRAGMA foreign_keys = ON","foreign_keys"),y(d,"PRAGMA synchronous = NORMAL","synchronous"),y(d,`PRAGMA journal_size_limit = ${Oe}`,"journal_size_limit"),t&&!fe(d)&&y(d,"PRAGMA auto_vacuum = INCREMENTAL","auto_vacuum"),s&&y(d,"PRAGMA journal_mode = WAL","journal_mode")}var j=class{db;constructor(e=V,s={}){e instanceof W.Database?this.db=e:(e!==":memory:"&&Y(I),this.db=new W.Database(e)),ne(this.db),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn(),this.createPendingMessagesTable(),this.renameSessionIdColumns(),this.addFailedAtEpochColumn(),this.addOnUpdateCascadeToForeignKeys(),this.addObservationContentHashColumn(),this.addSessionCustomTitleColumn(),this.addSessionPlatformSourceColumn(),this.addObservationModelColumns(),this.ensureMergedIntoProjectColumns(),this.addObservationSubagentColumns(),this.addObservationsUniqueContentHashIndex(),this.addObservationsMetadataColumn(),this.dropDeadPendingMessagesColumns(),this.ensurePendingMessagesToolUseIdColumn(),this.dropWorkerPidColumn(),this.ensureSDKSessionsPlatformContentIdentity(),this.ensureUserPromptsSessionDbId(),this.ensurePendingMessagesSessionToolUniqueIndex(),this.ensureSyncedAtColumns(s.cloudSyncStatePath??U.cloudSyncState()),this.requeuePromptCloudSyncAfterMapperFix()}getIndexColumns(e){return this.db.query(`PRAGMA index_info(${JSON.stringify(e)})`).all().map(s=>s.name)}hasUniqueIndexOnColumns(e,s){return this.db.query(`PRAGMA index_list(${e})`).all().some(r=>{if(r.unique!==1)return!1;let n=this.getIndexColumns(r.name);return n.length===s.length&&n.every((o,i)=>o===s[i])})}resolvePromptSessionDbId(e,s,t){if(s!==void 0)return s;let r=t?L(t):void 0;return r?this.db.prepare(`
|
|
SELECT id
|
|
FROM sdk_sessions
|
|
WHERE COALESCE(NULLIF(platform_source, ''), ?) = ?
|
|
AND content_session_id = ?
|
|
LIMIT 1
|
|
`).get(u,r,e)?.id??null:this.db.prepare(`
|
|
SELECT id
|
|
FROM sdk_sessions
|
|
WHERE content_session_id = ?
|
|
ORDER BY CASE COALESCE(NULLIF(platform_source, ''), '${u}')
|
|
WHEN '${u}' THEN 0
|
|
ELSE 1
|
|
END, id
|
|
LIMIT 1
|
|
`).get(e)?.id??null}dropWorkerPidColumn(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(32),t=this.db.query("PRAGMA table_info(pending_messages)").all().some(r=>r.name==="worker_pid");if(!(e&&!t)){if(t)try{this.db.run("DROP INDEX IF EXISTS idx_pending_messages_worker_pid"),this.db.run("ALTER TABLE pending_messages DROP COLUMN worker_pid"),a.debug("DB","Dropped worker_pid column and its index from pending_messages")}catch(r){a.warn("DB","Failed to drop worker_pid column from pending_messages",{},r instanceof Error?r:new Error(String(r)));return}e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(32,new Date().toISOString())}}ensureSDKSessionsPlatformContentIdentity(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(33),s=this.hasUniqueIndexOnColumns("sdk_sessions",["content_session_id"]),t=this.hasUniqueIndexOnColumns("sdk_sessions",["platform_source","content_session_id"]),n=this.db.query("PRAGMA table_info(sdk_sessions)").all().some(o=>o.name==="platform_source");if(!(e&&!s&&t&&n)){if(n||this.db.run(`ALTER TABLE sdk_sessions ADD COLUMN platform_source TEXT NOT NULL DEFAULT '${u}'`),this.db.run(`
|
|
UPDATE sdk_sessions
|
|
SET platform_source = '${u}'
|
|
WHERE platform_source IS NULL OR platform_source = ''
|
|
`),s){this.db.run("PRAGMA foreign_keys = OFF"),this.db.run("BEGIN TRANSACTION");try{this.rebuildSdkSessionsWithCompositeIdentity(e),this.db.run("COMMIT")}catch(o){this.db.run("ROLLBACK");let i=o instanceof Error?o:new Error(String(o));throw a.error("DB","Failed to rebuild sdk_sessions with composite identity, rolled back",{},i),o}finally{this.db.run("PRAGMA foreign_keys = ON")}return}this.db.run("CREATE UNIQUE INDEX IF NOT EXISTS ux_sdk_sessions_platform_content ON sdk_sessions(platform_source, content_session_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_platform_source ON sdk_sessions(platform_source)"),e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(33,new Date().toISOString())}}rebuildSdkSessionsWithCompositeIdentity(e){this.db.run("DROP TABLE IF EXISTS sdk_sessions_new"),this.db.run(`
|
|
CREATE TABLE sdk_sessions_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
content_session_id TEXT NOT NULL,
|
|
memory_session_id TEXT UNIQUE,
|
|
project TEXT NOT NULL,
|
|
platform_source TEXT NOT NULL DEFAULT '${u}',
|
|
user_prompt TEXT,
|
|
started_at TEXT NOT NULL,
|
|
started_at_epoch INTEGER NOT NULL,
|
|
completed_at TEXT,
|
|
completed_at_epoch INTEGER,
|
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed')),
|
|
worker_port INTEGER,
|
|
prompt_counter INTEGER DEFAULT 0,
|
|
custom_title TEXT
|
|
)
|
|
`),this.db.run(`
|
|
INSERT INTO sdk_sessions_new (
|
|
id, content_session_id, memory_session_id, project, platform_source,
|
|
user_prompt, started_at, started_at_epoch, completed_at, completed_at_epoch,
|
|
status, worker_port, prompt_counter, custom_title
|
|
)
|
|
SELECT
|
|
id, content_session_id, memory_session_id, project,
|
|
COALESCE(NULLIF(platform_source, ''), '${u}'),
|
|
user_prompt, started_at, started_at_epoch, completed_at, completed_at_epoch,
|
|
status, worker_port, prompt_counter, custom_title
|
|
FROM sdk_sessions
|
|
`),this.db.run("DROP TABLE sdk_sessions"),this.db.run("ALTER TABLE sdk_sessions_new RENAME TO sdk_sessions"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_claude_id ON sdk_sessions(content_session_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_sdk_id ON sdk_sessions(memory_session_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_project ON sdk_sessions(project)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_status ON sdk_sessions(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_started ON sdk_sessions(started_at_epoch DESC)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_platform_source ON sdk_sessions(platform_source)"),this.db.run("CREATE UNIQUE INDEX IF NOT EXISTS ux_sdk_sessions_platform_content ON sdk_sessions(platform_source, content_session_id)"),e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(33,new Date().toISOString())}ensureUserPromptsSessionDbId(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(34);if(this.db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='user_prompts'").all().length===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(34,new Date().toISOString());return}let r=this.db.query("PRAGMA table_info(user_prompts)").all().some(_=>_.name==="session_db_id"),o=this.db.query("PRAGMA foreign_key_list(user_prompts)").all().some(_=>_.table==="sdk_sessions"&&_.from==="content_session_id");if(e&&r&&!o)return;let i=this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='user_prompts_fts'").all().length>0,p=r?`COALESCE(up.session_db_id, (
|
|
SELECT s.id FROM sdk_sessions s
|
|
WHERE s.content_session_id = up.content_session_id
|
|
ORDER BY CASE COALESCE(NULLIF(s.platform_source, ''), '${u}')
|
|
WHEN '${u}' THEN 0
|
|
ELSE 1
|
|
END, s.id
|
|
LIMIT 1
|
|
))`:`(
|
|
SELECT s.id FROM sdk_sessions s
|
|
WHERE s.content_session_id = up.content_session_id
|
|
ORDER BY CASE COALESCE(NULLIF(s.platform_source, ''), '${u}')
|
|
WHEN '${u}' THEN 0
|
|
ELSE 1
|
|
END, s.id
|
|
LIMIT 1
|
|
)`;this.db.run("PRAGMA foreign_keys = OFF"),this.db.run("BEGIN TRANSACTION");try{this.rebuildUserPromptsWithSessionDbId(e,p,i),this.db.run("COMMIT")}catch(_){this.db.run("ROLLBACK");let E=_ instanceof Error?_:new Error(String(_));throw a.error("DB","Failed to rebuild user_prompts with session_db_id, rolled back",{},E),_}finally{this.db.run("PRAGMA foreign_keys = ON")}}rebuildUserPromptsWithSessionDbId(e,s,t){this.db.run("DROP TRIGGER IF EXISTS user_prompts_ai"),this.db.run("DROP TRIGGER IF EXISTS user_prompts_ad"),this.db.run("DROP TRIGGER IF EXISTS user_prompts_au"),this.db.run("DROP TABLE IF EXISTS user_prompts_new"),this.db.run(`
|
|
CREATE TABLE user_prompts_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_db_id INTEGER,
|
|
content_session_id TEXT NOT NULL,
|
|
prompt_number INTEGER NOT NULL,
|
|
prompt_text TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(session_db_id) REFERENCES sdk_sessions(id) ON DELETE CASCADE
|
|
)
|
|
`),this.db.run(`
|
|
INSERT INTO user_prompts_new (
|
|
id, session_db_id, content_session_id, prompt_number,
|
|
prompt_text, created_at, created_at_epoch
|
|
)
|
|
SELECT
|
|
up.id,
|
|
${s},
|
|
up.content_session_id,
|
|
up.prompt_number,
|
|
up.prompt_text,
|
|
up.created_at,
|
|
up.created_at_epoch
|
|
FROM user_prompts up
|
|
`),this.db.run("DROP TABLE user_prompts"),this.db.run("ALTER TABLE user_prompts_new RENAME TO user_prompts"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_session ON user_prompts(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_claude_session ON user_prompts(content_session_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_created ON user_prompts(created_at_epoch DESC)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_prompt_number ON user_prompts(prompt_number)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_lookup ON user_prompts(session_db_id, prompt_number)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_user_prompts_content_lookup ON user_prompts(content_session_id, prompt_number)"),t&&(this.db.run(`
|
|
CREATE TRIGGER user_prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(rowid, prompt_text)
|
|
VALUES (new.id, new.prompt_text);
|
|
END;
|
|
|
|
CREATE TRIGGER user_prompts_ad AFTER DELETE ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(user_prompts_fts, rowid, prompt_text)
|
|
VALUES('delete', old.id, old.prompt_text);
|
|
END;
|
|
|
|
CREATE TRIGGER user_prompts_au AFTER UPDATE ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(user_prompts_fts, rowid, prompt_text)
|
|
VALUES('delete', old.id, old.prompt_text);
|
|
INSERT INTO user_prompts_fts(rowid, prompt_text)
|
|
VALUES (new.id, new.prompt_text);
|
|
END;
|
|
`),this.db.run("INSERT INTO user_prompts_fts(user_prompts_fts) VALUES('rebuild')")),e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(34,new Date().toISOString())}ensurePendingMessagesSessionToolUniqueIndex(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(35);if(this.db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='pending_messages'").all().length===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(35,new Date().toISOString());return}let t=this.hasUniqueIndexOnColumns("pending_messages",["session_db_id","tool_use_id"]);if(!(e&&t)){this.db.run("BEGIN TRANSACTION");try{this.recreatePendingSessionToolUniqueIndex(e),this.db.run("COMMIT")}catch(r){this.db.run("ROLLBACK");let n=r instanceof Error?r:new Error(String(r));throw a.error("DB","Failed to recreate ux_pending_session_tool index, rolled back",{},n),r}}}recreatePendingSessionToolUniqueIndex(e){this.db.run("DROP INDEX IF EXISTS ux_pending_session_tool"),this.db.run(`
|
|
DELETE FROM pending_messages
|
|
WHERE id IN (
|
|
SELECT id
|
|
FROM (
|
|
SELECT id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY session_db_id, tool_use_id
|
|
ORDER BY CASE status
|
|
WHEN 'processing' THEN 0
|
|
WHEN 'pending' THEN 1
|
|
ELSE 2
|
|
END, id
|
|
) AS duplicate_rank
|
|
FROM pending_messages
|
|
WHERE tool_use_id IS NOT NULL
|
|
)
|
|
WHERE duplicate_rank > 1
|
|
)
|
|
`),this.db.run(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS ux_pending_session_tool
|
|
ON pending_messages(session_db_id, tool_use_id)
|
|
WHERE tool_use_id IS NOT NULL
|
|
`),e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(35,new Date().toISOString())}ensureSyncedAtColumns(e){let s=!1;for(let t of["observations","session_summaries","user_prompts"])this.db.query(`PRAGMA table_info(${t})`).all().some(o=>o.name==="synced_at")||(this.db.run(`ALTER TABLE ${t} ADD COLUMN synced_at INTEGER`),a.debug("DB",`Added synced_at column to ${t} table`),s=!0),this.db.run(`CREATE INDEX IF NOT EXISTS idx_${t}_unsynced ON ${t}(id) WHERE synced_at IS NULL`);s&&this.stampRowsSyncedByLegacyClient(e),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(39,new Date().toISOString())}requeuePromptCloudSyncAfterMapperFix(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(40))return;let s=this.db.prepare(`
|
|
UPDATE user_prompts SET synced_at = NULL WHERE synced_at IS NOT NULL
|
|
`).run();a.info("DB","Requeued prompt cloud sync after mapper fix (v40)",{requeued:s.changes}),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(40,new Date().toISOString())}stampRowsSyncedByLegacyClient(e){if(!(0,x.existsSync)(e))return;let s;try{s=JSON.parse((0,x.readFileSync)(e,"utf-8"))}catch(n){a.warn("DB","Failed to read legacy cloud-sync state, skipping synced_at adoption",{statePath:e},n instanceof Error?n:new Error(String(n)));return}if(s===null||typeof s!="object"){a.warn("DB","Legacy cloud-sync state is not an object, skipping synced_at adoption",{statePath:e});return}let t=Date.now(),r=[["observations",s.lastId],["session_summaries",s.lastSummaryId],["user_prompts",s.lastPromptId]];for(let[n,o]of r)typeof o=="number"&&o>0&&(this.db.prepare(`UPDATE ${n} SET synced_at = ? WHERE id <= ? AND synced_at IS NULL`).run(t,o),a.debug("DB",`Stamped synced_at on ${n} rows already uploaded by the legacy cloud-sync client`,{lastSyncedId:o}))}dropDeadPendingMessagesColumns(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(31),s=this.db.query("PRAGMA table_info(pending_messages)").all(),t=new Set(s.map(o=>o.name)),n=["retry_count","failed_at_epoch","completed_at_epoch"].filter(o=>t.has(o));if(!(e&&n.length===0)){if(n.length>0){this.db.run("BEGIN TRANSACTION");try{this.db.run("DELETE FROM pending_messages WHERE status NOT IN ('pending', 'processing')");for(let o of n)this.db.run(`ALTER TABLE pending_messages DROP COLUMN ${o}`),a.debug("DB",`Dropped dead column ${o} from pending_messages`);e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(31,new Date().toISOString()),this.db.run("COMMIT")}catch(o){this.db.run("ROLLBACK"),a.warn("DB","Failed to drop dead columns from pending_messages",{},o instanceof Error?o:new Error(String(o)));return}return}e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(31,new Date().toISOString())}}initializeSchema(){this.db.run(`
|
|
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
id INTEGER PRIMARY KEY,
|
|
version INTEGER UNIQUE NOT NULL,
|
|
applied_at TEXT NOT NULL
|
|
)
|
|
`),this.db.run(`
|
|
CREATE TABLE IF NOT EXISTS sdk_sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
content_session_id TEXT NOT NULL,
|
|
memory_session_id TEXT UNIQUE,
|
|
project TEXT NOT NULL,
|
|
platform_source TEXT NOT NULL DEFAULT 'claude',
|
|
user_prompt TEXT,
|
|
started_at TEXT NOT NULL,
|
|
started_at_epoch INTEGER NOT NULL,
|
|
completed_at TEXT,
|
|
completed_at_epoch INTEGER,
|
|
status TEXT CHECK(status IN ('active', 'completed', 'failed')) NOT NULL DEFAULT 'active'
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sdk_sessions_claude_id ON sdk_sessions(content_session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sdk_sessions_sdk_id ON sdk_sessions(memory_session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_sdk_sessions_project ON sdk_sessions(project);
|
|
CREATE INDEX IF NOT EXISTS idx_sdk_sessions_status ON sdk_sessions(status);
|
|
CREATE INDEX IF NOT EXISTS idx_sdk_sessions_started ON sdk_sessions(started_at_epoch DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS observations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
text TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_observations_sdk_session ON observations(memory_session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project);
|
|
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT UNIQUE NOT NULL,
|
|
project TEXT NOT NULL,
|
|
request TEXT,
|
|
investigated TEXT,
|
|
learned TEXT,
|
|
completed TEXT,
|
|
next_steps TEXT,
|
|
files_read TEXT,
|
|
files_edited TEXT,
|
|
notes TEXT,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(memory_session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
|
|
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
|
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString())}ensureWorkerPortColumn(){this.db.query("PRAGMA table_info(sdk_sessions)").all().some(t=>t.name==="worker_port")||(this.db.run("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),a.debug("DB","Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}ensurePromptTrackingColumns(){this.db.query("PRAGMA table_info(sdk_sessions)").all().some(i=>i.name==="prompt_counter")||(this.db.run("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),a.debug("DB","Added prompt_counter column to sdk_sessions table")),this.db.query("PRAGMA table_info(observations)").all().some(i=>i.name==="prompt_number")||(this.db.run("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),a.debug("DB","Added prompt_number column to observations table")),this.db.query("PRAGMA table_info(session_summaries)").all().some(i=>i.name==="prompt_number")||(this.db.run("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),a.debug("DB","Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}removeSessionSummariesUniqueConstraint(){if(!this.db.query("PRAGMA index_list(session_summaries)").all().some(t=>t.unique===1&&t.origin!=="pk")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}a.debug("DB","Removing UNIQUE constraint from session_summaries.memory_session_id"),this.db.run("BEGIN TRANSACTION"),this.db.run("DROP TABLE IF EXISTS session_summaries_new"),this.db.run(`
|
|
CREATE TABLE session_summaries_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
request TEXT,
|
|
investigated TEXT,
|
|
learned TEXT,
|
|
completed TEXT,
|
|
next_steps TEXT,
|
|
files_read TEXT,
|
|
files_edited TEXT,
|
|
notes TEXT,
|
|
prompt_number INTEGER,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE
|
|
)
|
|
`),this.db.run(`
|
|
INSERT INTO session_summaries_new
|
|
SELECT id, memory_session_id, project, request, investigated, learned,
|
|
completed, next_steps, files_read, files_edited, notes,
|
|
prompt_number, created_at, created_at_epoch
|
|
FROM session_summaries
|
|
`),this.db.run("DROP TABLE session_summaries"),this.db.run("ALTER TABLE session_summaries_new RENAME TO session_summaries"),this.db.run(`
|
|
CREATE INDEX idx_session_summaries_sdk_session ON session_summaries(memory_session_id);
|
|
CREATE INDEX idx_session_summaries_project ON session_summaries(project);
|
|
CREATE INDEX idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
|
`),this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString()),a.debug("DB","Successfully removed UNIQUE constraint from session_summaries.memory_session_id")}addObservationHierarchicalFields(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(8))return;if(this.db.query("PRAGMA table_info(observations)").all().some(r=>r.name==="title")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString());return}a.debug("DB","Adding hierarchical fields to observations table"),this.db.run(`
|
|
ALTER TABLE observations ADD COLUMN title TEXT;
|
|
ALTER TABLE observations ADD COLUMN subtitle TEXT;
|
|
ALTER TABLE observations ADD COLUMN facts TEXT;
|
|
ALTER TABLE observations ADD COLUMN narrative TEXT;
|
|
ALTER TABLE observations ADD COLUMN concepts TEXT;
|
|
ALTER TABLE observations ADD COLUMN files_read TEXT;
|
|
ALTER TABLE observations ADD COLUMN files_modified TEXT;
|
|
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),a.debug("DB","Successfully added hierarchical fields to observations table")}makeObservationsTextNullable(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let t=this.db.query("PRAGMA table_info(observations)").all().find(r=>r.name==="text");if(!t||t.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}a.debug("DB","Making observations.text nullable"),this.db.run("BEGIN TRANSACTION"),this.db.run("DROP TABLE IF EXISTS observations_new"),this.db.run(`
|
|
CREATE TABLE observations_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
text TEXT,
|
|
type TEXT NOT NULL,
|
|
title TEXT,
|
|
subtitle TEXT,
|
|
facts TEXT,
|
|
narrative TEXT,
|
|
concepts TEXT,
|
|
files_read TEXT,
|
|
files_modified TEXT,
|
|
prompt_number INTEGER,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE
|
|
)
|
|
`),this.db.run(`
|
|
INSERT INTO observations_new
|
|
SELECT id, memory_session_id, project, text, type, title, subtitle, facts,
|
|
narrative, concepts, files_read, files_modified, prompt_number,
|
|
created_at, created_at_epoch
|
|
FROM observations
|
|
`),this.db.run("DROP TABLE observations"),this.db.run("ALTER TABLE observations_new RENAME TO observations"),this.db.run(`
|
|
CREATE INDEX idx_observations_sdk_session ON observations(memory_session_id);
|
|
CREATE INDEX idx_observations_project ON observations(project);
|
|
CREATE INDEX idx_observations_type ON observations(type);
|
|
CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC);
|
|
`),this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),a.debug("DB","Successfully made observations.text nullable")}createUserPromptsTable(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(10))return;if(this.db.query("PRAGMA table_info(user_prompts)").all().length>0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(10,new Date().toISOString());return}a.debug("DB","Creating user_prompts table with FTS5 support"),this.db.run("BEGIN TRANSACTION"),this.db.run(`
|
|
CREATE TABLE user_prompts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_db_id INTEGER,
|
|
content_session_id TEXT NOT NULL,
|
|
prompt_number INTEGER NOT NULL,
|
|
prompt_text TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(session_db_id) REFERENCES sdk_sessions(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX idx_user_prompts_session ON user_prompts(session_db_id);
|
|
CREATE INDEX idx_user_prompts_claude_session ON user_prompts(content_session_id);
|
|
CREATE INDEX idx_user_prompts_created ON user_prompts(created_at_epoch DESC);
|
|
CREATE INDEX idx_user_prompts_prompt_number ON user_prompts(prompt_number);
|
|
CREATE INDEX idx_user_prompts_lookup ON user_prompts(session_db_id, prompt_number);
|
|
CREATE INDEX idx_user_prompts_content_lookup ON user_prompts(content_session_id, prompt_number);
|
|
`);let t=`
|
|
CREATE VIRTUAL TABLE user_prompts_fts USING fts5(
|
|
prompt_text,
|
|
content='user_prompts',
|
|
content_rowid='id'
|
|
);
|
|
`,r=`
|
|
CREATE TRIGGER user_prompts_ai AFTER INSERT ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(rowid, prompt_text)
|
|
VALUES (new.id, new.prompt_text);
|
|
END;
|
|
|
|
CREATE TRIGGER user_prompts_ad AFTER DELETE ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(user_prompts_fts, rowid, prompt_text)
|
|
VALUES('delete', old.id, old.prompt_text);
|
|
END;
|
|
|
|
CREATE TRIGGER user_prompts_au AFTER UPDATE ON user_prompts BEGIN
|
|
INSERT INTO user_prompts_fts(user_prompts_fts, rowid, prompt_text)
|
|
VALUES('delete', old.id, old.prompt_text);
|
|
INSERT INTO user_prompts_fts(rowid, prompt_text)
|
|
VALUES (new.id, new.prompt_text);
|
|
END;
|
|
`;try{this.db.run(t),this.db.run(r)}catch(n){n instanceof Error?a.warn("DB","FTS5 not available \u2014 user_prompts_fts skipped (search uses ChromaDB)",{},n):a.warn("DB","FTS5 not available \u2014 user_prompts_fts skipped (search uses ChromaDB)",{},new Error(String(n))),this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(10,new Date().toISOString()),a.debug("DB","Created user_prompts table (without FTS5)");return}this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(10,new Date().toISOString()),a.debug("DB","Successfully created user_prompts table")}ensureDiscoveryTokensColumn(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(11))return;this.db.query("PRAGMA table_info(observations)").all().some(o=>o.name==="discovery_tokens")||(this.db.run("ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0"),a.debug("DB","Added discovery_tokens column to observations table")),this.db.query("PRAGMA table_info(session_summaries)").all().some(o=>o.name==="discovery_tokens")||(this.db.run("ALTER TABLE session_summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0"),a.debug("DB","Added discovery_tokens column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(11,new Date().toISOString())}createPendingMessagesTable(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(16))return;if(this.db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='pending_messages'").all().length>0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString());return}a.debug("DB","Creating pending_messages table"),this.db.run(`
|
|
CREATE TABLE pending_messages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_db_id INTEGER NOT NULL,
|
|
content_session_id TEXT NOT NULL,
|
|
message_type TEXT NOT NULL CHECK(message_type IN ('observation', 'summarize')),
|
|
tool_name TEXT,
|
|
tool_input TEXT,
|
|
tool_response TEXT,
|
|
cwd TEXT,
|
|
last_user_message TEXT,
|
|
last_assistant_message TEXT,
|
|
prompt_number INTEGER,
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'processing')),
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY (session_db_id) REFERENCES sdk_sessions(id) ON DELETE CASCADE
|
|
)
|
|
`),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_session ON pending_messages(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_status ON pending_messages(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_claude_session ON pending_messages(content_session_id)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString()),a.debug("DB","pending_messages table created successfully")}renameSessionIdColumns(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(17))return;a.debug("DB","Checking session ID columns for semantic clarity rename");let s=0,t=(r,n,o)=>{let i=this.db.query(`PRAGMA table_info(${r})`).all(),p=i.some(E=>E.name===n);return i.some(E=>E.name===o)?!1:p?(this.db.run(`ALTER TABLE ${r} RENAME COLUMN ${n} TO ${o}`),a.debug("DB",`Renamed ${r}.${n} to ${o}`),!0):(a.warn("DB",`Column ${n} not found in ${r}, skipping rename`),!1)};t("sdk_sessions","claude_session_id","content_session_id")&&s++,t("sdk_sessions","sdk_session_id","memory_session_id")&&s++,t("pending_messages","claude_session_id","content_session_id")&&s++,t("observations","sdk_session_id","memory_session_id")&&s++,t("session_summaries","sdk_session_id","memory_session_id")&&s++,t("user_prompts","claude_session_id","content_session_id")&&s++,this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString()),s>0?a.debug("DB",`Successfully renamed ${s} session ID columns`):a.debug("DB","No session ID column renames needed (already up to date)")}addFailedAtEpochColumn(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(20))return;this.db.query("PRAGMA table_info(pending_messages)").all().some(r=>r.name==="failed_at_epoch")||(this.db.run("ALTER TABLE pending_messages ADD COLUMN failed_at_epoch INTEGER"),a.debug("DB","Added failed_at_epoch column to pending_messages table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(20,new Date().toISOString())}addOnUpdateCascadeToForeignKeys(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(21))return;a.debug("DB","Adding ON UPDATE CASCADE to FK constraints on observations and session_summaries"),this.db.run("PRAGMA foreign_keys = OFF"),this.db.run("BEGIN TRANSACTION"),this.db.run("DROP TRIGGER IF EXISTS observations_ai"),this.db.run("DROP TRIGGER IF EXISTS observations_ad"),this.db.run("DROP TRIGGER IF EXISTS observations_au"),this.db.run("DROP TABLE IF EXISTS observations_new");let s=this.db.query("PRAGMA table_info(observations)").all(),t=s.some(c=>c.name==="metadata"),r=s.some(c=>c.name==="content_hash"),n=t?`,
|
|
metadata TEXT`:"",o=t?", metadata":"",i=r?`,
|
|
content_hash TEXT`:"",p=r?", content_hash":"",_=`
|
|
CREATE TABLE observations_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
text TEXT,
|
|
type TEXT NOT NULL,
|
|
title TEXT,
|
|
subtitle TEXT,
|
|
facts TEXT,
|
|
narrative TEXT,
|
|
concepts TEXT,
|
|
files_read TEXT,
|
|
files_modified TEXT,
|
|
prompt_number INTEGER,
|
|
discovery_tokens INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL${n}${i},
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
)
|
|
`,E=`
|
|
INSERT INTO observations_new
|
|
SELECT id, memory_session_id, project, text, type, title, subtitle, facts,
|
|
narrative, concepts, files_read, files_modified, prompt_number,
|
|
discovery_tokens, created_at, created_at_epoch${o}${p}
|
|
FROM observations
|
|
`,h=`
|
|
CREATE INDEX idx_observations_sdk_session ON observations(memory_session_id);
|
|
CREATE INDEX idx_observations_project ON observations(project);
|
|
CREATE INDEX idx_observations_type ON observations(type);
|
|
CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC);
|
|
`,T=`
|
|
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)
|
|
VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
INSERT INTO observations_fts(observations_fts, rowid, title, subtitle, narrative, text, facts, concepts)
|
|
VALUES('delete', old.id, old.title, old.subtitle, old.narrative, old.text, old.facts, old.concepts);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
INSERT INTO observations_fts(observations_fts, rowid, title, subtitle, narrative, text, facts, concepts)
|
|
VALUES('delete', old.id, old.title, old.subtitle, old.narrative, old.text, old.facts, old.concepts);
|
|
INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)
|
|
VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts);
|
|
END;
|
|
`;this.db.run("DROP TRIGGER IF EXISTS session_summaries_ai"),this.db.run("DROP TRIGGER IF EXISTS session_summaries_ad"),this.db.run("DROP TRIGGER IF EXISTS session_summaries_au"),this.db.run("DROP TABLE IF EXISTS session_summaries_new");let N=`
|
|
CREATE TABLE session_summaries_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
memory_session_id TEXT NOT NULL,
|
|
project TEXT NOT NULL,
|
|
request TEXT,
|
|
investigated TEXT,
|
|
learned TEXT,
|
|
completed TEXT,
|
|
next_steps TEXT,
|
|
files_read TEXT,
|
|
files_edited TEXT,
|
|
notes TEXT,
|
|
prompt_number INTEGER,
|
|
discovery_tokens INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
created_at_epoch INTEGER NOT NULL,
|
|
FOREIGN KEY(memory_session_id) REFERENCES sdk_sessions(memory_session_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
)
|
|
`,O=`
|
|
INSERT INTO session_summaries_new
|
|
SELECT id, memory_session_id, project, request, investigated, learned,
|
|
completed, next_steps, files_read, files_edited, notes,
|
|
prompt_number, discovery_tokens, created_at, created_at_epoch
|
|
FROM session_summaries
|
|
`,R=`
|
|
CREATE INDEX idx_session_summaries_sdk_session ON session_summaries(memory_session_id);
|
|
CREATE INDEX idx_session_summaries_project ON session_summaries(project);
|
|
CREATE INDEX idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
|
`,m=`
|
|
CREATE TRIGGER IF NOT EXISTS session_summaries_ai AFTER INSERT ON session_summaries BEGIN
|
|
INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
|
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS session_summaries_ad AFTER DELETE ON session_summaries BEGIN
|
|
INSERT INTO session_summaries_fts(session_summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
|
|
VALUES('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS session_summaries_au AFTER UPDATE ON session_summaries BEGIN
|
|
INSERT INTO session_summaries_fts(session_summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
|
|
VALUES('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
|
|
INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
|
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
|
|
END;
|
|
`;try{this.recreateObservationsWithCascade(_,E,h,T),this.recreateSessionSummariesWithCascade(N,O,R,m),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(21,new Date().toISOString()),this.db.run("COMMIT"),this.db.run("PRAGMA foreign_keys = ON"),a.debug("DB","Successfully added ON UPDATE CASCADE to FK constraints")}catch(c){throw this.db.run("ROLLBACK"),this.db.run("PRAGMA foreign_keys = ON"),c instanceof Error?c:new Error(String(c))}}recreateObservationsWithCascade(e,s,t,r){this.db.run(e),this.db.run(s),this.db.run("DROP TABLE observations"),this.db.run("ALTER TABLE observations_new RENAME TO observations"),this.db.run(t),this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='observations_fts'").all().length>0&&this.db.run(r)}recreateSessionSummariesWithCascade(e,s,t,r){this.db.run(e),this.db.run(s),this.db.run("DROP TABLE session_summaries"),this.db.run("ALTER TABLE session_summaries_new RENAME TO session_summaries"),this.db.run(t),this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session_summaries_fts'").all().length>0&&this.db.run(r)}addObservationContentHashColumn(){if(this.db.query("PRAGMA table_info(observations)").all().some(t=>t.name==="content_hash")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(22,new Date().toISOString());return}this.db.run("ALTER TABLE observations ADD COLUMN content_hash TEXT"),this.db.run("UPDATE observations SET content_hash = substr(hex(randomblob(8)), 1, 16) WHERE content_hash IS NULL"),this.db.run("CREATE INDEX IF NOT EXISTS idx_observations_content_hash ON observations(content_hash, created_at_epoch)"),a.debug("DB","Added content_hash column to observations table with backfill and index"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(22,new Date().toISOString())}addSessionCustomTitleColumn(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(23),t=this.db.query("PRAGMA table_info(sdk_sessions)").all().some(r=>r.name==="custom_title");e&&t||(t||(this.db.run("ALTER TABLE sdk_sessions ADD COLUMN custom_title TEXT"),a.debug("DB","Added custom_title column to sdk_sessions table")),e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(23,new Date().toISOString()))}addSessionPlatformSourceColumn(){let s=this.db.query("PRAGMA table_info(sdk_sessions)").all().some(o=>o.name==="platform_source"),r=this.db.query("PRAGMA index_list(sdk_sessions)").all().some(o=>o.name==="idx_sdk_sessions_platform_source");this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(24)&&s&&r||(s||(this.db.run(`ALTER TABLE sdk_sessions ADD COLUMN platform_source TEXT NOT NULL DEFAULT '${u}'`),a.debug("DB","Added platform_source column to sdk_sessions table")),this.db.run(`
|
|
UPDATE sdk_sessions
|
|
SET platform_source = '${u}'
|
|
WHERE platform_source IS NULL OR platform_source = ''
|
|
`),r||this.db.run("CREATE INDEX IF NOT EXISTS idx_sdk_sessions_platform_source ON sdk_sessions(platform_source)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(24,new Date().toISOString()))}addObservationModelColumns(){let e=this.db.query("PRAGMA table_info(observations)").all(),s=e.some(r=>r.name==="generated_by_model"),t=e.some(r=>r.name==="relevance_count");s&&t||(s||this.db.run("ALTER TABLE observations ADD COLUMN generated_by_model TEXT"),t||this.db.run("ALTER TABLE observations ADD COLUMN relevance_count INTEGER DEFAULT 0"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(26,new Date().toISOString()))}ensureMergedIntoProjectColumns(){this.db.query("PRAGMA table_info(observations)").all().some(t=>t.name==="merged_into_project")||this.db.run("ALTER TABLE observations ADD COLUMN merged_into_project TEXT"),this.db.run("CREATE INDEX IF NOT EXISTS idx_observations_merged_into ON observations(merged_into_project)"),this.db.query("PRAGMA table_info(session_summaries)").all().some(t=>t.name==="merged_into_project")||this.db.run("ALTER TABLE session_summaries ADD COLUMN merged_into_project TEXT"),this.db.run("CREATE INDEX IF NOT EXISTS idx_summaries_merged_into ON session_summaries(merged_into_project)")}addObservationSubagentColumns(){let e=this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(27),s=this.db.query("PRAGMA table_info(observations)").all(),t=s.some(o=>o.name==="agent_type"),r=s.some(o=>o.name==="agent_id");t||this.db.run("ALTER TABLE observations ADD COLUMN agent_type TEXT"),r||this.db.run("ALTER TABLE observations ADD COLUMN agent_id TEXT"),this.db.run("CREATE INDEX IF NOT EXISTS idx_observations_agent_type ON observations(agent_type)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_observations_agent_id ON observations(agent_id)");let n=this.db.query("PRAGMA table_info(pending_messages)").all();if(n.length>0){let o=n.some(p=>p.name==="agent_type"),i=n.some(p=>p.name==="agent_id");o||this.db.run("ALTER TABLE pending_messages ADD COLUMN agent_type TEXT"),i||this.db.run("ALTER TABLE pending_messages ADD COLUMN agent_id TEXT")}e||this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(27,new Date().toISOString())}ensurePendingMessagesToolUseIdColumn(){if(this.db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='pending_messages'").all().length===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(28,new Date().toISOString());return}this.db.query("PRAGMA table_info(pending_messages)").all().some(r=>r.name==="tool_use_id")||this.db.run("ALTER TABLE pending_messages ADD COLUMN tool_use_id TEXT"),this.db.run("BEGIN TRANSACTION");try{this.dedupePendingMessagesByToolUseId(),this.db.run("COMMIT")}catch(r){this.db.run("ROLLBACK");let n=r instanceof Error?r:new Error(String(r));throw a.error("DB","Failed to de-dupe pending_messages by tool_use_id, rolled back",{},n),r}}dedupePendingMessagesByToolUseId(){this.db.run(`
|
|
DELETE FROM pending_messages
|
|
WHERE id IN (
|
|
SELECT id
|
|
FROM (
|
|
SELECT id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY session_db_id, tool_use_id
|
|
ORDER BY CASE status
|
|
WHEN 'processing' THEN 0
|
|
WHEN 'pending' THEN 1
|
|
ELSE 2
|
|
END, id
|
|
) AS duplicate_rank
|
|
FROM pending_messages
|
|
WHERE tool_use_id IS NOT NULL
|
|
)
|
|
WHERE duplicate_rank > 1
|
|
)
|
|
`),this.db.run(`
|
|
-- tool_use_id is optional for summaries and legacy rows; enforce de-dupe
|
|
-- only for rows that came from a concrete tool-use event.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS ux_pending_session_tool
|
|
ON pending_messages(session_db_id, tool_use_id)
|
|
WHERE tool_use_id IS NOT NULL
|
|
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(28,new Date().toISOString())}addObservationsUniqueContentHashIndex(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(29))return;let s=this.db.query("PRAGMA table_info(observations)").all(),t=s.some(n=>n.name==="memory_session_id"),r=s.some(n=>n.name==="content_hash");if(!t||!r){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(29,new Date().toISOString());return}this.db.run("BEGIN TRANSACTION");try{this.dedupeObservationsByContentHash(),this.db.run("COMMIT")}catch(n){this.db.run("ROLLBACK");let o=n instanceof Error?n:new Error(String(n));throw a.error("DB","Failed to de-dupe observations by content_hash, rolled back",{},o),n}}dedupeObservationsByContentHash(){this.db.run(`
|
|
UPDATE observations
|
|
SET content_hash = '__null_migration_' || id || '__'
|
|
WHERE content_hash IS NULL
|
|
`),this.db.run(`
|
|
DELETE FROM observations
|
|
WHERE id IN (
|
|
SELECT id
|
|
FROM (
|
|
SELECT id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY memory_session_id, content_hash
|
|
ORDER BY id
|
|
) AS duplicate_rank
|
|
FROM observations
|
|
)
|
|
WHERE duplicate_rank > 1
|
|
)
|
|
`),this.db.run(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS ux_observations_session_hash
|
|
ON observations(memory_session_id, content_hash)
|
|
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(29,new Date().toISOString())}addObservationsMetadataColumn(){this.db.query("PRAGMA table_info(observations)").all().some(t=>t.name==="metadata")||(this.db.run("ALTER TABLE observations ADD COLUMN metadata TEXT"),a.debug("DB","Added metadata column to observations table (#2116)")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(30,new Date().toISOString())}updateMemorySessionId(e,s){this.db.prepare(`
|
|
UPDATE sdk_sessions
|
|
SET memory_session_id = ?
|
|
WHERE id = ?
|
|
`).run(s,e),s&&this.requeuePromptSync(e)}requeuePromptSync(e){this.db.prepare(`
|
|
UPDATE user_prompts SET synced_at = NULL
|
|
WHERE session_db_id = ? AND synced_at IS NOT NULL
|
|
`).run(e)}markSessionCompleted(e){let s=Date.now(),t=new Date(s).toISOString();this.db.prepare(`
|
|
UPDATE sdk_sessions
|
|
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
|
WHERE id = ?
|
|
`).run(t,s,e)}ensureMemorySessionIdRegistered(e,s,t){let r=this.db.prepare(`
|
|
SELECT id, memory_session_id, worker_port FROM sdk_sessions WHERE id = ?
|
|
`).get(e);if(!r)throw new Error(`Session ${e} not found in sdk_sessions`);r.memory_session_id!==s&&(this.db.prepare(`
|
|
UPDATE sdk_sessions SET memory_session_id = ? WHERE id = ?
|
|
`).run(s,e),this.requeuePromptSync(e),a.info("DB","Registered memory_session_id before storage (FK fix)",{sessionDbId:e,oldId:r.memory_session_id,newId:s})),typeof t=="number"&&r.worker_port!==t&&this.db.prepare(`
|
|
UPDATE sdk_sessions SET worker_port = ? WHERE id = ?
|
|
`).run(t,e)}getAllProjects(e){let s=e?L(e):void 0,t=`
|
|
SELECT DISTINCT project
|
|
FROM sdk_sessions
|
|
WHERE project IS NOT NULL AND project != ''
|
|
AND project != ?
|
|
`,r=[X];return s&&(t+=" AND COALESCE(platform_source, ?) = ?",r.push(u,s)),t+=" ORDER BY project ASC",this.db.prepare(t).all(...r).map(o=>o.project)}getProjectCatalog(){let e=this.db.prepare(`
|
|
SELECT
|
|
COALESCE(platform_source, '${u}') as platform_source,
|
|
project,
|
|
MAX(started_at_epoch) as latest_epoch
|
|
FROM sdk_sessions
|
|
WHERE project IS NOT NULL AND project != ''
|
|
AND project != ?
|
|
GROUP BY COALESCE(platform_source, '${u}'), project
|
|
ORDER BY latest_epoch DESC
|
|
`).all(X),s=[],t=new Set,r={};for(let o of e){let i=L(o.platform_source);r[i]||(r[i]=[]),r[i].includes(o.project)||r[i].push(o.project),t.has(o.project)||(t.add(o.project),s.push(o.project))}let n=z(Object.keys(r));return{projects:s,sources:n,projectsBySource:Object.fromEntries(n.map(o=>[o,r[o]||[]]))}}getLatestUserPrompt(e,s){let t=this.resolvePromptSessionDbId(e,s),r=t!==null?"up.session_db_id = ?":"up.content_session_id = ?",n=t!==null?t:e;return this.db.prepare(`
|
|
SELECT
|
|
up.*,
|
|
s.memory_session_id,
|
|
s.project,
|
|
COALESCE(s.platform_source, '${u}') as platform_source
|
|
FROM user_prompts up
|
|
JOIN sdk_sessions s ON up.session_db_id = s.id
|
|
WHERE ${r}
|
|
ORDER BY up.created_at_epoch DESC
|
|
LIMIT 1
|
|
`).get(n)}findRecentDuplicateUserPrompt(e,s,t,r){return Z(this.db,e,w(s),t,this.resolvePromptSessionDbId(e,r)??void 0)}getRecentSessionsWithStatus(e,s=3,t){let r=[e],n="";return t&&(n=`AND COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?`,r.push(L(t))),r.push(s),this.db.prepare(`
|
|
SELECT * FROM (
|
|
SELECT
|
|
s.memory_session_id,
|
|
s.status,
|
|
s.started_at,
|
|
s.started_at_epoch,
|
|
s.user_prompt,
|
|
CASE WHEN sum.memory_session_id IS NOT NULL THEN 1 ELSE 0 END as has_summary
|
|
FROM sdk_sessions s
|
|
LEFT JOIN session_summaries sum ON s.memory_session_id = sum.memory_session_id
|
|
WHERE s.project = ? AND s.memory_session_id IS NOT NULL
|
|
${n}
|
|
GROUP BY s.memory_session_id
|
|
ORDER BY s.started_at_epoch DESC
|
|
LIMIT ?
|
|
)
|
|
ORDER BY started_at_epoch ASC
|
|
`).all(...r)}getObservationsForSession(e,s){let t=[e],r="";return s&&(r=`
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM sdk_sessions s
|
|
WHERE s.memory_session_id = observations.memory_session_id
|
|
AND COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?
|
|
)
|
|
`,t.push(L(s))),this.db.prepare(`
|
|
SELECT title, subtitle, type, prompt_number
|
|
FROM observations
|
|
WHERE memory_session_id = ?
|
|
${r}
|
|
ORDER BY created_at_epoch ASC
|
|
`).all(...t)}getObservationById(e,s){return s?this.db.prepare(`
|
|
SELECT o.*
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions s ON s.memory_session_id = o.memory_session_id
|
|
WHERE o.id = ?
|
|
AND COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?
|
|
`).get(e,L(s))||null:this.db.prepare(`
|
|
SELECT *
|
|
FROM observations
|
|
WHERE id = ?
|
|
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r,project:n,platformSource:o,type:i,concepts:p,files:_}=s,E=t==="relevance",h=E?"":`ORDER BY o.created_at_epoch ${t==="date_asc"?"ASC":"DESC"}`,T=r&&!E?`LIMIT ${r}`:"",N=e.map(()=>"?").join(","),O=[...e],R=[];if(n&&(R.push("o.project = ?"),O.push(n)),o&&(R.push(`COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?`),O.push(L(o))),i)if(Array.isArray(i)){let g=i.map(()=>"?").join(",");R.push(`o.type IN (${g})`),O.push(...i)}else R.push("o.type = ?"),O.push(i);if(p){let g=Array.isArray(p)?p:[p],S=g.map(()=>"EXISTS (SELECT 1 FROM json_each(o.concepts) WHERE value = ?)");O.push(...g),R.push(`(${S.join(" OR ")})`)}if(_){let g=Array.isArray(_)?_:[_],S=g.map(()=>"(EXISTS (SELECT 1 FROM json_each(o.files_read) WHERE value LIKE ?) OR EXISTS (SELECT 1 FROM json_each(o.files_modified) WHERE value LIKE ?))");g.forEach(A=>{O.push(`%${A}%`,`%${A}%`)}),R.push(`(${S.join(" OR ")})`)}let m=R.length>0?`WHERE o.id IN (${N}) AND ${R.join(" AND ")}`:`WHERE o.id IN (${N})`,f=this.db.prepare(`
|
|
SELECT o.*
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions s ON s.memory_session_id = o.memory_session_id
|
|
${m}
|
|
${h}
|
|
${T}
|
|
`).all(...O);if(!E)return f;let v=new Map(f.map(g=>[g.id,g])),b=e.map(g=>v.get(g)).filter(g=>!!g);return r?b.slice(0,r):b}getSummaryForSession(e,s){let t=[e],r="";return s&&(r=`
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM sdk_sessions sdk
|
|
WHERE sdk.memory_session_id = session_summaries.memory_session_id
|
|
AND COALESCE(NULLIF(sdk.platform_source, ''), '${u}') = ?
|
|
)
|
|
`,t.push(L(s))),this.db.prepare(`
|
|
SELECT
|
|
request, investigated, learned, completed, next_steps,
|
|
files_read, files_edited, notes, prompt_number, created_at,
|
|
created_at_epoch
|
|
FROM session_summaries
|
|
WHERE memory_session_id = ?
|
|
${r}
|
|
ORDER BY created_at_epoch DESC
|
|
LIMIT 1
|
|
`).get(...t)||null}getSessionById(e){return this.db.prepare(`
|
|
SELECT id, content_session_id, memory_session_id, project,
|
|
COALESCE(platform_source, '${u}') as platform_source,
|
|
user_prompt, custom_title, status
|
|
FROM sdk_sessions
|
|
WHERE id = ?
|
|
LIMIT 1
|
|
`).get(e)||null}getSdkSessionsBySessionIds(e){if(e.length===0)return[];let s=e.map(()=>"?").join(",");return this.db.prepare(`
|
|
SELECT id, content_session_id, memory_session_id, project,
|
|
COALESCE(platform_source, '${u}') as platform_source,
|
|
user_prompt, custom_title,
|
|
started_at, started_at_epoch, completed_at, completed_at_epoch, status
|
|
FROM sdk_sessions
|
|
WHERE memory_session_id IN (${s})
|
|
ORDER BY started_at_epoch DESC
|
|
`).all(...e)}getPromptNumberFromUserPrompts(e,s){let t=this.resolvePromptSessionDbId(e,s);return t!==null?this.db.prepare(`
|
|
SELECT COUNT(*) as count FROM user_prompts WHERE session_db_id = ?
|
|
`).get(t).count:this.db.prepare(`
|
|
SELECT COUNT(*) as count FROM user_prompts WHERE content_session_id = ?
|
|
`).get(e).count}createSDKSession(e,s,t,r,n){let o=new Date,i=o.getTime(),p=n?L(n):u,_=w(t),E=this.db.prepare(`
|
|
SELECT id, platform_source
|
|
FROM sdk_sessions
|
|
WHERE COALESCE(NULLIF(platform_source, ''), ?) = ?
|
|
AND content_session_id = ?
|
|
`).get(u,p,e);if(E)return s&&this.db.prepare(`
|
|
UPDATE sdk_sessions SET project = ?
|
|
WHERE id = ? AND (project IS NULL OR project = '')
|
|
`).run(s,E.id),r&&this.db.prepare(`
|
|
UPDATE sdk_sessions SET custom_title = ?
|
|
WHERE id = ? AND custom_title IS NULL
|
|
`).run(r,E.id),E.id;let h=this.db.prepare(`
|
|
INSERT INTO sdk_sessions
|
|
(content_session_id, memory_session_id, project, platform_source, user_prompt, custom_title, started_at, started_at_epoch, status)
|
|
VALUES (?, NULL, ?, ?, ?, ?, ?, ?, 'active')
|
|
`).run(e,s,p,_,r||null,o.toISOString(),i);return Number(h.lastInsertRowid)}saveUserPrompt(e,s,t,r){let n=new Date,o=n.getTime(),i=w(t),p=this.resolvePromptSessionDbId(e,r);return this.db.prepare(`
|
|
INSERT INTO user_prompts
|
|
(session_db_id, content_session_id, prompt_number, prompt_text, created_at, created_at_epoch)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`).run(p,e,s,i,n.toISOString(),o).lastInsertRowid}getUserPrompt(e,s,t){let r=this.resolvePromptSessionDbId(e,t);return r!==null?this.db.prepare(`
|
|
SELECT prompt_text
|
|
FROM user_prompts
|
|
WHERE session_db_id = ? AND prompt_number = ?
|
|
LIMIT 1
|
|
`).get(r,s)?.prompt_text??null:this.db.prepare(`
|
|
SELECT prompt_text
|
|
FROM user_prompts
|
|
WHERE content_session_id = ? AND prompt_number = ?
|
|
LIMIT 1
|
|
`).get(e,s)?.prompt_text??null}storeObservation(e,s,t,r,n=0,o,i){let p=this.storeObservations(e,s,[t],null,r,n,o,i);return{id:p.observationIds[0],createdAtEpoch:p.createdAtEpoch}}storeSummary(e,s,t,r,n=0,o){let i=o??Date.now(),p=new Date(i).toISOString(),E=this.db.prepare(`
|
|
INSERT INTO session_summaries
|
|
(memory_session_id, project, request, investigated, learned, completed,
|
|
next_steps, notes, prompt_number, discovery_tokens, created_at, created_at_epoch)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n,p,i);return{id:Number(E.lastInsertRowid),createdAtEpoch:i}}storeObservations(e,s,t,r,n,o=0,i,p){let _=i??Date.now(),E=new Date(_).toISOString();return this.db.transaction(()=>{let T=[],N=this.db.prepare(`
|
|
INSERT INTO observations
|
|
(memory_session_id, project, type, title, subtitle, facts, narrative, concepts,
|
|
files_read, files_modified, prompt_number, discovery_tokens, agent_type, agent_id, content_hash, created_at, created_at_epoch,
|
|
generated_by_model, metadata)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(memory_session_id, content_hash) DO NOTHING
|
|
RETURNING id
|
|
`),O=this.db.prepare("SELECT id FROM observations WHERE memory_session_id = ? AND content_hash = ?");for(let m of t){let c=J(e,m.title,m.narrative),f=N.get(e,s,m.type,m.title,m.subtitle,JSON.stringify(m.facts),m.narrative,JSON.stringify(m.concepts),JSON.stringify(m.files_read),JSON.stringify(m.files_modified),n||null,o,m.agent_type??null,m.agent_id??null,c,E,_,p||null,m.metadata??null);if(f){T.push(f.id);continue}let v=O.get(e,c);if(!v)throw new Error(`storeObservations: ON CONFLICT without existing row for content_hash=${c}`);T.push(v.id)}let R=null;if(r){let c=this.db.prepare(`
|
|
INSERT INTO session_summaries
|
|
(memory_session_id, project, request, investigated, learned, completed,
|
|
next_steps, notes, prompt_number, discovery_tokens, created_at, created_at_epoch)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(e,s,r.request,r.investigated,r.learned,r.completed,r.next_steps,r.notes,n||null,o,E,_);R=Number(c.lastInsertRowid)}return{observationIds:T,summaryId:R,createdAtEpoch:_}})()}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r,project:n,platformSource:o}=s,i=t==="relevance",p=i?"":`ORDER BY ss.created_at_epoch ${t==="date_asc"?"ASC":"DESC"}`,_=r&&!i?`LIMIT ${r}`:"",E=e.map(()=>"?").join(","),h=[...e],T=[];n&&(T.push("ss.project = ?"),h.push(n)),o&&(T.push(`COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?`),h.push(L(o)));let N=T.length>0?`AND ${T.join(" AND ")}`:"",R=this.db.prepare(`
|
|
SELECT ss.*
|
|
FROM session_summaries ss
|
|
LEFT JOIN sdk_sessions s ON s.memory_session_id = ss.memory_session_id
|
|
WHERE ss.id IN (${E}) ${N}
|
|
${p}
|
|
${_}
|
|
`).all(...h);if(!i)return R;let m=new Map(R.map(f=>[f.id,f])),c=e.map(f=>m.get(f)).filter(f=>!!f);return r?c.slice(0,r):c}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r,project:n,platformSource:o}=s,i=t==="relevance",p=i?"":`ORDER BY up.created_at_epoch ${t==="date_asc"?"ASC":"DESC"}`,_=r?`LIMIT ${r}`:"",E=e.map(()=>"?").join(","),h=[...e],T=[];n&&(T.push("s.project = ?"),h.push(n)),o&&(T.push(`COALESCE(NULLIF(s.platform_source, ''), '${u}') = ?`),h.push(L(o)));let N=T.length>0?`AND ${T.join(" AND ")}`:"",R=this.db.prepare(`
|
|
SELECT
|
|
up.*,
|
|
s.project,
|
|
s.memory_session_id,
|
|
COALESCE(NULLIF(s.platform_source, ''), '${u}') as platform_source
|
|
FROM user_prompts up
|
|
JOIN sdk_sessions s ON up.session_db_id = s.id
|
|
WHERE up.id IN (${E}) ${N}
|
|
${p}
|
|
${_}
|
|
`).all(...h);if(!i)return R;let m=new Map(R.map(c=>[c.id,c]));return e.map(c=>m.get(c)).filter(c=>!!c)}getTimelineAroundTimestamp(e,s=10,t=10,r,n){return this.getTimelineAroundObservation(null,e,s,t,r,n)}getTimelineAroundObservation(e,s,t=10,r=10,n,o){let i=o?L(o):void 0,p=(b,g)=>{let S=[],A=[];return n&&(S.push(`${b}.project = ?`),A.push(n)),i&&(S.push(`COALESCE(NULLIF(${g}.platform_source, ''), '${u}') = ?`),A.push(i)),{clause:S.length>0?`AND ${S.join(" AND ")}`:"",params:A}},_=p("o","src"),E=p("ss","src"),h=p("s","s"),T,N;if(e!==null){let b=`
|
|
SELECT o.id, o.created_at_epoch
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = o.memory_session_id
|
|
WHERE o.id <= ? ${_.clause}
|
|
ORDER BY o.id DESC
|
|
LIMIT ?
|
|
`,g=`
|
|
SELECT o.id, o.created_at_epoch
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = o.memory_session_id
|
|
WHERE o.id >= ? ${_.clause}
|
|
ORDER BY o.id ASC
|
|
LIMIT ?
|
|
`;try{let S=this.db.prepare(b).all(e,..._.params,t+1),A=this.db.prepare(g).all(e,..._.params,r+1);if(S.length===0&&A.length===0)return{observations:[],sessions:[],prompts:[]};T=S.length>0?S[S.length-1].created_at_epoch:s,N=A.length>0?A[A.length-1].created_at_epoch:s}catch(S){return S instanceof Error?a.error("DB","Error getting boundary observations",{project:n},S):a.error("DB","Error getting boundary observations with non-Error",{},new Error(String(S))),{observations:[],sessions:[],prompts:[]}}}else{let b=`
|
|
SELECT o.created_at_epoch
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = o.memory_session_id
|
|
WHERE o.created_at_epoch <= ? ${_.clause}
|
|
ORDER BY o.created_at_epoch DESC
|
|
LIMIT ?
|
|
`,g=`
|
|
SELECT o.created_at_epoch
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = o.memory_session_id
|
|
WHERE o.created_at_epoch >= ? ${_.clause}
|
|
ORDER BY o.created_at_epoch ASC
|
|
LIMIT ?
|
|
`;try{let S=this.db.prepare(b).all(s,..._.params,t),A=this.db.prepare(g).all(s,..._.params,r+1);if(S.length===0&&A.length===0)return{observations:[],sessions:[],prompts:[]};T=S.length>0?S[S.length-1].created_at_epoch:s,N=A.length>0?A[A.length-1].created_at_epoch:s}catch(S){return S instanceof Error?a.error("DB","Error getting boundary timestamps",{project:n},S):a.error("DB","Error getting boundary timestamps with non-Error",{},new Error(String(S))),{observations:[],sessions:[],prompts:[]}}}let O=`
|
|
SELECT o.*
|
|
FROM observations o
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = o.memory_session_id
|
|
WHERE o.created_at_epoch >= ? AND o.created_at_epoch <= ? ${_.clause}
|
|
ORDER BY o.created_at_epoch ASC
|
|
`,R=`
|
|
SELECT ss.*
|
|
FROM session_summaries ss
|
|
LEFT JOIN sdk_sessions src ON src.memory_session_id = ss.memory_session_id
|
|
WHERE ss.created_at_epoch >= ? AND ss.created_at_epoch <= ? ${E.clause}
|
|
ORDER BY ss.created_at_epoch ASC
|
|
`,m=`
|
|
SELECT up.*, s.project, s.memory_session_id, COALESCE(NULLIF(s.platform_source, ''), '${u}') as platform_source
|
|
FROM user_prompts up
|
|
JOIN sdk_sessions s ON up.session_db_id = s.id
|
|
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${h.clause}
|
|
ORDER BY up.created_at_epoch ASC
|
|
`,c=this.db.prepare(O).all(T,N,..._.params),f=this.db.prepare(R).all(T,N,...E.params),v=this.db.prepare(m).all(T,N,...h.params);return{observations:c,sessions:f.map(b=>({id:b.id,memory_session_id:b.memory_session_id,project:b.project,request:b.request,completed:b.completed,next_steps:b.next_steps,created_at:b.created_at,created_at_epoch:b.created_at_epoch})),prompts:v.map(b=>({id:b.id,content_session_id:b.content_session_id,prompt_number:b.prompt_number,prompt_text:b.prompt_text,project:b.project,platform_source:b.platform_source,created_at:b.created_at,created_at_epoch:b.created_at_epoch}))}}getOrCreateManualSession(e){let s=`manual-${e}`,t=`manual-content-${e}`;if(this.db.prepare("SELECT memory_session_id FROM sdk_sessions WHERE memory_session_id = ?").get(s))return s;let n=new Date;return this.db.prepare(`
|
|
INSERT INTO sdk_sessions (memory_session_id, content_session_id, project, platform_source, started_at, started_at_epoch, status)
|
|
VALUES (?, ?, ?, ?, ?, ?, 'active')
|
|
`).run(s,t,e,u,n.toISOString(),n.getTime()),a.info("SESSION","Created manual session",{memorySessionId:s,project:e}),s}close(){this.db.close()}importSdkSession(e){let s=L(e.platform_source),t=this.db.prepare(`SELECT id FROM sdk_sessions
|
|
WHERE platform_source = ? AND content_session_id = ?`).get(s,e.content_session_id);return t?{imported:!1,id:t.id}:{imported:!0,id:this.db.prepare(`
|
|
INSERT INTO sdk_sessions (
|
|
content_session_id, memory_session_id, project, platform_source, user_prompt,
|
|
started_at, started_at_epoch, completed_at, completed_at_epoch, status
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(e.content_session_id,e.memory_session_id,e.project,s,e.user_prompt,e.started_at,e.started_at_epoch,e.completed_at,e.completed_at_epoch,e.status).lastInsertRowid}}importSessionSummary(e){let s=this.db.prepare("SELECT id FROM session_summaries WHERE memory_session_id = ?").get(e.memory_session_id);return s?{imported:!1,id:s.id}:{imported:!0,id:this.db.prepare(`
|
|
INSERT INTO session_summaries (
|
|
memory_session_id, project, request, investigated, learned,
|
|
completed, next_steps, files_read, files_edited, notes,
|
|
prompt_number, discovery_tokens, created_at, created_at_epoch
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(e.memory_session_id,e.project,e.request,e.investigated,e.learned,e.completed,e.next_steps,e.files_read,e.files_edited,e.notes,e.prompt_number,e.discovery_tokens||0,e.created_at,e.created_at_epoch).lastInsertRowid}}importObservation(e){let s=this.db.prepare(`
|
|
SELECT id FROM observations
|
|
WHERE memory_session_id = ? AND title = ? AND created_at_epoch = ?
|
|
`).get(e.memory_session_id,e.title,e.created_at_epoch);return s?{imported:!1,id:s.id}:{imported:!0,id:this.db.prepare(`
|
|
INSERT INTO observations (
|
|
memory_session_id, project, text, type, title, subtitle,
|
|
facts, narrative, concepts, files_read, files_modified,
|
|
prompt_number, discovery_tokens, agent_type, agent_id,
|
|
created_at, created_at_epoch
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(e.memory_session_id,e.project,e.text,e.type,e.title,e.subtitle,e.facts,e.narrative,e.concepts,e.files_read,e.files_modified,e.prompt_number,e.discovery_tokens||0,e.agent_type??null,e.agent_id??null,e.created_at,e.created_at_epoch).lastInsertRowid}}rebuildObservationsFTSIndex(){this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='observations_fts'").all().length>0&&this.db.run("INSERT INTO observations_fts(observations_fts) VALUES('rebuild')")}importUserPrompt(e){let s=null,t=e.platform_source?L(e.platform_source):void 0;if(typeof e.session_db_id=="number"){let i=this.db.prepare(`
|
|
SELECT id, content_session_id, COALESCE(NULLIF(platform_source, ''), '${u}') as platform_source
|
|
FROM sdk_sessions
|
|
WHERE id = ?
|
|
LIMIT 1
|
|
`).get(e.session_db_id);i&&i.content_session_id===e.content_session_id&&(!t||L(i.platform_source)===t)&&(s=i.id)}s===null&&(s=this.resolvePromptSessionDbId(e.content_session_id,void 0,t));let r=this.db.prepare(`
|
|
SELECT id FROM user_prompts
|
|
WHERE ${s!==null?"session_db_id = ?":"content_session_id = ?"} AND prompt_number = ?
|
|
`).get(s??e.content_session_id,e.prompt_number);return r?{imported:!1,id:r.id}:{imported:!0,id:this.db.prepare(`
|
|
INSERT INTO user_prompts (
|
|
session_db_id, content_session_id, prompt_number, prompt_text,
|
|
created_at, created_at_epoch
|
|
) VALUES (?, ?, ?, ?, ?, ?)
|
|
`).run(s,e.content_session_id,e.prompt_number,e.prompt_text,e.created_at,e.created_at_epoch).lastInsertRowid}}};0&&(module.exports={SessionStore});
|