@clauderecallhq/cli 0.73.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/dist/cli.js +276 -276
- package/dist/daemon/entrypoint.js +471 -471
- package/dist/mcp-server.js +141 -135
- package/dist/web/assets/{dist-CM2Q2okx.js → dist-BByOdSt4.js} +1 -1
- package/dist/web/assets/index-eoqvpiAT.js +657 -0
- package/dist/web/index.html +1 -1
- package/package.json +2 -2
- package/dist/web/assets/index-D8oScLt4.js +0 -657
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* Claude Recall (proprietary). See LICENSE for terms. */
|
|
3
|
-
var
|
|
3
|
+
var dm=Object.defineProperty;var ae=(e,t)=>()=>(e&&(t=e(e=0)),t);var Rs=(e,t)=>{for(var s in t)dm(e,s,{get:t[s],enumerable:!0})};import{homedir as wi}from"node:os";import{join as Vn,basename as Hw}from"node:path";import{existsSync as pm,mkdirSync as mm,chmodSync as gm,readdirSync as Ww,statSync as qw}from"node:fs";function z(){pm(W)||mm(W,{recursive:!0,mode:448}),process.platform!=="win32"&&gm(W,448)}var $t,W,Zn,ee=ae(()=>{"use strict";$t=process.env.CLAUDE_PROJECTS_DIR?process.env.CLAUDE_PROJECTS_DIR:Vn(wi(),".claude","projects"),W=process.env.RECALL_HOME?process.env.RECALL_HOME:Vn(wi(),".recall"),Zn=Vn(W,"db.sqlite")});import{createRequire as Pm}from"node:module";var $m,Um,Hm,tr,sr,Di,Fi=ae(()=>{"use strict";{let e=process.emit.bind(process);process.emit=function(t,...s){let n=s[0];return t==="warning"&&n instanceof Error&&n.name==="ExperimentalWarning"&&/SQLite/i.test(n.message)?!1:e(t,...s)}}$m=Pm(import.meta.url),Um=["node","sqlite"].join(":"),Hm=$m(Um),tr=class{inner;constructor(t){this.inner=t}get(...t){return t.length===0?this.inner.get():this.inner.get(...t)}all(...t){return t.length===0?this.inner.all():this.inner.all(...t)}run(...t){let s=t.length===0?this.inner.run():this.inner.run(...t);return{changes:s.changes,lastInsertRowid:s.lastInsertRowid}}iterate(...t){return t.length===0?this.inner.iterate():this.inner.iterate(...t)}},sr=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,s={}){this.inner=new Hm.DatabaseSync(t,{readOnly:s.readonly??!1,allowExtension:!0})}prepare(t){return new tr(this.inner.prepare(t))}exec(t){this.inner.exec(t)}close(){this.inner.close()}pragma(t,s={}){if(t.includes("=")){this.inner.exec(`PRAGMA ${t}`);return}if(s.simple){let n=this.inner.prepare(`PRAGMA ${t}`).get();return n&&typeof n=="object"?Object.values(n)[0]:void 0}return this.inner.prepare(`PRAGMA ${t}`).all()}transaction(t){return((...n)=>{this.txDepth===0?this.inner.exec("BEGIN"):this.inner.exec(`SAVEPOINT sp_${this.txDepth}`),this.txDepth+=1;try{let r=t(...n);return this.txDepth-=1,this.txDepth===0?this.inner.exec("COMMIT"):this.inner.exec(`RELEASE sp_${this.txDepth}`),r}catch(r){this.txDepth-=1;try{this.txDepth===0?this.inner.exec("ROLLBACK"):(this.inner.exec(`ROLLBACK TO sp_${this.txDepth}`),this.inner.exec(`RELEASE sp_${this.txDepth}`))}catch{}throw r}})}loadExtension(t,s){this.extensionLoadingEnabled||(this.inner.enableLoadExtension(!0),this.extensionLoadingEnabled=!0),s===void 0?this.inner.loadExtension(t):this.inner.loadExtension(t,s)}},Di=sr});function $i(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),s=new Set(t.map(T=>T.name)),n=[["total_input_tokens","INTEGER"],["total_output_tokens","INTEGER"],["total_cache_create_tokens","INTEGER"],["total_cache_read_tokens","INTEGER"],["primary_model","TEXT"],["auto_title","TEXT"],["auto_title_source","TEXT"],["auto_title_generated_at","INTEGER"],["auto_title_history","TEXT"],["verification_status","TEXT"],["verification_computed_at","INTEGER"],["title_quality","TEXT"],["title_quality_computed_at","INTEGER"],["archive_status","TEXT NOT NULL DEFAULT 'live'"],["archived_at","TEXT"]];for(let[T,S]of n)s.has(T)||e.exec(`ALTER TABLE sessions ADD COLUMN ${T} ${S}`);let r=e.prepare("PRAGMA table_info(collection_sessions)").all(),o=new Set(r.map(T=>T.name)),a=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[T,S]of a)o.has(T)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${T} ${S}`);let c=e.prepare("PRAGMA table_info(session_notes)").all(),u=new Set(c.map(T=>T.name)),d=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[T,S]of d)u.has(T)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${T} ${S}`);let m=e.prepare("PRAGMA table_info(threads)").all();new Set(m.map(T=>T.name)).has("folder_id")||(e.exec("ALTER TABLE threads ADD COLUMN folder_id TEXT REFERENCES thread_folders(id) ON DELETE SET NULL"),e.exec("CREATE INDEX IF NOT EXISTS idx_threads_folder ON threads(folder_id)"));let h=e.prepare("PRAGMA table_info(thread_folders)").all(),b=new Set(h.map(T=>T.name));b.has("project_scope")||(e.exec("ALTER TABLE thread_folders ADD COLUMN project_scope TEXT"),e.exec("CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope)")),b.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
|
|
4
4
|
CREATE TABLE IF NOT EXISTS message_embeddings (
|
|
5
5
|
message_uuid TEXT PRIMARY KEY REFERENCES messages(uuid) ON DELETE CASCADE,
|
|
6
6
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
@@ -12,7 +12,7 @@ var nm=Object.defineProperty;var ue=(e,t)=>()=>(e&&(t=e(e=0)),t);var ys=(e,t)=>{
|
|
|
12
12
|
);
|
|
13
13
|
CREATE INDEX IF NOT EXISTS idx_message_embeddings_session ON message_embeddings(session_id);
|
|
14
14
|
CREATE INDEX IF NOT EXISTS idx_message_embeddings_generated ON message_embeddings(generated_at DESC);
|
|
15
|
-
`)}var
|
|
15
|
+
`)}var Pi,Ui=ae(()=>{"use strict";Pi=`
|
|
16
16
|
PRAGMA journal_mode = WAL;
|
|
17
17
|
PRAGMA synchronous = NORMAL;
|
|
18
18
|
PRAGMA foreign_keys = ON;
|
|
@@ -668,9 +668,9 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_target
|
|
|
668
668
|
ON bug_synthesis_results(scope, target_id, created_at DESC);
|
|
669
669
|
CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
670
670
|
ON bug_synthesis_results(created_at DESC);
|
|
671
|
-
`});import*as
|
|
672
|
-
`)),this.name="EmbedderUnavailableError",this.cause=t}}});import{writeFileSync as
|
|
673
|
-
GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function
|
|
671
|
+
`});import*as Hi from"sqlite-vec";function f(){if(ge)return ge;z(),ge=new Di(Zn),Hi.load(ge),ge.pragma("cache_size = -64000"),ge.pragma("mmap_size = 268435456"),ge.pragma("temp_store = MEMORY"),ge.pragma("busy_timeout = 5000"),ge.pragma("journal_size_limit = 67108864"),ge.pragma("wal_autocheckpoint = 1000"),ge.exec(Pi),$i(ge);try{ge.exec("PRAGMA optimize")}catch{}return ge}var ge,H=ae(()=>{"use strict";Fi();ee();Ui();ge=null});import{existsSync as Gm}from"node:fs";import{dirname as ea}from"node:path";import{fileURLToPath as Ym}from"node:url";function ta(){if(As)return As;let e=ea(Ym(import.meta.url));for(;!Gm(`${e}/package.json`);){let t=ea(e);if(t===e)throw new Error(`package.json not found walking up from ${import.meta.url}`);e=t}return As=e,As}var As,sa=ae(()=>{"use strict";As=null});var na,ra=ae(()=>{"use strict";na="BAAI/bge-base-en-v1.5"});var ia={};Rs(ia,{EmbedderUnavailableError:()=>Wt,embed:()=>ct,embedQuery:()=>ir,getEmbedderStatus:()=>_e,loadEmbedder:()=>Ue,unloadEmbedder:()=>tg});import{Worker as Km}from"node:worker_threads";import{join as Vm}from"node:path";import{existsSync as Zm}from"node:fs";function Qm(){return Vm(ta(),"dist","daemon","embedder-worker.js")}function oa(e){for(let t of Bt.values())t.reject(e);Bt.clear()}function eg(){if(De)return De;let e=Qm();if(!Zm(e))throw new Wt(new Error(`embedder-worker bundle not found at ${e}. Run \`npm run build:cli\` to emit it.`));let t=new Km(e);return t.on("message",s=>{let n=Bt.get(s.id);n&&(Bt.delete(s.id),n.resolve(s))}),t.on("error",s=>{console.error("[embedder-worker] thread error:",s);let n=s instanceof Error?s:new Error(String(s));oa(n),De=null,xe=!1}),t.on("exit",s=>{s!==0&&(console.error(`[embedder-worker] exited with code ${s}`),oa(new Error(`embedder worker exited with code ${s}`))),De=null,xe=!1}),De=t,t}function xs(e){return new Promise((t,s)=>{let n;try{n=eg()}catch(r){s(r instanceof Error?r:new Error(String(r)));return}Bt.set(e.id,{resolve:t,reject:s}),n.postMessage(e)})}function Ns(){return rr=rr+1>>>0,String(rr)}function or(e){if(!e.ok)throw e.unavailable?new Wt(new Error(e.error)):new Error(e.error);return e}async function Ue(){if(!(xe&&De))try{or(await xs({id:Ns(),type:"load"})),xe=!0}catch(e){throw xe=!1,e}}function _e(){return{loaded:xe,modelId:na,dim:768}}async function ct(e){if(!xe)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");return or(await xs({id:Ns(),type:"embed",texts:e})).embeddings.map(s=>new Float32Array(s))}async function ir(e){if(!xe)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=or(await xs({id:Ns(),type:"embedQuery",text:e}));return new Float32Array(t.embedding)}async function tg(){if(!De){xe=!1;return}try{await xs({id:Ns(),type:"unload"})}catch{}xe=!1;let e=De;De=null;try{await e.terminate()}catch{}}var De,Bt,rr,xe,Wt,Ve=ae(()=>{"use strict";sa();ra();De=null,Bt=new Map,rr=0,xe=!1,Wt=class extends Error{cause;constructor(t){let s=t instanceof Error?t.message:String(t);super(["Semantic search is unavailable on this platform.","",`Reason: ${s}`,"",`Platform: ${process.platform}/${process.arch}, Node ${process.version}`,"","Claude Recall supports macOS (arm64/x64), Linux (x64/arm64), and Windows (x64).","Core CLI features (search, list, context, daemon) work everywhere.","Only `recall semantic *` requires the on-device embedder.","","See: https://clauderecall.com/docs (Supported platforms) \u2014 or file an issue at","https://gitlab.com/clauderecallhq/clauderecallhq/-/issues with the platform line above."].join(`
|
|
672
|
+
`)),this.name="EmbedderUnavailableError",this.cause=t}}});import{writeFileSync as s_}from"node:fs";import{join as n_}from"node:path";function Qe(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function ut(e,t){let s=Qe(t);if(!s)throw new Error("tag must contain at least one alphanumeric character");let n=f(),r=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?{tag:s,added:!1}:(n.transaction(()=>{n.prepare("INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)").run(e,s,r),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,s,r)})(),Oa(),{tag:s,added:!0})}function Na(e,t){let s=Qe(t);if(!s)return{tag:"",removed:!1};let n=f(),r=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?(n.transaction(()=>{n.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(e,s),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'remove', ?)").run(e,s,r)})(),Oa(),{tag:s,removed:!0}):{tag:s,removed:!1}}function Gt(e){return f().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function dt(){return f().prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
|
|
673
|
+
GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function Oa(){try{z();let e=f(),t=e.prepare("SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag").all(),s=e.prepare("SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC").all(),n={schema:"claude-recall.tags.v1",backed_up_at:new Date().toISOString(),current:t,events:s};s_(r_,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var r_,pt=ae(()=>{"use strict";H();ee();r_=n_(W,"tags.json")});function o_(e,t){let s=e.filter(o=>o.content_text&&o.content_text.trim().length>0);if(s.length<=t)return s;let n=new Set;n.add(0),n.add(s.length-1);let r=(s.length-2)/Math.max(1,t-2);for(let o=1;o<t-1;o++)n.add(Math.floor(o*r));return Array.from(n).sort((o,a)=>o-a).slice(0,t).map(o=>s[o])}function mt(e){let t=f(),s={limit:e.limit??500},n=e.sessionIds&&e.sessionIds.length>0,r=n?"1=1":"s.message_count > 2";if(n){let a=e.sessionIds.map((c,u)=>`@sid_${u}`).join(", ");r+=` AND s.id IN (${a})`,e.sessionIds.forEach((c,u)=>{s[`sid_${u}`]=c})}return e.untaggedOnly&&(r+=" AND NOT EXISTS (SELECT 1 FROM session_tags st WHERE st.session_id = s.id)"),e.project&&(r+=" AND p.name = @project",s.project=e.project),e.collectionId&&(r+=" AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id = @col)",s.col=e.collectionId),t.prepare(`SELECT s.id, p.name AS project, s.git_branch,
|
|
674
674
|
NULLIF(sa.alias, '') AS alias,
|
|
675
675
|
COALESCE(s.first_user_message, '') AS first_user_message
|
|
676
676
|
FROM sessions s
|
|
@@ -680,15 +680,15 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
680
680
|
ORDER BY COALESCE(s.started_at, '') DESC
|
|
681
681
|
LIMIT @limit`).all(s).map(a=>{let c=t.prepare(`SELECT role, COALESCE(content_text, '') AS content_text
|
|
682
682
|
FROM messages WHERE session_id = ?
|
|
683
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(a.id),d=
|
|
683
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(a.id),d=o_(c,5).map(m=>`${m.role}: ${m.content_text.slice(0,400)}`).join(`
|
|
684
684
|
---
|
|
685
|
-
`);return{id:a.id,project:a.project,git_branch:a.git_branch,alias:a.alias,first_user_message:a.first_user_message,message_sample:d,current_tags:
|
|
686
|
-
`)}function
|
|
687
|
-
`)}function
|
|
688
|
-
`)}function
|
|
689
|
-
`)}function
|
|
685
|
+
`);return{id:a.id,project:a.project,git_branch:a.git_branch,alias:a.alias,first_user_message:a.first_user_message,message_sample:d,current_tags:Gt(a.id)}})}var vs=ae(()=>{"use strict";H();pt()});import{z as be}from"zod";function fr(e){let t=e.minTags??2,s=e.maxTags??4,n=e.untaggedOnly??!e.sessionId,r=["Auto-tag my Claude Recall sessions using the MCP tools available to you.","","1. Call `list_tags` first to see which tags I already use (prefer those for consistency over inventing new ones).","2. Call `list_sessions_to_tag` with these filters:"],o=[];return e.sessionId?(o.push("limit: 1"),r.push(` ${o.join(", ")}`),r.push(` Then match the session id ${e.sessionId} from the returned list.`)):(n&&o.push("untaggedOnly: true"),e.project&&o.push(`project: "${e.project}"`),e.collectionId&&o.push(`collectionId: "${e.collectionId}"`),o.push(`limit: ${e.limit??100}`),r.push(` ${o.join(", ")}`)),r.push(""),r.push(`3. For each session returned, look at the alias, first user message, git branch, and sampled messages. Pick ${t}-${s} concise, lowercase, hyphen-separated tags describing:`),r.push(" - domain/subsystem (auth, db, frontend, billing, etc.)"),r.push(" - kind of work (bugfix, feature, refactor, research)"),r.push(" - prominent tools or libraries if relevant"),r.push(""),r.push("4. Call `apply_tags` once per session to write the tags."),r.push(""),r.push("Work through EVERY session returned \u2014 do not stop partway. When done, reply with a short summary of how many sessions were tagged. Do not ask clarifying questions; just do the work."),r.join(`
|
|
686
|
+
`)}function c_(e){let t=e.mode==="detailed";return[`Summarize Claude Recall session ${e.sessionId} using the MCP tools available to you.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "condensed" to get the transcript as markdown.`,t?"2. Write a 1-paragraph overview (\u22643 sentences) of what this session was for, then 5-8 bullet points covering:":"2. Write 3-5 bullet points covering:"," - What was accomplished (shipped, decided, learned)"," - What was tried and abandoned"," - Any explicit open questions or follow-ups","","Rules:",'- Be concrete. Name files, functions, and decisions. Avoid vague "discussed X".',"- If nothing was actually shipped or decided, say so plainly.",'- Reply with just the summary \u2014 no preamble, no "Here is the summary:".'].join(`
|
|
687
|
+
`)}function u_(e){return[`Extract every architectural or product decision made in Claude Recall session ${e.sessionId}.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "full" (so tool I/O is included \u2014 sometimes the decision lives in a commit message or diff).`,"2. For each distinct decision, emit one block:",""," - **Decision:** one sentence."," - **Why:** the stated rationale (quote if possible).",' - **Alternatives considered:** bullet list, or "none mentioned".',' - **Where it landed:** file path / function name / commit SHA if identifiable, otherwise "TBD".',"","Rules:","- Include only decisions that were actually made, not ideas merely discussed.","- If an alternative was rejected, list it with a one-line reason.","- Group related decisions under a short heading when useful.",'- If the session made zero real decisions, reply exactly with: "No decisions made in this session."',"- No preamble, no closing, just the decision blocks."].join(`
|
|
688
|
+
`)}function p_(e){let t=e.limit??5;return[`Find ${t} Recall sessions most similar to session ${e.sessionId}.`,"",`1. Call \`get_session\` with id "${e.sessionId}" \u2014 note its alias, first user message, tags, and git branch.`,'2. Derive 2-3 short search queries from that content (topic words, library names, error strings \u2014 NOT generic words like "fix" or "add").',`3. Call \`search\` once per query (limit: ${Math.max(5,t*2)}). Dedupe hits by session_id.`,"4. Also call `list_sessions` with the same tag(s) if the target session has any (pick the most specific tag).","5. Union the results. Exclude the target session itself. Rank by a mix of:"," - Shared tags (strongest signal)"," - Matching search hits across multiple queries"," - Recency as a tiebreaker","",`6. Return the top ${t} as a numbered list. For each:`," - **<short_id> \xB7 <project>** \u2014 <alias or first_user_message truncated>",' - One sentence on WHY it is similar (not a generic "same topic" \u2014 be specific).',"","If fewer than 2 genuinely-similar sessions exist, say so rather than padding with weak matches.","No preamble. Just the ranked list."].join(`
|
|
689
|
+
`)}function La(e){return hr.find(t=>t.name===e)}var i_,a_,l_,d_,m_,g_,__,f_,hr,Er=ae(()=>{"use strict";i_={project:be.string().optional().describe("Exact project name match (optional)."),collectionId:be.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:be.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:be.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:be.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:be.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:be.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};a_={sessionId:be.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:be.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};l_={sessionId:be.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};d_={sessionId:be.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:be.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};m_={name:"auto_tag_sessions",title:"Auto-tag Recall sessions",description:"Have the agent auto-tag Recall sessions using the Recall MCP tools. Scope can be restricted to a project, collection, or single session.",argsSchema:i_,build:fr,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},g_={name:"summarize_session",title:"Summarize a session",description:"Produce a concise, concrete summary of one session \u2014 what shipped, what was tried, what's still open.",argsSchema:a_,build:c_,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},__={name:"extract_decisions",title:"Extract architectural decisions",description:"Scan a session and emit one structured block per architectural / product decision: what, why, alternatives, where it landed.",argsSchema:l_,build:u_,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},f_={name:"find_similar_sessions",title:"Find similar sessions",description:"Given a session, find other sessions that touched the same topic / library / error \u2014 ranked with reasons.",argsSchema:d_,build:p_,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},hr=[m_,g_,__,f_]});function js(e,t){let s=Yt.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}function Ca(e,t){let s=Yt.get(e);return s||(s=new Set,Yt.set(e,s)),s.add(t),()=>{let n=Yt.get(e);n&&(n.delete(t),n.size===0&&Yt.delete(e))}}var Yt,br=ae(()=>{"use strict";Yt=new Map});import{existsSync as h_,statSync as E_}from"node:fs";import{delimiter as b_,join as S_}from"node:path";function Ia(e){if(e.includes("/")||e.includes("\\")||e.includes(".."))return null;let t=(process.env.PATH??"").split(b_).filter(Boolean),s=process.platform==="win32"?[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e];for(let n of t)for(let r of s){let o=S_(n,r);try{if(h_(o)&&E_(o).isFile())return o}catch{}}return null}var va=ae(()=>{"use strict"});var tt={};Rs(tt,{_resetClaudePathCacheForTests:()=>R_,buildScanPrompt:()=>Ma,isClaudeCliAvailable:()=>pe,runClaudeCliScan:()=>Sr,spawnClaudePrompt:()=>et});import{spawn as T_}from"node:child_process";function ja(){if(gt!==void 0&&zt!==void 0)return{path:gt,available:zt};let e=Ia("claude");return gt=e??"claude",zt=e!==null,{path:gt,available:zt}}function w_(){return ja().path}function pe(){return ja().available}function R_(){gt=void 0,zt=void 0}function Ma(e){return fr({project:e.project,collectionId:e.collectionId,sessionId:e.sessionIds&&e.sessionIds.length===1?e.sessionIds[0]:void 0,untaggedOnly:e.untaggedOnly,limit:e.limit})}function k_(e,t){let s=t.get(e);return s||e.slice(0,8)}function A_(e){try{return mt(e).map(s=>({id:s.id,label:s.alias&&s.alias.trim().length>0?s.alias:s.first_user_message&&s.first_user_message.trim().length>0?s.first_user_message.slice(0,60):s.id.slice(0,8)}))}catch{return[]}}function x_(e){let{scanId:t,total:s,labelTable:n}=e,r=new Set;return o=>{let a=o.trim();if(!a.startsWith("{"))return;let c;try{c=JSON.parse(a)}catch{return}if(!c||typeof c!="object")return;let u=c;if(!(u.type!=="assistant"||!u.message?.content))for(let d of u.message.content){if(d?.type!=="tool_use"||d.name!=="mcp__recall__apply_tags")continue;let m=d.input,h=typeof m?.sessionId=="string"?m.sessionId:null;!h||r.has(h)||(r.add(h),js(t,{type:"progress",current:r.size,total:s,sessionId:h,sessionLabel:k_(h,n)}))}}}function N_(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
|
|
690
690
|
`);for(;n!==-1;){let r=t.slice(0,n);t=t.slice(n+1),r.length>0&&e(r),n=t.indexOf(`
|
|
691
|
-
`)}}}async function
|
|
691
|
+
`)}}}async function Sr(e,t={},s){let n=!!t.scanId,r=n?A_(e):[],o=new Map(r.map(u=>[u.id,u.label])),a=r.length,c;return n&&t.scanId&&(c=x_({scanId:t.scanId,total:a,labelTable:o})),Da({prompt:Ma(e),allowedTools:y_.split(","),opts:t,onProgress:s,onStdoutLine:c,outputFormat:n?"stream-json":"json"})}async function et(e,t,s={},n){return Da({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function Da(e){let{prompt:t,allowedTools:s,opts:n,onProgress:r,onStdoutLine:o,outputFormat:a}=e,c=["-p",t,"--output-format",a,"--allowedTools",s.join(","),"--permission-mode","bypassPermissions","--no-session-persistence"];return a==="stream-json"&&c.push("--verbose"),n.model&&c.push("--model",n.model),new Promise(u=>{let d=T_(w_(),c,{stdio:["ignore","pipe","pipe"],shell:process.platform==="win32"&>==="claude"}),m=[],h=[],b=o?N_(o):void 0;d.stdout.on("data",S=>{m.push(S),b&&b(S)}),d.stderr.on("data",S=>{if(h.push(S),r){let w=S.toString("utf8").trim();w&&r(w)}});let T=setTimeout(()=>{d.kill("SIGKILL")},1800*1e3);d.on("close",S=>{clearTimeout(T),u({success:S===0,stdout:Buffer.concat(m).toString("utf8"),stderr:Buffer.concat(h).toString("utf8"),exitCode:S})}),d.on("error",S=>{clearTimeout(T),u({success:!1,stdout:"",stderr:String(S),exitCode:null})})})}var y_,gt,zt,ye=ae(()=>{"use strict";vs();Er();br();va();y_=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});var Qa={};Rs(Qa,{RetentionConfigSchema:()=>Nr,readRetentionConfig:()=>Or,writeRetentionConfig:()=>Ws});import{existsSync as Ya,mkdirSync as ff,readFileSync as hf,writeFileSync as Ef}from"node:fs";import{homedir as bf}from"node:os";import{join as za}from"node:path";import{z as Hs}from"zod";function Ka(){return process.env.RECALL_HOME??za(bf(),".recall")}function Sf(){let e=Ka();Ya(e)||ff(e,{recursive:!0})}function Va(){return za(Ka(),"config.json")}function Za(){let e=Va();if(!Ya(e))return{};try{return JSON.parse(hf(e,"utf8"))}catch(t){let s=t instanceof Error?t.message:String(t);return console.error(`[retention-config] failed to parse config.json: ${s}`),{}}}function Or(){let e=Za().retention;if(!e)return{...Bs};let t=Nr.safeParse({...Bs,...e});return t.success?t.data:{...Bs}}function Ws(e){Sf();let t=Za(),s=Nr.parse({...Bs,...t.retention??{},...e}),n={...t,retention:s};return Ef(Va(),JSON.stringify(n,null,2)),s}var Nr,Bs,Lr=ae(()=>{"use strict";Nr=Hs.object({autoArchiveEnabled:Hs.boolean().default(!1),autoArchiveAfterDays:Hs.number().int().min(7).max(3650).default(90),lastRunAt:Hs.string().nullable().default(null)}),Bs={autoArchiveEnabled:!1,autoArchiveAfterDays:90,lastRunAt:null}});import Oe from"chalk";import{formatDistanceToNowStrict as mk,parseISO as gk}from"date-fns";var we,qs=ae(()=>{"use strict";we={dim:Oe.gray,bold:Oe.bold,project:Oe.cyan,user:Oe.blue,assistant:Oe.green,tool:Oe.magenta,warn:Oe.yellow,err:Oe.red,ok:Oe.green,accent:Oe.hex("#f97316")}});import{existsSync as Tf}from"node:fs";import{join as yf}from"node:path";function Vt(){if(ec&&Tf(Kt))return;z();let e=f(),t=Kt.replace(/'/g,"''");e.exec(`ATTACH DATABASE '${t}' AS archive`);try{e.exec(`
|
|
692
692
|
CREATE TABLE IF NOT EXISTS archive.messages_archive (
|
|
693
693
|
uuid TEXT PRIMARY KEY,
|
|
694
694
|
session_id TEXT NOT NULL,
|
|
@@ -703,12 +703,12 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
703
703
|
archived_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
704
704
|
);
|
|
705
705
|
CREATE INDEX IF NOT EXISTS archive.idx_messages_archive_session ON messages_archive(session_id);
|
|
706
|
-
`)}finally{e.exec("DETACH DATABASE archive")}
|
|
706
|
+
`)}finally{e.exec("DETACH DATABASE archive")}ec=!0}function tc(){let e=f();if(e.prepare("SELECT COUNT(*) AS n FROM messages_archive").get().n===0)return 0;Vt();let s=Kt.replace(/'/g,"''"),n=0;e.exec(`ATTACH DATABASE '${s}' AS archive`);try{e.transaction(()=>{n=e.prepare(`INSERT OR IGNORE INTO archive.messages_archive
|
|
707
707
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
708
708
|
is_sidechain, content_text, tool_names, raw_json, archived_at)
|
|
709
709
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
710
710
|
is_sidechain, content_text, tool_names, raw_json, archived_at
|
|
711
|
-
FROM main.messages_archive`).run().changes,e.prepare("DELETE FROM main.messages_archive").run()})()}finally{e.exec("DETACH DATABASE archive")}return n}var
|
|
711
|
+
FROM main.messages_archive`).run().changes,e.prepare("DELETE FROM main.messages_archive").run()})()}finally{e.exec("DETACH DATABASE archive")}return n}var Kt,ec,Cr=ae(()=>{"use strict";ee();H();Kt=yf(W,"archive.sqlite"),ec=!1});var nc={};Rs(nc,{runArchive:()=>Lf});function Ir(e){Vt();let t=f(),s=Kt.replace(/'/g,"''");t.exec(`ATTACH DATABASE '${s}' AS archive`);try{return e(t)}finally{t.exec("DETACH DATABASE archive")}}function wf(){return Ir(e=>{let t=e.prepare(`SELECT
|
|
712
712
|
SUM(CASE WHEN archive_status = 'archived' THEN 1 ELSE 0 END) AS archived,
|
|
713
713
|
SUM(CASE WHEN archive_status != 'archived' THEN 1 ELSE 0 END) AS live
|
|
714
714
|
FROM sessions`).get(),s=e.prepare("SELECT COUNT(*) AS n FROM messages").get().n,n=e.prepare(`SELECT
|
|
@@ -717,15 +717,15 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
717
717
|
SELECT MAX(timestamp) AS t FROM main.messages_archive WHERE timestamp IS NOT NULL
|
|
718
718
|
UNION ALL
|
|
719
719
|
SELECT MAX(timestamp) AS t FROM archive.messages_archive WHERE timestamp IS NOT NULL
|
|
720
|
-
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:s,archivedMessages:n,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function
|
|
720
|
+
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:s,archivedMessages:n,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function sc(e){return e?e.slice(0,10):"\u2014"}function Rf(){let e=wf();return console.log(we.dim("\u2014 Archive state \u2014")),console.log(` Live sessions ${e.liveSessions.toLocaleString()}`),console.log(` Archived sessions ${e.archivedSessions.toLocaleString()}`),console.log(` Live messages ${e.liveMessages.toLocaleString()}`),console.log(` Archived messages ${e.archivedMessages.toLocaleString()}`),console.log(` Oldest live ${sc(e.oldestLiveTimestamp)}`),console.log(` Newest archived ${sc(e.newestArchivedTimestamp)}`),console.log(""),console.log(we.dim(" recall archive run --before YYYY-MM-DD")),console.log(we.dim(" recall archive restore <session-id>")),0}function kf(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e.before))return console.error("--before must be YYYY-MM-DD"),1;let s=f().prepare(`SELECT s.id, s.ended_at, s.message_count
|
|
721
721
|
FROM sessions s
|
|
722
722
|
WHERE s.archive_status != 'archived'
|
|
723
|
-
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(s.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let n=s.reduce((c,u)=>c+(u.message_count??0),0);if(console.log(`${s.length.toLocaleString()} session(s), ${n.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(we.dim("Dry run \u2014 no rows moved. Re-run without --dry-run to apply.")),0;let r=s.map(c=>c.id),o=Date.now(),a=
|
|
723
|
+
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(s.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let n=s.reduce((c,u)=>c+(u.message_count??0),0);if(console.log(`${s.length.toLocaleString()} session(s), ${n.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(we.dim("Dry run \u2014 no rows moved. Re-run without --dry-run to apply.")),0;let r=s.map(c=>c.id),o=Date.now(),a=Ir(c=>c.transaction(d=>{let m=0,h=c.prepare(`INSERT OR IGNORE INTO archive.messages_archive
|
|
724
724
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
725
725
|
is_sidechain, content_text, tool_names, raw_json, archived_at)
|
|
726
726
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
727
727
|
is_sidechain, content_text, tool_names, raw_json, datetime('now')
|
|
728
|
-
FROM messages WHERE session_id = ?`),b=c.prepare("DELETE FROM messages WHERE session_id = ?"),T=c.prepare("UPDATE sessions SET archive_status = 'archived', archived_at = datetime('now') WHERE id = ?");for(let S of d){h.run(S);let w=b.run(S);m+=Number(w.changes??0),T.run(S)}return m})(r));return console.log(`Archived ${s.length.toLocaleString()} session(s), moved ${a.toLocaleString()} message(s) in ${Date.now()-o}ms.`),console.log(we.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function
|
|
728
|
+
FROM messages WHERE session_id = ?`),b=c.prepare("DELETE FROM messages WHERE session_id = ?"),T=c.prepare("UPDATE sessions SET archive_status = 'archived', archived_at = datetime('now') WHERE id = ?");for(let S of d){h.run(S);let w=b.run(S);m+=Number(w.changes??0),T.run(S)}return m})(r));return console.log(`Archived ${s.length.toLocaleString()} session(s), moved ${a.toLocaleString()} message(s) in ${Date.now()-o}ms.`),console.log(we.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function Af(e){if(!e)return console.error("Usage: recall archive restore <session-id>"),1;let s=f().prepare("SELECT id, archive_status FROM sessions WHERE id = ?").get(e);if(!s)return console.error(`Session ${e} not found.`),1;if(s.archive_status!=="archived")return console.error(`Session ${e} is not archived (status=${s.archive_status}).`),1;let n=Ir(r=>r.transaction(()=>{let a=r.prepare(`INSERT OR IGNORE INTO messages
|
|
729
729
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
730
730
|
is_sidechain, content_text, tool_names, raw_json)
|
|
731
731
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
@@ -735,32 +735,32 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
735
735
|
is_sidechain, content_text, tool_names, raw_json)
|
|
736
736
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
737
737
|
is_sidechain, content_text, tool_names, raw_json
|
|
738
|
-
FROM archive.messages_archive WHERE session_id = ?`),u=r.prepare("DELETE FROM main.messages_archive WHERE session_id = ?"),d=r.prepare("DELETE FROM archive.messages_archive WHERE session_id = ?"),m=Number(a.run(e).changes??0),h=Number(c.run(e).changes??0);return u.run(e),d.run(e),r.prepare("UPDATE sessions SET archive_status = 'live', archived_at = NULL WHERE id = ?").run(e),m+h})());return console.log(`Restored ${n.toLocaleString()} message(s) for session ${e}.`),0}function
|
|
738
|
+
FROM archive.messages_archive WHERE session_id = ?`),u=r.prepare("DELETE FROM main.messages_archive WHERE session_id = ?"),d=r.prepare("DELETE FROM archive.messages_archive WHERE session_id = ?"),m=Number(a.run(e).changes??0),h=Number(c.run(e).changes??0);return u.run(e),d.run(e),r.prepare("UPDATE sessions SET archive_status = 'live', archived_at = NULL WHERE id = ?").run(e),m+h})());return console.log(`Restored ${n.toLocaleString()} message(s) for session ${e}.`),0}function xf(){let e=Or();return console.log(we.dim("\u2014 Auto-archive \u2014")),console.log(` Enabled ${e.autoArchiveEnabled?we.ok("YES"):"no"}`),console.log(` After ${e.autoArchiveAfterDays} days`),console.log(` Last run ${e.lastRunAt??"\u2014"}`),0}function Nf(e){if(e===void 0||!Number.isFinite(e))return console.error("Usage: recall archive auto on --after <days>"),1;let t=Math.floor(e);if(t<7||t>3650)return console.error("--after must be between 7 and 3650 days"),1;let s=Ws({autoArchiveEnabled:!0,autoArchiveAfterDays:t});return console.log(`Auto-archive: ENABLED (after ${s.autoArchiveAfterDays} days).`),console.log(we.dim(" The daemon will run a daily archive pass on the next tick.")),0}function Of(){return Ws({autoArchiveEnabled:!1}),console.log("Auto-archive: DISABLED. Existing archived sessions stay archived."),0}async function Lf(e){let t=e._action??"list";if(t==="list"||t==="stats")return Rf();if(t==="run")return e.before?kf({before:e.before,dryRun:e.dryRun===!0}):(console.error("Usage: recall archive run --before YYYY-MM-DD [--dry-run]"),1);if(t==="restore")return Af(e._sessionId??"");if(t==="auto"){let s=e._subAction??"status";return s==="status"?xf():s==="on"||s==="enable"?Nf(e.after):s==="off"||s==="disable"?Of():(console.error("Usage: recall archive auto <status|on|off> [--after <days>]"),1)}return console.error(`Usage: recall archive <list|run|restore|auto> [args]
|
|
739
739
|
list \u2014 show archive counts
|
|
740
740
|
run --before YYYY-MM-DD [--dry-run] \u2014 move sessions older than DATE
|
|
741
741
|
restore <session-id> \u2014 pull a session back from archive
|
|
742
|
-
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}var
|
|
742
|
+
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}var rc=ae(()=>{"use strict";qs();H();Cr();Lr()});import{cpus as Rw}from"node:os";import{Hono as Yy}from"hono";import{serve as zy}from"@hono/node-server";ee();import{existsSync as _m,readFileSync as fm,writeFileSync as Gw,unlinkSync as Yw}from"node:fs";import{join as hm}from"node:path";var Ri=hm(W,"license.json");function Ut(){if(!_m(Ri))return null;try{let e=fm(Ri,"utf8"),t=JSON.parse(e);return typeof t.license_jwt!="string"||t.license_jwt.length===0?null:t}catch{return null}}import{jwtVerify as Em,importSPKI as bm}from"jose";var ki=`-----BEGIN PUBLIC KEY-----
|
|
743
743
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZysO2FffTLdyxQnTmnt78/ayvqz9
|
|
744
744
|
kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
745
745
|
-----END PUBLIC KEY-----
|
|
746
|
-
`,
|
|
747
|
-
`,{mode:384})}async function
|
|
746
|
+
`,Qn="ES256",Ai="clauderecall.com",xi="clauderecall-cli";var ks=null;async function Sm(){return ks||(ks=await bm(ki,Qn),ks)}async function Ni(e){try{let t=await Sm(),{payload:s}=await Em(e,t,{issuer:Ai,audience:xi,algorithms:[Qn]});return{valid:!0,claims:s}}catch(t){return{valid:!1,reason:t instanceof Error?t.message:"verification failed"}}}import{createHash as Tm}from"node:crypto";import{hostname as ym,userInfo as wm,platform as Rm,arch as km}from"node:os";function Oi(){let e="unknown";try{e=wm().username}catch{}let t=[ym(),e,Rm(),km()];return Tm("sha256").update(t.join("\0")).digest("hex")}ee();import{existsSync as Am,readFileSync as xm,writeFileSync as Nm}from"node:fs";import{join as Om}from"node:path";function Li(){let e=process.env.RECALL_API_BASE;if(e&&e.length>0){let t=e.replace(/\/$/,""),s;try{s=new URL(t)}catch{throw new Error(`RECALL_API_BASE is not a valid URL: ${t}`)}let n=s.hostname==="127.0.0.1"||s.hostname==="localhost"||s.hostname==="::1";if(s.protocol==="https:"||s.protocol==="http:"&&n)return t;throw new Error(`RECALL_API_BASE must be HTTPS, or HTTP with loopback hostname. Got: ${t}`)}return"https://clauderecall.com"}var er=Om(W,"license-check.json"),Lm=1440*60*1e3,Cm=720*60*60*1e3,Im=1e4;function Ci(){if(!Am(er))return null;try{let e=JSON.parse(xm(er,"utf8"));return typeof e.license_key!="string"||typeof e.last_checked_at!="string"||typeof e.revoked!="boolean"?null:e}catch{return null}}function vm(e){z(),Nm(er,JSON.stringify(e,null,2)+`
|
|
747
|
+
`,{mode:384})}async function jm(e,t){let s=null,n=null;try{s=new AbortController,n=setTimeout(()=>s?.abort(),Im);let r=await fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({license_key:e}),signal:s.signal});if(!r.ok)return null;let o=await r.json();return typeof o?.revoked!="boolean"?null:o}catch{return null}finally{n&&clearTimeout(n)}}async function Ii(e,t={}){let s=Ci(),n=t.apiUrl??`${Li()}/api/license/check`,r=s?.license_key===e,o=!s||!r||Date.now()-new Date(s.last_checked_at).getTime()>=Lm;if(!t.force&&!o)return s;let a=await jm(e,n);if(!a)return r?s:null;let c={license_key:e,last_checked_at:new Date().toISOString(),revoked:a.revoked,reason:a.reason??null};return vm(c),c}function vi(e){let t=Ci();return!t||t.license_key!==e?null:t.revoked?{revoked:!0,reason:t.reason?`license revoked: ${t.reason}`:"license revoked by issuer"}:Date.now()-new Date(t.last_checked_at).getTime()>Cm?{revoked:!0,reason:"license has not been validated with the server in 30+ days. Reconnect to the internet and run `recall license check`"}:null}var Mm=Date.UTC(2026,5,1,7,0,0);var Dm=1440*60*1e3,m0=60*Dm;async function Ke(){let e=Ut();if(!e)return{tier:"free"};let t=await Ni(e.license_jwt);if(!t.valid||!t.claims)return{tier:"free",invalid_reason:t.reason};if(t.claims.machine_fp&&t.claims.machine_fp!==Oi())return{tier:"free",invalid_reason:"machine fingerprint mismatch \u2014 re-activate on this device"};let s=vi(e.license_key);return s?.revoked?{tier:"free",invalid_reason:s.reason}:Fm(e,t.claims)}async function ji(e){let t=Ut();if(!t)return{ran:!1,revoked:!1,reason:null,last_checked_at:null};let s=await Ii(t.license_key,{force:e?.force??!1});return s?{ran:!0,revoked:s.revoked,reason:s.reason,last_checked_at:s.last_checked_at}:{ran:!0,revoked:!1,reason:null,last_checked_at:null}}function Fm(e,t){let s=t.test_mode===!0&&process.env.NODE_ENV==="production";return{tier:s?"free":"pro",key_short:e.key_short,customer_email:e.customer_email,activated_at:e.activated_at,test_mode:e.test_mode,...s?{test_mode_blocked:!0}:{},expires_at:typeof t.exp=="number"?new Date(t.exp*1e3).toISOString():null}}async function Mi(){return(await Ke()).tier==="pro"}H();H();import{createHash as sg}from"node:crypto";H();ee();import{writeFileSync as Bm,readFileSync as I0,existsSync as Wm,mkdirSync as qm,readdirSync as v0,unlinkSync as j0}from"node:fs";import{join as Bi}from"node:path";import{randomUUID as Wi}from"node:crypto";var nr=Bi(W,"bug-patterns");function Xm(){z(),Wm(nr)||qm(nr,{recursive:!0})}function $e(e){return{id:e.id,signature_hash:e.signature_hash,example_message:e.example_message,occurrence_count:e.occurrence_count,first_seen_at:e.first_seen_at,last_seen_at:e.last_seen_at,resolved_in_session_id:e.resolved_in_session_id,fix_summary:e.fix_summary}}function qi(e){return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at}}function Xi(e){if(!e.signature_hash)throw new Error("signature_hash is required");if(!e.example_message)throw new Error("example_message is required");if(!Array.isArray(e.member_session_ids)||e.member_session_ids.length===0)throw new Error("at least one member_session_id is required");let t=f(),s=new Date().toISOString(),n=e.id??Wi(),r=e.first_seen_at??s,o=e.last_seen_at??s,a=Array.from(new Set(e.member_session_ids));t.transaction(()=>{t.prepare(`INSERT INTO bug_pattern_clusters
|
|
748
748
|
(id, signature_hash, example_message, occurrence_count,
|
|
749
749
|
first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
|
|
750
750
|
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(n,e.signature_hash,e.example_message,a.length,r,o);let u=t.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
751
751
|
VALUES (?, ?, ?)
|
|
752
|
-
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of a)u.run(n,d,s)})();let c=
|
|
752
|
+
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of a)u.run(n,d,s)})();let c=Ji(n);if(!c)throw new Error("createCluster succeeded but read-back failed");return Ht(n),c}function Ji(e){let t=f(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ? ORDER BY matched_at ASC, session_id ASC").all(e);return{cluster:$e(s),members:n.map(qi)}}function Gi(e,t){if(!e)throw new Error("clusterId is required");if(!Array.isArray(t)||t.length===0)throw new Error("sessionIds must be a non-empty array");let s=f();if(!s.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);let r=new Date().toISOString(),o=0;s.transaction(()=>{let c=s.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
753
753
|
VALUES (?, ?, ?)
|
|
754
754
|
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let u of Array.from(new Set(t)))c.run(e,u,r).changes>0&&(o+=1);if(o>0){let u=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;s.prepare(`UPDATE bug_pattern_clusters
|
|
755
755
|
SET occurrence_count = ?, last_seen_at = ?
|
|
756
|
-
WHERE id = ?`).run(u,r,e)}})(),o>0&&
|
|
756
|
+
WHERE id = ?`).run(u,r,e)}})(),o>0&&Ht(e);let a=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:$e(a),added:o}}function Yi(e={}){let t=f(),s=[],n=[];typeof e.minOccurrenceCount=="number"&&(s.push("c.occurrence_count >= ?"),n.push(Math.max(1,Math.floor(e.minOccurrenceCount)))),typeof e.hasResolved=="boolean"&&s.push(e.hasResolved?"c.resolved_in_session_id IS NOT NULL":"c.resolved_in_session_id IS NULL"),e.project&&(s.push(`EXISTS (
|
|
757
757
|
SELECT 1 FROM bug_pattern_members m
|
|
758
758
|
JOIN sessions s ON s.id = m.session_id
|
|
759
759
|
JOIN projects p ON p.id = s.project_id
|
|
760
760
|
WHERE m.cluster_id = c.id AND p.name = ?
|
|
761
761
|
)`),n.push(e.project));let r=s.length>0?`WHERE ${s.join(" AND ")}`:"",o=t.prepare(`SELECT COUNT(*) AS n FROM bug_pattern_clusters c ${r}`).get(...n),a=Math.max(1,Math.min(5e3,e.limit??100)),c=Math.max(0,Math.floor(e.offset??0));return{clusters:t.prepare(`SELECT c.* FROM bug_pattern_clusters c ${r}
|
|
762
762
|
ORDER BY c.occurrence_count DESC, c.last_seen_at DESC
|
|
763
|
-
LIMIT ? OFFSET ?`).all(...n,a,c).map(
|
|
763
|
+
LIMIT ? OFFSET ?`).all(...n,a,c).map($e),total:o.n}}function Jm(e){let t=e.first_user_message?e.first_user_message.slice(0,80):null,s=e.alias??e.auto_title??t??e.session_id.slice(0,8);return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at,title:s,alias:e.alias,auto_title:e.auto_title,project:e.project,started_at:e.started_at}}function zi(e){let t=f(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT m.cluster_id, m.session_id, m.matched_at,
|
|
764
764
|
NULLIF(sa.alias, '') AS alias,
|
|
765
765
|
s.auto_title,
|
|
766
766
|
s.first_user_message,
|
|
@@ -771,31 +771,31 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
771
771
|
LEFT JOIN session_aliases sa ON sa.session_id = m.session_id
|
|
772
772
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
773
773
|
WHERE m.cluster_id = ?
|
|
774
|
-
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster
|
|
774
|
+
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:$e(s),members:n.map(Jm)}}function Ki(e,t,s){if(!e)throw new Error("clusterId is required");if(!t)throw new Error("sessionId is required");if(typeof s!="string"||!s.trim())throw new Error("fixSummary is required");let n=f();if(!n.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);if(!n.prepare("SELECT 1 FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?").get(e,t))throw new Error(`session ${t} is not a member of cluster ${e}`);n.prepare(`UPDATE bug_pattern_clusters
|
|
775
775
|
SET resolved_in_session_id = ?, fix_summary = ?
|
|
776
|
-
WHERE id = ?`).run(t,s.trim(),e),
|
|
777
|
-
WHERE cluster_id = ? AND session_id IN (${o})`).all(e,...r);if(a.length===0)throw new Error(`none of the supplied session_ids are members of cluster ${e}`);let c=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;if(a.length>=c)throw new Error(`cannot split: would leave the original cluster empty (move ${a.length} of ${c})`);let u
|
|
776
|
+
WHERE id = ?`).run(t,s.trim(),e),Ht(e);let a=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:$e(a)}}function Vi(e,t){if(!e)throw new Error("clusterId is required");if(!Array.isArray(t)||t.length===0)throw new Error("memberSessionIds must be a non-empty array");let s=f(),n=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!n)throw new Error(`cluster ${e} not found`);let r=Array.from(new Set(t)),o=r.map(()=>"?").join(","),a=s.prepare(`SELECT session_id, matched_at FROM bug_pattern_members
|
|
777
|
+
WHERE cluster_id = ? AND session_id IN (${o})`).all(e,...r);if(a.length===0)throw new Error(`none of the supplied session_ids are members of cluster ${e}`);let c=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;if(a.length>=c)throw new Error(`cannot split: would leave the original cluster empty (move ${a.length} of ${c})`);let u=Wi(),d=new Date().toISOString(),m=[];s.transaction(()=>{s.prepare(`INSERT INTO bug_pattern_clusters
|
|
778
778
|
(id, signature_hash, example_message, occurrence_count,
|
|
779
779
|
first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
|
|
780
780
|
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(u,n.signature_hash,n.example_message,a.length,n.first_seen_at,d);let T=s.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
781
|
-
VALUES (?, ?, ?)`),S=s.prepare("DELETE FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?");for(let R of a)T.run(u,R.session_id,R.matched_at),S.run(e,R.session_id);let w=c-a.length;s.prepare("UPDATE bug_pattern_clusters SET occurrence_count = ? WHERE id = ?").run(w,e),m=s.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ?").all(u)})(),
|
|
781
|
+
VALUES (?, ?, ?)`),S=s.prepare("DELETE FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?");for(let R of a)T.run(u,R.session_id,R.matched_at),S.run(e,R.session_id);let w=c-a.length;s.prepare("UPDATE bug_pattern_clusters SET occurrence_count = ? WHERE id = ?").run(w,e),m=s.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ?").all(u)})(),Ht(e),Ht(u);let h=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e),b=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(u);return{original:$e(h),split:$e(b),movedMembers:m.map(qi)}}function Ht(e){try{Xm();let t=Ji(e);if(!t)return;let s=Bi(nr,`${e}.json`),n={schema:"claude-recall.bug-pattern-cluster.v1",cluster:t.cluster,members:t.members,backed_up_at:new Date().toISOString()};Bm(s,JSON.stringify(n,null,2))}catch(t){console.error("[bug-patterns] backup failed:",t)}}function Zi(e){return e?f().prepare(`SELECT * FROM bug_pattern_clusters
|
|
782
782
|
WHERE signature_hash = ?
|
|
783
|
-
ORDER BY last_seen_at DESC, id ASC`).all(e).map(
|
|
783
|
+
ORDER BY last_seen_at DESC, id ASC`).all(e).map($e):[]}function Qi(e){if(!e)return new Set;let s=f().prepare(`SELECT DISTINCT m.session_id
|
|
784
784
|
FROM bug_pattern_members m
|
|
785
785
|
JOIN bug_pattern_clusters c ON c.id = m.cluster_id
|
|
786
|
-
WHERE c.signature_hash = ?`).all(e);return new Set(s.map(n=>n.session_id))}var
|
|
786
|
+
WHERE c.signature_hash = ?`).all(e);return new Set(s.map(n=>n.session_id))}var ng=/\b0x[0-9a-fA-F]+\b/g,rg=/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,og=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g,ig=/:\d+:\d+/g,ag=/\bline\s+\d+\b/gi,cg=/\bcolumn\s+\d+\b/gi,lg=/\b(?:pid|PID|process(?:\s+id)?)\s*[:=]?\s*\d+\b/gi,ug=/\b(?:port|:)\s*[:=]?\s*\d{2,5}\b/gi,dg=/\b\d{4,}\b/g,pg=/(['"`])[^'"`\n]{1,128}\1/g;function mg(e){if(!e)return"";let t=String(e);return t=t.replace(ng,"<hex>"),t=t.replace(rg,"<uuid>"),t=t.replace(og,"<ts>"),t=t.replace(ig,":<line>:<col>"),t=t.replace(ag,"line <n>"),t=t.replace(cg,"column <n>"),t=t.replace(lg,"pid <n>"),t=t.replace(ug,"port <n>"),t=t.replace(dg,"<num>"),t=t.replace(pg,"<arg>"),t=t.replace(/\s+/g," ").trim(),t.toLowerCase()}function gg(e){let t=(e.error_type??"unknown").toLowerCase().trim(),s=mg(e.snippet??e.message_hash??""),n=`${t}|${s}`;return sg("sha256").update(n).digest("hex").slice(0,16)}function _g(e){let t=f(),s=["oi.bug_signatures IS NOT NULL"],n=[];e&&(s.push("p.name = ?"),n.push(e));let r=`WHERE ${s.join(" AND ")}`,o=t.prepare(`SELECT oi.session_id AS session_id,
|
|
787
787
|
p.name AS project,
|
|
788
788
|
s.started_at AS started_at,
|
|
789
789
|
oi.bug_signatures AS bug_signatures
|
|
790
790
|
FROM session_output_index oi
|
|
791
791
|
LEFT JOIN sessions s ON s.id = oi.session_id
|
|
792
792
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
793
|
-
${r}`).all(...n),a=[];for(let c of o){if(!c.bug_signatures)continue;let u=[];try{let d=JSON.parse(c.bug_signatures);Array.isArray(d)&&(u=d)}catch{continue}for(let d of u){if(!d||typeof d!="object"||!(d.snippet??"").trim())continue;let h=
|
|
794
|
-
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${
|
|
795
|
-
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),d=!0}if(
|
|
796
|
-
WHERE id IN (${
|
|
793
|
+
${r}`).all(...n),a=[];for(let c of o){if(!c.bug_signatures)continue;let u=[];try{let d=JSON.parse(c.bug_signatures);Array.isArray(d)&&(u=d)}catch{continue}for(let d of u){if(!d||typeof d!="object"||!(d.snippet??"").trim())continue;let h=gg(d);a.push({session_id:c.session_id,project:c.project,started_at:c.started_at,signature:d,fingerprint:h})}}return a}function fg(e){let t=new Map;for(let n of e){let r=t.get(n.fingerprint);r||(r=[],t.set(n.fingerprint,r)),r.push(n)}let s=[];for(let[n,r]of t){let o=new Set,a=[];for(let m of r)o.has(m.session_id)||(o.add(m.session_id),a.push(m));let c=[...a].sort((m,h)=>{let b=m.started_at??"",T=h.started_at??"";return b&&T?b<T?-1:b>T?1:0:b?-1:T?1:0}),u=c.find(m=>m.started_at),d=[...c].reverse().find(m=>m.started_at);s.push({fingerprint:n,example_message:a[0].signature.snippet??a[0].signature.message_hash??"",members:a,first_seen_at:u?.started_at??new Date().toISOString(),last_seen_at:d?.started_at??new Date().toISOString()})}return s}function hg(e,t){if(e.length!==t.length)return 0;let s=0,n=0,r=0;for(let a=0;a<e.length;a++)s+=e[a]*t[a],n+=e[a]*e[a],r+=t[a]*t[a];let o=Math.sqrt(n)*Math.sqrt(r);return o>0?s/o:0}function Eg(e){let{records:t,vectors:s,epsilon:n,minPts:r}=e,o=t.length;if(o===0)return[];let a=[];for(let m=0;m<o;m++){let h=[];for(let b=0;b<o;b++){if(m===b)continue;1-hg(s[m],s[b])<=n&&h.push(b)}a.push(h)}let c=new Array(o).fill(!1),u=new Array(o).fill(-1),d=[];for(let m=0;m<o;m++){if(c[m])continue;c[m]=!0;let h=a[m];if(h.length<r)continue;let b=d.length;d.push({members:[t[m]]}),u[m]=b;let T=[...h];for(;T.length>0;){let S=T.shift();if(!c[S]&&(c[S]=!0,a[S].length>=r))for(let w of a[S])(!c[w]||u[w]===-1)&&T.push(w);u[S]===-1&&(u[S]=b,d[b].members.push(t[S]))}}return d}async function bg(e,t,s,n){if(e.length===0)return[];let r=e.map(u=>{let d=u.signature.snippet??u.signature.message_hash??"";return`${u.signature.error_type??""}: ${d}`.trim()}),o=await t(r);if(o.length!==e.length)throw new Error(`embedder returned ${o.length} vectors for ${e.length} inputs`);let a=Eg({records:e,vectors:o,epsilon:s,minPts:n}),c=[];for(let u of a){if(u.members.length===0)continue;let d=new Set,m=[];for(let R of u.members)d.has(R.session_id)||(d.add(R.session_id),m.push(R));if(m.length===0)continue;let b=`sem:${[...m.map(R=>R.fingerprint)].sort()[0]}`,T=[...m].sort((R,D)=>{let F=R.started_at??"",L=D.started_at??"";return F<L?-1:F>L?1:0}),S=T.find(R=>R.started_at),w=[...T].reverse().find(R=>R.started_at);c.push({fingerprint:b,example_message:m[0].signature.snippet??m[0].signature.message_hash??"",members:m,first_seen_at:S?.started_at??new Date().toISOString(),last_seen_at:w?.started_at??new Date().toISOString()})}return c}function aa(e,t){let s={clusters_created:0,clusters_merged:0,members_added:0,cluster_ids:[]};for(let n of e){if(n.members.length<t)continue;let r=Zi(n.fingerprint),o=Qi(n.fingerprint),a=n.members.map(d=>d.session_id).filter(d=>!o.has(d));if(r.length===0){let d=Xi({signature_hash:n.fingerprint,example_message:n.example_message.slice(0,256),member_session_ids:n.members.map(m=>m.session_id),first_seen_at:n.first_seen_at,last_seen_at:n.last_seen_at});s.clusters_created+=1,s.members_added+=d.members.length,s.cluster_ids.push(d.cluster.id);continue}if(a.length===0){s.cluster_ids.push(r[0].id);continue}let c=r[0],u=Gi(c.id,a);u.added>0&&(s.clusters_merged+=1,s.members_added+=u.added),s.cluster_ids.push(c.id)}return s}async function ca(e={}){let t=Math.max(2,Math.floor(e.minClusterSize??3)),s=Math.max(1,Math.min(5e3,Math.floor(e.limit??1e3))),n=e.semanticEpsilon??.15,r=Math.max(1,Math.floor(e.semanticMinPts??1)),o=_g(e.project),a=new Set(o.map(F=>F.session_id)),c=fg(o),u=[],d=!1;if(e.semantic){let F=e.embedder??null;if(!F)try{F=await yg()}catch(L){let U=(L instanceof Error?L.message:String(L)).split(`
|
|
794
|
+
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${U}
|
|
795
|
+
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),d=!0}if(F){let L=[];for(let j of c)j.members.length===1&&L.push(j.members[0]);L.length>=2&&(u=await bg(L,F,n,r))}}let m=c.filter(F=>F.members.length>=t),h=u.filter(F=>F.members.length>=t),b=aa(m,t),T=aa(h,t),S=[...b.cluster_ids,...T.cluster_ids],w=Array.from(new Set(S)),R=[];if(w.length>0){let F=f(),L=w.map(()=>"?").join(","),j=F.prepare(`SELECT * FROM bug_pattern_clusters
|
|
796
|
+
WHERE id IN (${L})
|
|
797
797
|
ORDER BY occurrence_count DESC, last_seen_at DESC
|
|
798
|
-
LIMIT ?`).all(...w,s);for(let
|
|
798
|
+
LIMIT ?`).all(...w,s);for(let U of j)R.push({id:U.id,signature_hash:U.signature_hash,example_message:U.example_message,occurrence_count:U.occurrence_count,first_seen_at:U.first_seen_at,last_seen_at:U.last_seen_at,resolved_in_session_id:U.resolved_in_session_id,fix_summary:U.fix_summary})}return{progress:{total_sessions:a.size,total_signatures:o.length,exact_match_groups:m.length,semantic_groups:h.length,clusters_created:b.clusters_created+T.clusters_created,clusters_merged:b.clusters_merged+T.clusters_merged,members_added:b.members_added+T.members_added,semantic_skipped:d},clusters:R}}var Sg=async()=>{let{embed:e,loadEmbedder:t,getEmbedderStatus:s}=await Promise.resolve().then(()=>(Ve(),ia));return s().loaded||await t(),e},Tg=Sg;async function yg(){return Tg()}H();H();ee();import{writeFileSync as la,readFileSync as tR,existsSync as ua,mkdirSync as da,readdirSync as sR}from"node:fs";import{join as Os}from"node:path";var wg=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),pa=new Set(["regex","llm","embedding","manual","auto","citation","git","terminal-registry"]),Rg=new Set(["pending","approved","rejected"]),kg=new Set(["L1","L2","L3","L4","user"]),ar=Os(W,"links"),cr=Os(W,"suggestions"),Ag=Os(cr,"index.json");function xg(){z(),ua(ar)||da(ar,{recursive:!0})}function Ng(){z(),ua(cr)||da(cr,{recursive:!0})}function ma(e){try{return JSON.parse(e)}catch{return e}}function Ls(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,source:e.source,evidence:ma(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function lr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,evidence:ma(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function ga(e){if(!Number.isFinite(e)||e<0||e>1)throw new Error("confidence must be a number in [0, 1]")}function ur(e){if(!wg.has(e))throw new Error(`invalid link_type: ${e}`)}function Og(e){if(!pa.has(e))throw new Error(`invalid source: ${e}`)}function _a(e){if(!kg.has(e))throw new Error(`invalid inferred_by: ${e}`)}function fa(e,t){if(!e||!t)throw new Error("source_session_id and target_session_id are required");if(e===t)throw new Error("a session cannot link to itself")}function ha(e){fa(e.source_session_id,e.target_session_id),ur(e.link_type),Og(e.source),ga(e.confidence);let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.evidence??null),r=e.approved?1:0;t.prepare(`INSERT INTO session_links
|
|
799
799
|
(source_session_id, target_session_id, link_type,
|
|
800
800
|
confidence, source, evidence, approved,
|
|
801
801
|
created_at, updated_at)
|
|
@@ -808,11 +808,11 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
808
808
|
updated_at = excluded.updated_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,e.source,n,r,s,s);let o=t.prepare(`SELECT * FROM session_links
|
|
809
809
|
WHERE source_session_id = ?
|
|
810
810
|
AND target_session_id = ?
|
|
811
|
-
AND link_type = ?`).get(e.source_session_id,e.target_session_id,e.link_type);if(!o)throw new Error("createLink succeeded but read-back failed");return
|
|
811
|
+
AND link_type = ?`).get(e.source_session_id,e.target_session_id,e.link_type);if(!o)throw new Error("createLink succeeded but read-back failed");return pr(e.source_session_id),Ls(o)}function Cs(e={}){let t=f(),s=[],n=[];e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.linkType&&(ur(e.linkType),s.push("link_type = ?"),n.push(e.linkType)),e.approvedOnly&&s.push("approved = 1");let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_links ${r}
|
|
812
812
|
ORDER BY confidence DESC, updated_at DESC
|
|
813
|
-
LIMIT ?`).all(...n,o).map(
|
|
813
|
+
LIMIT ?`).all(...n,o).map(Ls)}function qt(e){return f().prepare(`SELECT * FROM session_links
|
|
814
814
|
WHERE source_session_id = ? OR target_session_id = ?
|
|
815
|
-
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(
|
|
815
|
+
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(Ls)}function Ea(e){let t=f(),s=t.prepare("SELECT source_session_id FROM session_links WHERE id = ?").get(e);if(!s)return{removed:0,sourceSessionId:null};let n=t.prepare("DELETE FROM session_links WHERE id = ?").run(e);return n.changes>0&&pr(s.source_session_id),{removed:n.changes,sourceSessionId:s.source_session_id}}function Xt(e){fa(e.source_session_id,e.target_session_id),ur(e.link_type),ga(e.confidence),_a(e.inferred_by);let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.evidence??null);t.prepare(`INSERT INTO session_link_suggestions
|
|
816
816
|
(source_session_id, target_session_id, link_type,
|
|
817
817
|
confidence, evidence, status, inferred_by,
|
|
818
818
|
created_at, decided_at)
|
|
@@ -832,9 +832,9 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
832
832
|
WHERE source_session_id = ?
|
|
833
833
|
AND target_session_id = ?
|
|
834
834
|
AND link_type = ?
|
|
835
|
-
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!r)throw new Error("createSuggestion succeeded but read-back failed");return
|
|
835
|
+
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!r)throw new Error("createSuggestion succeeded but read-back failed");return ba(),lr(r)}function lt(e={}){let t=f(),s=[],n=[];if(e.status){if(!Rg.has(e.status))throw new Error(`invalid status: ${e.status}`);s.push("status = ?"),n.push(e.status)}e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.inferredBy&&(_a(e.inferredBy),s.push("inferred_by = ?"),n.push(e.inferredBy));let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${r}
|
|
836
836
|
ORDER BY confidence DESC, created_at DESC
|
|
837
|
-
LIMIT ?`).all(...n,o).map(
|
|
837
|
+
LIMIT ?`).all(...n,o).map(lr)}function dr(e,t,s={}){if(t!=="approved"&&t!=="rejected")throw new Error(`invalid decision: ${t}`);let n=s.source??"manual";if(!pa.has(n))throw new Error(`invalid source: ${n}`);let r=f(),o=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);if(!o)throw new Error(`suggestion ${e} not found`);if(o.status!=="pending")throw new Error(`suggestion ${e} already decided as ${o.status}`);let a=new Date().toISOString(),c;r.transaction(()=>{r.prepare(`UPDATE session_link_suggestions
|
|
838
838
|
SET status = ?, decided_at = ?
|
|
839
839
|
WHERE id = ?`).run(t,a,e),t==="approved"&&(r.prepare(`INSERT INTO session_links
|
|
840
840
|
(source_session_id, target_session_id, link_type,
|
|
@@ -849,7 +849,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
849
849
|
updated_at = excluded.updated_at`).run(o.source_session_id,o.target_session_id,o.link_type,o.confidence,n,o.evidence,a,a),c=r.prepare(`SELECT * FROM session_links
|
|
850
850
|
WHERE source_session_id = ?
|
|
851
851
|
AND target_session_id = ?
|
|
852
|
-
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),
|
|
852
|
+
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),ba(),t==="approved"&&pr(o.source_session_id);let u=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);return{suggestion:lr(u),link:c?Ls(c):null}}function pr(e){try{xg();let t=Cs({sourceSessionId:e}),s=Os(ar,`${e}.json`);if(t.length===0)return;let n={schema:"claude-recall.session-links.v1",source_session_id:e,backed_up_at:new Date().toISOString(),links:t};la(s,JSON.stringify(n,null,2))}catch(t){console.error("[session-links] backup failed:",t)}}function ba(){try{Ng();let e=lt({limit:5e3}),t={schema:"claude-recall.session-link-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:e};la(Ag,JSON.stringify(t,null,2))}catch(e){console.error("[session-links] suggestions backup failed:",e)}}H();ee();import{writeFileSync as Lg,readFileSync as cR,existsSync as Cg,mkdirSync as Ig,readdirSync as lR}from"node:fs";import{join as Sa}from"node:path";var mr=Sa(W,"output-index");function vg(){z(),Cg(mr)||Ig(mr,{recursive:!0})}function Jt(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function jg(e){if(!e)return null;try{return JSON.parse(e)}catch{return e}}function Ta(e){return{session_id:e.session_id,files_written:Jt(e.files_written),brands_mentioned:Jt(e.brands_mentioned),terms_introduced:Jt(e.terms_introduced),plan_ids_referenced:Jt(e.plan_ids_referenced),bug_signatures:Jt(e.bug_signatures),raw_extraction:jg(e.raw_extraction),extracted_at:e.extracted_at,extractor_version:e.extractor_version}}function ya(e){if(!e.session_id)throw new Error("session_id is required");let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.files_written??[]),r=JSON.stringify(e.brands_mentioned??[]),o=JSON.stringify(e.terms_introduced??[]),a=JSON.stringify(e.plan_ids_referenced??[]),c=JSON.stringify(e.bug_signatures??[]),u=e.raw_extraction===void 0?null:JSON.stringify(e.raw_extraction),d=Math.max(1,Math.floor(e.extractor_version??1));t.prepare(`INSERT INTO session_output_index
|
|
853
853
|
(session_id, files_written, brands_mentioned, terms_introduced,
|
|
854
854
|
plan_ids_referenced, bug_signatures, raw_extraction,
|
|
855
855
|
extracted_at, extractor_version)
|
|
@@ -862,11 +862,11 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
862
862
|
bug_signatures = excluded.bug_signatures,
|
|
863
863
|
raw_extraction = excluded.raw_extraction,
|
|
864
864
|
extracted_at = excluded.extracted_at,
|
|
865
|
-
extractor_version = excluded.extractor_version`).run(e.session_id,n,r,o,a,c,u,s,d);let m=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!m)throw new Error("setOutputIndex succeeded but read-back failed");let h=
|
|
865
|
+
extractor_version = excluded.extractor_version`).run(e.session_id,n,r,o,a,c,u,s,d);let m=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!m)throw new Error("setOutputIndex succeeded but read-back failed");let h=Ta(m);return Mg(e.session_id),h}function Ze(e){let s=f().prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e);return s?Ta(s):null}function Mg(e){try{vg();let t=Ze(e);if(!t)return;let s=Sa(mr,`${e}.json`),n={schema:"claude-recall.session-output-index.v1",backed_up_at:new Date().toISOString(),...t};Lg(s,JSON.stringify(n,null,2))}catch(t){console.error("[output-index] backup failed:",t)}}var gr={citation:"same-project",similar:"same-project",skill_track:"same-project",bug_pattern:"cross-project",wiki_link:"cross-project",temporal_proximity:"same-project"},Dg=2,Fg=.25,Pg=5,$g=60,Ug=25;function Is(e){return e.trim().toLowerCase()}function Hg(e){let t=new Set;for(let s of e.files_written)t.add(`file:${Is(s)}`);for(let s of e.brands_mentioned)t.add(`brand:${Is(s)}`);for(let s of e.terms_introduced)t.add(`term:${Is(s)}`);for(let s of e.plan_ids_referenced)t.add(`plan:${Is(s)}`);return t}function Bg(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/$g);return Math.max(.2,t)}function Wg(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function qg(e){let t=Ze(e);return t?{session_id:t.session_id,files_written:t.files_written,brands_mentioned:t.brands_mentioned,terms_introduced:t.terms_introduced.map(s=>s.term),plan_ids_referenced:t.plan_ids_referenced}:null}function Xg(e){return f().prepare(`SELECT s.id AS id, s.project_id AS project_id, s.started_at AS started_at
|
|
866
866
|
FROM sessions s
|
|
867
867
|
JOIN session_output_index oi ON oi.session_id = s.id
|
|
868
868
|
WHERE s.project_id = ?
|
|
869
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function
|
|
869
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function Jg(e,t){let s=new Map,n=new Map,r=new Map;for(let o of e){r.set(o.id,o.started_at);let a=t.get(o.id);if(!a)continue;let c=Hg(a);if(c.size!==0){n.set(o.id,c);for(let u of c){let d=s.get(u);d?d.push(o.id):s.set(u,[o.id])}}}return{posting:s,vocab:n,startedAt:r}}function Gg(e,t){let s=t.vocab.get(e),n=t.startedAt.get(e)??null;if(!s||!n)return[];let r=new Map;for(let a of s){let c=t.posting.get(a);if(c)for(let u of c){if(u===e)continue;let d=t.startedAt.get(u);if(!d||d>=n)continue;let m=r.get(u);m?m.push(a):r.set(u,[a])}}let o=[];for(let[a,c]of r){let u=c.length;if(u<Dg)continue;let d=t.startedAt.get(a)??null,m=Wg(n,d),h=Bg(m),b=Math.min(1,u/Pg*h);if(b<Fg)continue;let T=c.slice(0,12);o.push({target_session_id:a,matched_terms:T,overlap:u,days_apart:Math.round(m*10)/10,recency:Math.round(h*1e3)/1e3,confidence:Math.round(b*1e3)/1e3})}return o.sort((a,c)=>c.confidence-a.confidence),o.slice(0,Ug)}async function wa(e){if(gr.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project \u2014 refusing to run inference");let t=Xg(e.projectId),s=new Map;for(let a of t){let c=qg(a.id);c&&s.set(a.id,c)}let n=Jg(t,s),r={total_sessions:t.length,processed_sessions:0,suggestions_created:0,suggestions_skipped_existing:0,current_session_id:null};e.onProgress?.({...r});let o=[];for(let a of t){if(e.signal?.aborted)break;r.current_session_id=a.id,e.onProgress?.({...r});let c=Gg(a.id,n);for(let u of c)try{let d=Xt({source_session_id:a.id,target_session_id:u.target_session_id,link_type:"citation",confidence:u.confidence,evidence:{matched_terms:u.matched_terms,overlap_count:u.overlap,recency:u.recency,days_apart:u.days_apart},inferred_by:"L2"});o.push(d.id),r.suggestions_created+=1}catch(d){console.error("[citation-inference] createSuggestion failed:",d)}r.processed_sessions+=1,e.onProgress?.({...r})}return r.current_session_id=null,e.onProgress?.({...r}),{progress:r,suggestion_ids:o}}H();var Yg=/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,zg=[/\bv\d+\.\d+(?:\.[A-Za-z]+)?\b/g,/\bPhase\s+[A-Ha-h]\b/g,/\bL\d+\b/g,/MASTER-PLAN-[A-Za-z-]+/g],Kg=.95,Vg=.85;var _r=50;function Zg(e){if(!e)return[];let t=new Set,s=[],n=e.match(Yg);if(!n)return s;for(let r of n){let o=r.toLowerCase();if(!t.has(o)&&(t.add(o),s.push(o),s.length>=_r))break}return s}function Qg(e){if(!e)return[];let t=new Set,s=[];for(let n of zg){n.lastIndex=0;let r=e.match(n);if(r){for(let o of r){let a=o.trim().toLowerCase().replace(/\s+/g," ");if(!(!a||a.length>64)&&!t.has(a)&&(t.add(a),s.push(a),s.length>=_r))break}if(s.length>=_r)break}}return s}function Ra(e){let t=f();return typeof e=="number"?t.prepare("SELECT id, project_id FROM sessions WHERE project_id = ?").all(e):t.prepare("SELECT id, project_id FROM sessions").all()}function ka(e){let t=f();return typeof e=="number"?t.prepare(`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
|
|
870
870
|
s.project_id
|
|
871
871
|
FROM messages m
|
|
872
872
|
JOIN sessions s ON s.id = m.session_id
|
|
@@ -879,13 +879,13 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
879
879
|
JOIN sessions s ON s.id = m.session_id
|
|
880
880
|
WHERE m.is_sidechain = 0
|
|
881
881
|
AND m.content_text IS NOT NULL
|
|
882
|
-
AND length(m.content_text) > 0`).all()}function
|
|
882
|
+
AND length(m.content_text) > 0`).all()}function e_(e){let t=f(),s=typeof e=="number"?"WHERE s.project_id = ?":"",n=typeof e=="number"?[e]:[];return t.prepare(`SELECT oi.session_id AS session_id,
|
|
883
883
|
s.project_id AS project_id,
|
|
884
884
|
oi.plan_ids_referenced AS plan_ids_json
|
|
885
885
|
FROM session_output_index oi
|
|
886
886
|
JOIN sessions s ON s.id = oi.session_id
|
|
887
|
-
${s}`).all(...n)}function
|
|
888
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(e?"1":"0")}catch(t){let s=t instanceof Error?t.message:String(t);console.error(`[semantic-config] failed to sync semantic_enabled: ${s}`)}}var
|
|
887
|
+
${s}`).all(...n)}function t_(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>typeof s=="string")}catch{}return[]}function Aa(e={}){if(gr.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project");let t=Ra(e.projectId),s=new Set(t.map(o=>o.id)),n=new Map;for(let o of t)n.set(o.id,o.project_id);let r=0;for(let o of ka(e.projectId)){if(e.signal?.aborted)break;let a=Zg(o.content_text);if(a.length===0)continue;let c=n.get(o.session_id);if(c!==void 0)for(let u of a){if(u===o.session_id)continue;let d=n.get(u);if(!(d===void 0&&!s.has(u))&&!(d!==void 0&&c!==void 0&&d!==c))try{Xt({source_session_id:o.session_id,target_session_id:u,link_type:"citation",confidence:Kg,evidence:{matched_uuid:u,source_message_uuid:o.message_uuid,scanner:"uuid-ref"},inferred_by:"L1"}),r+=1}catch{}}}return{created:r}}function xa(e={}){let t=e_(e.projectId);if(t.length===0)return{created:0};let s=new Map;for(let c of t){let u=t_(c.plan_ids_json);for(let d of u){let m=d.trim().toLowerCase();if(!m)continue;let h=s.get(m);h?h.push({id:c.session_id,project_id:c.project_id}):s.set(m,[{id:c.session_id,project_id:c.project_id}])}}if(s.size===0)return{created:0};let n=Ra(e.projectId),r=new Map;for(let c of n)r.set(c.id,c.project_id);let o=0,a=new Map;for(let c of ka(e.projectId)){if(e.signal?.aborted)break;let u=Qg(c.content_text);if(u.length===0)continue;let d=r.get(c.session_id);if(d!==void 0)for(let m of u){let h=s.get(m);if(h)for(let b of h){if(b.id===c.session_id||b.project_id!==d)continue;let T=a.get(c.session_id);T||(T=new Map,a.set(c.session_id,T));let S=T.get(b.id);S||(S=new Set,T.set(b.id,S)),S.add(m)}}}for(let[c,u]of a)for(let[d,m]of u)try{Xt({source_session_id:c,target_session_id:d,link_type:"citation",confidence:Vg,evidence:{matched_plan_ids:Array.from(m).slice(0,12),scanner:"plan-ref"},inferred_by:"L1"}),o+=1}catch{}return{created:o}}H();ye();import{existsSync as j_,mkdirSync as M_,writeFileSync as D_}from"node:fs";import{homedir as F_}from"node:os";import{join as yr}from"node:path";H();import{existsSync as Fa,mkdirSync as O_,readFileSync as L_,writeFileSync as C_}from"node:fs";import{homedir as I_}from"node:os";import{join as Pa}from"node:path";import{z as Ne}from"zod";function $a(){return process.env.RECALL_HOME??Pa(I_(),".recall")}function v_(){let e=$a();Fa(e)||O_(e,{recursive:!0})}function Ua(){return Pa($a(),"config.json")}var Ds=Ne.object({enabled:Ne.boolean().default(!1),model:Ne.string().optional(),ratePerMinute:Ne.number().int().min(1).max(600).default(30),lastProcessedSessionId:Ne.string().nullable().default(null),backfillPaused:Ne.boolean().default(!1),autoExtractEnabled:Ne.boolean().default(!1),autoExtractIntervalMinutes:Ne.number().int().min(5).max(720).default(60),autoExtractBatchSize:Ne.number().int().min(1).max(20).default(1),autoResumeWorker:Ne.boolean().default(!1)}),Ms={enabled:!1,ratePerMinute:30,lastProcessedSessionId:null,backfillPaused:!1,autoExtractEnabled:!1,autoExtractIntervalMinutes:60,autoExtractBatchSize:1,autoResumeWorker:!1};function Ha(){let e=Ua();if(!Fa(e))return{};try{return JSON.parse(L_(e,"utf8"))}catch(t){return console.error("[semantic-config] failed to parse config.json, using defaults:",t),{}}}function ce(){let e=Ha().semantic;if(!e)return{...Ms};let t=Ds.safeParse({...Ms,...e});return t.success?t.data:{...Ms}}function Fs(e){v_();let t=Ha(),s=Ds.parse({...Ms,...t.semantic??{},...e}),n={...t,semantic:s};return C_(Ua(),JSON.stringify(n,null,2)),Tr(s.enabled),s}function Tr(e){try{f().prepare(`INSERT INTO app_settings(key, value) VALUES ('semantic_enabled', ?)
|
|
888
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(e?"1":"0")}catch(t){let s=t instanceof Error?t.message:String(t);console.error(`[semantic-config] failed to sync semantic_enabled: ${s}`)}}var P_=1,$_=12e3,Ba=3,U_=[];function H_(){return process.env.RECALL_HOME??yr(F_(),".recall")}function Wa(){return yr(H_(),"semantic")}function B_(){let e=Wa();j_(e)||M_(e,{recursive:!0})}function W_(e){let t=f(),s=t.prepare(`SELECT s.id, s.message_count, s.first_user_message,
|
|
889
889
|
p.name AS project,
|
|
890
890
|
NULLIF(sa.alias, '') AS alias
|
|
891
891
|
FROM sessions s
|
|
@@ -894,10 +894,10 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
894
894
|
WHERE s.id = ?`).get(e);if(!s)return null;let n=t.prepare(`SELECT role, content_text
|
|
895
895
|
FROM messages
|
|
896
896
|
WHERE session_id = ? AND is_sidechain = 0
|
|
897
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){if(!a.content_text)continue;let c=a.role??"system",u=a.content_text.replace(/```[\s\S]*?```/g,"[code]").replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g,"").trim();if(!u)continue;let d=u.length>1500?u.slice(0,1500)+"\u2026":u,m=`${c}: ${d}`;if(o+m.length
|
|
897
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){if(!a.content_text)continue;let c=a.role??"system",u=a.content_text.replace(/```[\s\S]*?```/g,"[code]").replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g,"").trim();if(!u)continue;let d=u.length>1500?u.slice(0,1500)+"\u2026":u,m=`${c}: ${d}`;if(o+m.length>$_)break;r.push(m),o+=m.length}return{id:s.id,alias:s.alias,project:s.project,firstUserMessage:s.first_user_message,excerpt:r.join(`
|
|
898
898
|
|
|
899
|
-
`),messageCount:s.message_count}}function
|
|
900
|
-
`)}function
|
|
899
|
+
`),messageCount:s.message_count}}function q_(e){return["You are summarizing a Claude Code session for a local semantic-search index. The summary will be stored as plain text and matched against future natural-language queries.","",`Session: ${e.alias??e.id}`,`Project: ${e.project}`,e.firstUserMessage?`Opening prompt: ${e.firstUserMessage}`:"","","Transcript excerpt (truncated):","---",e.excerpt,"---","","Output a single JSON object on one line, with no Markdown fences and no commentary:",'{"summary": "<3 sentences describing what the user was trying to do, what was built or debugged, and the outcome>", "keywords": ["<concept>", "<technology>", "<problem>", ...]}',"","Constraints:","- summary: 3 sentences, plain prose, no bullet points",'- keywords: 10\u201315 lowercase tokens, multi-word entries hyphenated (e.g. "memory-leak"); no duplicates; no generic words like "code" or "session"',"- Output JSON only. Do not echo this prompt."].filter(Boolean).join(`
|
|
900
|
+
`)}function X_(e){let t=e.trim();try{let o=JSON.parse(t);typeof o.result=="string"&&(t=o.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1);try{let o=JSON.parse(r),a=typeof o.summary=="string"?o.summary.trim():"",u=(Array.isArray(o.keywords)?o.keywords:[]).filter(d=>typeof d=="string").map(d=>d.trim().toLowerCase()).filter(d=>d.length>0&&d.length<64);return!a||u.length===0?null:{summary:a,keywords:Array.from(new Set(u)).slice(0,20)}}catch{return null}}function J_(e){let t=f(),s=e.keywords.join(",");t.prepare(`INSERT INTO session_semantic
|
|
901
901
|
(session_id, summary, keywords, model, source_message_count, generated_at)
|
|
902
902
|
VALUES (@session_id, @summary, @keywords, @model, @source_message_count, @generated_at)
|
|
903
903
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
@@ -905,16 +905,16 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
905
905
|
keywords = excluded.keywords,
|
|
906
906
|
model = excluded.model,
|
|
907
907
|
source_message_count = excluded.source_message_count,
|
|
908
|
-
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:s,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),
|
|
908
|
+
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:s,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),B_();let n=yr(Wa(),`${e.sessionId}.json`);D_(n,JSON.stringify({version:P_,session_id:e.sessionId,summary:e.summary,keywords:e.keywords,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt},null,2))}var Ps=null;function G_(){let t=ce().ratePerMinute,s=t/6e4;return(!Ps||Ps.capacity!==t)&&(Ps={tokens:t,capacity:t,refillPerMs:s,lastRefill:Date.now()}),Ps}function Y_(e){let t=Date.now(),s=t-e.lastRefill;s>0&&(e.tokens=Math.min(e.capacity,e.tokens+s*e.refillPerMs),e.lastRefill=t)}async function z_(e){for(;;){if(e?.aborted)throw new Error("aborted");let t=G_();if(Y_(t),t.tokens>=1){t.tokens-=1;return}let s=1-t.tokens,n=Math.max(50,Math.ceil(s/t.refillPerMs));await new Promise(r=>setTimeout(r,Math.min(n,5e3)))}}async function $s(e,t={}){let s=ce();if(!s.enabled)return{sessionId:e,ok:!1,reason:"disabled"};if(!pe())return{sessionId:e,ok:!1,reason:"claude-cli-missing"};let n=W_(e);if(!n)return{sessionId:e,ok:!1,reason:"session-not-found"};if(n.messageCount<Ba)return{sessionId:e,ok:!1,reason:"too-short"};if(!n.excerpt.trim())return{sessionId:e,ok:!1,reason:"empty-excerpt"};await z_(t.signal);let r=q_(n),o=await et(r,U_,{model:s.model});if(!o.success)return{sessionId:e,ok:!1,reason:`claude-cli-exit-${o.exitCode??"null"}`,model:s.model??null};let a=X_(o.stdout);return a?(J_({sessionId:n.id,summary:a.summary,keywords:a.keywords,model:s.model??null,sourceMessageCount:n.messageCount,generatedAt:new Date().toISOString()}),{sessionId:e,ok:!0,model:s.model??null}):{sessionId:e,ok:!1,reason:"parse-failed",model:s.model??null}}async function Us(e={}){let t=ce();if(!t.enabled)return{total:0,processed:0,ok:0,failed:0,currentSessionId:null};let s=f(),r={limit:e.limit??1e3},o="s.message_count >= 3";e.force||(o+=" AND ss.session_id IS NULL"),typeof e.projectId=="number"&&(o+=" AND s.project_id = @projectId",r.projectId=e.projectId),t.lastProcessedSessionId&&e.force;let a=s.prepare(`SELECT s.id
|
|
909
909
|
FROM sessions s
|
|
910
910
|
LEFT JOIN session_semantic ss ON ss.session_id = s.id
|
|
911
911
|
WHERE ${o}
|
|
912
912
|
ORDER BY COALESCE(s.started_at, '') ASC, s.id ASC
|
|
913
|
-
LIMIT @limit`).all(r),c={total:a.length,processed:0,ok:0,failed:0,currentSessionId:null};e.onProgress?.(c);for(let{id:u}of a){if(e.signal?.aborted||
|
|
913
|
+
LIMIT @limit`).all(r),c={total:a.length,processed:0,ok:0,failed:0,currentSessionId:null};e.onProgress?.(c);for(let{id:u}of a){if(e.signal?.aborted||ce().backfillPaused)break;c.currentSessionId=u,e.onProgress?.({...c});try{(await $s(u,{signal:e.signal})).ok?c.ok+=1:c.failed+=1}catch(m){c.failed+=1,console.error("[semantic.backfill] failed for",u,m)}c.processed+=1,Fs({lastProcessedSessionId:u}),e.onProgress?.({...c})}return c.currentSessionId=null,e.onProgress?.({...c}),c}async function qa(e){if(!ce().enabled)return;let n=f().prepare(`SELECT s.message_count, s.ended_at,
|
|
914
914
|
ss.generated_at, ss.source_message_count
|
|
915
915
|
FROM sessions s
|
|
916
916
|
LEFT JOIN session_semantic ss ON ss.session_id = s.id
|
|
917
|
-
WHERE s.id = ?`).get(e);if(n&&!(n.message_count<
|
|
917
|
+
WHERE s.id = ?`).get(e);if(n&&!(n.message_count<Ba)&&!(n.generated_at&&n.source_message_count!=null&&n.source_message_count>=n.message_count))try{await $s(e)}catch(r){console.error("[semantic] processSession error for",e,r)}}function wr(){let e=ce(),t=f(),s=t.prepare("SELECT COUNT(*) AS n FROM sessions WHERE message_count >= 3").get().n,n=t.prepare("SELECT COUNT(*) AS n FROM session_semantic").get().n;return{enabled:e.enabled,claudeCliAvailable:pe(),ratePerMinute:e.ratePerMinute,model:e.model??null,totalSessions:s,processedSessions:n,pendingSessions:Math.max(0,s-n),lastProcessedSessionId:e.lastProcessedSessionId,backfillPaused:e.backfillPaused}}H();import{createHash as Q_}from"node:crypto";var K_=[{name:"Anthropic API key",regex:/sk-ant-[a-zA-Z0-9_\-]{40,}/g,severity:"high"},{name:"OpenAI API key",regex:/sk-(?:proj-)?[a-zA-Z0-9]{32,}/g,severity:"high"},{name:"AWS access key ID",regex:/AKIA[0-9A-Z]{16}/g,severity:"high"},{name:"GitHub PAT",regex:/gh[pousr]_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Stripe live/test key",regex:/(?:sk|rk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}/g,severity:"high"},{name:"Slack token",regex:/xox[abprs]-[A-Za-z0-9\-]{10,}/g,severity:"high"},{name:"Google API key",regex:/AIza[0-9A-Za-z_\-]{35}/g,severity:"high"},{name:"Private key block",regex:/-----BEGIN (?:RSA |DSA |EC |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,severity:"high"},{name:"Apify token",regex:/apify_api_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Notion integration",regex:/(?:secret_|ntn_)[A-Za-z0-9]{40,}/g,severity:"high"},{name:"Vercel token",regex:/vercel_[A-Za-z0-9]{24,}/g,severity:"high"},{name:"Supabase service key",regex:/sbp_[A-Za-z0-9]{40,}/g,severity:"high"},{name:"SendGrid key",regex:/SG\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}/g,severity:"high"},{name:"Mailgun key",regex:/key-[a-f0-9]{32}/g,severity:"high"},{name:"Twilio SID",regex:/AC[a-f0-9]{32}/g,severity:"high"},{name:"Discord bot token",regex:/[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27,38}/g,severity:"high"},{name:"npm token",regex:/npm_[A-Za-z0-9]{36}/g,severity:"high"},{name:"HuggingFace token",regex:/hf_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"Replicate token",regex:/r8_[A-Za-z0-9]{32,}/g,severity:"high"},{name:"Figma token",regex:/figd_[A-Za-z0-9_\-]{30,}/g,severity:"high"},{name:"Linear key",regex:/lin_api_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"DigitalOcean token",regex:/dop_v1_[a-f0-9]{64}/g,severity:"high"},{name:"Generic provider token",regex:/\b[a-z][a-z0-9]{2,20}_(?:api|pat|token|sk|pk|key|auth)_(?=[A-Za-z0-9_\-]*\d)[A-Za-z0-9_\-]{20,}\b/g,severity:"high"},{name:"Bearer token",regex:/\b[Bb]earer\s+[A-Za-z0-9_\-\.=]{24,}\b/g,severity:"medium"},{name:"Slack webhook URL",regex:/https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Discord webhook URL",regex:/https:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_\-]{40,}/g,severity:"high"},{name:"Teams webhook URL",regex:/https:\/\/[a-zA-Z0-9.\-]+\.webhook\.office\.com\/webhookb2\/[A-Za-z0-9@_\-\/]{30,}/g,severity:"high"},{name:"Secret near keyword",regex:/\b(?:webhook[_\s\-]?secret|signing[_\s\-]?secret|webhook[_\s\-]?signing[_\s\-]?secret|api[_\s\-]?secret|client[_\s\-]?secret|private[_\s\-]?key|access[_\s\-]?token|auth[_\s\-]?token|api[_\s\-]?key)\b[\s\S]{0,200}?\b(?:[a-fA-F0-9]{32,}|[A-Za-z0-9+/_\-]{20,}(?:\.[A-Za-z0-9+/_\-]{10,}){1,2}|[A-Za-z0-9+/_\-]{40,}={0,2})\b/gi,severity:"high"},{name:"JWT",regex:/eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}/g,severity:"medium"},{name:"URL with password",regex:/https?:\/\/[^:\s/@]+:[^@\s]{6,}@[^\s/]+/g,severity:"high"},{name:"Password assignment",regex:/(?<![A-Za-z])(?:password|passwd|pwd|secret|token|api[_\-]?key|access[_\-]?key|auth[_\-]?token|webhook[_\-]?secret|client[_\-]?secret|private[_\-]?key)\b\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{16,}/gi,severity:"high"}];function V_(e){if(e.length<=8)return e.slice(0,2)+"\u2022".repeat(Math.max(0,e.length-4))+e.slice(-2);let t=e.slice(0,Math.min(6,Math.floor(e.length/3))),s=e.slice(-Math.min(4,Math.floor(e.length/4)));return`${t}${"\u2022".repeat(Math.max(3,e.length-t.length-s.length))}${s}`}function Z_(e){let t=5381;for(let s=0;s<e.length;s++)t=(t<<5)+t+e.charCodeAt(s)|0;return(t>>>0).toString(36)}function Se(e){if(!e)return{redacted:e,count:0};let t=e,s=0,n=new Set;for(let r of K_)r.regex.lastIndex=0,t=t.replace(r.regex,o=>{let a=`${r.name}::${Z_(o)}`;return n.has(a)||(n.add(a),s+=1),`[REDACTED ${r.name}: ${V_(o)}]`});return{redacted:t,count:s}}ye();var Ar=1,st="claude-haiku-4-5-20251001",ef=3,tf=32e3,Xa=2e3,sf=30,nf=30,rf=30,of=30;function af(e){let s=f().prepare(`SELECT s.id,
|
|
918
918
|
NULLIF(sa.alias, '') AS alias,
|
|
919
919
|
s.auto_title,
|
|
920
920
|
s.auto_title_source,
|
|
@@ -925,15 +925,15 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
925
925
|
FROM sessions s
|
|
926
926
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
927
927
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
928
|
-
WHERE s.id = ?`).get(e);return s?{...s,alias_source:s.alias?"manual":null}:null}function
|
|
928
|
+
WHERE s.id = ?`).get(e);return s?{...s,alias_source:s.alias?"manual":null}:null}function Ja(e,t={}){if(e.message_count<ef)return{eligible:!1,reason:"too-short"};let s=e.title_quality;if(s==="programmatic"||s==="recursive_meta")return{eligible:!1,reason:"low-signal-title"};if(e.auto_title_source==="agent"&&!e.alias)return{eligible:!1,reason:"agent-titled-no-override"};if(!t.force){let n=Ze(e.id);if(n&&n.extractor_version>=Ar)return{eligible:!1,reason:"already-extracted"}}return{eligible:!0}}function cf(e){let t=af(e);if(!t)return null;let n=f().prepare(`SELECT role, content_text
|
|
929
929
|
FROM messages
|
|
930
930
|
WHERE session_id = ?
|
|
931
931
|
AND is_sidechain = 0
|
|
932
932
|
AND content_text IS NOT NULL
|
|
933
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){let c=a.role??"system",u=a.content_text.trim();if(!u)continue;let d=u.length>
|
|
933
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){let c=a.role??"system",u=a.content_text.trim();if(!u)continue;let d=u.length>Xa?u.slice(0,Xa)+"\u2026":u,m=`${c}: ${d}`;if(o+m.length>tf)break;r.push(m),o+=m.length}return{meta:t,excerpt:r.join(`
|
|
934
934
|
|
|
935
|
-
`)}}function
|
|
936
|
-
`)}function
|
|
935
|
+
`)}}function lf(e){let{meta:t}=e;return["You are extracting a structured Output Index from a Claude Code session for a local knowledge graph.","","Read the transcript and produce a single JSON object with EXACTLY these fields. Output JSON only \u2014 no Markdown fences, no commentary, no explanation.","","Fields (every field MUST appear; use [] for empty):","- files_written: array of strings. Relative or absolute file paths the assistant created, edited, or extensively discussed (Write/Edit tool calls or paths quoted verbatim).",'- brands_mentioned: array of strings. Distinct proper-noun company / product / brand names mentioned. Examples of valid: "Glaser Group", "Apollo", "TikTok", "Cloudflare R2". NOT generic terms like "the company" or "users".','- terms_introduced: array of objects { "term": "<lowercase phrase>", "freq": <int> }. Distinctive multi-word noun phrases the session introduced or extensively discussed. Skip generic words. Cap at 30 entries.','- plan_ids_referenced: array of strings. Planning identifiers like "v0.18.A", "Phase D", "L3", "MASTER-PLAN-cognitive-graph". Empty array if none.','- bug_signatures: array of objects { "error_type": "<class or null>", "snippet": "<first line of the error>", "file": "<path or null>" }. Errors actually encountered in the session (not hypothetical). The error_type is the exception class (e.g. "TypeError", "ReferenceError") or null if unspecified.',"","Hard constraints:","- Do NOT fabricate. If a field has no concrete content from the transcript, output an empty array.","- Output JSON ONLY. No backticks, no markdown, no preamble.","- terms_introduced \u2264 30 entries; brands_mentioned \u2264 30 distinct entries; plan_ids_referenced \u2264 30; bug_signatures \u2264 30.","",`Session: ${t.alias??t.id.slice(0,8)}`,`Project: ${t.project??"unknown"}`,t.first_user_message?`Opening prompt: ${t.first_user_message.slice(0,500)}`:"","","Transcript excerpt (may be truncated):","---",e.excerpt,"---"].filter(Boolean).join(`
|
|
936
|
+
`)}function uf(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function Rr(e,t,s=!1){if(!Array.isArray(e))return[];let n=new Set,r=[];for(let o of e){if(typeof o!="string")continue;let a=s?o.trim().toLowerCase():o.trim();if(!(!a||a.length>256)&&!n.has(a)&&(n.add(a),r.push(a),r.length>=t))break}return r}function df(e,t){if(!Array.isArray(e))return[];let s=new Set,n=[];for(let r of e){if(!r||typeof r!="object")continue;let o=r.term,a=r.freq;if(typeof o!="string")continue;let c=o.trim().toLowerCase();if(!c||c.length>128||s.has(c))continue;s.add(c);let u=typeof a=="number"&&Number.isFinite(a)&&a>0?Math.floor(a):1;if(n.push({term:c,frequency:u}),n.length>=t)break}return n}function pf(e,t){if(!Array.isArray(e))return[];let s=[];for(let n of e){if(!n||typeof n!="object")continue;let r=n.error_type,o=n.snippet,a=n.file,c=typeof r=="string"&&r.trim().length>0?r.trim().slice(0,64):"unknown",u=typeof o=="string"?o.trim().replace(/\s+/g," ").slice(0,256):"";if(!u)continue;let d=typeof a=="string"&&a.trim().length>0?a.trim().slice(0,256):null,m=Q_("sha256").update(`${c}::${u}`).digest("hex").slice(0,12);if(s.push({error_type:c,message_hash:m,snippet:u,file:d}),s.length>=t)break}return s}function mf(e){let t=e.trim();try{let b=JSON.parse(t);typeof b.result=="string"&&(t=b.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1),o;try{o=JSON.parse(r)}catch{return null}let a=Rr(o.files_written,200),c=Rr(o.brands_mentioned,nf),u=df(o.terms_introduced,sf),d=Rr(o.plan_ids_referenced,rf),m=pf(o.bug_signatures,of);return a.length===0&&c.length===0&&u.length===0&&d.length===0&&m.length===0&&!uf(o.files_written)?null:{files_written:a,brands_mentioned:c,terms_introduced:u,plan_ids_referenced:d,bug_signatures:m}}var kr=null;async function gf(e,t){return kr?kr(e,t):et(e,[],{model:t})}async function xr(e,t={}){if(t.signal?.aborted)return{session_id:e,ok:!1,failed:"aborted"};let s=cf(e);if(!s)return{session_id:e,ok:!1,skipped:"session-not-found"};let n=Ja(s.meta,{force:t.force});if(!n.eligible)return{session_id:e,ok:!1,skipped:n.reason};if(!kr&&!pe())return{session_id:e,ok:!1,failed:"claude-cli-missing"};let r=lf(s),o=t.model??st,a=await gf(r,o);if(!a.success){let m=(a.stderr||a.stdout||"").slice(0,400),h=m?Se(m).redacted:void 0;return{session_id:e,ok:!1,failed:"claude-cli-error",exit_code:a.exitCode,stderr_excerpt:h}}let c=mf(a.stdout);if(!c){let m=a.stdout.slice(0,400),h=m?Se(m).redacted:void 0;return{session_id:e,ok:!1,failed:"parse-failed",exit_code:a.exitCode,stderr_excerpt:h}}let u=_f(a.stdout),d=ya({session_id:e,files_written:c.files_written,brands_mentioned:c.brands_mentioned,terms_introduced:c.terms_introduced,plan_ids_referenced:c.plan_ids_referenced,bug_signatures:c.bug_signatures,raw_extraction:{model:o,usage:u,raw_response_excerpt:a.stdout.slice(0,4e3)},extractor_version:Ar});return{session_id:e,ok:!0,index:d,usage:u}}function _f(e){try{let t=JSON.parse(e.trim());if(t&&typeof t=="object"&&t.usage){let s=t.usage,n={};return typeof s.input_tokens=="number"&&(n.input_tokens=s.input_tokens),typeof s.output_tokens=="number"&&(n.output_tokens=s.output_tokens),n}}catch{}return null}function _t(e={}){let t=f(),s=[],n=[];typeof e.projectId=="number"&&(s.push("s.project_id = ?"),n.push(e.projectId));let r=Math.max(1,Math.min(1e4,e.limit??1e3)),o=s.length?`WHERE ${s.join(" AND ")}`:"",a=t.prepare(`SELECT s.id,
|
|
937
937
|
NULLIF(sa.alias, '') AS alias,
|
|
938
938
|
s.auto_title,
|
|
939
939
|
s.auto_title_source,
|
|
@@ -945,7 +945,298 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
945
945
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
946
946
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
947
947
|
${o}
|
|
948
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...n),c=[],u=new Map;for(let d of a){let m={...d,alias_source:d.alias?"manual":null},h
|
|
948
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...n),c=[],u=new Map;for(let d of a){let m={...d,alias_source:d.alias?"manual":null},h=Ja(m,{force:e.force});if(!h.eligible){let b=h.reason??"session-not-found";u.set(b,(u.get(b)??0)+1);continue}if(c.push(m),c.length>=r)break}return{eligible:c,skipped:u}}async function Ga(e={}){let t=_t({projectId:e.projectId,limit:e.limit,force:e.force}),s={total:t.eligible.length,processed:0,ok:0,failed:0,skipped:0,current_session_id:null,total_input_tokens:0,total_output_tokens:0};for(let r of t.skipped.values())s.skipped+=r;e.onProgress?.({...s});let n=[];for(let r of t.eligible){if(e.signal?.aborted)break;s.current_session_id=r.id,e.onProgress?.({...s});let o=await xr(r.id,{model:e.model,force:e.force,signal:e.signal});n.push(o),e.onResult?.(o),o.ok?s.ok+=1:o.skipped?s.skipped+=1:s.failed+=1,o.usage?.input_tokens&&(s.total_input_tokens+=o.usage.input_tokens),o.usage?.output_tokens&&(s.total_output_tokens+=o.usage.output_tokens),s.processed+=1,e.onProgress?.({...s})}return s.current_session_id=null,e.onProgress?.({...s}),{progress:s,results:n}}ye();var te="[daemon:inference]",vr=!1,jr=!1,Mr=!1,Dr=!1,Fr=!1,oc=0,Pr=!1,$r=!1,Xs=3,nt=0,ft=!1,Js=null,ht=null,ic=!1;function Cf(){return f().prepare("SELECT id, name FROM projects").all()}async function ac(){if(vr)return;vr=!0;let e=Date.now();try{let s=(await ca({minClusterSize:2})).progress;(s.clusters_created||s.clusters_merged||s.members_added)&&console.log(`${te} bug-patterns: created=${s.clusters_created} merged=${s.clusters_merged} members_added=${s.members_added} (${Date.now()-e}ms)`)}catch(t){console.error(`${te} bug-patterns failed:`,t)}finally{vr=!1}}async function cc(){if(jr)return;jr=!0;let e=Date.now();try{let t=0,s=0;for(let n of Cf())try{let r=await wa({projectId:n.id});t+=r.progress.suggestions_created,s+=1}catch(r){console.error(`${te} citations failed for project "${n.name}":`,r)}t>0&&console.log(`${te} citations: ${t} suggestion(s) across ${s} project(s) (${Date.now()-e}ms)`)}catch(t){console.error(`${te} citations failed:`,t)}finally{jr=!1}}function lc(){if(Mr)return;Mr=!0;let e=Date.now();try{let t=Aa({}),s=xa({});t.created+s.created>0&&console.log(`${te} l1: uuid=${t.created} plan=${s.created} (${Date.now()-e}ms)`)}catch(t){console.error(`${te} l1 failed:`,t)}finally{Mr=!1}}async function uc(){if(Dr)return;let e=ce();if(!e.enabled||e.backfillPaused)return;Dr=!0;let t=Date.now();try{let s=await Us({limit:25});s.processed>0&&console.log(`${te} backfill: processed=${s.processed} ok=${s.ok} failed=${s.failed} (${Date.now()-t}ms)`)}catch(s){console.error(`${te} backfill failed:`,s)}finally{Dr=!1}}async function dc(){if(Fr)return;let e=ce();if(e.autoExtractEnabled&&!ic&&ft&&(If(),console.log(`${te} auto-extract: circuit breaker reset (config toggled on).`)),ic=e.autoExtractEnabled,!e.autoExtractEnabled||ft)return;let t=e.autoExtractIntervalMinutes*60*1e3;if(Date.now()-oc<t)return;if(!pe()){Pr||(console.log(`${te} auto-extract: claude CLI not on PATH \u2014 pausing nibbler (will retry; install Claude Code or run \`recall semantic auto-extract off\` to silence)`),Pr=!0);return}if(Pr=!1,!await Mi().catch(()=>!1)){$r||(console.log(`${te} auto-extract: Pro license required \u2014 pausing nibbler (run \`recall semantic auto-extract off\` to silence; upgrade unlocks)`),$r=!0);return}$r=!1,Fr=!0,oc=Date.now();let n=Date.now();try{let r=e.autoExtractBatchSize,{eligible:o}=_t({limit:r});if(o.length===0)return;let a=0,c=0,u=0,d=0,m=new Map,h=null;for(let S of o){let w=await xr(S.id,{model:st});if(w.ok)a+=1;else if(!w.skipped){c+=1;let R=w.failed??"unknown";m.set(R,(m.get(R)??0)+1),!h&&w.stderr_excerpt&&(h=w.stderr_excerpt)}w.usage?.input_tokens&&(u+=w.usage.input_tokens),w.usage?.output_tokens&&(d+=w.usage.output_tokens)}let b=m.size>0?" reasons="+Array.from(m.entries()).map(([S,w])=>`${S}:${w}`).join(","):"";if(console.log(`${te} auto-extract: processed=${o.length} ok=${a} failed=${c} tokens=${u}+${d} (${Date.now()-n}ms)${b}`),h){let w=Se(h).redacted.replace(/\s+/g," ").trim().slice(0,300);console.log(`${te} auto-extract: first failure excerpt: ${w}`)}o.length>0&&u===0&&d===0?(nt+=1,nt>=Xs&&(ft=!0,Js=Date.now(),ht=`${Xs} consecutive zero-token runs (claude CLI returning no usage)`,console.log(`${te} auto-extract: CIRCUIT BREAKER tripped \u2014 ${ht}. Pausing nibbler. Run \`recall semantic auto-extract off\` then \`... on\` to reset, or restart the daemon.`))):(a>0||u>0||d>0)&&(nt=0)}catch(r){console.error(`${te} auto-extract failed:`,r),nt+=1,nt>=Xs&&(ft=!0,Js=Date.now(),ht=`${Xs} consecutive thrown errors`,console.log(`${te} auto-extract: CIRCUIT BREAKER tripped \u2014 ${ht}. Pausing nibbler.`))}finally{Fr=!1}}function mc(){return{broken:ft,brokenAt:Js,reason:ht,consecutiveZeroTokenRuns:nt}}function If(){ft=!1,Js=null,ht=null,nt=0}var Ur=!1;async function pc(){if(Ur)return;let{readRetentionConfig:e,writeRetentionConfig:t}=await Promise.resolve().then(()=>(Lr(),Qa)),s=e();if(!s.autoArchiveEnabled)return;let n=1380*60*1e3;if(s.lastRunAt&&Date.now()-Date.parse(s.lastRunAt)<n)return;Ur=!0;let r=Date.now();try{let a=new Date(Date.now()-s.autoArchiveAfterDays*24*60*60*1e3).toISOString().slice(0,10),{runArchive:c}=await Promise.resolve().then(()=>(rc(),nc)),u=await c({_action:"run",before:a,dryRun:!1});t({lastRunAt:new Date().toISOString()}),console.log(`${te} auto-archive: cutoff=${a} exit=${u} (${Date.now()-r}ms)`)}catch(o){let a=o instanceof Error?o.message:String(o);console.error(`${te} auto-archive failed: ${a}`)}finally{Ur=!1}}function gc(){let m=[],h=[];m.push(setTimeout(()=>{ac()},9e4)),h.push(setInterval(()=>{ac()},18e5)),m.push(setTimeout(()=>{cc()},18e4)),h.push(setInterval(()=>{cc()},36e5)),m.push(setTimeout(()=>lc(),12e4)),h.push(setInterval(()=>lc(),18e5)),m.push(setTimeout(()=>{uc()},24e4)),h.push(setInterval(()=>{uc()},9e5)),m.push(setTimeout(()=>{dc()},3e5)),h.push(setInterval(()=>{dc()},9e5));let b=3600*1e3,T=300*1e3;return m.push(setTimeout(()=>{pc()},T)),h.push(setInterval(()=>{pc()},b)),console.log(`${te} scheduled: bug-patterns (30m), citations (60m), l1 (30m), backfill (15m, when enabled), auto-extract (60m, when enabled), auto-archive (24h, when enabled)`),{startupTimers:m,intervalTimers:h,stop:()=>{for(let S of m)clearTimeout(S);for(let S of h)clearInterval(S)}}}H();import{serveStatic as Jp}from"@hono/node-server/serve-static";import{existsSync as Ky,readFileSync as _i}from"node:fs";import{stat as Vy,readFile as Zy,realpath as Qy}from"node:fs/promises";import{timingSafeEqual as ew}from"node:crypto";import{homedir as tw}from"node:os";import{dirname as Ei,join as Kn}from"node:path";import{fileURLToPath as bi}from"node:url";import{existsSync as mx,readFileSync as gx,statSync as _x,statfsSync as zE}from"node:fs";qs();H();ee();ee();H();import{watch as BE}from"chokidar";import{readdirSync as WE,statSync as mo}from"node:fs";import{createReadStream as vf}from"node:fs";import{createInterface as jf}from"node:readline";function Gs(e){return typeof e=="number"&&Number.isFinite(e)?e:0}function Wr(e){let t=e?.usage;if(!t||typeof t!="object")return null;let s={inputTokens:Gs(t.input_tokens),outputTokens:Gs(t.output_tokens),cacheCreateTokens:Gs(t.cache_creation_input_tokens),cacheReadTokens:Gs(t.cache_read_input_tokens)};return s.inputTokens===0&&s.outputTokens===0&&s.cacheCreateTokens===0&&s.cacheReadTokens===0?null:s}var Mf=/\x1B\[[0-9;]*[a-zA-Z]/g;function Hr(e){return e.replace(Mf,"")}var Br=12e3;function _c(e,t){if(e.length<=Br)return e;let s=e.slice(0,Br),n=e.length-Br;return`${s}
|
|
949
|
+
|
|
950
|
+
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function Df(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function Ff(e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content)){let t=[];for(let s of e.content)if(s&&typeof s=="object"){let n=s;n.type==="text"&&typeof n.text=="string"?t.push(n.text):n.type==="image"&&t.push("[image]")}return t.join(`
|
|
951
|
+
`)}return""}function Pf(e){if(!e)return{text:"",toolNames:[]};if(typeof e.content=="string")return{text:Hr(e.content),toolNames:[]};if(!Array.isArray(e.content))return{text:"",toolNames:[]};let t=[],s=[];for(let n of e.content)if(!(!n||typeof n!="object")){if(n.type==="text"&&typeof n.text=="string"){t.push(Hr(n.text));continue}if(n.type==="tool_use"&&typeof n.name=="string"){s.push(n.name);let r=n.input!=null?Df(n.input):"",o=_c(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${n.name}\`**
|
|
952
|
+
|
|
953
|
+
\`\`\`json
|
|
954
|
+
${o}
|
|
955
|
+
\`\`\``);continue}if(n.type==="tool_result"){let r=Hr(Ff(n));if(r){let o=_c(r,"tool result");t.push(`**Tool result**
|
|
956
|
+
|
|
957
|
+
\`\`\`
|
|
958
|
+
${o}
|
|
959
|
+
\`\`\``)}else t.push("_(tool result was empty or image-only)_");continue}if(n.type==="image"){t.push("_(image)_");continue}t.push(`_(unknown block: ${n.type})_`)}return{text:t.join(`
|
|
960
|
+
|
|
961
|
+
`),toolNames:s}}async function*fc(e){let t=vf(e,{encoding:"utf8"}),s=jf({input:t,crlfDelay:1/0});for await(let n of s){if(!n.trim())continue;let r;try{r=JSON.parse(n)}catch{continue}if(!r.uuid||!r.sessionId)continue;let{text:o,toolNames:a}=Pf(r.message);yield{uuid:r.uuid,parentUuid:r.parentUuid??null,sessionId:r.sessionId,type:r.type??"unknown",role:r.message?.role??null,timestamp:r.timestamp??null,isSidechain:r.isSidechain===!0,cwd:r.cwd??null,gitBranch:r.gitBranch??null,version:r.version??null,contentText:o,toolNames:a,raw:n,usage:Wr(r.message),model:r.message?.model??null}}}import{basename as Sl,join as qE}from"node:path";import{execFile as ah}from"node:child_process";import{promisify as ch}from"node:util";import{existsSync as lh}from"node:fs";import{basename as uh}from"node:path";ee();import{existsSync as $f,readFileSync as Uf,writeFileSync as Hf}from"node:fs";import{join as Bf}from"node:path";import{z as fe}from"zod";var qr=Bf(W,"terminals.json"),hc=1440*60*1e3,Wf=3e4,qf=6e4,Xf=fe.object({shell_pid:fe.number(),tab_name:fe.string(),cwd:fe.string().nullable().optional(),opened_at:fe.string(),last_seen_at:fe.string()}),Jf=fe.object({schema:fe.string().optional(),saved_at:fe.string().optional(),terminals:fe.array(Xf).max(500).default([]),sessions_by_pid:fe.record(fe.string(),fe.array(fe.string()).max(50)).optional().default({})}),Ec=/^[⠀-⣿✳\s]+/,bc=/^\d+(\.\d+){1,3}$/;function le(e){let t=e.trim();return!!(!t||Ec.test(t)||bc.test(t))}function Et(e){let t=e.trim();if(!t||bc.test(t))return null;let s=t.replace(Ec,"").trim();return s.length>0?s:null}function Xr(e,t){if(!le(e))return e;let s=Et(e);return s||(t&&!le(t)?t:e)}function Gf(e){let t=e.now-e.withinMs,s=e.pending.filter(n=>{let r=Date.parse(n.started_at);return Number.isFinite(r)&&r>=t});if(s.length===0)return{kind:"none"};if(e.shellPid!=null){let n=s.find(r=>r.shell_pid===e.shellPid);if(n)return{kind:"pid-match",entry:n}}if(e.cwd){let n=e.cwd.replace(/\/+$/,""),r=s.filter(o=>o.cwd&&o.cwd.replace(/\/+$/,"")===n);if(r.length===1)return{kind:"singleton-cwd",entry:r[0]};if(r.length>=2)return{kind:"ambiguous",candidates:r}}return{kind:"none"}}var Jr=class{entries=new Map;origins=new Map;sessionsByPid=new Map;pendingClaudeStarts=[];deferredLinks=new Map;pidOwnership=new Map;loaded=!1;ensureLoaded(){if(!this.loaded&&(this.loaded=!0,!!$f(qr)))try{let t=Uf(qr,"utf8"),s=JSON.parse(t),n=Jf.safeParse(s);if(!n.success){console.warn("[terminal-registry] terminals.json failed validation, starting with empty registry:",n.error.issues);return}let r=n.data;for(let o of r.terminals)this.entries.set(o.shell_pid,{shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd??null,opened_at:o.opened_at,last_seen_at:o.last_seen_at});for(let[o,a]of Object.entries(r.sessions_by_pid??{})){let c=Number(o);if(!Number.isFinite(c))continue;let u=new Set;for(let d of a)d.length>0&&u.add(d);u.size>0&&this.sessionsByPid.set(c,u)}this.gc()}catch{}}save(){try{z();let t={schema:"claude-recall.terminals.v1",saved_at:new Date().toISOString(),terminals:Array.from(this.entries.values()),sessions_by_pid:Object.fromEntries(Array.from(this.sessionsByPid.entries()).map(([s,n])=>[String(s),Array.from(n)]))};Hf(qr,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let s=new Date().toISOString(),n=this.entries.get(t.shell_pid),r=Xr(t.tab_name,n?.tab_name),o=n?.opened_at??t.opened_at,a={...t,tab_name:r,opened_at:o,last_seen_at:s};return this.entries.set(t.shell_pid,a),this.gc(),this.save(),a}rename(t,s){this.ensureLoaded();let n=this.entries.get(t);if(!n)return null;let r=Xr(s,n.tab_name),o={...n,tab_name:r,last_seen_at:new Date().toISOString()};return this.entries.set(t,o),this.save(),o}remove(t){this.ensureLoaded();let s=this.entries.delete(t),n=this.sessionsByPid.delete(t);return this.outputTails.delete(t),this.pidOwnership.delete(t),(s||n)&&this.save(),s}claimPidOwnership(t,s,n=Date.now()){if(this.ensureLoaded(),!s)return"anonymous";let r=this.pidOwnership.get(t);return r?r.instance_id===s?(r.last_claim_at=n,"refreshed"):n-r.last_claim_at>qf?(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed"):"rejected":(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed")}pidOwnershipSnapshot(){return this.ensureLoaded(),Array.from(this.pidOwnership.entries()).map(([t,s])=>({shell_pid:t,instance_id:s.instance_id,last_claim_at:s.last_claim_at}))}sync(t){this.ensureLoaded();let s=new Date().toISOString(),n=0,r=0;for(let o of t){let a=this.entries.get(o.shell_pid),c=Xr(o.tab_name,a?.tab_name),u=a?.opened_at??o.opened_at;this.entries.set(o.shell_pid,{...o,tab_name:c,opened_at:u,last_seen_at:s}),a?(a.tab_name!==c||a.cwd!==o.cwd)&&r++:n++}return this.gc(),this.save(),{added:n,updated:r,removed:0}}get(t){return this.ensureLoaded(),this.entries.get(t)??null}all(){return this.ensureLoaded(),this.gc(),Array.from(this.entries.values())}size(){return this.ensureLoaded(),this.entries.size}linkSession(t,s){this.ensureLoaded();let n=this.sessionsByPid.get(s);n||(n=new Set,this.sessionsByPid.set(s,n)),n.has(t)||(n.add(t),this.save())}sessionsFor(t){return this.ensureLoaded(),Array.from(this.sessionsByPid.get(t)??[])}isSessionAutoLinked(t){this.ensureLoaded();for(let s of this.sessionsByPid.values())if(s.has(t))return!0;return!1}unlinkSession(t){this.ensureLoaded();let s=!1;for(let[n,r]of this.sessionsByPid)r.delete(t)&&(s=!0,r.size===0&&this.sessionsByPid.delete(n));return s&&this.save(),s}pushPending(t){this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.push({...t})}takePendingMatched(t){this.ensureLoaded(),this.gcPending();let s=Gf({pending:this.pendingClaudeStarts,shellPid:t.shellPid,cwd:t.cwd,withinMs:t.withinMs,now:Date.now()});if(s.kind==="pid-match"||s.kind==="singleton-cwd"){let n=this.pendingClaudeStarts.indexOf(s.entry);n>=0&&this.pendingClaudeStarts.splice(n,1)}return s}pendingSize(){return this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.length}deferSessionLink(t,s,n,r){this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.set(t,{parent_shell_pid:s,queued_at:Date.now(),cwd:n,git_branch:r})}allDeferredLinks(){return this.ensureLoaded(),this.gcDeferredLinks(),Array.from(this.deferredLinks.entries()).map(([t,s])=>({session_id:t,...s}))}resolveDeferredLink(t){return this.deferredLinks.delete(t)}deferredLinkSize(){return this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.size}gcDeferredLinks(){let t=Date.now()-9e4;for(let[s,n]of this.deferredLinks)n.queued_at<t&&this.deferredLinks.delete(s)}gcPending(){let t=Date.now()-Wf;this.pendingClaudeStarts.length!==0&&(this.pendingClaudeStarts=this.pendingClaudeStarts.filter(s=>{let n=Date.parse(s.started_at);return Number.isFinite(n)&&n>=t}))}outputTails=new Map;setOutputTail(t,s,n){this.ensureLoaded(),this.outputTails.set(t,{text:s,captured_at:n})}getOutputTail(t){return this.ensureLoaded(),this.outputTails.get(t)??null}allOutputTails(){return this.ensureLoaded(),new Map(this.outputTails)}removeOutputTail(t){return this.outputTails.delete(t)}setOrigin(t,s){this.ensureLoaded(),this.origins.set(t,s),this.gcOrigins()}getOrigin(t){return this.ensureLoaded(),this.origins.get(t)??null}removeOrigin(t){return this.ensureLoaded(),this.origins.delete(t)}allOrigins(){return this.ensureLoaded(),this.gcOrigins(),new Map(this.origins)}originSize(){return this.ensureLoaded(),this.origins.size}gc(){let t=Date.now()-hc;for(let[s,n]of this.entries){let r=Date.parse(n.last_seen_at);!Number.isNaN(r)&&r<t&&this.entries.delete(s)}}gcOrigins(){let t=Date.now()-hc;for(let[s,n]of this.origins)n.detectedAt<t&&this.origins.delete(s)}reapStaleLinks(t=10080*60*1e3){this.ensureLoaded();let n=Date.now()-t,r=0,o=0;for(let[a,c]of this.sessionsByPid){let u=this.entries.get(a);if(u){let d=Date.parse(u.last_seen_at);if(Number.isFinite(d)&&d>=n)continue}o+=c.size,this.sessionsByPid.delete(a),u&&(this.entries.delete(a),r++)}return(r||o)&&this.save(),{pruned_pids:r,pruned_sessions:o}}gcDeadPids(){this.ensureLoaded();let t=0,s=0;for(let n of[...this.entries.keys()]){let r=!0;try{process.kill(n,0)}catch(a){a.code==="ESRCH"&&(r=!1)}if(r)continue;this.entries.delete(n),t++;let o=this.sessionsByPid.get(n);o&&(s+=o.size,this.sessionsByPid.delete(n)),this.outputTails.delete(n),this.pidOwnership.delete(n)}return(t||s)&&this.save(),{pruned_pids:t,pruned_sessions:s}}},v=new Jr;H();ee();import{writeFileSync as Yf}from"node:fs";import{join as zf}from"node:path";var Kf=zf(W,"aliases.json");function Gr(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Te(e){return f().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e)?.alias??null}function Vf(){return f().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(t=>({session_id:t.session_id,alias:t.alias,updated_at:t.updated_at,previous_aliases:Gr(t.previous_aliases)}))}function he(e,t){let s=t.trim();if(!s)throw new Error("alias must be non-empty");let n=f(),r=new Date().toISOString(),o=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),a=[];return o&&(a=Gr(o.previous_aliases),o.alias!==s&&a.push({alias:o.alias,replaced_at:r})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
|
|
962
|
+
VALUES (?, ?, ?, ?)
|
|
963
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
964
|
+
alias = excluded.alias,
|
|
965
|
+
updated_at = excluded.updated_at,
|
|
966
|
+
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(a)),Sc(),{session_id:e,alias:s,updated_at:r,previous_aliases:a}}function Ys(e){let t=f(),s=new Date().toISOString(),n=t.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e);if(!n)return;let r=Gr(n.previous_aliases);r.push({alias:n.alias,replaced_at:s}),t.prepare(`UPDATE session_aliases SET alias = '', updated_at = ?, previous_aliases = ?
|
|
967
|
+
WHERE session_id = ?`).run(s,JSON.stringify(r),e),Sc()}function Sc(){try{z();let e=Vf(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};Yf(Kf,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}H();import{execFile as Zf}from"node:child_process";import{readFile as Qf}from"node:fs/promises";import{promisify as eh}from"node:util";var th=eh(Zf),Tc=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function sh(e){let t={};for(let s of Tc){let n=e[s];typeof n=="string"&&n.length>0&&(t[s]=n)}return t}function nh(e){let t=Date.now();if(e.CURSOR_TRACE_ID)return{editor:"cursor",label:"Cursor",detectedAt:t};if(e.VSCODE_PID||e.VSCODE_INJECTION)return{editor:"vscode",label:"VS Code",detectedAt:t};let s=e.TERM_PROGRAM;return s==="WarpTerminal"?{editor:"warp",label:"Warp",detectedAt:t}:s==="iTerm.app"?{editor:"iterm",label:"iTerm",detectedAt:t}:s==="Apple_Terminal"?{editor:"apple-terminal",label:"Terminal",detectedAt:t}:s==="WezTerm"?{editor:"wezterm",label:"WezTerm",detectedAt:t}:e.WT_SESSION?{editor:"windows-terminal",label:"Windows Terminal",detectedAt:t}:e.KITTY_WINDOW_ID?{editor:"kitty",label:"Kitty",detectedAt:t}:e.TERM==="alacritty"||e.ALACRITTY_SOCKET?{editor:"alacritty",label:"Alacritty",detectedAt:t}:null}async function rh(e){if(process.platform==="linux")try{let t=await Qf(`/proc/${e}/environ`,"utf8");return oh(t)}catch{return{}}try{let{stdout:t}=await th("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return ih(t)}catch{return{}}}function oh(e){let t={};for(let s of e.split("\0")){if(!s)continue;let n=s.indexOf("=");if(n<=0)continue;let r=s.slice(0,n),o=s.slice(n+1);t[r]=o}return t}function ih(e){let t={},s=new Set(Tc),n=e.replace(/\s+/g," ").trim().split(" ");for(let r of n){let o=r.indexOf("=");if(o<=0)continue;let a=r.slice(0,o);s.has(a)&&(t[a]=r.slice(o+1))}return t}async function yc(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await rh(e),s=sh(t);return nh(s)}catch{return null}}var Ks=ch(ah),zs;function dh(){if(zs!==void 0)return zs;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(lh(t))return zs=t,t;return zs=null,null}var ph=3,mh=3600*1e3,Zt=new Map;function kc(){let e=v.all(),t=e.map(s=>s.shell_pid).sort((s,n)=>s-n).join(",");return`${e.length}:${t}`}function gh(e){let t=Zt.get(e);return t?Date.now()-t.lastAt>mh?(Zt.delete(e),!1):t.refusals<ph?!1:t.fingerprint===kc():!1}function wc(e){let t=kc(),s=Zt.get(e);s&&s.fingerprint===t?(s.refusals+=1,s.lastAt=Date.now()):Zt.set(e,{refusals:1,fingerprint:t,lastAt:Date.now()})}function _h(e){Zt.delete(e)}async function fh(e){let t=dh();if(!t)return null;try{let{stdout:s}=await Ks(t,["-Fpc",e],{timeout:2e3}),n=s.split(`
|
|
968
|
+
`),r=null,o=null,a=null;for(let c of n)c.startsWith("p")?(a=Number(c.slice(1)),Number.isFinite(a)&&a>0?r==null&&(r=a):a=null):c.startsWith("c")&&a!=null&&c.slice(1).trim()==="claude"&&o==null&&(o=a);return o??r}catch{return null}}async function Rc(e){try{let{stdout:t}=await Ks("/bin/ps",["-o","ppid=","-p",String(e)],{timeout:2e3}),s=Number(t.trim());return Number.isFinite(s)&&s>0?s:null}catch{return null}}async function hh(e){try{let{stdout:t}=await Ks("/bin/ps",["-o","lstart=","-p",String(e)],{timeout:2e3}),s=Date.parse(t.trim());return Number.isFinite(s)?s:null}catch{return null}}var Eh=new Set(["zsh","bash","fish","sh","dash","ksh","tcsh","csh","pwsh","powershell","cmd","nu","node","deno","bun","python","python3","ruby","ts-node","tsx","go","cargo","java","php","irb","pry","iex","terminal","shell","console"]);function de(e){let t=e.trim().toLowerCase();if(!t)return!0;let s=t.replace(/^[-/]+/,"").replace(/^.*\//,"").replace(/\s*\(\d+\)\s*$/,"").trim();return Eh.has(s)}function bt(e){let t=e.tabName?.trim();return t&&!de(t)&&!le(t)?t:null}function bh(e){try{return f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(e)??null}catch{return null}}async function Vs(e){let t=uh(e,".jsonl");if(Te(t)||gh(t))return;let s=bh(t),n=await fh(e),r=n?await Rc(n):null,o=n?await yc(n):null;o&&v.setOrigin(t,o);let a=null,c=null,u=null,d=v.takePendingMatched({shellPid:r,cwd:s?.cwd??null,withinMs:3e4});if(d.kind==="ambiguous"){console.log(`[correlator] ambiguous pending for ${t.slice(0,8)} (${d.candidates.length} candidates in ${s?.cwd??"?"}) \u2014 refusing to guess; heuristic title will display`),wc(t);return}if(d.kind==="pid-match"||d.kind==="singleton-cwd"){let S=d.entry;c=S.shell_pid,u=d.kind==="pid-match"?"pending-pid":"pending-cwd",a=v.get(S.shell_pid)??{shell_pid:S.shell_pid,tab_name:S.tab_name,cwd:S.cwd,opened_at:S.started_at,last_seen_at:S.started_at}}let m=null;if(!a&&n){let S=n;for(let w=0;w<4&&S!=null;w++){let R=await Rc(S);if(!R)break;let D=v.get(R);if(D){a=D,c=R,u="ppid";break}m==null&&(m=R),S=R}}if(!a&&s?.cwd){let S=s.cwd.replace(/\/+$/,""),w=v.all().filter(R=>R.cwd&&R.cwd.replace(/\/+$/,"")===S);w.length===1?(a=w[0],c=a.shell_pid,u="cwd"):w.length>=2&&(console.log(`[correlator] ${w.length} registered terminals in ${S} for ${t.slice(0,8)} \u2014 refusing to guess; heuristic title will display`),wc(t))}let h=null;if(a?.tab_name&&!de(a.tab_name)&&!le(a.tab_name))h=a.tab_name;else if(a?.tab_name&&le(a.tab_name)){let S=Et(a.tab_name);S&&!de(S)&&(h=S)}if(!h&&!(u==="pending-pid"||u==="pending-cwd")&&a&&s?.cwd){let S=s.cwd.replace(/\/+$/,""),w=v.all().filter(R=>R.shell_pid!==c&&R.cwd&&R.cwd.replace(/\/+$/,"")===S&&!de(R.tab_name)&&!le(R.tab_name)).sort((R,D)=>Date.parse(D.last_seen_at)-Date.parse(R.last_seen_at))[0];w&&(h=w.tab_name)}let T=bt({tabName:h,origin:o,cwd:s?.cwd??null,gitBranch:s?.git_branch??null});if(m!=null&&!c&&v.deferSessionLink(t,m,s?.cwd??null,s?.git_branch??null),!!T)try{he(t,T),_h(t);let S=!!h&&!de(h)&&!le(h)&&T===h.trim();c!=null&&v.linkSession(t,c);let w=u==="pending-pid"?"pending PID-exact match":u==="pending-cwd"?"pending cwd-singleton match":u??"unknown";console.log(`[correlator] auto-aliased ${t.slice(0,8)} \u2192 "${T}"`+(S?` (tab name via ${w}, shell pid ${c})`:a?` (generic shell name "${a.tab_name}" \u2192 ${o?.editor??"origin"} fallback, shell pid ${c})`:o?` (${o.editor} origin \u2014 no terminal match)`:""))}catch{}}async function Sh(){try{let{stdout:e}=await Ks("/bin/ps",["-eo","pid=,ppid=,comm="],{timeout:2e3}),t=new Map;for(let s of e.split(`
|
|
969
|
+
`)){let n=s.trim();if(!n)continue;let r=n.match(/^(\d+)\s+(\d+)\s+(.+)$/);if(!r)continue;let o=Number(r[1]),a=Number(r[2]),c=r[3].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&(!Number.isFinite(o)||!Number.isFinite(a)||t.set(o,a))}return t}catch{return new Map}}var Th=9e4;function yh(e){let t=(Date.now()-Th)/1e3,s=e.replace(/\/+$/,"");return f().prepare(`SELECT s.id, NULLIF(sa.alias, '') AS alias, s.started_at AS started_at
|
|
970
|
+
FROM sessions s
|
|
971
|
+
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
972
|
+
WHERE s.cwd = ? AND s.file_mtime > ?`).all(s,t).map(r=>({id:r.id,alias:r.alias,started_at_ms:r.started_at?Date.parse(r.started_at):null}))}async function wh(e){let t=await import("node:fs/promises"),s="";try{let r=await t.open(e,"r");try{let o=Buffer.alloc(32768),{bytesRead:a}=await r.read(o,0,o.length,0);s=o.toString("utf8",0,a)}finally{await r.close()}}catch{return[]}let n=[];for(let r of s.split(`
|
|
973
|
+
`)){if(!r.trim())continue;let o;try{o=JSON.parse(r)}catch{continue}let a=o;if(!a||a.type!=="user"&&a.type!=="assistant")continue;let c=a.message?.content,u="";if(typeof c=="string")u=c;else if(Array.isArray(c))for(let d of c)d&&typeof d=="object"&&"text"in d&&typeof d.text=="string"&&(u+=d.text+`
|
|
974
|
+
`);if(u){for(let d of u.split(/\r?\n/)){let m=d.trim();m.length>=60&&m.length<=400&&n.push(m)}if(n.length>=8)break}}return n}async function Ac(e){if(Te(e))return null;let t=f().prepare("SELECT cwd, file_path FROM sessions WHERE id = ?").get(e);if(!t?.cwd||!t.file_path)return null;let s=await wh(t.file_path);if(s.length===0)return null;let n=t.cwd.replace(/\/+$/,""),r=v.allOutputTails(),o=[];for(let[a,c]of r){let u=v.get(a);if(!u||!u.cwd||u.cwd.replace(/\/+$/,"")!==n||de(u.tab_name)||le(u.tab_name))continue;let d=0;for(let m of s)c.text.includes(m)&&d++;d>0&&o.push({shell_pid:a,tab_name:u.tab_name,matched_fingerprints:d})}return o.length===0||(o.sort((a,c)=>c.matched_fingerprints-a.matched_fingerprints),o.length>1&&o[0].matched_fingerprints===o[1].matched_fingerprints)?null:o[0]}var Rh=3e4;function xc(){let e=Date.now(),t=v.all(),s=o=>{let a=Date.parse(o.last_seen_at);return!Number.isFinite(a)||e-a>Rh},n=new Map;for(let o of t){if(s(o)||!o.cwd||de(o.tab_name)||le(o.tab_name))continue;let a=`${o.cwd.replace(/\/+$/,"")}::${o.tab_name}`,c=n.get(a);c||(c=[],n.set(a,c)),c.push({shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd})}let r={rebound:0,ghosts:0,ambiguous:0};for(let o of t){if(!s(o))continue;let a=v.sessionsFor(o.shell_pid);if(a.length!==0){r.ghosts++;for(let c of a){let u=Te(c);if(!u)continue;let d=f().prepare("SELECT cwd FROM sessions WHERE id = ?").get(c);if(!d?.cwd)continue;let m=`${d.cwd.replace(/\/+$/,"")}::${u}`,h=n.get(m)??[];if(h.length===0)continue;if(h.length>1){r.ambiguous++;continue}let b=h[0];b.shell_pid!==o.shell_pid&&(v.unlinkSession(c),v.linkSession(c,b.shell_pid),r.rebound++)}}}return r}function Nc(){let e={resolved:0,expired:0},t=v.allDeferredLinks();for(let s of t){let n=v.get(s.parent_shell_pid);if(!n||de(n.tab_name)||le(n.tab_name))continue;let r=Te(s.session_id);if(r&&!v.isSessionAutoLinked(s.session_id)){v.resolveDeferredLink(s.session_id);continue}let o=v.getOrigin(s.session_id),a=bt({tabName:n.tab_name,origin:o??null,cwd:s.cwd,gitBranch:s.git_branch});if(!a){v.resolveDeferredLink(s.session_id);continue}r!==a&&he(s.session_id,a),v.linkSession(s.session_id,s.parent_shell_pid),v.resolveDeferredLink(s.session_id),e.resolved++}return e}var kh=6e4;async function Zs(){let e=await Sh(),t={scanned:e.size,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0};if(e.size===0)return t;let s=[];for(let[a,c]of e){let u=v.get(c);if(!u||!u.cwd||de(u.tab_name)||le(u.tab_name))continue;let d=u.tab_name.trim();if(!d)continue;let m=await hh(a);s.push({claudePid:a,shellPid:c,cwd:u.cwd.replace(/\/+$/,""),target:d,startTimeMs:m})}let n=new Map,r=a=>{let c=n.get(a);if(c)return c;let u=yh(a);return n.set(a,u),u},o=new Set;for(let a of s){let c=r(a.cwd).filter(d=>!o.has(d.id));if(c.length===0)continue;let u=null;if(a.startTimeMs!=null){let d=kh;for(let m of c){if(m.started_at_ms==null)continue;let h=Math.abs(m.started_at_ms-a.startTimeMs);h<d&&(d=h,u=m)}}if(!u&&c.length===1&&(u=c[0]),!u){t.ambiguous_cwd++;continue}if(o.add(u.id),u.alias&&!v.isSessionAutoLinked(u.id)){t.skipped_manual++;continue}if(u.alias===a.target){v.linkSession(u.id,a.shellPid);continue}try{he(u.id,a.target),v.linkSession(u.id,a.shellPid),u.alias?t.renamed++:t.linked++,console.log(`[correlator] linked ${u.id.slice(0,8)} \u2192 "${a.target}" (live sweep, claude pid ${a.claudePid}, shell pid ${a.shellPid})`)}catch{}}return t}function Oc(e,t,s){e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let n=e.prepare(`
|
|
975
|
+
INSERT INTO message_usage (
|
|
976
|
+
message_uuid, session_id, model,
|
|
977
|
+
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
978
|
+
timestamp
|
|
979
|
+
) VALUES (
|
|
980
|
+
@uuid, @session_id, @model,
|
|
981
|
+
@input, @output, @cc, @cr,
|
|
982
|
+
@ts
|
|
983
|
+
)
|
|
984
|
+
ON CONFLICT(message_uuid) DO UPDATE SET
|
|
985
|
+
model = excluded.model,
|
|
986
|
+
input_tokens = excluded.input_tokens,
|
|
987
|
+
output_tokens = excluded.output_tokens,
|
|
988
|
+
cache_create_tokens = excluded.cache_create_tokens,
|
|
989
|
+
cache_read_tokens = excluded.cache_read_tokens,
|
|
990
|
+
timestamp = excluded.timestamp
|
|
991
|
+
`);for(let r of s)r.usage&&r.role==="assistant"&&n.run({uuid:r.uuid,session_id:t,model:r.model,input:r.usage.inputTokens,output:r.usage.outputTokens,cc:r.usage.cacheCreateTokens,cr:r.usage.cacheReadTokens,ts:r.timestamp})}function Qs(e,t){let s=e.prepare(`SELECT
|
|
992
|
+
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
993
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
994
|
+
COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
|
|
995
|
+
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens
|
|
996
|
+
FROM message_usage WHERE session_id = ?`).get(t),n=e.prepare(`SELECT model, SUM(output_tokens) AS out
|
|
997
|
+
FROM message_usage
|
|
998
|
+
WHERE session_id = ? AND model IS NOT NULL
|
|
999
|
+
GROUP BY model
|
|
1000
|
+
ORDER BY out DESC LIMIT 1`).get(t);e.prepare(`UPDATE sessions SET
|
|
1001
|
+
total_input_tokens = @input,
|
|
1002
|
+
total_output_tokens = @output,
|
|
1003
|
+
total_cache_create_tokens = @cc,
|
|
1004
|
+
total_cache_read_tokens = @cr,
|
|
1005
|
+
primary_model = @model
|
|
1006
|
+
WHERE id = @id`).run({id:t,input:s.input_tokens,output:s.output_tokens,cc:s.cache_create_tokens,cr:s.cache_read_tokens,model:n?.model??null})}H();import{execFile as Ah}from"node:child_process";import{promisify as xh}from"node:util";import{stat as Nh}from"node:fs/promises";var Lc=xh(Ah),Cc=1e4,Oh="%H%x09%aI%x09%s";async function Lh(e){try{let{stdout:t}=await Lc("git",["rev-parse","--is-inside-work-tree"],{cwd:e,timeout:Cc});return t.trim()==="true"}catch{return!1}}async function Ch(e,t,s){let n=["--no-pager","log","--all","--no-color","--since",t,"--until",s,`--pretty=format:${Oh}`],{stdout:r}=await Lc("git",n,{cwd:e,timeout:Cc,maxBuffer:8*1024*1024}),o=[],a=new Set;for(let c of r.split(`
|
|
1007
|
+
`)){if(!c)continue;let[u,d,...m]=c.split(" ");!u||a.has(u)||(a.add(u),o.push({commit_sha:u,committed_at:d??null,subject:m.join(" ")||null}))}return o}function Ih(e){return f().prepare(`SELECT id, cwd, started_at, ended_at
|
|
1008
|
+
FROM sessions WHERE id = ?`).get(e)??null}function vh(e,t,s){if(s.length===0)return 0;let n=f(),r=new Date().toISOString(),o=n.prepare(`INSERT OR IGNORE INTO session_commits
|
|
1009
|
+
(session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
|
|
1010
|
+
VALUES (?, ?, ?, ?, ?, ?)`),a=0;return n.transaction(u=>{for(let d of u)o.run(e,d.commit_sha,d.committed_at,d.subject,t,r).changes>0&&(a+=1)})(s),a}async function Yr(e){let t=Ih(e);if(!t)return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:"session not found"};if(!t.cwd)return{sessionId:e,status:"no-cwd",commitsFound:0,commitsInserted:0};if(!t.started_at||!t.ended_at)return{sessionId:e,status:"no-window",commitsFound:0,commitsInserted:0};let s=t.started_at,n=t.ended_at===t.started_at?new Date(Date.parse(t.ended_at)+1e3).toISOString():t.ended_at;try{if(!(await Nh(t.cwd)).isDirectory())return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}catch{return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}if(!await Lh(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await Ch(t.cwd,s,n),a=vh(e,t.cwd,o);return{sessionId:e,status:"ok",commitsFound:o.length,commitsInserted:a}}catch(o){return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:o.message}}}function en(e){let t=f(),s=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(s))return[];let n=`${s.toLowerCase()}%`;return t.prepare(`SELECT sc.session_id AS sessionId,
|
|
1011
|
+
NULLIF(sa.alias, '') AS alias,
|
|
1012
|
+
p.name AS project,
|
|
1013
|
+
s.started_at AS startedAt,
|
|
1014
|
+
s.ended_at AS endedAt,
|
|
1015
|
+
sc.commit_sha AS commitSha,
|
|
1016
|
+
sc.committed_at AS committedAt,
|
|
1017
|
+
sc.subject AS subject
|
|
1018
|
+
FROM session_commits sc
|
|
1019
|
+
JOIN sessions s ON s.id = sc.session_id
|
|
1020
|
+
JOIN projects p ON p.id = s.project_id
|
|
1021
|
+
LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
|
|
1022
|
+
WHERE lower(sc.commit_sha) = lower(?)
|
|
1023
|
+
OR lower(sc.commit_sha) LIKE ?
|
|
1024
|
+
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function zr(e){return f().prepare(`SELECT commit_sha, committed_at, subject, correlated_at
|
|
1025
|
+
FROM session_commits
|
|
1026
|
+
WHERE session_id = ?
|
|
1027
|
+
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var jh=3e4;function Ic(e){try{let s=f().prepare(`SELECT MAX(correlated_at) AS last_at
|
|
1028
|
+
FROM session_commits WHERE session_id = ?`).get(e),n=s?.last_at?Date.parse(s.last_at):0;if(n&&Date.now()-n<jh)return}catch{}Yr(e).catch(t=>{console.error(`[git-correlator] ${e.slice(0,8)} failed:`,t)})}H();ee();import{writeFileSync as Hh,mkdirSync as Bh,existsSync as Wh}from"node:fs";import{join as Bc}from"node:path";H();var vc=80;function jc(e){if(e.alias&&e.alias.trim())return e.alias.trim();if(e.auto_title&&e.auto_title.trim())return e.auto_title.trim();let t=(e.first_user_message??"").trim();if(!t)return e.id.slice(0,8);let s=t.split(`
|
|
1029
|
+
`)[0].trim();return s.length>vc?s.slice(0,vc)+"\u2026":s}function Mh(e){return f().prepare(`SELECT s.id AS id,
|
|
1030
|
+
sa.alias AS alias,
|
|
1031
|
+
s.auto_title AS auto_title,
|
|
1032
|
+
s.first_user_message AS first_user_message
|
|
1033
|
+
FROM sessions s
|
|
1034
|
+
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1035
|
+
WHERE s.id = ?`).get(e)??null}function Dh(e){let t=Mh(e);return t?jc(t):e.slice(0,8)}function Mc(e){if(!e)return null;let t=f(),s=t.prepare(`SELECT e.thread_id AS thread_id,
|
|
1036
|
+
t.name AS thread_name,
|
|
1037
|
+
e.parent_session_id AS parent_session_id,
|
|
1038
|
+
e.added_at AS added_at
|
|
1039
|
+
FROM thread_edges e
|
|
1040
|
+
JOIN threads t ON t.id = e.thread_id
|
|
1041
|
+
WHERE e.session_id = ?
|
|
1042
|
+
AND t.archived = 0
|
|
1043
|
+
ORDER BY e.added_at DESC
|
|
1044
|
+
LIMIT 1`).get(e);if(!s)return null;let n=s.parent_session_id?{id:s.parent_session_id,title:Dh(s.parent_session_id)}:null,r=s.parent_session_id?[e,s.parent_session_id]:[e],o=r.map(()=>"?").join(", "),c=t.prepare(`SELECT e.session_id AS session_id,
|
|
1045
|
+
s.id AS id,
|
|
1046
|
+
sa.alias AS alias,
|
|
1047
|
+
s.auto_title AS auto_title,
|
|
1048
|
+
s.first_user_message AS first_user_message
|
|
1049
|
+
FROM thread_edges e
|
|
1050
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
1051
|
+
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1052
|
+
WHERE e.thread_id = ?
|
|
1053
|
+
AND e.session_id NOT IN (${o})
|
|
1054
|
+
ORDER BY e.added_at ASC`).all(s.thread_id,...r).map(u=>({id:u.session_id,title:u.id?jc(u):u.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:c}}var Dc=[/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Base directory for this skill:/i,/^You are Claude Code in a fresh terminal/i,/^You are extracting a structured Output Index from a Claude/i];function Kr(e){return Dc.some(t=>t.test(e))}var Fh=[/^You are summarizing a Claude Code session/i,/^You are extracting a structured Output Index from a Claude/i,/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Thread context:\n- This session is part of thread/i];function Fc(e){return e?Fh.some(t=>t.test(e)):!1}var Ph=[/^Score this person'?s relevance/i,/^You are a [^\n.]{1,60}co-pilot/i,/^Return ONLY valid JSON/i,/^You are summarizing a Claude Code session/i],$h=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],Uh=20;function Qt(e){if(e.auto_title_source==="agent"&&e.auto_title)return"agent";if(e.has_alias)return"manual_alias";if(e.auto_title&&e.auto_title.includes(" \xB7 "))return"fixed_v0.16.1";if(!e.auto_title||e.auto_title.length<Uh)return"low_signal";for(let t of Dc)if(t.test(e.auto_title))return"recursive_meta";for(let t of Ph)if(t.test(e.auto_title))return"programmatic";for(let t of $h)if(t.test(e.auto_title))return"template_pending";return"clean"}function tn(e){if(!e)return"";let t=e.split("|")[0];return t=t.replace(/\s*\([^)]*\)\s*$/,""),t=t.replace(/\s+/g," ").trim(),t}function Pc(e){if(!e)return e;let t=e.lastIndexOf(" \xB7 ");if(t===-1)return e;let s=e.slice(0,t),n=e.slice(t+3),r=tn(n);return!r||r===n?e:`${s} \xB7 ${r}`}var Qr=Bc(W,"titles"),qh=80,Xh=60,Jh=100,Gh=50,es=5,sn=15,Yh=500;function Wc(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>!!s&&typeof s=="object"&&typeof s.title=="string"&&typeof s.replaced_at=="string")}catch{}return[]}function Tt(e){if(!e)return null;let t=e;if(t=t.replace(/```[\s\S]*?```/g," "),t=t.replace(/`[^`]+`/g," "),t=t.replace(/https?:\/\/\S+/g,"[url]"),t=t.replace(/\s+/g," ").trim(),!t)return null;let s=zh(t,e);if(s)return s;let n=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(n&&n.length<=qh?n:t.slice(0,Xh)).trim()||null}function zh(e,t){let s=e.match(/^\/([A-Za-z0-9][A-Za-z0-9_-]*)\s+([\s\S]*)$/);if(s){let n=`/${s[1]}`,r=t.replace(/^\s*\/[A-Za-z0-9][A-Za-z0-9_-]*\s+/,""),o=eo(r);return o?St(`${n} \xB7 ${o}`):n}for(let n of Kh){if(!e.match(n.match))continue;let o=n.prefix,a=n.extract?n.extract(e,t):eo(t);return a?n.completeFromExtract?St(a):St(`${o} \xB7 ${a}`):o}for(let n of Qh){if(!e.match(n.match))continue;let o=n.extract?n.extract(e,t):nn(t);return o?n.completeFromExtract?St(o):St(`${n.prefix} \xB7 ${o}`):n.prefix}return null}var Kh=[{match:/^Draft (?:a|an) brand brief\b/i,prefix:"Draft brand brief"},{match:/^Draft (?:a|an) sales brief\b/i,prefix:"Draft sales brief"},{match:/^Draft (?:a|an) leave-behind\b/i,prefix:"Draft leave-behind"},{match:/^Draft (?:a|an) audio identity brief\b/i,prefix:"Draft audio identity brief"},{match:/^Draft marketing ideas\b/i,prefix:"Draft marketing ideas"},{match:/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i,prefix:"Draft",extract:(e,t)=>{let n=e.match(/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i)?.[1]?.trim(),r=eo(t);return n&&r?`${n} \xB7 ${r}`:n||r}},{match:/^Read the file at /i,prefix:"Read",extract:e=>{let s=e.match(/^Read the file at\s+([^\s]+)/i)?.[1];return s?s.split("/").filter(Boolean).slice(-2).join("/")||s:null}},{match:/^(?:read|inspect) this and then begin\b/i,prefix:"Begin from preamble",completeFromExtract:!0,extract:(e,t)=>Zh(t)},{match:/^Base directory for this skill:/i,prefix:"[skill]",completeFromExtract:!0,extract:(e,t)=>{let s=t.match(/\.claude\/skills\/([^/\s]+)/);return s?.[1]?`[skill] ${s[1]}`:null}},{match:/^You are extracting a structured Output Index/i,prefix:"[output-index]",completeFromExtract:!0,extract:(e,t)=>Vh(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>$c(t,"[meta] auto-title (full-arc)")},{match:/^You will receive the first \d+ user messages from a Claude Code session/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>$c(t,"[meta] auto-title (initial)")},{match:/^You are implementing v\d+\.\d+/i,prefix:"Implementing",completeFromExtract:!0,extract:e=>{let t=e.match(/^You are implementing (v\d+\.\d+\w*)\s*(?:\(([^)]+)\))?/i);if(!t)return null;let s=t[1],n=t[2]?.trim();return n?`Implementing ${s} \xB7 ${n}`:`Implementing ${s}`}}];function Vh(e){if(!e)return"[output-index] extractor";let t=e.match(/^Session:\s*([0-9a-f]{8})\b/im),s=e.match(/^Opening prompt:\s*([^\n]{1,140})/im),n=t?.[1]?.trim(),r=s?.[1]?.trim().replace(/\s+/g," "),o=r&&r.length>70?r.slice(0,67)+"\u2026":r;return n&&o?`[output-index] \xB7 ${n} \xB7 ${o}`:n?`[output-index] \xB7 ${n}`:o?`[output-index] \xB7 ${o}`:"[output-index] extractor"}function $c(e,t){if(!e)return t;let s=e.indexOf("Messages:");if(s===-1)return t;let n=e.slice(s+9),r=/^\s*\d+\.\s*([^\n]+)/gm;for(let o of n.matchAll(r)){let a=o[1]?.trim()??"";if(!a)continue;let c=a.replace(/^<[^>]+>[\s\S]*?<\/[^>]+>\s*/g,"").replace(/^\[Pasted text[^\]]*\]\s*/i,"").trim();if(!c||/^<local-command-/.test(c))continue;let u=c.length>60?c.slice(0,57)+"\u2026":c;return`${t} \xB7 ${u}`}return t}function Zh(e){if(!e)return null;let t=e.match(/([\/\w.\-]+\.(?:md|markdown|txt|json|yaml|yml|toml|ts|tsx|js|jsx|py|sql))(?=[\s,;:)\]"'`]|$)/i);if(!t)return null;let s=t[1].split("/").filter(Boolean);return(s[s.length-1]??"").replace(/\.[^.]+$/,"")||null}var Qh=[{match:/^Score this person'?s relevance/i,prefix:"Score relevance",extract:(e,t)=>nn(t)},{match:/^You are a [^\n.]{1,60}co-pilot/i,prefix:"co-pilot",completeFromExtract:!0,extract:(e,t)=>{let n=e.match(/^You are a ([^\n.]{1,60}?)co-pilot/i)?.[1]?.trim(),r=n?`${n} co-pilot`:"co-pilot",o=nn(t);return o?`${r} \xB7 ${o}`:r}},{match:/^Return ONLY valid JSON/i,prefix:"JSON-only",extract:(e,t)=>nn(t)}];function nn(e){let t=/^\s*(Name|Company|Prospect|Author|Brand|Client)\s*:\s*([^\n]+)/gim,s;for(;(s=t.exec(e))!==null;){let n=s[2].trim();if(!n||/^(null|none|n\/a|—|-)$/i.test(n))continue;let r=n.replace(/\s+/g," ");return tn(r)||r}return null}function St(e){return e.slice(0,Jh).trim()}var eE=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],tE=[/^(?:brand|brand brief|brand summary)$/i,/^(?:known facts?|facts)$/i,/^(?:client|prospect|customer|account)$/i,/^(?:topic|subject|brief|task)$/i,/^(?:about|for|re)$/i];function Vr(e){let t=e.trim();return t.length<3?!0:eE.some(s=>s.test(t))}function sE(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return tE.some(s=>s.test(t))}function eo(e){let t=nE(e);return t===null?null:tn(t)||t}function nE(e){if(/^\s*Skill smoke test\b/i.test(e))return"smoke test";let t=/(^|\n)#{1,3}\s+([^\n]+?)\s+(?:—|–|-|:)\s+([^\n]{2,80})/g,s,n=[];for(;(s=t.exec(e))!==null;){let u=s[2].trim(),d=s[3].trim().replace(/\s+/g," ");if(!Vr(d)){if(sE(u))return d;n.push(d)}}if(n.length>0)return n[0];let r=e.match(/\b(?:Topic|Subject|Brand|About|Client|For|Re)\s*:\s*([^\n]{2,80})/i);if(r?.[1]&&!Vr(r[1]))return r[1].trim().replace(/\s+/g," ");let o=/===\s*([^=\n]{2,120}?)\s*===/g;for(;(s=o.exec(e))!==null;){let u=s[1].trim().replace(/\.(md|txt|json)$/i,""),d=u.replace(/\s*\([^)]*\)\s*$/,"").trim();if(d&&!Vr(d)&&!/product context|reference/i.test(u))return d}let a=e.replace(/^Context for this run[^:]*:\s*/i,"");if(a!==e){let u=a.split(`
|
|
1055
|
+
`).map(d=>d.trim()).find(d=>d.length>=4);if(u)return u.slice(0,60)}let c=e.split(`
|
|
1056
|
+
`).map(u=>u.trim()).find(u=>u.length>=4);return c?c.slice(0,60):null}function to(e){let t=f(),s=t.prepare(`SELECT rowid AS rid, content_text
|
|
1057
|
+
FROM messages
|
|
1058
|
+
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1059
|
+
AND content_text IS NOT NULL AND content_text != ''
|
|
1060
|
+
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1061
|
+
LIMIT ?`).all(e,es),n=t.prepare(`SELECT rowid AS rid, content_text
|
|
1062
|
+
FROM messages
|
|
1063
|
+
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1064
|
+
AND content_text IS NOT NULL AND content_text != ''
|
|
1065
|
+
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1066
|
+
LIMIT ?`).all(e,sn),r=new Map;for(let m of s)r.set(m.rid,m.content_text);for(let m of n)r.set(m.rid,m.content_text);if(r.size===0)throw new Error("no user messages available to summarise");let o=Array.from(r.entries()).sort((m,h)=>m[0]-h[0]).map(([,m])=>({content_text:m})),a=s.length===es&&n.length===sn&&r.size===es+sn,c=o.map((m,h)=>{let b=(m.content_text??"").slice(0,Yh);return a&&h===es?`--- (middle of session omitted) ---
|
|
1067
|
+
${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
|
|
1068
|
+
`),u=null;try{u=Mc(e)}catch(m){console.error("[autoTitle] thread context resolution failed:",m),u=null}let d=[];return u&&(d.push(rE(u)),d.push("")),d.push(`You will receive a sample of user messages from a Claude Code session: the first ${es}`,`messages (initial intent) and the last ${sn} messages (current direction).`,"Write a single descriptive title, max 50 characters, focused on what the user is","currently trying to accomplish. If initial intent and current direction differ, prefer","the current direction. Output ONLY the title, with no quotes and no trailing punctuation.","","Messages:",c),d.join(`
|
|
1069
|
+
`)}var Zr=5;function rE(e){let t=[];t.push("Thread context:"),t.push(`- This session is part of thread "${e.thread_name}".`),e.parent_session&&t.push(`- Parent session: "${e.parent_session.title}"`);let s=e.siblings.length;if(s>0){let r=e.siblings.slice(0,Zr).map(a=>`"${a.title}"`).join(", "),o=s>Zr?`, and ${s-Zr} more`:"";t.push(`- Sibling sessions (${s}): ${r}${o}`)}return t.push(""),t.push("Generate a title that reflects this session's role in the thread."),t.push('If siblings use a pattern like "Phase A / Phase B" or "Wave 1 of 4",'),t.push("follow the same pattern."),t.join(`
|
|
1070
|
+
`)}async function qc(e){let t=to(e),{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ye(),tt));if(!n())throw new Error("claude CLI not found on PATH");let r=await s(t,[],{});if(!r.success)throw new Error(`claude CLI exited ${r.exitCode}: ${r.stderr.slice(-500)}`);let o=oE(r.stdout);if(!o)throw new Error("claude CLI returned an empty title");return o.slice(0,Gh)}function oE(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return Uc(n)}}catch{}return Uc(t)}function Uc(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}function Ee(e,t,s){let n=t.trim();if(!n)return;let r=f(),o=r.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1071
|
+
FROM sessions WHERE id = ?`).get(e);if(!o||s==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===n&&o.auto_title_source===s)return;let a=Wc(o.auto_title_history),c=new Date().toISOString();o.auto_title&&o.auto_title_source&&a.push({title:o.auto_title,source:o.auto_title_source,replaced_at:c}),r.prepare(`UPDATE sessions
|
|
1072
|
+
SET auto_title = ?,
|
|
1073
|
+
auto_title_source = ?,
|
|
1074
|
+
auto_title_generated_at = ?,
|
|
1075
|
+
auto_title_history = ?
|
|
1076
|
+
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(a),e),lE(e,n,s,c)}function Le(e){let t=f().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1077
|
+
FROM sessions WHERE id = ?`).get(e);return t?{auto_title:t.auto_title,auto_title_source:t.auto_title_source??null,auto_title_generated_at:t.auto_title_generated_at,auto_title_history:Wc(t.auto_title_history)}:null}function Xc(){let t=f().prepare(`SELECT id, first_user_message
|
|
1078
|
+
FROM sessions
|
|
1079
|
+
WHERE auto_title IS NULL
|
|
1080
|
+
AND first_user_message IS NOT NULL`).all(),s=0;for(let n of t){let r=Tt(n.first_user_message);r&&(Ee(n.id,r,"heuristic"),s+=1)}return{updated:s}}function Jc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId,e.projectId]:[],r=t.prepare(`WITH dups AS (
|
|
1081
|
+
SELECT auto_title, project_id
|
|
1082
|
+
FROM sessions
|
|
1083
|
+
WHERE auto_title IS NOT NULL
|
|
1084
|
+
AND auto_title_source = 'heuristic'
|
|
1085
|
+
AND auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
|
|
1086
|
+
GROUP BY auto_title, project_id
|
|
1087
|
+
HAVING COUNT(*) > 1
|
|
1088
|
+
)
|
|
1089
|
+
SELECT s.id
|
|
1090
|
+
FROM sessions s
|
|
1091
|
+
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1092
|
+
AND s.auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
|
|
1093
|
+
AND (
|
|
1094
|
+
(s.auto_title LIKE '/%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1095
|
+
OR (s.auto_title LIKE 'Draft %' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1096
|
+
OR (s.auto_title LIKE 'Read the file at %')
|
|
1097
|
+
OR (s.auto_title LIKE 'read this and then begin%')
|
|
1098
|
+
OR (s.auto_title LIKE 'Begin from preamble%')
|
|
1099
|
+
OR (s.auto_title LIKE 'inspect this and then begin%')
|
|
1100
|
+
OR (s.auto_title LIKE '**Tool result**%')
|
|
1101
|
+
OR (s.auto_title LIKE 'Base directory for this skill%')
|
|
1102
|
+
OR (s.auto_title LIKE '[skill]%')
|
|
1103
|
+
OR (s.auto_title LIKE '[output-index]%')
|
|
1104
|
+
OR (s.auto_title LIKE '[meta]%')
|
|
1105
|
+
OR (s.auto_title LIKE 'You are extracting%')
|
|
1106
|
+
OR (s.auto_title LIKE 'You will receive%')
|
|
1107
|
+
OR (s.auto_title LIKE 'You are implementing%')
|
|
1108
|
+
OR (s.auto_title LIKE 'Implementing v%')
|
|
1109
|
+
OR s.auto_title = 'All right.'
|
|
1110
|
+
OR s.auto_title = 'Alright.'
|
|
1111
|
+
OR s.auto_title = 'inspect this.'
|
|
1112
|
+
OR s.auto_title = 'resolve this.'
|
|
1113
|
+
OR s.auto_title = 'do it.'
|
|
1114
|
+
OR s.auto_title = 'continue.'
|
|
1115
|
+
OR s.auto_title = '**Tool result**'
|
|
1116
|
+
OR (s.auto_title LIKE '[vacuous]%')
|
|
1117
|
+
OR (s.auto_title LIKE 'Score this person%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1118
|
+
OR (s.auto_title LIKE 'You are a%co-pilot%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1119
|
+
OR (s.auto_title LIKE 'Return ONLY valid JSON%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1120
|
+
OR EXISTS (
|
|
1121
|
+
SELECT 1 FROM dups d
|
|
1122
|
+
WHERE d.auto_title = s.auto_title
|
|
1123
|
+
AND d.project_id = s.project_id
|
|
1124
|
+
)
|
|
1125
|
+
)`).all(...n),o=t.prepare(`SELECT content_text
|
|
1126
|
+
FROM messages
|
|
1127
|
+
WHERE session_id = ?
|
|
1128
|
+
AND role = 'user'
|
|
1129
|
+
AND is_sidechain = 0
|
|
1130
|
+
AND content_text IS NOT NULL
|
|
1131
|
+
AND content_text != ''
|
|
1132
|
+
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1133
|
+
LIMIT 8`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),m=null;for(let h of d){let b=h.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();if(!b||/^<local-command-caveat>/.test(b))continue;let T=Tt(b);if(T&&!Hc(T)){m=T;break}}if(!m){let h=d.map(T=>T.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim()).find(T=>T.length>0&&!/^<local-command-caveat>/.test(T)),b=h?Tt(h):null;b&&Hc(b)&&(m=`[vacuous] ${b}`)}m&&(Ee(u.id,m,"heuristic"),c+=1)}return{scanned:a,updated:c}}function Hc(e){let t=e.trim();return!t||/^\*\*Tool result\*\*$/i.test(t)?!0:[/^all right\.?$/i,/^alright\.?$/i,/^inspect this\.?$/i,/^resolve this\.?$/i,/^do it\.?$/i,/^go\.?$/i,/^continue\.?$/i,/^proceed\.?$/i,/^keep going\.?$/i,/^ok\.?$/i,/^okay\.?$/i,/^yes\.?$/i,/^yep\.?$/i].some(n=>n.test(t))}function Gc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1134
|
+
FROM sessions s
|
|
1135
|
+
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1136
|
+
AND (
|
|
1137
|
+
s.auto_title LIKE 'You will receive %'
|
|
1138
|
+
OR s.auto_title LIKE 'Base directory for this skill:%'
|
|
1139
|
+
OR s.auto_title LIKE 'You are Claude Code in a fresh terminal%'
|
|
1140
|
+
)`).all(...n),o=t.prepare(`SELECT content_text
|
|
1141
|
+
FROM messages
|
|
1142
|
+
WHERE session_id = ?
|
|
1143
|
+
AND role = 'user'
|
|
1144
|
+
AND is_sidechain = 0
|
|
1145
|
+
AND content_text IS NOT NULL
|
|
1146
|
+
AND content_text != ''
|
|
1147
|
+
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1148
|
+
LIMIT 10`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),m=iE(d,u.auto_title);m&&(Ee(u.id,m,"heuristic"),c+=1)}return{scanned:a,updated:c}}function iE(e,t){for(let s of e){let n=aE(s.content_text);if(!n||Kr(n))continue;let r=Tt(n);if(r){if(r===t)return null;if(!Kr(r))return r}}return t.startsWith("[meta]")?null:St(`[meta] ${t}`)}function Yc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1149
|
+
FROM sessions s
|
|
1150
|
+
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1151
|
+
AND s.auto_title IS NOT NULL
|
|
1152
|
+
AND s.auto_title LIKE '% \xB7 %'
|
|
1153
|
+
AND (s.auto_title LIKE '%|%' OR s.auto_title LIKE '%(%')`).all(...n),o=0,a=0;for(let c of r){o+=1;let u=Pc(c.auto_title);u!==c.auto_title&&(Ee(c.id,u,"heuristic"),a+=1)}return{scanned:o,updated:a}}function aE(e){return e.replace(/<command-(?:name|message|args|stdout|stderr)>[\s\S]*?<\/command-(?:name|message|args|stdout|stderr)>/g,"").replace(/<local-command-(?:stdout|stderr|caveat)>[\s\S]*?<\/local-command-(?:stdout|stderr|caveat)>/g,"").trim()}function cE(){z(),Wh(Qr)||Bh(Qr,{recursive:!0})}function lE(e,t,s,n){try{cE();let r=Bc(Qr,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
|
|
1154
|
+
`;Hh(r,o+t+`
|
|
1155
|
+
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}import{existsSync as zc,mkdirSync as uE,readFileSync as dE,writeFileSync as pE}from"node:fs";import{homedir as mE}from"node:os";import{join as Kc}from"node:path";import{z as so}from"zod";function Vc(){return process.env.RECALL_HOME??Kc(mE(),".recall")}function gE(){let e=Vc();zc(e)||uE(e,{recursive:!0})}function Zc(){return Kc(Vc(),"config.json")}var on=so.object({heuristicEnabled:so.boolean().default(!0),agentEnabled:so.boolean().default(!1)}),rn={heuristicEnabled:!0,agentEnabled:!1};function Qc(){let e=Zc();if(!zc(e))return{};try{return JSON.parse(dE(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function rt(){let e=Qc().autoTitle;if(!e)return{...rn};let t=on.safeParse({...rn,...e});return t.success?t.data:{...rn}}function el(e){gE();let t=Qc(),s=on.parse({...rn,...t.autoTitle??{},...e}),n={...t,autoTitle:s};return pE(Zc(),JSON.stringify(n,null,2)),s}H();ee();import{randomUUID as io}from"node:crypto";import{existsSync as TE,mkdirSync as yE,writeFileSync as ll}from"node:fs";import{homedir as wE}from"node:os";import{basename as RE,join as ao}from"node:path";H();ee();import{randomUUID as _E}from"node:crypto";import{writeFileSync as fE,readFileSync as vA,existsSync as jA}from"node:fs";import{join as hE}from"node:path";var EE=hE(W,"collections.json"),an=8;function cn(e){return{...e}}function Re(e,t,s,n=null,r=new Date().toISOString()){f().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
|
|
1156
|
+
VALUES (?, ?, ?, ?, ?)`).run(e,n,t,s?JSON.stringify(s):null,r)}function ln(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function tl(e){if(!e)return 0;let t=0,s=e,n=new Set,r=f();for(;s;){if(n.has(s))throw new Error("collection cycle detected");n.add(s);let o=r.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!o)break;t+=1,s=o.parent_id}return t}function bE(e,t){let s=f(),n=e,r=new Set;for(;n;){if(r.has(n))return!1;if(r.add(n),n===t)return!0;let o=s.prepare("SELECT parent_id FROM collections WHERE id = ?").get(n);if(!o)return!1;n=o.parent_id}return!1}function sl(e=!1){return f().prepare(`SELECT c.*,
|
|
1157
|
+
(SELECT COUNT(*) FROM collection_sessions cs WHERE cs.collection_id = c.id) AS session_count
|
|
1158
|
+
FROM collections c
|
|
1159
|
+
${e?"":"WHERE c.archived_at IS NULL"}
|
|
1160
|
+
ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(n=>({...cn(n),session_count:n.session_count}))}function He(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?cn(t):null}function nl(e,t=!0){let s=f(),n=t?no(e):[e];if(n.length===0)return[];let r=n.map(()=>"?").join(",");return s.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1161
|
+
FROM collection_sessions
|
|
1162
|
+
WHERE collection_id IN (${r})
|
|
1163
|
+
ORDER BY added_at DESC`).all(...n)}function no(e){let t=f(),s=[e],n=[e],r=new Set([e]);for(;n.length>0;){let o=n.map(()=>"?").join(","),a=t.prepare(`SELECT id FROM collections WHERE parent_id IN (${o})`).all(...n),c=[];for(let u of a)r.has(u.id)||(r.add(u.id),s.push(u.id),c.push(u.id));n=c}return s}function rl(e){return f().prepare(`SELECT c.* FROM collections c
|
|
1164
|
+
JOIN collection_sessions cs ON cs.collection_id = c.id
|
|
1165
|
+
WHERE cs.session_id = ? AND c.archived_at IS NULL
|
|
1166
|
+
ORDER BY LOWER(c.name)`).all(e)}function ts(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");if(t.length>120)throw new Error("name too long (max 120 chars)");let s=f(),n=new Date().toISOString(),r=_E();if(e.parent_id){if(!He(e.parent_id))throw new Error("parent collection not found");if(tl(e.parent_id)>=an-1)throw new Error(`max collection depth is ${an}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
|
|
1167
|
+
(id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
|
|
1168
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),Re(r,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),ot(),He(r)}function ol(e,t){let s=f(),n=ln(e),r=new Date().toISOString(),o={name:t.name!==void 0?t.name.trim():n.name,description:t.description!==void 0?t.description:n.description,icon:t.icon!==void 0?t.icon:n.icon,color:t.color!==void 0?t.color:n.color,parent_id:t.parent_id!==void 0?t.parent_id:n.parent_id,sort_key:t.sort_key!==void 0?t.sort_key:n.sort_key};if(!o.name)throw new Error("name required");if(o.name.length>120)throw new Error("name too long (max 120 chars)");if(t.parent_id!==void 0&&t.parent_id!==n.parent_id&&t.parent_id){if(t.parent_id===e)throw new Error("cannot set parent to self");if(!He(t.parent_id))throw new Error("parent collection not found");if(bE(t.parent_id,e))throw new Error("cannot move collection into one of its descendants");if(tl(t.parent_id)>=an-1)throw new Error(`max collection depth is ${an}`)}return s.transaction(()=>{s.prepare(`UPDATE collections
|
|
1169
|
+
SET name = ?, description = ?, icon = ?, color = ?,
|
|
1170
|
+
parent_id = ?, sort_key = ?, updated_at = ?
|
|
1171
|
+
WHERE id = ?`).run(o.name,o.description,o.icon,o.color,o.parent_id,o.sort_key,r,e),t.name!==void 0&&t.name!==n.name&&Re(e,"rename",{from:n.name,to:o.name},null,r),t.description!==void 0&&t.description!==n.description&&Re(e,"describe",{description:o.description},null,r),(t.icon!==void 0&&t.icon!==n.icon||t.color!==void 0&&t.color!==n.color)&&Re(e,"recolor",{icon:o.icon,color:o.color},null,r),t.parent_id!==void 0&&t.parent_id!==n.parent_id&&Re(e,"move",{from:n.parent_id,to:o.parent_id},null,r),t.sort_key!==void 0&&t.sort_key!==n.sort_key&&Re(e,"reorder",{from:n.sort_key,to:o.sort_key},null,r)})(),ot(),He(e)}function il(e){let t=f(),s=ln(e);if(s.archived_at)return cn(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = ?, updated_at = ? WHERE id = ?").run(n,n,e),Re(e,"archive",{name:s.name},null,n)})(),ot(),He(e)}function al(e){let t=f(),s=ln(e);if(!s.archived_at)return cn(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = NULL, updated_at = ? WHERE id = ?").run(n,e),Re(e,"restore",{name:s.name},null,n)})(),ot(),He(e)}function ss(e,t,s=null,n={}){let r=f();if(ln(e),!r.prepare("SELECT 1 FROM sessions WHERE id = ?").get(t))throw new Error(`session not found: ${t}`);if(r.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{added:!1};let c=n.source??"manual",u=n.rule_id??null;if(c==="auto"&&!u)throw new Error("auto membership requires a rule_id");let d=new Date().toISOString();return r.transaction(()=>{r.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
|
|
1172
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,d,s,c,u),Re(e,"add",{note:s,source:c,rule_id:u},t,d)})(),ot(),{added:!0}}function cl(e,t){let s=f();if(!s.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let r=new Date().toISOString();return s.transaction(()=>{s.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),Re(e,"remove",null,t,r)})(),ot(),{removed:!0}}function un(e){let t=f(),s=t.prepare(`SELECT collection_id, session_id FROM collection_sessions
|
|
1173
|
+
WHERE rule_id = ?`).all(e);if(s.length===0)return{removed:0};let n=new Date().toISOString();return t.transaction(()=>{t.prepare("DELETE FROM collection_sessions WHERE rule_id = ?").run(e);for(let o of s)Re(o.collection_id,"remove",{rule_id:e},o.session_id,n)})(),ot(),{removed:s.length}}function SE(){return f().prepare(`SELECT id, collection_id, session_id, action, payload, at
|
|
1174
|
+
FROM collection_events
|
|
1175
|
+
ORDER BY at ASC, id ASC`).all()}function ot(){try{z();let e=f(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
|
|
1176
|
+
created_at, updated_at, archived_at
|
|
1177
|
+
FROM collections
|
|
1178
|
+
ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),s=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1179
|
+
FROM collection_sessions
|
|
1180
|
+
ORDER BY collection_id, added_at`).all(),n=SE(),r={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};fE(EE,JSON.stringify(r,null,2))}catch(e){console.error("[collections] backup failed:",e)}}var dn=ao(W,"auto-rules"),kE=ao(dn,"rules.json"),AE=ao(dn,"suggestions.json"),ro="Repositories",xE="Topics",ul=3;var NE=5,OE=2,LE=[/\bROADMAP\.md\b/g,/\bPROJECT\.md\b/g,/\bdocs\/[A-Za-z0-9._-]+\.md\b/g,/\.planning\/[A-Za-z0-9._\-/]+/g];function co(e){return{id:e.id,name:e.name,type:e.type,pattern:e.pattern,collection_id:e.collection_id,priority:e.priority,enabled:e.enabled!==0,created_at:e.created_at,created_by:e.created_by}}function CE(e){return{id:e.id,type:e.type,pattern:e.pattern,suggested_name:e.suggested_name,suggested_parent_collection_id:e.suggested_parent_collection_id,session_count:e.session_count,detected_at:e.detected_at,dismissed:e.dismissed!==0}}function IE(e){switch(e){case"cwd-prefix":case"project-id":case"git-branch-prefix":return ro;case"tag":return xE;case"plan-file":return null}}function vE(e){let s=f().prepare("SELECT id FROM collections WHERE name = ? AND parent_id IS NULL AND archived_at IS NULL").get(e);if(s)return s.id;let o=ts({name:e,icon:e===ro?"\u{1F4E6}":"\u{1F3F7}",sort_key:e===ro?"0000-repos":"0001-topics"});return f().prepare(`INSERT INTO auto_collection_rules
|
|
1181
|
+
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1182
|
+
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(io(),`seed:${e}`,o.id,new Date().toISOString()),o.id}function jE(e,t,s){let n;if(s!==void 0)n=s;else{let o=IE(t);n=o?vE(o):null}return ts({name:e,parent_id:n}).id}function pn(e){let t=f().prepare("SELECT * FROM auto_collection_rules WHERE id = ?").get(e);return t?co(t):null}function ME(e){let t=f();switch(e.type){case"cwd-prefix":return t.prepare("SELECT id FROM sessions WHERE cwd IS NOT NULL AND cwd LIKE ? ESCAPE '\\'").all(mn(e.pattern)+"%").map(n=>n.id);case"git-branch-prefix":return t.prepare("SELECT id FROM sessions WHERE git_branch IS NOT NULL AND git_branch LIKE ? ESCAPE '\\'").all(mn(e.pattern)+"%").map(n=>n.id);case"project-id":{let s=Number(e.pattern);return Number.isFinite(s)?t.prepare("SELECT id FROM sessions WHERE project_id = ?").all(s).map(r=>r.id):[]}case"tag":return t.prepare("SELECT session_id FROM session_tags WHERE tag = ?").all(e.pattern).map(n=>n.session_id);case"plan-file":return t.prepare(`SELECT id, first_user_message FROM sessions
|
|
1183
|
+
WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(n=>n.id)}}function dl(e,t,s=3){let n=f(),r=`s.id AS id, s.cwd AS cwd, s.started_at AS started_at,
|
|
1184
|
+
sa.alias AS alias, s.auto_title AS auto_title, s.first_user_message AS first_user_message`,o="LEFT JOIN session_aliases sa ON sa.session_id = s.id",a=[];switch(e){case"cwd-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1185
|
+
WHERE s.cwd IS NOT NULL AND s.cwd LIKE ? ESCAPE '\\'
|
|
1186
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(mn(t)+"%",s);break;case"git-branch-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1187
|
+
WHERE s.git_branch IS NOT NULL AND s.git_branch LIKE ? ESCAPE '\\'
|
|
1188
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(mn(t)+"%",s);break;case"project-id":{let c=Number(t);Number.isFinite(c)&&(a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1189
|
+
WHERE s.project_id = ?
|
|
1190
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(c,s));break}case"tag":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1191
|
+
JOIN session_tags st ON st.session_id = s.id
|
|
1192
|
+
WHERE st.tag = ?
|
|
1193
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(t,s);break;case"plan-file":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1194
|
+
WHERE s.first_user_message IS NOT NULL
|
|
1195
|
+
AND s.first_user_message LIKE ?
|
|
1196
|
+
ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",s);break}return a.map(c=>({id:c.id,title:DE(c),cwd:c.cwd,started_at:c.started_at}))}function DE(e){if(e.alias&&e.alias.trim())return e.alias.trim();if(e.auto_title&&e.auto_title.trim())return e.auto_title.trim();let t=(e.first_user_message??"").trim();if(!t)return`session ${e.id.slice(0,8)}`;let s=t.split(`
|
|
1197
|
+
`)[0].trim();return s.length>80?s.slice(0,80)+"\u2026":s}function FE(e,t,s=f()){switch(e.type){case"cwd-prefix":return!!t.cwd&&t.cwd.startsWith(e.pattern);case"git-branch-prefix":return!!t.git_branch&&t.git_branch.startsWith(e.pattern);case"project-id":{let n=Number(e.pattern);return Number.isFinite(n)&&t.project_id===n}case"tag":return!!s.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(t.id,e.pattern);case"plan-file":return!!t.first_user_message&&t.first_user_message.includes(e.pattern)}}function pl(e){let t=f(),s=t.prepare("SELECT id, project_id, cwd, git_branch, first_user_message FROM sessions WHERE id = ?").get(e);if(!s)return{added:0};let n=t.prepare(`SELECT * FROM auto_collection_rules
|
|
1198
|
+
WHERE enabled = 1 AND created_by != 'seed'
|
|
1199
|
+
ORDER BY priority, created_at`).all(),r=0;for(let o of n){let a=co(o);if(FE(a,s,t))try{ss(a.collection_id,e,null,{source:"auto",rule_id:a.id}).added&&(r+=1)}catch(c){console.error(`[auto-collections] failed to apply rule ${a.id} to session ${e}:`,c)}}return{added:r}}function oo(e){if(!e.enabled)return{added:0};let t=0;for(let s of ME(e))try{ss(e.collection_id,s,null,{source:"auto",rule_id:e.id}).added&&(t+=1)}catch(n){console.error(`[auto-collections] backfill failed for rule ${e.id} / session ${s}:`,n)}return{added:t}}function lo(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");let s=(e.pattern??"").trim();if(!s)throw new Error("pattern required");let n=f(),r=new Date().toISOString(),o=io(),a=e.collection_id;a||(a=jE(t,e.type,e.parent_collection_id)),n.prepare(`INSERT INTO auto_collection_rules
|
|
1200
|
+
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1201
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(o,t,e.type,s,a,e.priority??100,e.enabled===!1?0:1,r,e.created_by??"user"),n.prepare("DELETE FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").run(e.type,s);let c=pn(o);return oo(c),yt(),c}function ml(e={}){let t=e.includeSeed?"SELECT * FROM auto_collection_rules ORDER BY priority, created_at":"SELECT * FROM auto_collection_rules WHERE created_by != 'seed' ORDER BY priority, created_at";return f().prepare(t).all().map(co)}function gl(e,t){let s=f(),n=pn(e);if(!n)throw new Error(`rule not found: ${e}`);let r={name:t.name!==void 0?t.name.trim():n.name,pattern:t.pattern!==void 0?t.pattern.trim():n.pattern,enabled:t.enabled!==void 0?t.enabled:n.enabled,priority:t.priority!==void 0?t.priority:n.priority};if(!r.name)throw new Error("name required");if(!r.pattern)throw new Error("pattern required");s.prepare(`UPDATE auto_collection_rules
|
|
1202
|
+
SET name = ?, pattern = ?, enabled = ?, priority = ?
|
|
1203
|
+
WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o=pn(e);return t.pattern!==void 0&&t.pattern!==n.pattern?(un(e),o.enabled&&oo(o)):t.enabled!==void 0&&t.enabled!==n.enabled&&(o.enabled?oo(o):un(e)),yt(),o}function _l(e){let t=f();if(!pn(e))return{removed:0};let n=un(e);return t.prepare("DELETE FROM auto_collection_rules WHERE id = ?").run(e),yt(),n}function gn(e={}){let t=e.includeDismissed?"SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC":"SELECT * FROM auto_collection_suggestions WHERE dismissed = 0 ORDER BY detected_at DESC";return f().prepare(t).all().map(CE)}function fl(e){f().prepare("UPDATE auto_collection_suggestions SET dismissed = 1 WHERE id = ?").run(e),yt()}function hl(e){let t=f(),s=t.prepare("SELECT * FROM auto_collection_suggestions WHERE id = ?").get(e);if(!s)throw new Error(`suggestion not found: ${e}`);if(s.dismissed)throw new Error(`suggestion already dismissed: ${e}`);let n=lo({name:s.suggested_name,type:s.type,pattern:s.pattern,parent_collection_id:s.suggested_parent_collection_id===null?void 0:s.suggested_parent_collection_id,created_by:"suggestion-accepted"});return t.prepare("DELETE FROM auto_collection_suggestions WHERE id = ?").run(e),yt(),n}function _n(){let e=f(),t=new Date().toISOString(),s=wE(),n=new Set(e.prepare("SELECT decoded_path FROM projects").all().map(c=>c.decoded_path)),r=[...PE(s,t).filter(c=>!n.has(c.pattern)),...$E(t),...UE(t)];if(e.prepare("DELETE FROM auto_collection_suggestions WHERE type = 'project-id'").run(),n.size>0){let c=Array.from(n).map(()=>"?").join(",");e.prepare(`DELETE FROM auto_collection_suggestions WHERE type = 'cwd-prefix' AND pattern IN (${c})`).run(...Array.from(n))}let o=e.prepare("SELECT type, pattern FROM auto_collection_rules").all(),a=new Set(o.map(c=>`${c.type}:${c.pattern}`));for(let c of r){let u=`${c.type}:${c.pattern}`;if(a.has(u))continue;let d=e.prepare("SELECT id FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").get(c.type,c.pattern);d?e.prepare(`UPDATE auto_collection_suggestions
|
|
1204
|
+
SET session_count = ?, detected_at = ?, suggested_name = ?, suggested_parent_collection_id = ?
|
|
1205
|
+
WHERE id = ?`).run(c.session_count,t,c.suggested_name,c.suggested_parent_collection_id,d.id):e.prepare(`INSERT INTO auto_collection_suggestions
|
|
1206
|
+
(id, type, pattern, suggested_name, suggested_parent_collection_id, session_count, detected_at, dismissed)
|
|
1207
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(io(),c.type,c.pattern,c.suggested_name,c.suggested_parent_collection_id,c.session_count,t)}return yt(),gn()}function PE(e,t){let n=f().prepare("SELECT id, cwd FROM sessions WHERE cwd IS NOT NULL AND cwd != ''").all(),r=new Map;for(let a of n){let c=a.cwd.split("/").filter(Boolean),u="";for(let d of c){if(u=`${u}/${d}`,u===e||u==="/")continue;let m=r.get(u);m||(m=new Set,r.set(u,m)),m.add(a.id)}}let o=[];for(let[a,c]of r.entries()){if(c.size<ul)continue;let u=!1;for(let[d,m]of r.entries())if(d!==a&&d.startsWith(a+"/")&&m.size>=ul){u=!0;break}u||o.push({type:"cwd-prefix",pattern:a,suggested_name:RE(a)||a,suggested_parent_collection_id:null,session_count:c.size,detected_at:t,dismissed:!1})}return o}function $E(e){return f().prepare("SELECT tag, COUNT(*) AS n FROM session_tags GROUP BY tag HAVING n >= ?").all(NE).map(s=>({type:"tag",pattern:s.tag,suggested_name:s.tag,suggested_parent_collection_id:null,session_count:s.n,detected_at:e,dismissed:!1}))}function UE(e){let t=f().prepare(`SELECT id, first_user_message FROM sessions
|
|
1208
|
+
WHERE first_user_message IS NOT NULL AND first_user_message != ''`).all(),s=new Map;for(let r of t)for(let o of LE){o.lastIndex=0;let a=r.first_user_message.match(o);if(a)for(let c of a){let u=s.get(c);u||(u=new Set,s.set(c,u)),u.add(r.id)}}let n=[];for(let[r,o]of s.entries())o.size<OE||n.push({type:"plan-file",pattern:r,suggested_name:r,suggested_parent_collection_id:null,session_count:o.size,detected_at:e,dismissed:!1});return n}function mn(e){return e.replace(/[\\%_]/g,"\\$&")}function yt(){try{z(),TE(dn)||yE(dn,{recursive:!0});let e=f(),t=e.prepare("SELECT * FROM auto_collection_rules ORDER BY created_at, id").all(),s=e.prepare("SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC, id").all();ll(kE,JSON.stringify({schema:"claude-recall.auto-rules.v1",backed_up_at:new Date().toISOString(),rules:t},null,2)),ll(AE,JSON.stringify({schema:"claude-recall.auto-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:s},null,2))}catch(e){console.error("[auto-collections] backup failed:",e)}}function El(){let e=f().prepare("SELECT DISTINCT collection_id FROM auto_collection_rules").all();return new Set(e.map(t=>t.collection_id))}var HE=/^[ \t]*<!--\s*claude-recall-alias\s*:\s*([^\n]+?)\s*-->[ \t]*\n?/;function uo(e){if(e==null)return{alias:null,stripped:""};let t=e.match(HE);if(!t)return{alias:null,stripped:e};let s=t[1].trim();return s?{alias:s.length>200?s.slice(0,200):s,stripped:e.slice(t[0].length)}:{alias:null,stripped:e.slice(t[0].length)}}var XE=1500,po=new Map;function Tl(e){let t=e.split(/[/\\]/),s=t.findIndex(n=>n==="projects");return s===-1||s+1>=t.length?null:t[s+1]??null}function yl(e){return e.replace(/\\/g,"/").includes("/subagents/")}function JE(e){let n=f().prepare("SELECT COUNT(*) AS n FROM sessions WHERE file_path = ?").get(e).n;console.log(`[watcher] indexed ${Sl(e)} (${n} session${n===1?"":"s"})`)}async function wl(e){let t=0;try{t=mo(e).mtimeMs}catch{return}let s=Tl(e);if(!s)return;let n=f(),r=n.prepare("SELECT id, file_mtime FROM sessions WHERE file_path = ? LIMIT 1").get(e);if(r&&r.file_mtime>=t)return;let o=new Map,a=null;for await(let w of fc(e)){let R=o.get(w.sessionId);if(R||(R={sessionId:w.sessionId,entries:[],earliest:null,latest:null,firstUser:null,users:0,assistants:0,cwd:null,branch:null,version:null},o.set(w.sessionId,R)),R.entries.push(w),w.timestamp&&((!R.earliest||w.timestamp<R.earliest)&&(R.earliest=w.timestamp),(!R.latest||w.timestamp>R.latest)&&(R.latest=w.timestamp)),w.role==="user"&&!w.isSidechain){if(R.users+=1,!R.firstUser&&w.contentText){let D=w.contentText.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();D&&!/^<local-command-caveat>/.test(D)&&(R.firstUser=Se(D).redacted.slice(0,2e3))}}else w.role==="assistant"&&!w.isSidechain&&(R.assistants+=1);!R.cwd&&w.cwd&&(R.cwd=w.cwd),!R.branch&&w.gitBranch&&(R.branch=w.gitBranch),!R.version&&w.version&&(R.version=w.version),!a&&w.cwd&&(a=w.cwd)}let c=a?Sl(a)||a:s,u=a??s.replace(/^-/,"/").replace(/-/g,"/"),d=n.prepare(`INSERT INTO projects (encoded_path, decoded_path, name)
|
|
1209
|
+
VALUES (?, ?, ?)
|
|
1210
|
+
ON CONFLICT(encoded_path) DO UPDATE SET decoded_path = excluded.decoded_path, name = excluded.name
|
|
1211
|
+
RETURNING id`),m=n.prepare(`
|
|
1212
|
+
INSERT INTO sessions (
|
|
1213
|
+
id, project_id, file_path, file_mtime,
|
|
1214
|
+
started_at, ended_at, message_count,
|
|
1215
|
+
user_message_count, assistant_message_count,
|
|
1216
|
+
first_user_message, cwd, git_branch, version, indexed_at
|
|
1217
|
+
) VALUES (@id, @project_id, @file_path, @file_mtime, @started_at, @ended_at, @message_count,
|
|
1218
|
+
@user_message_count, @assistant_message_count, @first_user_message,
|
|
1219
|
+
@cwd, @git_branch, @version, @indexed_at)
|
|
1220
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1221
|
+
file_path = excluded.file_path,
|
|
1222
|
+
file_mtime = excluded.file_mtime,
|
|
1223
|
+
started_at = excluded.started_at,
|
|
1224
|
+
ended_at = excluded.ended_at,
|
|
1225
|
+
message_count = excluded.message_count,
|
|
1226
|
+
user_message_count = excluded.user_message_count,
|
|
1227
|
+
assistant_message_count = excluded.assistant_message_count,
|
|
1228
|
+
first_user_message = excluded.first_user_message,
|
|
1229
|
+
cwd = excluded.cwd,
|
|
1230
|
+
git_branch = excluded.git_branch,
|
|
1231
|
+
version = excluded.version,
|
|
1232
|
+
indexed_at = excluded.indexed_at
|
|
1233
|
+
`),h=n.prepare(`
|
|
1234
|
+
INSERT INTO messages (uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1235
|
+
is_sidechain, content_text, tool_names, raw_json)
|
|
1236
|
+
VALUES (@uuid, @session_id, @parent_uuid, @type, @role, @timestamp,
|
|
1237
|
+
@is_sidechain, @content_text, @tool_names, @raw_json)
|
|
1238
|
+
ON CONFLICT(uuid) DO NOTHING
|
|
1239
|
+
`),b=n.prepare("DELETE FROM messages WHERE session_id = ?"),T=new Date().toISOString();if(n.transaction(()=>{let{id:w}=d.get(s,u,c);for(let R of o.values()){if(Fc(R.firstUser)){console.log(`[watcher] skipping daemon-spawn phantom ${R.sessionId} (first message matches autonomous-spawn pattern)`);continue}let D=uo(R.firstUser),F=D.alias?D.stripped:R.firstUser;if(m.run({id:R.sessionId,project_id:w,file_path:e,file_mtime:t,started_at:R.earliest,ended_at:R.latest,message_count:R.entries.length,user_message_count:R.users,assistant_message_count:R.assistants,first_user_message:F,cwd:R.cwd,git_branch:R.branch,version:R.version,indexed_at:T}),D.alias&&!Te(R.sessionId))try{he(R.sessionId,D.alias)}catch(L){console.error(`[watcher] header-alias setAlias failed for ${R.sessionId}:`,L)}b.run(R.sessionId);for(let L of R.entries)h.run(GE(L));Oc(n,R.sessionId,R.entries),Qs(n,R.sessionId)}})(),rt().heuristicEnabled)for(let w of o.values()){let R=uo(w.firstUser).stripped,D=Tt(R);D&&Ee(w.sessionId,D,"heuristic")}}function GE(e){let t=Se(e.contentText).redacted,s=Se(e.raw).redacted;return{uuid:e.uuid,session_id:e.sessionId,parent_uuid:e.parentUuid,type:e.type,role:e.role,timestamp:e.timestamp,is_sidechain:e.isSidechain?1:0,content_text:t,tool_names:e.toolNames.join(","),raw_json:s}}function bl(e){let t=po.get(e);t?.timer&&clearTimeout(t.timer);let s={timer:null};s.timer=setTimeout(()=>{po.delete(e),wl(e).then(async()=>{JE(e),Vs(e);try{let r=f().prepare("SELECT id FROM sessions WHERE file_path = ?").all(e);for(let o of r){qa(o.id),Ic(o.id);try{pl(o.id)}catch(a){console.error("[watcher] auto-collections apply failed:",a)}}}catch(n){console.error("[watcher] semantic dispatch failed:",n)}}).catch(n=>{console.error(`[watcher] reindex failed for ${e}:`,n)})},XE),po.set(e,s)}function Rl(){let e=BE($t,{depth:4,ignoreInitial:!0,persistent:!0,awaitWriteFinish:{stabilityThreshold:500,pollInterval:200},ignored:t=>{if(t.endsWith(".jsonl"))return!1;try{if(mo(t).isDirectory())return!1}catch{}return!0}});return e.on("add",t=>t.endsWith(".jsonl")&&bl(t)),e.on("change",t=>t.endsWith(".jsonl")&&bl(t)),e.on("ready",()=>{console.log(`[watcher] ready, watching ${$t}`)}),e.on("error",t=>{console.error("[watcher] chokidar error:",t)}),process.env.RECALL_WATCHER_DEBUG==="1"&&(e.on("add",t=>console.log(`[watcher.debug] add: ${t}`)),e.on("change",t=>console.log(`[watcher.debug] change: ${t}`)),e.on("unlink",t=>console.log(`[watcher.debug] unlink: ${t}`))),e}var YE=4;function*go(e){let t;try{t=WE(e,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(let s of t){if(s.isSymbolicLink())continue;let n=qE(e,s.name);s.isDirectory()?yield*go(n):s.isFile()&&s.name.endsWith(".jsonl")&&(yield n)}}async function _o(){let e=Date.now(),t={scanned:0,reindexed:0,upToDate:0,skipped:0,errors:0,durationMs:0},s=f(),n=[],r=s.prepare("SELECT file_mtime FROM sessions WHERE file_path = ? LIMIT 1");for(let d of go($t)){if(t.scanned+=1,yl(d)){t.skipped+=1;continue}let m;try{m=mo(d).mtimeMs}catch{t.errors+=1;continue}let h=r.get(d);if(h&&h.file_mtime>=m){t.upToDate+=1;continue}n.push(d)}if(n.length===0)return t.durationMs=Date.now()-e,t;let o=n.slice(),a=async()=>{for(;o.length>0;){let d=o.shift();if(!d)break;try{await wl(d),t.reindexed+=1}catch(m){t.errors+=1;let h=m instanceof Error?m.message:String(m);console.error(`[ingestion-sweep] failed for ${d}: ${h}`)}}},c=Math.min(YE,n.length),u=[];for(let d=0;d<c;d+=1)u.push(a());return await Promise.all(u),t.durationMs=Date.now()-e,t}async function kl(){try{let e=await _o();e.reindexed>0&&console.log(`[safety-sweep] reindexed ${e.reindexed} file(s) the watcher missed (scanned=${e.scanned}, upToDate=${e.upToDate})`)}catch(e){console.error("[safety-sweep] failed:",e)}}var KE=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],Sx=new RegExp(`^(${KE.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);var Tx=5*6e4;function fo(){try{let e=zE(W);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}function Al(e){let{projects:t,sessions:s,messages:n,port:r,version:o}=e;return`<!DOCTYPE html>
|
|
949
1240
|
<html lang="en">
|
|
950
1241
|
<head>
|
|
951
1242
|
<meta charset="utf-8" />
|
|
@@ -1000,91 +1291,32 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1000
1291
|
</footer>
|
|
1001
1292
|
</main>
|
|
1002
1293
|
</body>
|
|
1003
|
-
</html>`}var
|
|
1294
|
+
</html>`}var VE=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,ZE=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,QE=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function eb(e){return e.replace(VE,"").trim()}function tb(e){let t=e.replace(ZE,"[tool call]");return t=t.replace(QE,"[tool result]"),t=t.replace(/_\(unknown block: thinking\)_/g,""),t=t.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),t=t.replace(/\n{3,}/g,`
|
|
1004
1295
|
|
|
1005
|
-
`),t.trim()}function
|
|
1006
|
-
`)}
|
|
1007
|
-
VALUES (?, ?, ?, ?)
|
|
1008
|
-
ON CONFLICT(session_id) DO UPDATE SET
|
|
1009
|
-
alias = excluded.alias,
|
|
1010
|
-
updated_at = excluded.updated_at,
|
|
1011
|
-
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(a)),dc(),{session_id:e,alias:s,updated_at:r,previous_aliases:a}}function Xs(e){let t=f(),s=new Date().toISOString(),n=t.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e);if(!n)return;let r=Hr(n.previous_aliases);r.push({alias:n.alias,replaced_at:s}),t.prepare(`UPDATE session_aliases SET alias = '', updated_at = ?, previous_aliases = ?
|
|
1012
|
-
WHERE session_id = ?`).run(s,JSON.stringify(r),e),dc()}function dc(){try{z();let e=vf(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};Lf(If,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}function Kt(e){if(!e.sessionStartedAt)return{allowed:!1,reason:"missing-session-started-at"};if(!e.terminalOpenedAt)return{allowed:!1,reason:"missing-terminal-opened-at"};let t=Date.parse(e.sessionStartedAt),s=Date.parse(e.terminalOpenedAt);return Number.isFinite(t)?Number.isFinite(s)?s-t>6e4?{allowed:!1,reason:"terminal-postdates-session"}:{allowed:!0}:{allowed:!1,reason:"missing-terminal-opened-at"}:{allowed:!1,reason:"missing-session-started-at"}}U();ee();import{writeFileSync as jf,mkdirSync as Mf,existsSync as Df}from"node:fs";import{join as mc}from"node:path";var Br=mc(B,"notes"),pc=200,Ff=12e3,Pf=800,Uf=8e3;function gc(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function _c(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t.filter(s=>!!s&&typeof s=="object"&&typeof s.synopsis=="string"&&typeof s.replaced_at=="string"):[]}catch{return[]}}function $f(){z(),Df(Br)||Mf(Br,{recursive:!0})}function Hf(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:gc(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:_c(e.auto_synopsis_history)}}var Bf="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function Js(e){let t=f().prepare(`SELECT ${Bf} FROM session_notes WHERE session_id = ?`).get(e);return t?Hf(t):null}function fc(e,t){let s=f(),n=new Date().toISOString(),r=s.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),o=[];return r&&(o=gc(r.previous_versions),r.content!==t&&r.content.length>0&&o.push({content:r.content,replaced_at:n})),s.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
|
|
1296
|
+
`),t.trim()}function sb(e){return e.role??e.type??"message"}function xl(e,t,s={}){let n=s.mode??"condensed",r=s.includeSidechain===!0,o=s.since?Date.parse(s.since):0,a=t.filter(m=>!(!r&&m.is_sidechain===1||o&&m.timestamp&&Date.parse(m.timestamp)<o)),c=[];s.prelude&&(c.push(s.prelude.trim()),c.push("")),c.push(`# Claude Recall, past session context (${n})`),c.push(""),c.push(`- **Project**: ${e.project_name}`),e.decoded_path&&c.push(`- **Path**: \`${e.decoded_path}\``),c.push(`- **Session ID**: \`${e.id}\``),e.started_at&&c.push(`- **Started**: ${e.started_at}`),e.ended_at&&c.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&c.push(`- **Branch**: \`${e.git_branch}\``),c.push(`- **Messages**: ${a.length}`),c.push(""),c.push("> This is a transcript of a previous Claude Code session, included for context. Refer back to it when the user asks about past decisions, code written, or problems debugged in this work."),c.push(""),c.push("---"),c.push("");let u=0,d=0;for(let m of a){let h=m.content_text??"",b=eb(h);n==="condensed"&&(b=tb(b));let T=b.length>0,S=!!m.tool_names&&m.tool_names.length>0;if(!T&&!S){d+=1;continue}let w=sb(m),R=m.timestamp?` \`${m.timestamp}\``:"";c.push(`## ${w}${R}`),c.push(""),S&&n==="condensed"&&(c.push(`_tools used: ${m.tool_names}_`),c.push("")),T&&(c.push(b),c.push("")),u+=1}return c.push("---"),c.push(""),c.push(`_${u} messages included_`+(d?`, ${d} empty skipped`:"")+(r?"":", subagent/sidechain hidden")+"."),c.join(`
|
|
1297
|
+
`)}function ns(e){if(!e.sessionStartedAt)return{allowed:!1,reason:"missing-session-started-at"};if(!e.terminalOpenedAt)return{allowed:!1,reason:"missing-terminal-opened-at"};let t=Date.parse(e.sessionStartedAt),s=Date.parse(e.terminalOpenedAt);return Number.isFinite(t)?Number.isFinite(s)?s-t>6e4?{allowed:!1,reason:"terminal-postdates-session"}:{allowed:!0}:{allowed:!1,reason:"missing-terminal-opened-at"}:{allowed:!1,reason:"missing-session-started-at"}}H();ee();import{writeFileSync as nb,mkdirSync as rb,existsSync as ob}from"node:fs";import{join as Ol}from"node:path";var ho=Ol(W,"notes"),Nl=200,ib=12e3,ab=800,cb=8e3;function Ll(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Cl(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t.filter(s=>!!s&&typeof s=="object"&&typeof s.synopsis=="string"&&typeof s.replaced_at=="string"):[]}catch{return[]}}function lb(){z(),ob(ho)||rb(ho,{recursive:!0})}function ub(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:Ll(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:Cl(e.auto_synopsis_history)}}var db="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function fn(e){let t=f().prepare(`SELECT ${db} FROM session_notes WHERE session_id = ?`).get(e);return t?ub(t):null}function Il(e,t){let s=f(),n=new Date().toISOString(),r=s.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),o=[];return r&&(o=Ll(r.previous_versions),r.content!==t&&r.content.length>0&&o.push({content:r.content,replaced_at:n})),s.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
|
|
1013
1298
|
VALUES (?, ?, ?, ?)
|
|
1014
1299
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
1015
1300
|
content = excluded.content,
|
|
1016
1301
|
updated_at = excluded.updated_at,
|
|
1017
|
-
previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(o)),
|
|
1302
|
+
previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(o)),mb(e,t,n),fn(e)??{session_id:e,content:t,updated_at:n,previous_versions:o,auto_synopsis:null,auto_synopsis_generated_at:null,auto_synopsis_history:[]}}async function vl(e){let s=f().prepare(`SELECT rowid AS rid, role, content_text
|
|
1018
1303
|
FROM messages
|
|
1019
1304
|
WHERE session_id = ? AND is_sidechain = 0
|
|
1020
1305
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1021
1306
|
AND role IN ('user', 'assistant')
|
|
1022
1307
|
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1023
|
-
LIMIT ?`).all(e,
|
|
1308
|
+
LIMIT ?`).all(e,Nl);if(s.length===0)throw new Error("no messages available to summarise");let n=ib,r=[];for(let b of s){if(n<=0)break;let T=(b.content_text??"").slice(0,ab);r.push({rid:b.rid,role:b.role,content:T}),n-=T.length}r.reverse();let o=s.length===Nl||n<=0,a=r.map(b=>`**${b.role}**: ${b.content}`).join(`
|
|
1024
1309
|
|
|
1025
1310
|
`),c=["You will receive a sampled chronological transcript from a Claude Code session.",o?"The sample is the most recent slice that fits in the context budget; older messages were dropped.":"The full session is included.","","Write a clean markdown synopsis of the session. Use these sections \u2014 omit any that genuinely have nothing to say:","","## Goal","One sentence \u2014 what the user was trying to accomplish.","","## What was done","Bullet list \u2014 concrete actions, code changes, decisions. Skip pleasantries.","","## Key decisions","Bullet list \u2014 non-obvious choices and the reason behind them.","","## Files touched","Bullet list \u2014 file paths mentioned in the conversation. Omit the section if none.","","## Open follow-ups","Bullet list \u2014 anything left undone or flagged for later. Omit the section if none.","","Output ONLY the markdown \u2014 no surrounding prose, no code fences around the whole thing, no closing summary.","","---","",a].join(`
|
|
1026
|
-
`),{spawnClaudePrompt:u,isClaudeCliAvailable:d}=await Promise.resolve().then(()=>(ye(),tt));if(!d())throw new Error("claude CLI not found on PATH");let m=await u(c,[],{});if(!m.success)throw new Error(`claude CLI exited ${m.exitCode}: ${m.stderr.slice(-500)}`);let h=
|
|
1311
|
+
`),{spawnClaudePrompt:u,isClaudeCliAvailable:d}=await Promise.resolve().then(()=>(ye(),tt));if(!d())throw new Error("claude CLI not found on PATH");let m=await u(c,[],{});if(!m.success)throw new Error(`claude CLI exited ${m.exitCode}: ${m.stderr.slice(-500)}`);let h=pb(m.stdout);if(!h)throw new Error("claude CLI returned an empty synopsis");return h.slice(0,cb)}function pb(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return n.trim()}}catch{}return t}function jl(e,t){let s=f(),n=new Date().toISOString(),r=Date.now(),o=s.prepare("SELECT auto_synopsis, auto_synopsis_history, content, updated_at FROM session_notes WHERE session_id = ?").get(e),a=Cl(o?.auto_synopsis_history??null);return o?.auto_synopsis&&o.auto_synopsis!==t&&o.auto_synopsis.length>0&&a.push({synopsis:o.auto_synopsis,replaced_at:n}),o?s.prepare(`UPDATE session_notes
|
|
1027
1312
|
SET auto_synopsis = ?,
|
|
1028
1313
|
auto_synopsis_generated_at = ?,
|
|
1029
1314
|
auto_synopsis_history = ?
|
|
1030
1315
|
WHERE session_id = ?`).run(t,r,JSON.stringify(a),e):s.prepare(`INSERT INTO session_notes
|
|
1031
1316
|
(session_id, content, updated_at, previous_versions, auto_synopsis,
|
|
1032
1317
|
auto_synopsis_generated_at, auto_synopsis_history)
|
|
1033
|
-
VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,n,t,r,JSON.stringify(a)),
|
|
1034
|
-
`;
|
|
1035
|
-
VALUES (?, ?, ?, ?, ?)`).run(e,n,t,s?JSON.stringify(s):null,r)}function zs(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function bc(e){if(!e)return 0;let t=0,s=e,n=new Set,r=f();for(;s;){if(n.has(s))throw new Error("collection cycle detected");n.add(s);let o=r.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!o)break;t+=1,s=o.parent_id}return t}function zf(e,t){let s=f(),n=e,r=new Set;for(;n;){if(r.has(n))return!1;if(r.add(n),n===t)return!0;let o=s.prepare("SELECT parent_id FROM collections WHERE id = ?").get(n);if(!o)return!1;n=o.parent_id}return!1}function Sc(e=!1){return f().prepare(`SELECT c.*,
|
|
1036
|
-
(SELECT COUNT(*) FROM collection_sessions cs WHERE cs.collection_id = c.id) AS session_count
|
|
1037
|
-
FROM collections c
|
|
1038
|
-
${e?"":"WHERE c.archived_at IS NULL"}
|
|
1039
|
-
ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(n=>({...Ys(n),session_count:n.session_count}))}function He(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?Ys(t):null}function Tc(e,t=!0){let s=f(),n=t?Wr(e):[e];if(n.length===0)return[];let r=n.map(()=>"?").join(",");return s.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1040
|
-
FROM collection_sessions
|
|
1041
|
-
WHERE collection_id IN (${r})
|
|
1042
|
-
ORDER BY added_at DESC`).all(...n)}function Wr(e){let t=f(),s=[e],n=[e],r=new Set([e]);for(;n.length>0;){let o=n.map(()=>"?").join(","),a=t.prepare(`SELECT id FROM collections WHERE parent_id IN (${o})`).all(...n),c=[];for(let u of a)r.has(u.id)||(r.add(u.id),s.push(u.id),c.push(u.id));n=c}return s}function yc(e){return f().prepare(`SELECT c.* FROM collections c
|
|
1043
|
-
JOIN collection_sessions cs ON cs.collection_id = c.id
|
|
1044
|
-
WHERE cs.session_id = ? AND c.archived_at IS NULL
|
|
1045
|
-
ORDER BY LOWER(c.name)`).all(e)}function Vt(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");if(t.length>120)throw new Error("name too long (max 120 chars)");let s=f(),n=new Date().toISOString(),r=Xf();if(e.parent_id){if(!He(e.parent_id))throw new Error("parent collection not found");if(bc(e.parent_id)>=Gs-1)throw new Error(`max collection depth is ${Gs}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
|
|
1046
|
-
(id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
|
|
1047
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),Re(r,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),rt(),He(r)}function wc(e,t){let s=f(),n=zs(e),r=new Date().toISOString(),o={name:t.name!==void 0?t.name.trim():n.name,description:t.description!==void 0?t.description:n.description,icon:t.icon!==void 0?t.icon:n.icon,color:t.color!==void 0?t.color:n.color,parent_id:t.parent_id!==void 0?t.parent_id:n.parent_id,sort_key:t.sort_key!==void 0?t.sort_key:n.sort_key};if(!o.name)throw new Error("name required");if(o.name.length>120)throw new Error("name too long (max 120 chars)");if(t.parent_id!==void 0&&t.parent_id!==n.parent_id&&t.parent_id){if(t.parent_id===e)throw new Error("cannot set parent to self");if(!He(t.parent_id))throw new Error("parent collection not found");if(zf(t.parent_id,e))throw new Error("cannot move collection into one of its descendants");if(bc(t.parent_id)>=Gs-1)throw new Error(`max collection depth is ${Gs}`)}return s.transaction(()=>{s.prepare(`UPDATE collections
|
|
1048
|
-
SET name = ?, description = ?, icon = ?, color = ?,
|
|
1049
|
-
parent_id = ?, sort_key = ?, updated_at = ?
|
|
1050
|
-
WHERE id = ?`).run(o.name,o.description,o.icon,o.color,o.parent_id,o.sort_key,r,e),t.name!==void 0&&t.name!==n.name&&Re(e,"rename",{from:n.name,to:o.name},null,r),t.description!==void 0&&t.description!==n.description&&Re(e,"describe",{description:o.description},null,r),(t.icon!==void 0&&t.icon!==n.icon||t.color!==void 0&&t.color!==n.color)&&Re(e,"recolor",{icon:o.icon,color:o.color},null,r),t.parent_id!==void 0&&t.parent_id!==n.parent_id&&Re(e,"move",{from:n.parent_id,to:o.parent_id},null,r),t.sort_key!==void 0&&t.sort_key!==n.sort_key&&Re(e,"reorder",{from:n.sort_key,to:o.sort_key},null,r)})(),rt(),He(e)}function Rc(e){let t=f(),s=zs(e);if(s.archived_at)return Ys(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = ?, updated_at = ? WHERE id = ?").run(n,n,e),Re(e,"archive",{name:s.name},null,n)})(),rt(),He(e)}function kc(e){let t=f(),s=zs(e);if(!s.archived_at)return Ys(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = NULL, updated_at = ? WHERE id = ?").run(n,e),Re(e,"restore",{name:s.name},null,n)})(),rt(),He(e)}function Zt(e,t,s=null,n={}){let r=f();if(zs(e),!r.prepare("SELECT 1 FROM sessions WHERE id = ?").get(t))throw new Error(`session not found: ${t}`);if(r.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{added:!1};let c=n.source??"manual",u=n.rule_id??null;if(c==="auto"&&!u)throw new Error("auto membership requires a rule_id");let d=new Date().toISOString();return r.transaction(()=>{r.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
|
|
1051
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,d,s,c,u),Re(e,"add",{note:s,source:c,rule_id:u},t,d)})(),rt(),{added:!0}}function Ac(e,t){let s=f();if(!s.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let r=new Date().toISOString();return s.transaction(()=>{s.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),Re(e,"remove",null,t,r)})(),rt(),{removed:!0}}function Ks(e){let t=f(),s=t.prepare(`SELECT collection_id, session_id FROM collection_sessions
|
|
1052
|
-
WHERE rule_id = ?`).all(e);if(s.length===0)return{removed:0};let n=new Date().toISOString();return t.transaction(()=>{t.prepare("DELETE FROM collection_sessions WHERE rule_id = ?").run(e);for(let o of s)Re(o.collection_id,"remove",{rule_id:e},o.session_id,n)})(),rt(),{removed:s.length}}function Kf(){return f().prepare(`SELECT id, collection_id, session_id, action, payload, at
|
|
1053
|
-
FROM collection_events
|
|
1054
|
-
ORDER BY at ASC, id ASC`).all()}function rt(){try{z();let e=f(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
|
|
1055
|
-
created_at, updated_at, archived_at
|
|
1056
|
-
FROM collections
|
|
1057
|
-
ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),s=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1058
|
-
FROM collection_sessions
|
|
1059
|
-
ORDER BY collection_id, added_at`).all(),n=Kf(),r={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};Jf(Yf,JSON.stringify(r,null,2))}catch(e){console.error("[collections] backup failed:",e)}}U();ee();import{randomUUID as Jr}from"node:crypto";import{existsSync as Vf,mkdirSync as Zf,writeFileSync as xc}from"node:fs";import{homedir as Qf}from"node:os";import{basename as eh,join as Gr}from"node:path";var Vs=Gr(B,"auto-rules"),th=Gr(Vs,"rules.json"),sh=Gr(Vs,"suggestions.json"),qr="Repositories",nh="Topics",Nc=3;var rh=5,oh=2,ih=[/\bROADMAP\.md\b/g,/\bPROJECT\.md\b/g,/\bdocs\/[A-Za-z0-9._-]+\.md\b/g,/\.planning\/[A-Za-z0-9._\-/]+/g];function Yr(e){return{id:e.id,name:e.name,type:e.type,pattern:e.pattern,collection_id:e.collection_id,priority:e.priority,enabled:e.enabled!==0,created_at:e.created_at,created_by:e.created_by}}function ah(e){return{id:e.id,type:e.type,pattern:e.pattern,suggested_name:e.suggested_name,suggested_parent_collection_id:e.suggested_parent_collection_id,session_count:e.session_count,detected_at:e.detected_at,dismissed:e.dismissed!==0}}function ch(e){switch(e){case"cwd-prefix":case"project-id":case"git-branch-prefix":return qr;case"tag":return nh;case"plan-file":return null}}function lh(e){let s=f().prepare("SELECT id FROM collections WHERE name = ? AND parent_id IS NULL AND archived_at IS NULL").get(e);if(s)return s.id;let o=Vt({name:e,icon:e===qr?"\u{1F4E6}":"\u{1F3F7}",sort_key:e===qr?"0000-repos":"0001-topics"});return f().prepare(`INSERT INTO auto_collection_rules
|
|
1060
|
-
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1061
|
-
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(Jr(),`seed:${e}`,o.id,new Date().toISOString()),o.id}function uh(e,t,s){let n;if(s!==void 0)n=s;else{let o=ch(t);n=o?lh(o):null}return Vt({name:e,parent_id:n}).id}function Zs(e){let t=f().prepare("SELECT * FROM auto_collection_rules WHERE id = ?").get(e);return t?Yr(t):null}function dh(e){let t=f();switch(e.type){case"cwd-prefix":return t.prepare("SELECT id FROM sessions WHERE cwd IS NOT NULL AND cwd LIKE ? ESCAPE '\\'").all(Qs(e.pattern)+"%").map(n=>n.id);case"git-branch-prefix":return t.prepare("SELECT id FROM sessions WHERE git_branch IS NOT NULL AND git_branch LIKE ? ESCAPE '\\'").all(Qs(e.pattern)+"%").map(n=>n.id);case"project-id":{let s=Number(e.pattern);return Number.isFinite(s)?t.prepare("SELECT id FROM sessions WHERE project_id = ?").all(s).map(r=>r.id):[]}case"tag":return t.prepare("SELECT session_id FROM session_tags WHERE tag = ?").all(e.pattern).map(n=>n.session_id);case"plan-file":return t.prepare(`SELECT id, first_user_message FROM sessions
|
|
1062
|
-
WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(n=>n.id)}}function Oc(e,t,s=3){let n=f(),r=`s.id AS id, s.cwd AS cwd, s.started_at AS started_at,
|
|
1063
|
-
sa.alias AS alias, s.auto_title AS auto_title, s.first_user_message AS first_user_message`,o="LEFT JOIN session_aliases sa ON sa.session_id = s.id",a=[];switch(e){case"cwd-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1064
|
-
WHERE s.cwd IS NOT NULL AND s.cwd LIKE ? ESCAPE '\\'
|
|
1065
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(Qs(t)+"%",s);break;case"git-branch-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1066
|
-
WHERE s.git_branch IS NOT NULL AND s.git_branch LIKE ? ESCAPE '\\'
|
|
1067
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(Qs(t)+"%",s);break;case"project-id":{let c=Number(t);Number.isFinite(c)&&(a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1068
|
-
WHERE s.project_id = ?
|
|
1069
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(c,s));break}case"tag":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1070
|
-
JOIN session_tags st ON st.session_id = s.id
|
|
1071
|
-
WHERE st.tag = ?
|
|
1072
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(t,s);break;case"plan-file":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1073
|
-
WHERE s.first_user_message IS NOT NULL
|
|
1074
|
-
AND s.first_user_message LIKE ?
|
|
1075
|
-
ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",s);break}return a.map(c=>({id:c.id,title:ph(c),cwd:c.cwd,started_at:c.started_at}))}function ph(e){if(e.alias&&e.alias.trim())return e.alias.trim();if(e.auto_title&&e.auto_title.trim())return e.auto_title.trim();let t=(e.first_user_message??"").trim();if(!t)return`session ${e.id.slice(0,8)}`;let s=t.split(`
|
|
1076
|
-
`)[0].trim();return s.length>80?s.slice(0,80)+"\u2026":s}function mh(e,t,s=f()){switch(e.type){case"cwd-prefix":return!!t.cwd&&t.cwd.startsWith(e.pattern);case"git-branch-prefix":return!!t.git_branch&&t.git_branch.startsWith(e.pattern);case"project-id":{let n=Number(e.pattern);return Number.isFinite(n)&&t.project_id===n}case"tag":return!!s.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(t.id,e.pattern);case"plan-file":return!!t.first_user_message&&t.first_user_message.includes(e.pattern)}}function Lc(e){let t=f(),s=t.prepare("SELECT id, project_id, cwd, git_branch, first_user_message FROM sessions WHERE id = ?").get(e);if(!s)return{added:0};let n=t.prepare(`SELECT * FROM auto_collection_rules
|
|
1077
|
-
WHERE enabled = 1 AND created_by != 'seed'
|
|
1078
|
-
ORDER BY priority, created_at`).all(),r=0;for(let o of n){let a=Yr(o);if(mh(a,s,t))try{Zt(a.collection_id,e,null,{source:"auto",rule_id:a.id}).added&&(r+=1)}catch(c){console.error(`[auto-collections] failed to apply rule ${a.id} to session ${e}:`,c)}}return{added:r}}function Xr(e){if(!e.enabled)return{added:0};let t=0;for(let s of dh(e))try{Zt(e.collection_id,s,null,{source:"auto",rule_id:e.id}).added&&(t+=1)}catch(n){console.error(`[auto-collections] backfill failed for rule ${e.id} / session ${s}:`,n)}return{added:t}}function zr(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");let s=(e.pattern??"").trim();if(!s)throw new Error("pattern required");let n=f(),r=new Date().toISOString(),o=Jr(),a=e.collection_id;a||(a=uh(t,e.type,e.parent_collection_id)),n.prepare(`INSERT INTO auto_collection_rules
|
|
1079
|
-
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1080
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(o,t,e.type,s,a,e.priority??100,e.enabled===!1?0:1,r,e.created_by??"user"),n.prepare("DELETE FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").run(e.type,s);let c=Zs(o);return Xr(c),ht(),c}function Cc(e={}){let t=e.includeSeed?"SELECT * FROM auto_collection_rules ORDER BY priority, created_at":"SELECT * FROM auto_collection_rules WHERE created_by != 'seed' ORDER BY priority, created_at";return f().prepare(t).all().map(Yr)}function Ic(e,t){let s=f(),n=Zs(e);if(!n)throw new Error(`rule not found: ${e}`);let r={name:t.name!==void 0?t.name.trim():n.name,pattern:t.pattern!==void 0?t.pattern.trim():n.pattern,enabled:t.enabled!==void 0?t.enabled:n.enabled,priority:t.priority!==void 0?t.priority:n.priority};if(!r.name)throw new Error("name required");if(!r.pattern)throw new Error("pattern required");s.prepare(`UPDATE auto_collection_rules
|
|
1081
|
-
SET name = ?, pattern = ?, enabled = ?, priority = ?
|
|
1082
|
-
WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o=Zs(e);return t.pattern!==void 0&&t.pattern!==n.pattern?(Ks(e),o.enabled&&Xr(o)):t.enabled!==void 0&&t.enabled!==n.enabled&&(o.enabled?Xr(o):Ks(e)),ht(),o}function vc(e){let t=f();if(!Zs(e))return{removed:0};let n=Ks(e);return t.prepare("DELETE FROM auto_collection_rules WHERE id = ?").run(e),ht(),n}function en(e={}){let t=e.includeDismissed?"SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC":"SELECT * FROM auto_collection_suggestions WHERE dismissed = 0 ORDER BY detected_at DESC";return f().prepare(t).all().map(ah)}function jc(e){f().prepare("UPDATE auto_collection_suggestions SET dismissed = 1 WHERE id = ?").run(e),ht()}function Mc(e){let t=f(),s=t.prepare("SELECT * FROM auto_collection_suggestions WHERE id = ?").get(e);if(!s)throw new Error(`suggestion not found: ${e}`);if(s.dismissed)throw new Error(`suggestion already dismissed: ${e}`);let n=zr({name:s.suggested_name,type:s.type,pattern:s.pattern,parent_collection_id:s.suggested_parent_collection_id===null?void 0:s.suggested_parent_collection_id,created_by:"suggestion-accepted"});return t.prepare("DELETE FROM auto_collection_suggestions WHERE id = ?").run(e),ht(),n}function tn(){let e=f(),t=new Date().toISOString(),s=Qf(),n=new Set(e.prepare("SELECT decoded_path FROM projects").all().map(c=>c.decoded_path)),r=[...gh(s,t).filter(c=>!n.has(c.pattern)),..._h(t),...fh(t)];if(e.prepare("DELETE FROM auto_collection_suggestions WHERE type = 'project-id'").run(),n.size>0){let c=Array.from(n).map(()=>"?").join(",");e.prepare(`DELETE FROM auto_collection_suggestions WHERE type = 'cwd-prefix' AND pattern IN (${c})`).run(...Array.from(n))}let o=e.prepare("SELECT type, pattern FROM auto_collection_rules").all(),a=new Set(o.map(c=>`${c.type}:${c.pattern}`));for(let c of r){let u=`${c.type}:${c.pattern}`;if(a.has(u))continue;let d=e.prepare("SELECT id FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").get(c.type,c.pattern);d?e.prepare(`UPDATE auto_collection_suggestions
|
|
1083
|
-
SET session_count = ?, detected_at = ?, suggested_name = ?, suggested_parent_collection_id = ?
|
|
1084
|
-
WHERE id = ?`).run(c.session_count,t,c.suggested_name,c.suggested_parent_collection_id,d.id):e.prepare(`INSERT INTO auto_collection_suggestions
|
|
1085
|
-
(id, type, pattern, suggested_name, suggested_parent_collection_id, session_count, detected_at, dismissed)
|
|
1086
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(Jr(),c.type,c.pattern,c.suggested_name,c.suggested_parent_collection_id,c.session_count,t)}return ht(),en()}function gh(e,t){let n=f().prepare("SELECT id, cwd FROM sessions WHERE cwd IS NOT NULL AND cwd != ''").all(),r=new Map;for(let a of n){let c=a.cwd.split("/").filter(Boolean),u="";for(let d of c){if(u=`${u}/${d}`,u===e||u==="/")continue;let m=r.get(u);m||(m=new Set,r.set(u,m)),m.add(a.id)}}let o=[];for(let[a,c]of r.entries()){if(c.size<Nc)continue;let u=!1;for(let[d,m]of r.entries())if(d!==a&&d.startsWith(a+"/")&&m.size>=Nc){u=!0;break}u||o.push({type:"cwd-prefix",pattern:a,suggested_name:eh(a)||a,suggested_parent_collection_id:null,session_count:c.size,detected_at:t,dismissed:!1})}return o}function _h(e){return f().prepare("SELECT tag, COUNT(*) AS n FROM session_tags GROUP BY tag HAVING n >= ?").all(rh).map(s=>({type:"tag",pattern:s.tag,suggested_name:s.tag,suggested_parent_collection_id:null,session_count:s.n,detected_at:e,dismissed:!1}))}function fh(e){let t=f().prepare(`SELECT id, first_user_message FROM sessions
|
|
1087
|
-
WHERE first_user_message IS NOT NULL AND first_user_message != ''`).all(),s=new Map;for(let r of t)for(let o of ih){o.lastIndex=0;let a=r.first_user_message.match(o);if(a)for(let c of a){let u=s.get(c);u||(u=new Set,s.set(c,u)),u.add(r.id)}}let n=[];for(let[r,o]of s.entries())o.size<oh||n.push({type:"plan-file",pattern:r,suggested_name:r,suggested_parent_collection_id:null,session_count:o.size,detected_at:e,dismissed:!1});return n}function Qs(e){return e.replace(/[\\%_]/g,"\\$&")}function ht(){try{z(),Vf(Vs)||Zf(Vs,{recursive:!0});let e=f(),t=e.prepare("SELECT * FROM auto_collection_rules ORDER BY created_at, id").all(),s=e.prepare("SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC, id").all();xc(th,JSON.stringify({schema:"claude-recall.auto-rules.v1",backed_up_at:new Date().toISOString(),rules:t},null,2)),xc(sh,JSON.stringify({schema:"claude-recall.auto-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:s},null,2))}catch(e){console.error("[auto-collections] backup failed:",e)}}function Dc(){let e=f().prepare("SELECT DISTINCT collection_id FROM auto_collection_rules").all();return new Set(e.map(t=>t.collection_id))}U();ee();import{randomUUID as Fc}from"node:crypto";import{writeFileSync as Pc,readFileSync as cA,existsSync as hh,mkdirSync as Eh}from"node:fs";import{join as Kr}from"node:path";var sn=Kr(B,"threads"),bh=Kr(sn,"index.json");function Uc(){z(),hh(sn)||Eh(sn,{recursive:!0})}function Vr(e,t,s){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:t.session_count,origin_count:t.origin_count,project:s?.project??null,project_count:s?.project_count??0,folder_id:e.folder_id??null}}function $c(e){let t=new Map;if(e.length===0)return t;let s=f(),n=e.map(()=>"?").join(","),r=s.prepare(`SELECT te.thread_id AS thread_id,
|
|
1318
|
+
VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,n,t,r,JSON.stringify(a)),fn(e)}function mb(e,t,s){try{lb();let n=Ol(ho,`${e}.md`),r=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${s} -->
|
|
1319
|
+
`;nb(n,r+t)}catch(n){console.error("[notes] mirror write failed:",n)}}pt();H();ee();import{randomUUID as Ml}from"node:crypto";import{writeFileSync as Dl,readFileSync as vx,existsSync as gb,mkdirSync as _b}from"node:fs";import{join as Eo}from"node:path";var hn=Eo(W,"threads"),fb=Eo(hn,"index.json");function Fl(){z(),gb(hn)||_b(hn,{recursive:!0})}function bo(e,t,s){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:t.session_count,origin_count:t.origin_count,project:s?.project??null,project_count:s?.project_count??0,folder_id:e.folder_id??null}}function Pl(e){let t=new Map;if(e.length===0)return t;let s=f(),n=e.map(()=>"?").join(","),r=s.prepare(`SELECT te.thread_id AS thread_id,
|
|
1088
1320
|
p.name AS project,
|
|
1089
1321
|
COUNT(*) AS n,
|
|
1090
1322
|
SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
|
|
@@ -1092,7 +1324,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1092
1324
|
LEFT JOIN sessions s ON s.id = te.session_id
|
|
1093
1325
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1094
1326
|
WHERE te.thread_id IN (${n})
|
|
1095
|
-
GROUP BY te.thread_id, p.name`).all(...e),o=new Map;for(let a of r){let c=o.get(a.thread_id);c||(c=[],o.set(a.thread_id,c)),c.push(a)}for(let[a,c]of o){let u=c.filter(h=>h.project!==null),d=u.length,m=null;u.length>0&&(m=[...u].sort((b,T)=>T.n-b.n||T.origin_n-b.origin_n||(b.project??"").localeCompare(T.project??""))[0].project),t.set(a,{project:m,project_count:d})}return t}function
|
|
1327
|
+
GROUP BY te.thread_id, p.name`).all(...e),o=new Map;for(let a of r){let c=o.get(a.thread_id);c||(c=[],o.set(a.thread_id,c)),c.push(a)}for(let[a,c]of o){let u=c.filter(h=>h.project!==null),d=u.length,m=null;u.length>0&&(m=[...u].sort((b,T)=>T.n-b.n||T.origin_n-b.origin_n||(b.project??"").localeCompare(T.project??""))[0].project),t.set(a,{project:m,project_count:d})}return t}function $l(e){let t=e.auto_title_source;return{thread_id:e.thread_id,session_id:e.session_id,parent_session_id:e.parent_session_id,role:e.role,confidence:e.confidence,source:e.source,added_at:e.added_at,alias:e.alias,auto_title:e.auto_title,auto_title_source:t==="agent"||t==="heuristic"?t:null,alias_source:e.alias?"manual":null,first_user_message:e.first_user_message,project:e.project}}function Ul(e){let s=f().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
|
|
1096
1328
|
s.auto_title AS auto_title,
|
|
1097
1329
|
s.auto_title_source AS auto_title_source,
|
|
1098
1330
|
s.first_user_message AS first_user_message,
|
|
@@ -1100,11 +1332,11 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1100
1332
|
FROM (SELECT ? AS sid) q
|
|
1101
1333
|
LEFT JOIN sessions s ON s.id = q.sid
|
|
1102
1334
|
LEFT JOIN session_aliases sa ON sa.session_id = q.sid
|
|
1103
|
-
LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function
|
|
1335
|
+
LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function So(e){let s=f().prepare(`SELECT
|
|
1104
1336
|
COUNT(*) AS session_count,
|
|
1105
1337
|
SUM(CASE WHEN role = 'origin' THEN 1 ELSE 0 END) AS origin_count
|
|
1106
|
-
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function ke(e){let t=oe(e);t&&(
|
|
1107
|
-
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),ke(n);let o=oe(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function
|
|
1338
|
+
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function ke(e){let t=oe(e);t&&(Fl(),Dl(Eo(hn,`${e}.json`),JSON.stringify(t,null,2)),Hl())}function Hl(){Fl();let e=To({includeArchived:!0});Dl(fb,JSON.stringify({threads:e},null,2))}function En(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=f(),n=Ml(),r=new Date().toISOString();s.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(n,t,e.summary?.trim()||null,r),e.originSessionId&&s.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1339
|
+
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),ke(n);let o=oe(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function To(e={}){let t=f(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),r=Pl(n.map(o=>o.id));return n.map(o=>bo(o,So(o.id),r.get(o.id)))}function oe(e){let t=f(),s=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT e.*,
|
|
1108
1340
|
NULLIF(sa.alias, '') AS alias,
|
|
1109
1341
|
s.auto_title AS auto_title,
|
|
1110
1342
|
s.auto_title_source AS auto_title_source,
|
|
@@ -1115,10 +1347,10 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1115
1347
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1116
1348
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1117
1349
|
WHERE e.thread_id = ?
|
|
1118
|
-
ORDER BY e.added_at ASC`).all(e).map(
|
|
1350
|
+
ORDER BY e.added_at ASC`).all(e).map($l),r=Pl([e]).get(e);return{...bo(s,So(s.id),r),edges:n}}function Bl(e){return f().prepare(`SELECT t.* FROM threads t
|
|
1119
1351
|
JOIN thread_edges e ON e.thread_id = t.id
|
|
1120
1352
|
WHERE e.session_id = ? AND t.archived = 0
|
|
1121
|
-
ORDER BY t.created_at DESC`).all(e).map(n=>
|
|
1353
|
+
ORDER BY t.created_at DESC`).all(e).map(n=>bo(n,So(n.id)))}function bn(e){let t=f();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let n=new Date().toISOString(),r=e.parentSessionId??null,o=e.role??(r?"child":"origin"),a=e.confidence??1,c=e.source??"manual";if(a<0||a>1)throw new Error("confidence must be 0..1");t.prepare(`INSERT INTO thread_edges
|
|
1122
1354
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1123
1355
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1124
1356
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
@@ -1126,22 +1358,22 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1126
1358
|
role = excluded.role,
|
|
1127
1359
|
confidence = excluded.confidence,
|
|
1128
1360
|
source = excluded.source,
|
|
1129
|
-
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,n),ke(e.threadId);let u=
|
|
1361
|
+
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,n),ke(e.threadId);let u=Ul(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:r,role:o,confidence:a,source:c,added_at:n,alias:u.alias,auto_title:u.auto_title,auto_title_source:u.auto_title_source,alias_source:u.alias?"manual":null,first_user_message:u.first_user_message,project:u.project}}function Wl(e,t){let n=f().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&ke(e),{removed:n.changes}}function rs(e,t,s){let n=f(),r=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e,t);if(!r)throw new Error("edge not found; add the session first");if(s!==null){if(s===t)throw new Error("cycle detected: session cannot be its own parent");let c=n.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),u=s,d=new Set;for(;u!==null;){if(u===t)throw new Error(`cycle detected: setting parent of ${t} to ${s} would create a loop`);if(d.has(u))break;d.add(u),u=c.get(e,u)?.parent_session_id??null}}let o=s?"child":"origin";n.prepare(`UPDATE thread_edges
|
|
1130
1362
|
SET parent_session_id = ?, role = ?, added_at = ?
|
|
1131
|
-
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),ke(e);let a=
|
|
1363
|
+
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),ke(e);let a=Ul(t);return $l({...r,parent_session_id:s,role:o,added_at:new Date().toISOString(),alias:a.alias,auto_title:a.auto_title,auto_title_source:a.auto_title_source,first_user_message:a.first_user_message,project:a.project})}function ql(e,t){let s=t.trim();if(!s)throw new Error("name cannot be empty");f().prepare("UPDATE threads SET name = ? WHERE id = ?").run(s,e),ke(e);let r=oe(e);if(!r)throw new Error(`thread ${e} not found`);return r}function Xl(e){f().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),ke(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Jl(e){f().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),ke(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Gl(e){f().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),ke(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Yl(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let s=f(),n=new Date().toISOString();s.transaction(()=>{let o=s.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let a of o)s.prepare(`INSERT INTO thread_edges
|
|
1132
1364
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1133
1365
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1134
1366
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
1135
1367
|
parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
|
|
1136
1368
|
role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
|
|
1137
1369
|
confidence = MAX(thread_edges.confidence, excluded.confidence),
|
|
1138
|
-
source = thread_edges.source`).run(t,a.session_id,a.parent_session_id,a.role,a.confidence,a.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),ke(t),
|
|
1370
|
+
source = thread_edges.source`).run(t,a.session_id,a.parent_session_id,a.role,a.confidence,a.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),ke(t),Hl();let r=oe(t);if(!r)throw new Error("merge destination disappeared");return r}function zl(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=f(),s=new Date().toISOString(),n=Ml();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(n,e.newThreadName.trim(),s);for(let o of e.sessionIds){let a=t.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e.threadId,o);a&&(t.prepare(`INSERT INTO thread_edges
|
|
1139
1371
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1140
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,o,a.parent_session_id,a.role,a.confidence,a.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),ke(e.threadId),ke(n);let r=oe(n);if(!r)throw new Error("split destination disappeared");return r}
|
|
1141
|
-
`).toLowerCase();for(let a of e.authored_paths){let c=a.toLowerCase();if(t.touched_files.has(a)||o.includes(c)){s+=.5,n=a;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=
|
|
1142
|
-
`,d),h=m===-1?u.length:m,b=u.slice(d,h);if(d=m===-1?u.length:m+1,!b.trim())continue;let T;try{T=JSON.parse(b)}catch{continue}let S=T;if(S.type==="user"&&S.message?.role==="user"&&typeof S.message.content=="string"&&o.length<s){let R=S.message.content.trim();R&&o.push(R.length>n?R.slice(0,n):R)}let w=S.message?.content;if(Array.isArray(w))for(let R of w){if(!R||typeof R!="object")continue;let
|
|
1143
|
-
`)){let o=r.trim().match(/^(\d+)\s+(.+)$/);if(!o)continue;let a=Number(o[1]),c=o[2].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&Number.isFinite(a)&&n.push(a)}return n}catch{continue}return[]}async function
|
|
1144
|
-
`))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function
|
|
1372
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,o,a.parent_session_id,a.role,a.confidence,a.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),ke(e.threadId),ke(n);let r=oe(n);if(!r)throw new Error("split destination disappeared");return r}H();import{execFile as Cb}from"node:child_process";import{promisify as Ib}from"node:util";import{readlink as vb,readFile as eu}from"node:fs/promises";import{platform as Rn}from"node:os";import{readFileSync as hb,statSync as Eb}from"node:fs";var bb=200*1024*1024,Tn=.7,yn=.5,Zl=yn,Sb=[{maxGapMs:3600*1e3,weight:.7,label:"<1h gap"},{maxGapMs:14400*1e3,weight:.4,label:"<4h gap"},{maxGapMs:1440*60*1e3,weight:.2,label:"<24h gap"}],Tb=["let's commit","lets commit","now commit","inspect the diff","inspect this diff","review the diff","review this diff","diff of all changes","diff of changes","based on what we just did","based on the things done","based on all the things","continue from","continuing from","pick up where","next step","now fix","now lets","now let's","from the previous session","from our last session","from the last session"];function Kl(e){if(!e)return null;if(e.startsWith("/")){let s=e.split(" \xB7 ");if(s.length>1)return s[1].trim().toLowerCase()}let t=e.split(" \xB7 ");return t.length>1?t[t.length-1].trim().toLowerCase():null}function yb(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of Sb)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function wb(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let t=[.4,.35,.3,.25,.2,.15,.1],s=Math.min(e.length,t.length);for(let n=0;n<s;n++){let r=e[n];if(!r)continue;let o=r.toLowerCase();for(let a of Tb)if(o.includes(a))return{weight:t[n],matched:a,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function yo(e,t){if(!e||!t||e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function Rb(e,t){if(e.length===0||t.length===0)return 0;let s=0;for(let n of e)for(let r of t){let o=yo(n,r);o>s&&(s=o)}return s}function kb(e,t){let s=yo(e.mean_embedding,t.mean_embedding),n=yo(e.tail_pool,t.head_pool),r=Rb(e.sample_chunks,t.sample_chunks),o=0,a=null;if(s>o&&(o=s,a="mean"),n>o&&(o=n,a="asymmetric"),r>o&&(o=r,a="max_pool"),o<.65)return{weight:0,cosine:o,mode:null};if(o>=.85)return{weight:.3,cosine:o,mode:a};let c=(o-.65)/.2*.3;return{weight:Math.round(c*100)/100,cosine:o,mode:a}}function Ab(e,t){return e.cluster_id===null||t.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==t.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function xb(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let s=0;for(let r of t)e.has(r)&&s++;return s===0?{weight:0,count:0}:{weight:Math.min(.4,s*.1),count:s}}function Nb(e,t){let s=Kl(e),n=Kl(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function Vl(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function Ob(e,t){let s=0,n=null,r=!1;if(e.authored_paths.size>0){let o=t.recent_user_messages.slice(0,3).join(`
|
|
1373
|
+
`).toLowerCase();for(let a of e.authored_paths){let c=a.toLowerCase();if(t.touched_files.has(a)||o.includes(c)){s+=.5,n=a;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=Vl(t.recent_user_messages[0]);if(o.length>=200)for(let a of e.authored_content){let c=Vl(a);if(c.length<200)continue;let u=Math.min(c.length,240),d=c.slice(0,u);if(o.includes(d)){s+=.4,r=!0;break}}}return{weight:Math.min(.6,s),pathMatch:n,contentMatch:r}}function Lb(e,t,s=Zl){if(t.started_at_ms<=e.started_at_ms)return null;let n=e.ended_at_ms??e.started_at_ms;if(t.started_at_ms<n)return null;let r=yb(n,t.started_at_ms),o=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],a=wb(o),c=xb(e.touched_files,t.touched_files),u=Nb(e.auto_title,t.auto_title),d=kb(e,t),m=Ab(e,t),h=Ob(e,t),b=r.weight+a.weight+c.weight+u.weight+d.weight+m.weight+h.weight;if(b<s)return null;let T=[];if(r.label&&T.push(`temporal ${r.label} (+${r.weight})`),a.matched){let S=a.matchedIndex===0?"opening message":`message #${a.matchedIndex+1}`;T.push(`continuation phrase "${a.matched}" in ${S} (+${a.weight})`)}if(c.count>0&&T.push(`${c.count} file${c.count===1?"":"s"} overlap (+${c.weight.toFixed(1)})`),u.brand&&T.push(`shared brand "${u.brand}" (+${u.weight})`),d.weight>0&&d.mode&&T.push(`semantic ${d.mode==="asymmetric"?"tail\u2192head":d.mode==="max_pool"?"best-chunk":"mean"} ${d.cosine.toFixed(2)} (+${d.weight.toFixed(2)})`),m.same&&T.push(`same cluster (+${m.weight})`),h.weight>0){let S=[];h.pathMatch&&S.push(`opened authored path "${h.pathMatch.split("/").pop()}"`),h.contentMatch&&S.push("verbatim-paste of authored content"),T.push(`doc-authorship: ${S.join(" + ")} (+${h.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:t.id,confidence:Math.min(1,b),signals:{temporal:r.weight,continuation:a.weight,file_overlap:c.weight,same_brand:u.weight,semantic:d.weight,cluster:m.weight,doc_authorship:h.weight},reasons:T}}function wt(e,t=Zl){let s=[];for(let n=0;n<e.length;n++){let r=e[n],o=null;for(let a=0;a<n;a++){let c=e[a],u=Lb(c,r,t);u&&(!o||u.confidence>o.confidence)&&(o=u)}o&&s.push(o)}return s}function Ql(e,t){let s=new Map,n=c=>{let u=c;for(;s.get(u)!==u;)u=s.get(u);let d=c;for(;s.get(d)!==u;){let m=s.get(d);s.set(d,u),d=m}return u},r=(c,u)=>{let d=n(c),m=n(u);d!==m&&s.set(d,m)};for(let c of e)s.has(c.parent_id)||s.set(c.parent_id,c.parent_id),s.has(c.child_id)||s.set(c.child_id,c.child_id),r(c.parent_id,c.child_id);let o=new Map;for(let c of s.keys()){let u=n(c),d=o.get(u);d||(d=[],o.set(u,d)),d.push(c)}let a=new Map;for(let c of t)a.set(c.id,c.started_at_ms);return Array.from(o.values()).map(c=>(c.sort((u,d)=>(a.get(u)??0)-(a.get(d)??0)),{rootId:c[0],sessionIds:c}))}function wn(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,r=new Set,o=[],a=new Set,c=[],u;try{if(Eb(e).size>bb)return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c};u=hb(e,"utf8")}catch{return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}let d=0;for(;d<u.length;){let m=u.indexOf(`
|
|
1374
|
+
`,d),h=m===-1?u.length:m,b=u.slice(d,h);if(d=m===-1?u.length:m+1,!b.trim())continue;let T;try{T=JSON.parse(b)}catch{continue}let S=T;if(S.type==="user"&&S.message?.role==="user"&&typeof S.message.content=="string"&&o.length<s){let R=S.message.content.trim();R&&o.push(R.length>n?R.slice(0,n):R)}let w=S.message?.content;if(Array.isArray(w))for(let R of w){if(!R||typeof R!="object")continue;let D=R;if(D.type!=="tool_use")continue;let F=D.input??{},L=typeof F.file_path=="string"?F.file_path:null;if(L){let j=Sn(L);j&&r.add(j)}if((D.name==="Write"||D.name==="Edit"||D.name==="MultiEdit")&&L){let j=Sn(L);j&&a.add(j);let U=typeof F.content=="string"?F.content:typeof F.new_string=="string"?F.new_string:null;U&&U.length>=200&&c.push(U.length>4096?U.slice(0,4096):U)}if(D.name==="Bash"&&typeof F.command=="string")for(let j of F.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let U=Sn(j[1]);U&&r.add(U)}if((D.name==="Glob"||D.name==="Grep")&&typeof F.pattern=="string"){let j=Sn(F.pattern);j&&!j.includes("*")&&r.add(j)}}}return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}function Sn(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var Ro=Ib(Cb),jb=6,tu="Active ",su=" sessions \u2014 ",Mb=6e4;async function Db(){if(Rn()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await Ro(t,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let r of s.split(`
|
|
1375
|
+
`)){let o=r.trim().match(/^(\d+)\s+(.+)$/);if(!o)continue;let a=Number(o[1]),c=o[2].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&Number.isFinite(a)&&n.push(a)}return n}catch{continue}return[]}async function Fb(e){let t=Rn();if(t==="linux")try{return(await vb(`/proc/${e}/cwd`)).replace(/\/+$/,"")}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let s of["/usr/sbin/lsof","/usr/bin/lsof"])try{let{stdout:n}=await Ro(s,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let r of n.split(`
|
|
1376
|
+
`))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function Pb(e){let t=Rn();if(t==="linux")try{let s=await eu(`/proc/${e}/stat`,"utf8"),n=s.lastIndexOf(")");if(n===-1)return null;let r=s.slice(n+1).trim().split(/\s+/),o=Number(r[19]);if(!Number.isFinite(o))return null;let a=await eu("/proc/uptime","utf8"),c=Number(a.split(/\s+/)[0]);return Number.isFinite(c)?Date.now()-c*1e3+o/100*1e3:null}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let s of["/bin/ps","/usr/bin/ps"])try{let{stdout:n}=await Ro(s,["-o","lstart=","-p",String(e)],{timeout:2e3}),r=Date.parse(n.trim());return Number.isFinite(r)?r:null}catch{continue}return null}async function $b(e,t){let s=await Db();if(s.length===0)return null;let n=e.replace(/\/+$/,""),r=[];for(let a of s){let c=await Fb(a);if(c&&(c===n||c.startsWith(n+"/"))){let u=await Pb(a);r.push({pid:a,startMs:u})}}if(r.length===0)return new Set;let o=new Set;for(let{startMs:a}of r){if(a==null)continue;let c=null,u=Mb;for(let d of t){if(o.has(d.session_id)||!d.started_at)continue;let m=Date.parse(d.started_at);if(!Number.isFinite(m))continue;let h=Math.abs(m-a);h<u&&(u=h,c=d)}c&&o.add(c.session_id)}return o}function Ub(e){let s=f().prepare("SELECT id, name, decoded_path FROM projects WHERE id = ? LIMIT 1").get(e);if(!s)throw new Error(`project ${e} not found`);return s}function Hb(e,t){let s=f(),n=`${tu}${t}${su}%`,r=s.prepare(`SELECT t.id
|
|
1145
1377
|
FROM threads t
|
|
1146
1378
|
WHERE t.archived = 0
|
|
1147
1379
|
AND t.name LIKE ?
|
|
@@ -1152,7 +1384,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1152
1384
|
WHERE s.project_id = ?
|
|
1153
1385
|
AND t.archived = 0
|
|
1154
1386
|
AND t.name LIKE ?
|
|
1155
|
-
LIMIT 1`).get(e,n);return o?oe(o.id):null}function
|
|
1387
|
+
LIMIT 1`).get(e,n);return o?oe(o.id):null}function nu(e){let t=e?new Date(e):new Date,s=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${s}-${n}-${r}`}function wo(e,t){let s=f(),n=t>0,r=n?Date.now()-t*60*60*1e3:0;return n?s.prepare(`SELECT s.id AS session_id,
|
|
1156
1388
|
sa.alias AS alias,
|
|
1157
1389
|
s.auto_title AS auto_title,
|
|
1158
1390
|
s.first_user_message AS first_user_message,
|
|
@@ -1172,19 +1404,19 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1172
1404
|
FROM sessions s
|
|
1173
1405
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1174
1406
|
WHERE s.project_id = ?
|
|
1175
|
-
ORDER BY s.started_at ASC`).all(e)}function
|
|
1407
|
+
ORDER BY s.started_at ASC`).all(e)}function Bb(e){let t=f(),s=[];for(let n of e){if(!n.started_at)continue;let r=Date.parse(n.started_at);if(!Number.isFinite(r))continue;let o=t.prepare("SELECT file_path, ended_at FROM sessions WHERE id = ?").get(n.session_id);if(!o?.file_path)continue;let a=o.ended_at?Date.parse(o.ended_at):null,c=wn(o.file_path,{maxUserMessages:7});s.push({id:n.session_id,started_at_ms:r,ended_at_ms:Number.isFinite(a)?a:null,first_user_message:n.first_user_message,recent_user_messages:c.recent_user_messages,auto_title:n.auto_title,touched_files:c.touched_files,mean_embedding:null,head_pool:null,tail_pool:null,sample_chunks:[],cluster_id:null,authored_paths:c.authored_paths,authored_content:c.authored_content})}return s}function Wb(e){let s=f().prepare(`SELECT session_id, parent_session_id, source
|
|
1176
1408
|
FROM thread_edges
|
|
1177
|
-
WHERE thread_id = ?`).all(e),n=new Map;for(let r of s)n.set(r.session_id,{parent_session_id:r.parent_session_id,source:r.source});return n}async function
|
|
1409
|
+
WHERE thread_id = ?`).all(e),n=new Map;for(let r of s)n.set(r.session_id,{parent_session_id:r.parent_session_id,source:r.source});return n}async function ru(e,t={}){let s=Ub(e),n=t.windowHours??jb,r=t.scoreThreshold??yn,o=t.useLivePids??!0,a=[],c=[];if(o&&s.decoded_path){let w=wo(e,0),R=await $b(s.decoded_path,w);if(R===null){let F=Rn()==="win32"?"Windows live-PID detection is not yet supported \u2014 falling back to the rolling mtime window.":"No live `claude` processes detected \u2014 falling back to the rolling mtime window. Output may include sessions that are no longer open.";a.push(F),c=wo(e,n)}else R.size===0?(a.push(`No active terminals open in ${s.name} (cwd=${s.decoded_path}). Open a Claude terminal in this repo and re-run.`),c=[]):c=w.filter(D=>R.has(D.session_id))}else c=wo(e,n);c.length===0&&!a.length&&a.push(`No active sessions in ${s.name} within the last ${n}h.`);let u=Hb(e,s.name),d=new Set(u?u.edges.map(w=>w.session_id):[]),m=c.filter(w=>!d.has(w.session_id)),h=Bb(c);h.sort((w,R)=>w.started_at_ms-R.started_at_ms);let b=wt(h,r),T=u?u.edges.filter(w=>w.source!=="auto-active"&&(w.parent_session_id||w.role==="origin")).map(w=>({session_id:w.session_id,parent_session_id:w.parent_session_id})):[],S=u?u.name:`${tu}${s.name}${su}${nu(t.todayIso)}`;return{project:s,thread:{id:u?.id??null,name:S,exists:!!u,existing_session_count:u?.edges.length??0},candidates:c,proposed_additions:m,proposed_edges:b,preserved_manual_edges:T,warnings:a}}function ou(e){let t={thread_id:"",added:0,edges_set:0,preserved_manual:e.preserved_manual_edges.length},s;e.thread.exists&&e.thread.id?s=e.thread.id:s=En({name:e.thread.name,summary:`Auto-captured by sync-active on ${nu()}. Members are sessions in ${e.project.name} that were active within the rolling window. Re-runnable: subsequent runs append new active sessions and never overwrite manual edges.`}).id,t.thread_id=s;let n=Wb(s);for(let a of e.candidates)n.has(a.session_id)||(bn({threadId:s,sessionId:a.session_id,source:"auto-active",confidence:.5}),t.added++,n.set(a.session_id,{parent_session_id:null,source:"auto-active"}));for(let a of e.proposed_edges){let c=n.get(a.child_id);if(c&&c.source==="auto-active"&&c.parent_session_id!==a.parent_id&&n.has(a.parent_id))try{rs(s,a.child_id,a.parent_id),t.edges_set++,n.set(a.child_id,{parent_session_id:a.parent_id,source:c.source})}catch{}}let o=f().prepare(`SELECT session_id FROM thread_edges
|
|
1178
1410
|
WHERE thread_id = ?
|
|
1179
1411
|
AND source = 'auto-active'
|
|
1180
1412
|
AND parent_session_id IS NULL
|
|
1181
|
-
AND role = 'child'`).all(s);for(let a of o)try{
|
|
1182
|
-
VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,s,n,o,a),
|
|
1413
|
+
AND role = 'child'`).all(s);for(let a of o)try{rs(s,a.session_id,null)}catch{}return t}H();ee();import{randomUUID as qb}from"node:crypto";import{writeFileSync as Xb}from"node:fs";import{join as Jb}from"node:path";var Gb=Jb(W,"thread-folders.json");function iu(e){return{id:e.id,name:e.name,parent_folder_id:e.parent_folder_id,project_scope:e.project_scope,created_at:e.created_at,archived:e.archived===1,sort_order:e.sort_order}}function os(){try{z();let e=ko({includeArchived:!0});Xb(Gb,JSON.stringify({folders:e},null,2))}catch{}}function ko(e={}){let t=e.includeArchived?"":"WHERE archived = 0";return f().prepare(`SELECT * FROM thread_folders ${t} ORDER BY sort_order, name`).all().map(iu)}function it(e){let t=f().prepare("SELECT * FROM thread_folders WHERE id = ?").get(e);return t?iu(t):null}function au(e){let t=e.name.trim();if(!t)throw new Error("folder name cannot be empty");if(t.length>200)throw new Error("folder name too long (200 char max)");let s=e.parentFolderId??null,n=e.projectScope??null;if(s){let c=it(s);if(!c)throw new Error(`parent folder ${s} not found`);n=c.project_scope}let r=qb(),o=new Date().toISOString(),a=cu(s,n);return f().prepare(`INSERT INTO thread_folders (id, name, parent_folder_id, project_scope, created_at, archived, sort_order)
|
|
1414
|
+
VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,s,n,o,a),os(),{id:r,name:t,parent_folder_id:s,project_scope:n,created_at:o,archived:!1,sort_order:a}}function cu(e,t){return f().prepare(`SELECT COALESCE(MAX(sort_order), -100) + 100 AS next
|
|
1183
1415
|
FROM thread_folders
|
|
1184
1416
|
WHERE parent_folder_id IS ?
|
|
1185
|
-
AND project_scope IS ?`).get(e,t)?.next??0}function
|
|
1417
|
+
AND project_scope IS ?`).get(e,t)?.next??0}function lu(e,t){let s=t.trim();if(!s)throw new Error("folder name cannot be empty");if(s.length>200)throw new Error("folder name too long (200 char max)");let n=it(e);if(!n)throw new Error(`folder ${e} not found`);return f().prepare("UPDATE thread_folders SET name = ? WHERE id = ?").run(s,e),os(),{...n,name:s}}function uu(e,t){let s=it(e);if(!s)throw new Error(`folder ${e} not found`);if(t===e)throw new Error("cannot move a folder under itself");let n=s.project_scope;if(t!==null){let o=it(t);if(!o)throw new Error(`parent folder ${t} not found`);let a=o.parent_folder_id,c=0;for(;a!==null&&c<1024;){if(a===e)throw new Error("cannot move a folder into one of its own descendants (cycle)");let u=it(a);if(!u)break;a=u.parent_folder_id,c++}n=o.project_scope}let r=cu(t,n);return f().prepare("UPDATE thread_folders SET parent_folder_id = ?, project_scope = ?, sort_order = ? WHERE id = ?").run(t,n,r,e),os(),{...s,parent_folder_id:t,project_scope:n,sort_order:r}}function du(e,t,s){if(!Array.isArray(s)||s.length===0)throw new Error("ordered_ids must be a non-empty array");let n=new Set;for(let d of s){if(typeof d!="string"||!d)throw new Error("ordered_ids must contain non-empty strings");if(n.has(d))throw new Error(`duplicate id in ordered_ids: ${d}`);n.add(d)}let r=f(),o=r.prepare(`SELECT id FROM thread_folders
|
|
1186
1418
|
WHERE parent_folder_id IS ?
|
|
1187
|
-
AND project_scope IS ?`).all(e,t),a=new Set(o.map(d=>d.id));if(a.size!==s.length)throw new Error(`ordered_ids length ${s.length} does not match sibling count ${a.size}`);for(let d of s)if(!a.has(d))throw new Error(`folder ${d} is not in the named sibling bucket`);let c=r.prepare("UPDATE thread_folders SET sort_order = ? WHERE id = ?");r.transaction(d=>{d.forEach((m,h)=>c.run(h*100,m))})(s),
|
|
1419
|
+
AND project_scope IS ?`).all(e,t),a=new Set(o.map(d=>d.id));if(a.size!==s.length)throw new Error(`ordered_ids length ${s.length} does not match sibling count ${a.size}`);for(let d of s)if(!a.has(d))throw new Error(`folder ${d} is not in the named sibling bucket`);let c=r.prepare("UPDATE thread_folders SET sort_order = ? WHERE id = ?");r.transaction(d=>{d.forEach((m,h)=>c.run(h*100,m))})(s),os()}function pu(e){if(!it(e))throw new Error(`folder ${e} not found`);f().prepare("DELETE FROM thread_folders WHERE id = ?").run(e),os()}function mu(e,t){if(t!==null&&!it(t))throw new Error(`folder ${t} not found`);if(f().prepare("UPDATE threads SET folder_id = ? WHERE id = ?").run(t,e).changes===0)throw new Error(`thread ${e} not found`)}function gu(e,t){let s=new Set,n=[];for(let r of t){if(s.has(r.id))continue;let o=null,a="";if(r.source_session_id===e)o="outbound",a=r.target_session_id;else if(r.target_session_id===e)o="inbound",a=r.source_session_id;else continue;s.add(r.id),n.push({linkId:r.id,otherSessionId:a,direction:o,updatedAt:r.updated_at,link:r})}return n.sort((r,o)=>r.updatedAt<o.updatedAt?1:r.updatedAt>o.updatedAt?-1:0),n}H();var Yb=4e3,zb=2,Kb=30,Vb=.2,Zb={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function kn(e){return e?Math.ceil(e.length/4):0}function _u(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Kb);return Math.max(Vb,t)}function fu(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function hu(e){return f().prepare(`SELECT s.id,
|
|
1188
1420
|
NULLIF(sa.alias, '') AS alias,
|
|
1189
1421
|
s.auto_title,
|
|
1190
1422
|
s.auto_title_source,
|
|
@@ -1195,206 +1427,72 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1195
1427
|
FROM sessions s
|
|
1196
1428
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1197
1429
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1198
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
1430
|
+
WHERE s.id = ?`).get(e)??null}function Eu(e){let s=f().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!s||!s.summary)return null;let n=s.summary.trim();return n.length>0?n:null}function bu(e){let t=e.alias?.trim(),s=e.auto_title?.trim(),n=e.first_user_message?.trim();return s&&e.auto_title_source==="agent"?s:t||s||(n?n.slice(0,80):e.id.slice(0,8))}function Qb(e){let s=f().prepare(`SELECT id, auto_title, started_at
|
|
1199
1431
|
FROM sessions
|
|
1200
1432
|
WHERE project_id = ?
|
|
1201
|
-
ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,r=new Set,o=[];for(let b of s){if(!b.auto_title||!b.auto_title.startsWith("/")){o.push({id:b.id,brand:null,skill:null});continue}let T=b.auto_title.split(" \xB7 "),S=T[0].trim(),w=T.length>1?T.slice(1).join(" \xB7 ").trim():null;o.push({id:b.id,brand:w||null,skill:S||null}),w&&n.add(w),S&&r.add(S)}let a=[...n].sort(),c=new Map;a.forEach((b,T)=>c.set(b,T));let u=[...r].sort(),d=new Map;u.forEach((b,T)=>d.set(b,T));let m=new Map,h=new Map;for(let b of o){if(!b.brand||!b.skill)continue;let T=c.get(b.brand),S=d.get(b.skill);if(T===void 0||S===void 0)continue;let w=`${T}.${S}`,R=(m.get(w)??0)+1;m.set(w,R),h.set(b.id,`${T}.${S}.${R}`)}return{byId:h}}function
|
|
1433
|
+
ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,r=new Set,o=[];for(let b of s){if(!b.auto_title||!b.auto_title.startsWith("/")){o.push({id:b.id,brand:null,skill:null});continue}let T=b.auto_title.split(" \xB7 "),S=T[0].trim(),w=T.length>1?T.slice(1).join(" \xB7 ").trim():null;o.push({id:b.id,brand:w||null,skill:S||null}),w&&n.add(w),S&&r.add(S)}let a=[...n].sort(),c=new Map;a.forEach((b,T)=>c.set(b,T));let u=[...r].sort(),d=new Map;u.forEach((b,T)=>d.set(b,T));let m=new Map,h=new Map;for(let b of o){if(!b.brand||!b.skill)continue;let T=c.get(b.brand),S=d.get(b.skill);if(T===void 0||S===void 0)continue;let w=`${T}.${S}`,R=(m.get(w)??0)+1;m.set(w,R),h.set(b.id,`${T}.${S}.${R}`)}return{byId:h}}function eS(e){return{table:e!==null?Qb(e):null,originProjectId:e,cache:new Map}}function An(e,t){let s=e.cache.get(t);if(s)return s;let n=hu(t);if(!n)return null;let r=e.table&&n.project_id===e.originProjectId?e.table.byId.get(t)??null:null,o={session_id:n.id,title:bu(n),decimal:r,summary:Eu(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,o),o}function tS(e,t){let n=f().prepare(`SELECT DISTINCT te.parent_session_id AS pid
|
|
1202
1434
|
FROM thread_edges te
|
|
1203
1435
|
WHERE te.session_id = ?
|
|
1204
|
-
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let a=
|
|
1436
|
+
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let a=An(e,o.pid);a&&r.push(a)}return r}function sS(e,t){let n=f().prepare(`SELECT DISTINCT te.session_id AS sid
|
|
1205
1437
|
FROM thread_edges te
|
|
1206
|
-
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let a=
|
|
1207
|
-
${o}`}return r}function
|
|
1438
|
+
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let a=An(e,o.sid);a&&r.push(a)}return r}function Su(e){let t=Zb[e.linkType]??.5,s=Rt(e.confidence),n=t*s,r=_u(e.daysApart),o=e.embeddingCosine??.5,a=Rt(e.pagerank);if(e.scoring==="pagerank")return Rt(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?Rt(n):Rt(o);let c=.35*n+.2*r+.2*o+.25*a;return Rt(c)}function Rt(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function nS(e,t,s,n,r){let o=new Map;function a(c,u){if(c===u)return;let d=o.get(c);d||(d=new Set,o.set(c,d)),d.add(u)}for(let c of t)a(c.source_session_id,c.target_session_id),a(c.target_session_id,c.source_session_id);for(let c of s)a(e,c.session_id);for(let c of s)a(c.session_id,e);for(let c of n)a(e,c.session_id);for(let c of n)a(c.session_id,e);if(r>1){let c=new Set([e]),u=new Set([e]);for(let d=1;d<r;d++){let m=new Set;for(let h of c){let b=o.get(h);if(b)for(let T of b){if(u.has(T))continue;let S=qt(T).filter(w=>w.approved);for(let w of S)a(w.source_session_id,w.target_session_id),a(w.target_session_id,w.source_session_id);u.add(T),m.add(T)}}if(m.size===0)break;for(let h of m)c.add(h)}}return{edges:o}}function rS(e,t={}){let s=t.iterations??12,n=t.damping??.85,r=Array.from(e.edges.keys());if(r.length===0)return new Map;let o=1/r.length,a=new Map(r.map(d=>[d,o]));for(let d=0;d<s;d++){let m=new Map(r.map(h=>[h,(1-n)/r.length]));for(let h of r){let b=e.edges.get(h);if(!b||b.size===0)continue;let T=(a.get(h)??0)/b.size;for(let S of b)m.set(S,(m.get(S)??0)+n*T)}a=m}let c=0;for(let d of a.values())d>c&&(c=d);if(c<=0)return a;let u=new Map;for(let[d,m]of a)u.set(d,m/c);return u}var Tu=240;function yu(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function oS(e){let t=e.decimal?`${e.decimal} `:"",s=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",r=`- ${t}${e.title} (${s})${n}`;if(e.summary){let o=yu(e.summary,Tu);return`${r}
|
|
1439
|
+
${o}`}return r}function iS(e,t,s){let n=[],r=[],o=0,a=e.decimal?`${e.decimal}: `:"",c=`# Neighborhood for ${e.session_id} (${a}${e.title})`;if(n.push(c),o+=kn(c),e.summary){let u=yu(e.summary,Tu*4);n.push(u),o+=kn(u)}n.push("");for(let u of t){if(u.refs.length===0)continue;let d=`## ${u.heading}`,m=kn(d),h=[],b=0;for(let T of u.refs){let S=oS(T),w=kn(S);if(o+m+b+w>s){r.push({session_id:T.session_id,title:T.title,decimal:T.decimal,summary:T.summary,project:T.project,started_at:T.started_at});continue}h.push(S),b+=w}if(h.length>0){n.push(d);for(let T of h)n.push(T);n.push(""),o+=m+b}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
|
|
1208
1440
|
`)+`
|
|
1209
|
-
`,budgetUsed:o,truncated:r}}function
|
|
1210
|
-
`)[0].trim();return s.length>kl?s.slice(0,kl)+"\u2026":s}function dE(e){return f().prepare(`SELECT s.id AS id,
|
|
1211
|
-
sa.alias AS alias,
|
|
1212
|
-
s.auto_title AS auto_title,
|
|
1213
|
-
s.first_user_message AS first_user_message
|
|
1214
|
-
FROM sessions s
|
|
1215
|
-
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1216
|
-
WHERE s.id = ?`).get(e)??null}function pE(e){let t=dE(e);return t?Al(t):e.slice(0,8)}function xl(e){if(!e)return null;let t=f(),s=t.prepare(`SELECT e.thread_id AS thread_id,
|
|
1217
|
-
t.name AS thread_name,
|
|
1218
|
-
e.parent_session_id AS parent_session_id,
|
|
1219
|
-
e.added_at AS added_at
|
|
1220
|
-
FROM thread_edges e
|
|
1221
|
-
JOIN threads t ON t.id = e.thread_id
|
|
1222
|
-
WHERE e.session_id = ?
|
|
1223
|
-
AND t.archived = 0
|
|
1224
|
-
ORDER BY e.added_at DESC
|
|
1225
|
-
LIMIT 1`).get(e);if(!s)return null;let n=s.parent_session_id?{id:s.parent_session_id,title:pE(s.parent_session_id)}:null,r=s.parent_session_id?[e,s.parent_session_id]:[e],o=r.map(()=>"?").join(", "),c=t.prepare(`SELECT e.session_id AS session_id,
|
|
1226
|
-
s.id AS id,
|
|
1227
|
-
sa.alias AS alias,
|
|
1228
|
-
s.auto_title AS auto_title,
|
|
1229
|
-
s.first_user_message AS first_user_message
|
|
1230
|
-
FROM thread_edges e
|
|
1231
|
-
LEFT JOIN sessions s ON s.id = e.session_id
|
|
1232
|
-
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1233
|
-
WHERE e.thread_id = ?
|
|
1234
|
-
AND e.session_id NOT IN (${o})
|
|
1235
|
-
ORDER BY e.added_at ASC`).all(s.thread_id,...r).map(u=>({id:u.session_id,title:u.id?Al(u):u.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:c}}var Nl=[/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Base directory for this skill:/i,/^You are Claude Code in a fresh terminal/i,/^You are extracting a structured Output Index from a Claude/i];function ro(e){return Nl.some(t=>t.test(e))}var mE=[/^You are summarizing a Claude Code session/i,/^You are extracting a structured Output Index from a Claude/i,/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Thread context:\n- This session is part of thread/i];function Ol(e){return e?mE.some(t=>t.test(e)):!1}var gE=[/^Score this person'?s relevance/i,/^You are a [^\n.]{1,60}co-pilot/i,/^Return ONLY valid JSON/i,/^You are summarizing a Claude Code session/i],_E=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],fE=20;function ts(e){if(e.auto_title_source==="agent"&&e.auto_title)return"agent";if(e.has_alias)return"manual_alias";if(e.auto_title&&e.auto_title.includes(" \xB7 "))return"fixed_v0.16.1";if(!e.auto_title||e.auto_title.length<fE)return"low_signal";for(let t of Nl)if(t.test(e.auto_title))return"recursive_meta";for(let t of gE)if(t.test(e.auto_title))return"programmatic";for(let t of _E)if(t.test(e.auto_title))return"template_pending";return"clean"}function gn(e){if(!e)return"";let t=e.split("|")[0];return t=t.replace(/\s*\([^)]*\)\s*$/,""),t=t.replace(/\s+/g," ").trim(),t}function Ll(e){if(!e)return e;let t=e.lastIndexOf(" \xB7 ");if(t===-1)return e;let s=e.slice(0,t),n=e.slice(t+3),r=gn(n);return!r||r===n?e:`${s} \xB7 ${r}`}var ao=jl(B,"titles"),SE=80,TE=60,yE=100,wE=50,ss=5,_n=15,RE=500;function Ml(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>!!s&&typeof s=="object"&&typeof s.title=="string"&&typeof s.replaced_at=="string")}catch{}return[]}function Tt(e){if(!e)return null;let t=e;if(t=t.replace(/```[\s\S]*?```/g," "),t=t.replace(/`[^`]+`/g," "),t=t.replace(/https?:\/\/\S+/g,"[url]"),t=t.replace(/\s+/g," ").trim(),!t)return null;let s=kE(t,e);if(s)return s;let n=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(n&&n.length<=SE?n:t.slice(0,TE)).trim()||null}function kE(e,t){let s=e.match(/^\/([A-Za-z0-9][A-Za-z0-9_-]*)\s+([\s\S]*)$/);if(s){let n=`/${s[1]}`,r=t.replace(/^\s*\/[A-Za-z0-9][A-Za-z0-9_-]*\s+/,""),o=co(r);return o?St(`${n} \xB7 ${o}`):n}for(let n of AE){if(!e.match(n.match))continue;let o=n.prefix,a=n.extract?n.extract(e,t):co(t);return a?n.completeFromExtract?St(a):St(`${o} \xB7 ${a}`):o}for(let n of OE){if(!e.match(n.match))continue;let o=n.extract?n.extract(e,t):fn(t);return o?n.completeFromExtract?St(o):St(`${n.prefix} \xB7 ${o}`):n.prefix}return null}var AE=[{match:/^Draft (?:a|an) brand brief\b/i,prefix:"Draft brand brief"},{match:/^Draft (?:a|an) sales brief\b/i,prefix:"Draft sales brief"},{match:/^Draft (?:a|an) leave-behind\b/i,prefix:"Draft leave-behind"},{match:/^Draft (?:a|an) audio identity brief\b/i,prefix:"Draft audio identity brief"},{match:/^Draft marketing ideas\b/i,prefix:"Draft marketing ideas"},{match:/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i,prefix:"Draft",extract:(e,t)=>{let n=e.match(/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i)?.[1]?.trim(),r=co(t);return n&&r?`${n} \xB7 ${r}`:n||r}},{match:/^Read the file at /i,prefix:"Read",extract:e=>{let s=e.match(/^Read the file at\s+([^\s]+)/i)?.[1];return s?s.split("/").filter(Boolean).slice(-2).join("/")||s:null}},{match:/^(?:read|inspect) this and then begin\b/i,prefix:"Begin from preamble",completeFromExtract:!0,extract:(e,t)=>NE(t)},{match:/^Base directory for this skill:/i,prefix:"[skill]",completeFromExtract:!0,extract:(e,t)=>{let s=t.match(/\.claude\/skills\/([^/\s]+)/);return s?.[1]?`[skill] ${s[1]}`:null}},{match:/^You are extracting a structured Output Index/i,prefix:"[output-index]",completeFromExtract:!0,extract:(e,t)=>xE(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Cl(t,"[meta] auto-title (full-arc)")},{match:/^You will receive the first \d+ user messages from a Claude Code session/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Cl(t,"[meta] auto-title (initial)")},{match:/^You are implementing v\d+\.\d+/i,prefix:"Implementing",completeFromExtract:!0,extract:e=>{let t=e.match(/^You are implementing (v\d+\.\d+\w*)\s*(?:\(([^)]+)\))?/i);if(!t)return null;let s=t[1],n=t[2]?.trim();return n?`Implementing ${s} \xB7 ${n}`:`Implementing ${s}`}}];function xE(e){if(!e)return"[output-index] extractor";let t=e.match(/^Session:\s*([0-9a-f]{8})\b/im),s=e.match(/^Opening prompt:\s*([^\n]{1,140})/im),n=t?.[1]?.trim(),r=s?.[1]?.trim().replace(/\s+/g," "),o=r&&r.length>70?r.slice(0,67)+"\u2026":r;return n&&o?`[output-index] \xB7 ${n} \xB7 ${o}`:n?`[output-index] \xB7 ${n}`:o?`[output-index] \xB7 ${o}`:"[output-index] extractor"}function Cl(e,t){if(!e)return t;let s=e.indexOf("Messages:");if(s===-1)return t;let n=e.slice(s+9),r=/^\s*\d+\.\s*([^\n]+)/gm;for(let o of n.matchAll(r)){let a=o[1]?.trim()??"";if(!a)continue;let c=a.replace(/^<[^>]+>[\s\S]*?<\/[^>]+>\s*/g,"").replace(/^\[Pasted text[^\]]*\]\s*/i,"").trim();if(!c||/^<local-command-/.test(c))continue;let u=c.length>60?c.slice(0,57)+"\u2026":c;return`${t} \xB7 ${u}`}return t}function NE(e){if(!e)return null;let t=e.match(/([\/\w.\-]+\.(?:md|markdown|txt|json|yaml|yml|toml|ts|tsx|js|jsx|py|sql))(?=[\s,;:)\]"'`]|$)/i);if(!t)return null;let s=t[1].split("/").filter(Boolean);return(s[s.length-1]??"").replace(/\.[^.]+$/,"")||null}var OE=[{match:/^Score this person'?s relevance/i,prefix:"Score relevance",extract:(e,t)=>fn(t)},{match:/^You are a [^\n.]{1,60}co-pilot/i,prefix:"co-pilot",completeFromExtract:!0,extract:(e,t)=>{let n=e.match(/^You are a ([^\n.]{1,60}?)co-pilot/i)?.[1]?.trim(),r=n?`${n} co-pilot`:"co-pilot",o=fn(t);return o?`${r} \xB7 ${o}`:r}},{match:/^Return ONLY valid JSON/i,prefix:"JSON-only",extract:(e,t)=>fn(t)}];function fn(e){let t=/^\s*(Name|Company|Prospect|Author|Brand|Client)\s*:\s*([^\n]+)/gim,s;for(;(s=t.exec(e))!==null;){let n=s[2].trim();if(!n||/^(null|none|n\/a|—|-)$/i.test(n))continue;let r=n.replace(/\s+/g," ");return gn(r)||r}return null}function St(e){return e.slice(0,yE).trim()}var LE=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],CE=[/^(?:brand|brand brief|brand summary)$/i,/^(?:known facts?|facts)$/i,/^(?:client|prospect|customer|account)$/i,/^(?:topic|subject|brief|task)$/i,/^(?:about|for|re)$/i];function oo(e){let t=e.trim();return t.length<3?!0:LE.some(s=>s.test(t))}function IE(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return CE.some(s=>s.test(t))}function co(e){let t=vE(e);return t===null?null:gn(t)||t}function vE(e){if(/^\s*Skill smoke test\b/i.test(e))return"smoke test";let t=/(^|\n)#{1,3}\s+([^\n]+?)\s+(?:—|–|-|:)\s+([^\n]{2,80})/g,s,n=[];for(;(s=t.exec(e))!==null;){let u=s[2].trim(),d=s[3].trim().replace(/\s+/g," ");if(!oo(d)){if(IE(u))return d;n.push(d)}}if(n.length>0)return n[0];let r=e.match(/\b(?:Topic|Subject|Brand|About|Client|For|Re)\s*:\s*([^\n]{2,80})/i);if(r?.[1]&&!oo(r[1]))return r[1].trim().replace(/\s+/g," ");let o=/===\s*([^=\n]{2,120}?)\s*===/g;for(;(s=o.exec(e))!==null;){let u=s[1].trim().replace(/\.(md|txt|json)$/i,""),d=u.replace(/\s*\([^)]*\)\s*$/,"").trim();if(d&&!oo(d)&&!/product context|reference/i.test(u))return d}let a=e.replace(/^Context for this run[^:]*:\s*/i,"");if(a!==e){let u=a.split(`
|
|
1236
|
-
`).map(d=>d.trim()).find(d=>d.length>=4);if(u)return u.slice(0,60)}let c=e.split(`
|
|
1237
|
-
`).map(u=>u.trim()).find(u=>u.length>=4);return c?c.slice(0,60):null}function lo(e){let t=f(),s=t.prepare(`SELECT rowid AS rid, content_text
|
|
1238
|
-
FROM messages
|
|
1239
|
-
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1240
|
-
AND content_text IS NOT NULL AND content_text != ''
|
|
1241
|
-
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1242
|
-
LIMIT ?`).all(e,ss),n=t.prepare(`SELECT rowid AS rid, content_text
|
|
1243
|
-
FROM messages
|
|
1244
|
-
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1245
|
-
AND content_text IS NOT NULL AND content_text != ''
|
|
1246
|
-
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1247
|
-
LIMIT ?`).all(e,_n),r=new Map;for(let m of s)r.set(m.rid,m.content_text);for(let m of n)r.set(m.rid,m.content_text);if(r.size===0)throw new Error("no user messages available to summarise");let o=Array.from(r.entries()).sort((m,h)=>m[0]-h[0]).map(([,m])=>({content_text:m})),a=s.length===ss&&n.length===_n&&r.size===ss+_n,c=o.map((m,h)=>{let b=(m.content_text??"").slice(0,RE);return a&&h===ss?`--- (middle of session omitted) ---
|
|
1248
|
-
${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
|
|
1249
|
-
`),u=null;try{u=xl(e)}catch(m){console.error("[autoTitle] thread context resolution failed:",m),u=null}let d=[];return u&&(d.push(jE(u)),d.push("")),d.push(`You will receive a sample of user messages from a Claude Code session: the first ${ss}`,`messages (initial intent) and the last ${_n} messages (current direction).`,"Write a single descriptive title, max 50 characters, focused on what the user is","currently trying to accomplish. If initial intent and current direction differ, prefer","the current direction. Output ONLY the title, with no quotes and no trailing punctuation.","","Messages:",c),d.join(`
|
|
1250
|
-
`)}var io=5;function jE(e){let t=[];t.push("Thread context:"),t.push(`- This session is part of thread "${e.thread_name}".`),e.parent_session&&t.push(`- Parent session: "${e.parent_session.title}"`);let s=e.siblings.length;if(s>0){let r=e.siblings.slice(0,io).map(a=>`"${a.title}"`).join(", "),o=s>io?`, and ${s-io} more`:"";t.push(`- Sibling sessions (${s}): ${r}${o}`)}return t.push(""),t.push("Generate a title that reflects this session's role in the thread."),t.push('If siblings use a pattern like "Phase A / Phase B" or "Wave 1 of 4",'),t.push("follow the same pattern."),t.join(`
|
|
1251
|
-
`)}async function Dl(e){let t=lo(e),{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ye(),tt));if(!n())throw new Error("claude CLI not found on PATH");let r=await s(t,[],{});if(!r.success)throw new Error(`claude CLI exited ${r.exitCode}: ${r.stderr.slice(-500)}`);let o=ME(r.stdout);if(!o)throw new Error("claude CLI returned an empty title");return o.slice(0,wE)}function ME(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return Il(n)}}catch{}return Il(t)}function Il(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}function Ee(e,t,s){let n=t.trim();if(!n)return;let r=f(),o=r.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1252
|
-
FROM sessions WHERE id = ?`).get(e);if(!o||s==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===n&&o.auto_title_source===s)return;let a=Ml(o.auto_title_history),c=new Date().toISOString();o.auto_title&&o.auto_title_source&&a.push({title:o.auto_title,source:o.auto_title_source,replaced_at:c}),r.prepare(`UPDATE sessions
|
|
1253
|
-
SET auto_title = ?,
|
|
1254
|
-
auto_title_source = ?,
|
|
1255
|
-
auto_title_generated_at = ?,
|
|
1256
|
-
auto_title_history = ?
|
|
1257
|
-
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(a),e),UE(e,n,s,c)}function Le(e){let t=f().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1258
|
-
FROM sessions WHERE id = ?`).get(e);return t?{auto_title:t.auto_title,auto_title_source:t.auto_title_source??null,auto_title_generated_at:t.auto_title_generated_at,auto_title_history:Ml(t.auto_title_history)}:null}function Fl(){let t=f().prepare(`SELECT id, first_user_message
|
|
1259
|
-
FROM sessions
|
|
1260
|
-
WHERE auto_title IS NULL
|
|
1261
|
-
AND first_user_message IS NOT NULL`).all(),s=0;for(let n of t){let r=Tt(n.first_user_message);r&&(Ee(n.id,r,"heuristic"),s+=1)}return{updated:s}}function Pl(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId,e.projectId]:[],r=t.prepare(`WITH dups AS (
|
|
1262
|
-
SELECT auto_title, project_id
|
|
1263
|
-
FROM sessions
|
|
1264
|
-
WHERE auto_title IS NOT NULL
|
|
1265
|
-
AND auto_title_source = 'heuristic'
|
|
1266
|
-
AND auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
|
|
1267
|
-
GROUP BY auto_title, project_id
|
|
1268
|
-
HAVING COUNT(*) > 1
|
|
1269
|
-
)
|
|
1270
|
-
SELECT s.id
|
|
1271
|
-
FROM sessions s
|
|
1272
|
-
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1273
|
-
AND s.auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
|
|
1274
|
-
AND (
|
|
1275
|
-
(s.auto_title LIKE '/%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1276
|
-
OR (s.auto_title LIKE 'Draft %' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1277
|
-
OR (s.auto_title LIKE 'Read the file at %')
|
|
1278
|
-
OR (s.auto_title LIKE 'read this and then begin%')
|
|
1279
|
-
OR (s.auto_title LIKE 'Begin from preamble%')
|
|
1280
|
-
OR (s.auto_title LIKE 'inspect this and then begin%')
|
|
1281
|
-
OR (s.auto_title LIKE '**Tool result**%')
|
|
1282
|
-
OR (s.auto_title LIKE 'Base directory for this skill%')
|
|
1283
|
-
OR (s.auto_title LIKE '[skill]%')
|
|
1284
|
-
OR (s.auto_title LIKE '[output-index]%')
|
|
1285
|
-
OR (s.auto_title LIKE '[meta]%')
|
|
1286
|
-
OR (s.auto_title LIKE 'You are extracting%')
|
|
1287
|
-
OR (s.auto_title LIKE 'You will receive%')
|
|
1288
|
-
OR (s.auto_title LIKE 'You are implementing%')
|
|
1289
|
-
OR (s.auto_title LIKE 'Implementing v%')
|
|
1290
|
-
OR s.auto_title = 'All right.'
|
|
1291
|
-
OR s.auto_title = 'Alright.'
|
|
1292
|
-
OR s.auto_title = 'inspect this.'
|
|
1293
|
-
OR s.auto_title = 'resolve this.'
|
|
1294
|
-
OR s.auto_title = 'do it.'
|
|
1295
|
-
OR s.auto_title = 'continue.'
|
|
1296
|
-
OR s.auto_title = '**Tool result**'
|
|
1297
|
-
OR (s.auto_title LIKE '[vacuous]%')
|
|
1298
|
-
OR (s.auto_title LIKE 'Score this person%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1299
|
-
OR (s.auto_title LIKE 'You are a%co-pilot%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1300
|
-
OR (s.auto_title LIKE 'Return ONLY valid JSON%' AND s.auto_title NOT LIKE '% \xB7 %')
|
|
1301
|
-
OR EXISTS (
|
|
1302
|
-
SELECT 1 FROM dups d
|
|
1303
|
-
WHERE d.auto_title = s.auto_title
|
|
1304
|
-
AND d.project_id = s.project_id
|
|
1305
|
-
)
|
|
1306
|
-
)`).all(...n),o=t.prepare(`SELECT content_text
|
|
1307
|
-
FROM messages
|
|
1308
|
-
WHERE session_id = ?
|
|
1309
|
-
AND role = 'user'
|
|
1310
|
-
AND is_sidechain = 0
|
|
1311
|
-
AND content_text IS NOT NULL
|
|
1312
|
-
AND content_text != ''
|
|
1313
|
-
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1314
|
-
LIMIT 8`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),m=null;for(let h of d){let b=h.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();if(!b||/^<local-command-caveat>/.test(b))continue;let T=Tt(b);if(T&&!vl(T)){m=T;break}}if(!m){let h=d.map(T=>T.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim()).find(T=>T.length>0&&!/^<local-command-caveat>/.test(T)),b=h?Tt(h):null;b&&vl(b)&&(m=`[vacuous] ${b}`)}m&&(Ee(u.id,m,"heuristic"),c+=1)}return{scanned:a,updated:c}}function vl(e){let t=e.trim();return!t||/^\*\*Tool result\*\*$/i.test(t)?!0:[/^all right\.?$/i,/^alright\.?$/i,/^inspect this\.?$/i,/^resolve this\.?$/i,/^do it\.?$/i,/^go\.?$/i,/^continue\.?$/i,/^proceed\.?$/i,/^keep going\.?$/i,/^ok\.?$/i,/^okay\.?$/i,/^yes\.?$/i,/^yep\.?$/i].some(n=>n.test(t))}function Ul(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1315
|
-
FROM sessions s
|
|
1316
|
-
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1317
|
-
AND (
|
|
1318
|
-
s.auto_title LIKE 'You will receive %'
|
|
1319
|
-
OR s.auto_title LIKE 'Base directory for this skill:%'
|
|
1320
|
-
OR s.auto_title LIKE 'You are Claude Code in a fresh terminal%'
|
|
1321
|
-
)`).all(...n),o=t.prepare(`SELECT content_text
|
|
1322
|
-
FROM messages
|
|
1323
|
-
WHERE session_id = ?
|
|
1324
|
-
AND role = 'user'
|
|
1325
|
-
AND is_sidechain = 0
|
|
1326
|
-
AND content_text IS NOT NULL
|
|
1327
|
-
AND content_text != ''
|
|
1328
|
-
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1329
|
-
LIMIT 10`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),m=DE(d,u.auto_title);m&&(Ee(u.id,m,"heuristic"),c+=1)}return{scanned:a,updated:c}}function DE(e,t){for(let s of e){let n=FE(s.content_text);if(!n||ro(n))continue;let r=Tt(n);if(r){if(r===t)return null;if(!ro(r))return r}}return t.startsWith("[meta]")?null:St(`[meta] ${t}`)}function $l(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1330
|
-
FROM sessions s
|
|
1331
|
-
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1332
|
-
AND s.auto_title IS NOT NULL
|
|
1333
|
-
AND s.auto_title LIKE '% \xB7 %'
|
|
1334
|
-
AND (s.auto_title LIKE '%|%' OR s.auto_title LIKE '%(%')`).all(...n),o=0,a=0;for(let c of r){o+=1;let u=Ll(c.auto_title);u!==c.auto_title&&(Ee(c.id,u,"heuristic"),a+=1)}return{scanned:o,updated:a}}function FE(e){return e.replace(/<command-(?:name|message|args|stdout|stderr)>[\s\S]*?<\/command-(?:name|message|args|stdout|stderr)>/g,"").replace(/<local-command-(?:stdout|stderr|caveat)>[\s\S]*?<\/local-command-(?:stdout|stderr|caveat)>/g,"").trim()}function PE(){z(),bE(ao)||EE(ao,{recursive:!0})}function UE(e,t,s,n){try{PE();let r=jl(ao,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
|
|
1335
|
-
`;hE(r,o+t+`
|
|
1336
|
-
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}var $E=50;function HE(e){if(e.length===0)return[];let t=[...e].sort((u,d)=>u.added_at<d.added_at?-1:u.added_at>d.added_at?1:0),s=new Map,n=new Set,r=[];for(let u of t)if(u.parent_session_id){let d=s.get(u.parent_session_id)??[];d.push(u),s.set(u.parent_session_id,d)}let o=t.filter(u=>u.role==="origin"),c=[...o.length>0?o:t.filter(u=>!u.parent_session_id)];for(;c.length>0;){let u=c.shift();if(n.has(u.session_id))continue;n.add(u.session_id),r.push(u.session_id);let d=s.get(u.session_id)??[];for(let m of d)n.has(m.session_id)||c.push(m)}for(let u of t)n.has(u.session_id)||(n.add(u.session_id),r.push(u.session_id));return r}function BE(e){let t=lo(e.sessionId),s=["",`BULK CONTEXT: You are titling session ${e.current} of ${e.total} in this thread.`,"The parent and earlier siblings already have titles (shown above). YOUR JOB:","",'1. Identify the naming pattern. If parent is "Build Feature X" and earlier',' siblings are "Phase A: API design" and "Phase B: client wiring", the pattern',' is "Phase {LETTER}: {topic}".',"2. If no pattern is yet established (this is the first child), INVENT a pattern",' that will scale to N children. Good patterns: "Phase A/B/C", "Wave 1 of M",',' "Step N", or a domain-specific structure if the thread name suggests one.',"3. Output a title that follows the pattern AND describes what THIS session"," does in 50 characters max.","","Output ONLY the title, no quotes, no trailing punctuation."].join(`
|
|
1441
|
+
`,budgetUsed:o,truncated:r}}function aS(e,t,s,n,r,o){let a=[];for(let c of s){if(n&&!n.has(c.link_type))continue;let u=null;if(c.source_session_id===t.session_id?u=c.target_session_id:c.target_session_id===t.session_id&&(u=c.source_session_id),!u)continue;let d=An(e,u);if(!d)continue;let m=fu(t.started_at,d.started_at),h=Su({confidence:c.confidence,linkType:c.link_type,daysApart:m,embeddingCosine:null,pagerank:o.get(u)??0,scoring:r});a.push({...d,score:h,evidence:`(suggestion, ${c.inferred_by}) confidence=${c.confidence.toFixed(2)} ${Math.round(m)}d apart`,link_type:c.link_type})}return a}function xn(e,t={}){let s=Math.max(100,Math.floor(t.budget??Yb)),n=t.scoring??"hybrid",r=Math.max(1,Math.min(5,t.maxDepth??zb)),o=t.includeWikiLinks??!0,a=t.includeSuggestions??!1,c=t.edgeTypes?new Set(t.edgeTypes):null,u=hu(e);if(!u)throw new Error(`session not found: ${e}`);let d=eS(u.project_id),m={session_id:u.id,title:bu(u),decimal:d.table?.byId.get(u.id)??null,summary:Eu(u.id),project:u.project,started_at:u.started_at};d.cache.set(u.id,m);let h=tS(d,e),b=sS(d,e),T=qt(e).filter(B=>B.approved).filter(B=>!c||c.has(B.link_type)).filter(B=>o||B.link_type!=="wiki_link"),S=nS(e,T,h,b,r),w=rS(S),R=[],D=[],F=[],L=[];for(let B of T){let se=B.source_session_id===e?B.target_session_id:B.source_session_id,i=An(d,se);if(!i)continue;let l=fu(m.started_at,i.started_at),p=Su({confidence:B.confidence,linkType:B.link_type,daysApart:l,embeddingCosine:null,pagerank:w.get(se)??0,scoring:n}),g=_u(l),_=`${B.link_type} confidence=${B.confidence.toFixed(2)} recency=${g.toFixed(2)} (${Math.round(l)}d apart)`,E={...i,score:p,evidence:_,link_type:B.link_type};B.link_type==="citation"?R.push(E):B.link_type==="similar"?D.push(E):B.link_type==="wiki_link"?L.push(E):F.push(E)}if(a){let B=lt({sourceSessionId:e,status:"pending",limit:100}),se=lt({targetSessionId:e,status:"pending",limit:100}),i=[...B,...se],l=new Set,p=i.filter(_=>l.has(_.id)?!1:(l.add(_.id),!0)),g=aS(d,m,p,c,n,w);for(let _ of g)_.link_type==="citation"?R.push(_):_.link_type==="similar"?D.push(_):_.link_type==="wiki_link"?L.push(_):F.push(_)}let j=(B,se)=>se.score-B.score;R.sort(j),D.sort(j),F.sort(j),L.sort(j);let ie=iS(m,[{heading:"Parents",refs:h},{heading:"Children",refs:b},{heading:"Citations (approved)",refs:R},{heading:"Similar sessions",refs:D},{heading:"Cousins (skill track + temporal)",refs:F},{heading:"Wiki links (manual)",refs:L}],s);return{origin:m,parents:h,children:b,citations:R,similar:D,cousins:F,wikiLinks:L,bundle:ie.bundle,budgetUsed:ie.budgetUsed,budgetRemaining:Math.max(0,s-ie.budgetUsed),truncated:ie.truncated}}import{randomUUID as gS}from"node:crypto";var cS=50;function lS(e){if(e.length===0)return[];let t=[...e].sort((u,d)=>u.added_at<d.added_at?-1:u.added_at>d.added_at?1:0),s=new Map,n=new Set,r=[];for(let u of t)if(u.parent_session_id){let d=s.get(u.parent_session_id)??[];d.push(u),s.set(u.parent_session_id,d)}let o=t.filter(u=>u.role==="origin"),c=[...o.length>0?o:t.filter(u=>!u.parent_session_id)];for(;c.length>0;){let u=c.shift();if(n.has(u.session_id))continue;n.add(u.session_id),r.push(u.session_id);let d=s.get(u.session_id)??[];for(let m of d)n.has(m.session_id)||c.push(m)}for(let u of t)n.has(u.session_id)||(n.add(u.session_id),r.push(u.session_id));return r}function uS(e){let t=to(e.sessionId),s=["",`BULK CONTEXT: You are titling session ${e.current} of ${e.total} in this thread.`,"The parent and earlier siblings already have titles (shown above). YOUR JOB:","",'1. Identify the naming pattern. If parent is "Build Feature X" and earlier',' siblings are "Phase A: API design" and "Phase B: client wiring", the pattern',' is "Phase {LETTER}: {topic}".',"2. If no pattern is yet established (this is the first child), INVENT a pattern",' that will scale to N children. Good patterns: "Phase A/B/C", "Wave 1 of M",',' "Step N", or a domain-specific structure if the thread name suggests one.',"3. Output a title that follows the pattern AND describes what THIS session"," does in 50 characters max.","","Output ONLY the title, no quotes, no trailing punctuation."].join(`
|
|
1337
1442
|
`);return`${t}
|
|
1338
|
-
${s}`}function WE(e){return Le(e)?.auto_title_source??null}async function qE(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(ye(),tt));if(!s())throw new Error("claude CLI not found on PATH");let n=await t(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let r=XE(n.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,$E)}function XE(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return Hl(n)}}catch{}return Hl(t)}function Hl(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function Wl(e,t={}){let s=oe(e);if(!s)throw new Error(`thread not found: ${e}`);let n=HE(s.edges),r=n.length,o=t.force??!1,a=t.signal,c=[],u=[],d=[];for(let m=0;m<n.length;m++){let h=n[m],b=m+1;if(a?.aborted){let S={sessionId:h,reason:"cancelled"};u.push(S),t.onSkipped?.(S);continue}if(!o&&WE(h)==="agent"){let S={sessionId:h,reason:"already-titled"};u.push(S),t.onSkipped?.(S);continue}let T;try{if(Bl)T=await Bl({sessionId:h,current:b,total:r});else{let S=BE({sessionId:h,current:b,total:r});T=await qE({prompt:S,model:t.model})}}catch(S){let w=S instanceof Error?S.message:String(S??"unknown error"),R={sessionId:h,error:w};d.push(R),t.onFailed?.(R);continue}try{Ee(h,T,"agent")}catch(S){let w=S instanceof Error?S.message:String(S??"unknown error"),R={sessionId:h,error:`setAutoTitle failed: ${w}`};d.push(R),t.onFailed?.(R);continue}c.push(h),t.onProgress?.({current:b,total:r,sessionId:h,title:T})}return{generated:c,skipped:u,failed:d}}var Bl=null;var rs=new Map,GE=300*1e3;function ns(e,t,s){let n=e.events.length+1;e.events.push({id:n,kind:t,data:s});for(let r of e.waiters)r.resolve();e.waiters.clear()}function YE(e){e.cleanupTimer&&clearTimeout(e.cleanupTimer),e.cleanupTimer=setTimeout(()=>{rs.delete(e.jobId)},GE)}function zE(e){let t=0,s=0,n=0;for(let r of e.events)r.kind==="progress"&&(t+=1),r.kind==="skipped"&&(s+=1),r.kind==="error"&&(n+=1);return{jobId:e.jobId,threadId:e.threadId,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.total,done:t,skipped:s,failed:n,result:e.result}}function ql(e){let t=JE(),s=new AbortController,n={jobId:t,threadId:e.threadId,status:"running",startedAt:new Date().toISOString(),endedAt:null,total:0,events:[],waiters:new Set,controller:s,result:null,cleanupTimer:null};return rs.set(t,n),(async()=>{await Promise.resolve();try{let r=await Wl(e.threadId,{force:e.force??!1,signal:s.signal,model:e.model,onProgress:o=>{n.total=o.total,ns(n,"progress",o)},onSkipped:o=>{ns(n,"skipped",o)},onFailed:o=>{ns(n,"error",o)}});n.result=r,n.status=s.signal.aborted?"cancelled":"done",n.endedAt=new Date().toISOString(),ns(n,"done",r)}catch(r){let o=r instanceof Error?r.message:String(r??"unknown error");n.result={generated:[],skipped:[],failed:[{sessionId:e.threadId,error:o}]},n.status="failed",n.endedAt=new Date().toISOString(),ns(n,"done",n.result)}finally{YE(n)}})(),t}async function*Xl(e,t=0){let s=rs.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(n+=1,yield r,r.kind==="done")return}if(s.endedAt)return;await new Promise(r=>{s.waiters.add({resolve:r})})}}function Jl(e){let t=rs.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function uo(e){let t=rs.get(e);return t?zE(t):null}ee();import{existsSync as KE,readFileSync as VE,writeFileSync as ZE}from"node:fs";import{join as QE}from"node:path";import{z as fe}from"zod";var po=QE(B,"terminals.json"),Gl=1440*60*1e3,eb=3e4,tb=6e4,sb=fe.object({shell_pid:fe.number(),tab_name:fe.string(),cwd:fe.string().nullable().optional(),opened_at:fe.string(),last_seen_at:fe.string()}),nb=fe.object({schema:fe.string().optional(),saved_at:fe.string().optional(),terminals:fe.array(sb).max(500).default([]),sessions_by_pid:fe.record(fe.string(),fe.array(fe.string()).max(50)).optional().default({})}),Yl=/^[⠀-⣿✳\s]+/,zl=/^\d+(\.\d+){1,3}$/;function ce(e){let t=e.trim();return!!(!t||Yl.test(t)||zl.test(t))}function yt(e){let t=e.trim();if(!t||zl.test(t))return null;let s=t.replace(Yl,"").trim();return s.length>0?s:null}function mo(e,t){if(!ce(e))return e;let s=yt(e);return s||(t&&!ce(t)?t:e)}function rb(e){let t=e.now-e.withinMs,s=e.pending.filter(n=>{let r=Date.parse(n.started_at);return Number.isFinite(r)&&r>=t});if(s.length===0)return{kind:"none"};if(e.shellPid!=null){let n=s.find(r=>r.shell_pid===e.shellPid);if(n)return{kind:"pid-match",entry:n}}if(e.cwd){let n=e.cwd.replace(/\/+$/,""),r=s.filter(o=>o.cwd&&o.cwd.replace(/\/+$/,"")===n);if(r.length===1)return{kind:"singleton-cwd",entry:r[0]};if(r.length>=2)return{kind:"ambiguous",candidates:r}}return{kind:"none"}}var go=class{entries=new Map;origins=new Map;sessionsByPid=new Map;pendingClaudeStarts=[];deferredLinks=new Map;pidOwnership=new Map;loaded=!1;ensureLoaded(){if(!this.loaded&&(this.loaded=!0,!!KE(po)))try{let t=VE(po,"utf8"),s=JSON.parse(t),n=nb.safeParse(s);if(!n.success){console.warn("[terminal-registry] terminals.json failed validation, starting with empty registry:",n.error.issues);return}let r=n.data;for(let o of r.terminals)this.entries.set(o.shell_pid,{shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd??null,opened_at:o.opened_at,last_seen_at:o.last_seen_at});for(let[o,a]of Object.entries(r.sessions_by_pid??{})){let c=Number(o);if(!Number.isFinite(c))continue;let u=new Set;for(let d of a)d.length>0&&u.add(d);u.size>0&&this.sessionsByPid.set(c,u)}this.gc()}catch{}}save(){try{z();let t={schema:"claude-recall.terminals.v1",saved_at:new Date().toISOString(),terminals:Array.from(this.entries.values()),sessions_by_pid:Object.fromEntries(Array.from(this.sessionsByPid.entries()).map(([s,n])=>[String(s),Array.from(n)]))};ZE(po,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let s=new Date().toISOString(),n=this.entries.get(t.shell_pid),r=mo(t.tab_name,n?.tab_name),o=n?.opened_at??t.opened_at,a={...t,tab_name:r,opened_at:o,last_seen_at:s};return this.entries.set(t.shell_pid,a),this.gc(),this.save(),a}rename(t,s){this.ensureLoaded();let n=this.entries.get(t);if(!n)return null;let r=mo(s,n.tab_name),o={...n,tab_name:r,last_seen_at:new Date().toISOString()};return this.entries.set(t,o),this.save(),o}remove(t){this.ensureLoaded();let s=this.entries.delete(t),n=this.sessionsByPid.delete(t);return this.outputTails.delete(t),this.pidOwnership.delete(t),(s||n)&&this.save(),s}claimPidOwnership(t,s,n=Date.now()){if(this.ensureLoaded(),!s)return"anonymous";let r=this.pidOwnership.get(t);return r?r.instance_id===s?(r.last_claim_at=n,"refreshed"):n-r.last_claim_at>tb?(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed"):"rejected":(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed")}pidOwnershipSnapshot(){return this.ensureLoaded(),Array.from(this.pidOwnership.entries()).map(([t,s])=>({shell_pid:t,instance_id:s.instance_id,last_claim_at:s.last_claim_at}))}sync(t){this.ensureLoaded();let s=new Date().toISOString(),n=0,r=0;for(let o of t){let a=this.entries.get(o.shell_pid),c=mo(o.tab_name,a?.tab_name),u=a?.opened_at??o.opened_at;this.entries.set(o.shell_pid,{...o,tab_name:c,opened_at:u,last_seen_at:s}),a?(a.tab_name!==c||a.cwd!==o.cwd)&&r++:n++}return this.gc(),this.save(),{added:n,updated:r,removed:0}}get(t){return this.ensureLoaded(),this.entries.get(t)??null}all(){return this.ensureLoaded(),this.gc(),Array.from(this.entries.values())}size(){return this.ensureLoaded(),this.entries.size}linkSession(t,s){this.ensureLoaded();let n=this.sessionsByPid.get(s);n||(n=new Set,this.sessionsByPid.set(s,n)),n.has(t)||(n.add(t),this.save())}sessionsFor(t){return this.ensureLoaded(),Array.from(this.sessionsByPid.get(t)??[])}isSessionAutoLinked(t){this.ensureLoaded();for(let s of this.sessionsByPid.values())if(s.has(t))return!0;return!1}unlinkSession(t){this.ensureLoaded();let s=!1;for(let[n,r]of this.sessionsByPid)r.delete(t)&&(s=!0,r.size===0&&this.sessionsByPid.delete(n));return s&&this.save(),s}pushPending(t){this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.push({...t})}takePendingMatched(t){this.ensureLoaded(),this.gcPending();let s=rb({pending:this.pendingClaudeStarts,shellPid:t.shellPid,cwd:t.cwd,withinMs:t.withinMs,now:Date.now()});if(s.kind==="pid-match"||s.kind==="singleton-cwd"){let n=this.pendingClaudeStarts.indexOf(s.entry);n>=0&&this.pendingClaudeStarts.splice(n,1)}return s}pendingSize(){return this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.length}deferSessionLink(t,s,n,r){this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.set(t,{parent_shell_pid:s,queued_at:Date.now(),cwd:n,git_branch:r})}allDeferredLinks(){return this.ensureLoaded(),this.gcDeferredLinks(),Array.from(this.deferredLinks.entries()).map(([t,s])=>({session_id:t,...s}))}resolveDeferredLink(t){return this.deferredLinks.delete(t)}deferredLinkSize(){return this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.size}gcDeferredLinks(){let t=Date.now()-9e4;for(let[s,n]of this.deferredLinks)n.queued_at<t&&this.deferredLinks.delete(s)}gcPending(){let t=Date.now()-eb;this.pendingClaudeStarts.length!==0&&(this.pendingClaudeStarts=this.pendingClaudeStarts.filter(s=>{let n=Date.parse(s.started_at);return Number.isFinite(n)&&n>=t}))}outputTails=new Map;setOutputTail(t,s,n){this.ensureLoaded(),this.outputTails.set(t,{text:s,captured_at:n})}getOutputTail(t){return this.ensureLoaded(),this.outputTails.get(t)??null}allOutputTails(){return this.ensureLoaded(),new Map(this.outputTails)}removeOutputTail(t){return this.outputTails.delete(t)}setOrigin(t,s){this.ensureLoaded(),this.origins.set(t,s),this.gcOrigins()}getOrigin(t){return this.ensureLoaded(),this.origins.get(t)??null}removeOrigin(t){return this.ensureLoaded(),this.origins.delete(t)}allOrigins(){return this.ensureLoaded(),this.gcOrigins(),new Map(this.origins)}originSize(){return this.ensureLoaded(),this.origins.size}gc(){let t=Date.now()-Gl;for(let[s,n]of this.entries){let r=Date.parse(n.last_seen_at);!Number.isNaN(r)&&r<t&&this.entries.delete(s)}}gcOrigins(){let t=Date.now()-Gl;for(let[s,n]of this.origins)n.detectedAt<t&&this.origins.delete(s)}reapStaleLinks(t=10080*60*1e3){this.ensureLoaded();let n=Date.now()-t,r=0,o=0;for(let[a,c]of this.sessionsByPid){let u=this.entries.get(a);if(u){let d=Date.parse(u.last_seen_at);if(Number.isFinite(d)&&d>=n)continue}o+=c.size,this.sessionsByPid.delete(a),u&&(this.entries.delete(a),r++)}return(r||o)&&this.save(),{pruned_pids:r,pruned_sessions:o}}gcDeadPids(){this.ensureLoaded();let t=0,s=0;for(let n of[...this.entries.keys()]){let r=!0;try{process.kill(n,0)}catch(a){a.code==="ESRCH"&&(r=!1)}if(r)continue;this.entries.delete(n),t++;let o=this.sessionsByPid.get(n);o&&(s+=o.size,this.sessionsByPid.delete(n)),this.outputTails.delete(n),this.pidOwnership.delete(n)}return(t||s)&&this.save(),{pruned_pids:t,pruned_sessions:s}}},v=new go;import{execFile as gb}from"node:child_process";import{promisify as _b}from"node:util";import{existsSync as fb}from"node:fs";import{basename as hb}from"node:path";U();import{execFile as ob}from"node:child_process";import{readFile as ib}from"node:fs/promises";import{promisify as ab}from"node:util";var cb=ab(ob),Kl=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function lb(e){let t={};for(let s of Kl){let n=e[s];typeof n=="string"&&n.length>0&&(t[s]=n)}return t}function ub(e){let t=Date.now();if(e.CURSOR_TRACE_ID)return{editor:"cursor",label:"Cursor",detectedAt:t};if(e.VSCODE_PID||e.VSCODE_INJECTION)return{editor:"vscode",label:"VS Code",detectedAt:t};let s=e.TERM_PROGRAM;return s==="WarpTerminal"?{editor:"warp",label:"Warp",detectedAt:t}:s==="iTerm.app"?{editor:"iterm",label:"iTerm",detectedAt:t}:s==="Apple_Terminal"?{editor:"apple-terminal",label:"Terminal",detectedAt:t}:s==="WezTerm"?{editor:"wezterm",label:"WezTerm",detectedAt:t}:e.WT_SESSION?{editor:"windows-terminal",label:"Windows Terminal",detectedAt:t}:e.KITTY_WINDOW_ID?{editor:"kitty",label:"Kitty",detectedAt:t}:e.TERM==="alacritty"||e.ALACRITTY_SOCKET?{editor:"alacritty",label:"Alacritty",detectedAt:t}:null}async function db(e){if(process.platform==="linux")try{let t=await ib(`/proc/${e}/environ`,"utf8");return pb(t)}catch{return{}}try{let{stdout:t}=await cb("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return mb(t)}catch{return{}}}function pb(e){let t={};for(let s of e.split("\0")){if(!s)continue;let n=s.indexOf("=");if(n<=0)continue;let r=s.slice(0,n),o=s.slice(n+1);t[r]=o}return t}function mb(e){let t={},s=new Set(Kl),n=e.replace(/\s+/g," ").trim().split(" ");for(let r of n){let o=r.indexOf("=");if(o<=0)continue;let a=r.slice(0,o);s.has(a)&&(t[a]=r.slice(o+1))}return t}async function Vl(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await db(e),s=lb(t);return ub(s)}catch{return null}}var En=_b(gb),hn;function Eb(){if(hn!==void 0)return hn;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(fb(t))return hn=t,t;return hn=null,null}var bb=3,Sb=3600*1e3,os=new Map;function eu(){let e=v.all(),t=e.map(s=>s.shell_pid).sort((s,n)=>s-n).join(",");return`${e.length}:${t}`}function Tb(e){let t=os.get(e);return t?Date.now()-t.lastAt>Sb?(os.delete(e),!1):t.refusals<bb?!1:t.fingerprint===eu():!1}function Zl(e){let t=eu(),s=os.get(e);s&&s.fingerprint===t?(s.refusals+=1,s.lastAt=Date.now()):os.set(e,{refusals:1,fingerprint:t,lastAt:Date.now()})}function yb(e){os.delete(e)}async function wb(e){let t=Eb();if(!t)return null;try{let{stdout:s}=await En(t,["-Fpc",e],{timeout:2e3}),n=s.split(`
|
|
1339
|
-
`),r=null,o=null,a=null;for(let c of n)c.startsWith("p")?(a=Number(c.slice(1)),Number.isFinite(a)&&a>0?r==null&&(r=a):a=null):c.startsWith("c")&&a!=null&&c.slice(1).trim()==="claude"&&o==null&&(o=a);return o??r}catch{return null}}async function Ql(e){try{let{stdout:t}=await En("/bin/ps",["-o","ppid=","-p",String(e)],{timeout:2e3}),s=Number(t.trim());return Number.isFinite(s)&&s>0?s:null}catch{return null}}async function Rb(e){try{let{stdout:t}=await En("/bin/ps",["-o","lstart=","-p",String(e)],{timeout:2e3}),s=Date.parse(t.trim());return Number.isFinite(s)?s:null}catch{return null}}var kb=new Set(["zsh","bash","fish","sh","dash","ksh","tcsh","csh","pwsh","powershell","cmd","nu","node","deno","bun","python","python3","ruby","ts-node","tsx","go","cargo","java","php","irb","pry","iex","terminal","shell","console"]);function de(e){let t=e.trim().toLowerCase();if(!t)return!0;let s=t.replace(/^[-/]+/,"").replace(/^.*\//,"").replace(/\s*\(\d+\)\s*$/,"").trim();return kb.has(s)}function wt(e){let t=e.tabName?.trim();return t&&!de(t)&&!ce(t)?t:null}function Ab(e){try{return f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(e)??null}catch{return null}}async function bn(e){let t=hb(e,".jsonl");if(Te(t)||Tb(t))return;let s=Ab(t),n=await wb(e),r=n?await Ql(n):null,o=n?await Vl(n):null;o&&v.setOrigin(t,o);let a=null,c=null,u=null,d=v.takePendingMatched({shellPid:r,cwd:s?.cwd??null,withinMs:3e4});if(d.kind==="ambiguous"){console.log(`[correlator] ambiguous pending for ${t.slice(0,8)} (${d.candidates.length} candidates in ${s?.cwd??"?"}) \u2014 refusing to guess; heuristic title will display`),Zl(t);return}if(d.kind==="pid-match"||d.kind==="singleton-cwd"){let S=d.entry;c=S.shell_pid,u=d.kind==="pid-match"?"pending-pid":"pending-cwd",a=v.get(S.shell_pid)??{shell_pid:S.shell_pid,tab_name:S.tab_name,cwd:S.cwd,opened_at:S.started_at,last_seen_at:S.started_at}}let m=null;if(!a&&n){let S=n;for(let w=0;w<4&&S!=null;w++){let R=await Ql(S);if(!R)break;let j=v.get(R);if(j){a=j,c=R,u="ppid";break}m==null&&(m=R),S=R}}if(!a&&s?.cwd){let S=s.cwd.replace(/\/+$/,""),w=v.all().filter(R=>R.cwd&&R.cwd.replace(/\/+$/,"")===S);w.length===1?(a=w[0],c=a.shell_pid,u="cwd"):w.length>=2&&(console.log(`[correlator] ${w.length} registered terminals in ${S} for ${t.slice(0,8)} \u2014 refusing to guess; heuristic title will display`),Zl(t))}let h=null;if(a?.tab_name&&!de(a.tab_name)&&!ce(a.tab_name))h=a.tab_name;else if(a?.tab_name&&ce(a.tab_name)){let S=yt(a.tab_name);S&&!de(S)&&(h=S)}if(!h&&!(u==="pending-pid"||u==="pending-cwd")&&a&&s?.cwd){let S=s.cwd.replace(/\/+$/,""),w=v.all().filter(R=>R.shell_pid!==c&&R.cwd&&R.cwd.replace(/\/+$/,"")===S&&!de(R.tab_name)&&!ce(R.tab_name)).sort((R,j)=>Date.parse(j.last_seen_at)-Date.parse(R.last_seen_at))[0];w&&(h=w.tab_name)}let T=wt({tabName:h,origin:o,cwd:s?.cwd??null,gitBranch:s?.git_branch??null});if(m!=null&&!c&&v.deferSessionLink(t,m,s?.cwd??null,s?.git_branch??null),!!T)try{he(t,T),yb(t);let S=!!h&&!de(h)&&!ce(h)&&T===h.trim();c!=null&&v.linkSession(t,c);let w=u==="pending-pid"?"pending PID-exact match":u==="pending-cwd"?"pending cwd-singleton match":u??"unknown";console.log(`[correlator] auto-aliased ${t.slice(0,8)} \u2192 "${T}"`+(S?` (tab name via ${w}, shell pid ${c})`:a?` (generic shell name "${a.tab_name}" \u2192 ${o?.editor??"origin"} fallback, shell pid ${c})`:o?` (${o.editor} origin \u2014 no terminal match)`:""))}catch{}}async function xb(){try{let{stdout:e}=await En("/bin/ps",["-eo","pid=,ppid=,comm="],{timeout:2e3}),t=new Map;for(let s of e.split(`
|
|
1340
|
-
`)){let n=s.trim();if(!n)continue;let r=n.match(/^(\d+)\s+(\d+)\s+(.+)$/);if(!r)continue;let o=Number(r[1]),a=Number(r[2]),c=r[3].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&(!Number.isFinite(o)||!Number.isFinite(a)||t.set(o,a))}return t}catch{return new Map}}var Nb=9e4;function Ob(e){let t=(Date.now()-Nb)/1e3,s=e.replace(/\/+$/,"");return f().prepare(`SELECT s.id, NULLIF(sa.alias, '') AS alias, s.started_at AS started_at
|
|
1341
|
-
FROM sessions s
|
|
1342
|
-
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1343
|
-
WHERE s.cwd = ? AND s.file_mtime > ?`).all(s,t).map(r=>({id:r.id,alias:r.alias,started_at_ms:r.started_at?Date.parse(r.started_at):null}))}async function Lb(e){let t=await import("node:fs/promises"),s="";try{let r=await t.open(e,"r");try{let o=Buffer.alloc(32768),{bytesRead:a}=await r.read(o,0,o.length,0);s=o.toString("utf8",0,a)}finally{await r.close()}}catch{return[]}let n=[];for(let r of s.split(`
|
|
1344
|
-
`)){if(!r.trim())continue;let o;try{o=JSON.parse(r)}catch{continue}let a=o;if(!a||a.type!=="user"&&a.type!=="assistant")continue;let c=a.message?.content,u="";if(typeof c=="string")u=c;else if(Array.isArray(c))for(let d of c)d&&typeof d=="object"&&"text"in d&&typeof d.text=="string"&&(u+=d.text+`
|
|
1345
|
-
`);if(u){for(let d of u.split(/\r?\n/)){let m=d.trim();m.length>=60&&m.length<=400&&n.push(m)}if(n.length>=8)break}}return n}async function tu(e){if(Te(e))return null;let t=f().prepare("SELECT cwd, file_path FROM sessions WHERE id = ?").get(e);if(!t?.cwd||!t.file_path)return null;let s=await Lb(t.file_path);if(s.length===0)return null;let n=t.cwd.replace(/\/+$/,""),r=v.allOutputTails(),o=[];for(let[a,c]of r){let u=v.get(a);if(!u||!u.cwd||u.cwd.replace(/\/+$/,"")!==n||de(u.tab_name)||ce(u.tab_name))continue;let d=0;for(let m of s)c.text.includes(m)&&d++;d>0&&o.push({shell_pid:a,tab_name:u.tab_name,matched_fingerprints:d})}return o.length===0||(o.sort((a,c)=>c.matched_fingerprints-a.matched_fingerprints),o.length>1&&o[0].matched_fingerprints===o[1].matched_fingerprints)?null:o[0]}var Cb=3e4;function su(){let e=Date.now(),t=v.all(),s=o=>{let a=Date.parse(o.last_seen_at);return!Number.isFinite(a)||e-a>Cb},n=new Map;for(let o of t){if(s(o)||!o.cwd||de(o.tab_name)||ce(o.tab_name))continue;let a=`${o.cwd.replace(/\/+$/,"")}::${o.tab_name}`,c=n.get(a);c||(c=[],n.set(a,c)),c.push({shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd})}let r={rebound:0,ghosts:0,ambiguous:0};for(let o of t){if(!s(o))continue;let a=v.sessionsFor(o.shell_pid);if(a.length!==0){r.ghosts++;for(let c of a){let u=Te(c);if(!u)continue;let d=f().prepare("SELECT cwd FROM sessions WHERE id = ?").get(c);if(!d?.cwd)continue;let m=`${d.cwd.replace(/\/+$/,"")}::${u}`,h=n.get(m)??[];if(h.length===0)continue;if(h.length>1){r.ambiguous++;continue}let b=h[0];b.shell_pid!==o.shell_pid&&(v.unlinkSession(c),v.linkSession(c,b.shell_pid),r.rebound++)}}}return r}function nu(){let e={resolved:0,expired:0},t=v.allDeferredLinks();for(let s of t){let n=v.get(s.parent_shell_pid);if(!n||de(n.tab_name)||ce(n.tab_name))continue;let r=Te(s.session_id);if(r&&!v.isSessionAutoLinked(s.session_id)){v.resolveDeferredLink(s.session_id);continue}let o=v.getOrigin(s.session_id),a=wt({tabName:n.tab_name,origin:o??null,cwd:s.cwd,gitBranch:s.git_branch});if(!a){v.resolveDeferredLink(s.session_id);continue}r!==a&&he(s.session_id,a),v.linkSession(s.session_id,s.parent_shell_pid),v.resolveDeferredLink(s.session_id),e.resolved++}return e}var Ib=6e4;async function Sn(){let e=await xb(),t={scanned:e.size,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0};if(e.size===0)return t;let s=[];for(let[a,c]of e){let u=v.get(c);if(!u||!u.cwd||de(u.tab_name)||ce(u.tab_name))continue;let d=u.tab_name.trim();if(!d)continue;let m=await Rb(a);s.push({claudePid:a,shellPid:c,cwd:u.cwd.replace(/\/+$/,""),target:d,startTimeMs:m})}let n=new Map,r=a=>{let c=n.get(a);if(c)return c;let u=Ob(a);return n.set(a,u),u},o=new Set;for(let a of s){let c=r(a.cwd).filter(d=>!o.has(d.id));if(c.length===0)continue;let u=null;if(a.startTimeMs!=null){let d=Ib;for(let m of c){if(m.started_at_ms==null)continue;let h=Math.abs(m.started_at_ms-a.startTimeMs);h<d&&(d=h,u=m)}}if(!u&&c.length===1&&(u=c[0]),!u){t.ambiguous_cwd++;continue}if(o.add(u.id),u.alias&&!v.isSessionAutoLinked(u.id)){t.skipped_manual++;continue}if(u.alias===a.target){v.linkSession(u.id,a.shellPid);continue}try{he(u.id,a.target),v.linkSession(u.id,a.shellPid),u.alias?t.renamed++:t.linked++,console.log(`[correlator] linked ${u.id.slice(0,8)} \u2192 "${a.target}" (live sweep, claude pid ${a.claudePid}, shell pid ${a.shellPid})`)}catch{}}return t}import{existsSync as ru,mkdirSync as vb,readFileSync as jb,writeFileSync as Mb,chmodSync as Db}from"node:fs";import{homedir as Fb}from"node:os";import{join as ou}from"node:path";import{z as Be}from"zod";function iu(){return process.env.RECALL_HOME??ou(Fb(),".recall")}function Pb(){let e=iu();ru(e)||vb(e,{recursive:!0})}function au(){return ou(iu(),"config.json")}var yn=Be.object({enabled:Be.boolean().default(!1),backend:Be.enum(["api","mcp"]).default("api"),apiKey:Be.string().optional(),model:Be.string().default("claude-opus-4-7"),maxTagsPerSession:Be.number().int().min(1).max(10).default(4),minTagsPerSession:Be.number().int().min(1).max(10).default(2),autopilot:Be.boolean().default(!1)}),Tn={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function cu(){let e=au();if(!ru(e))return{};try{return JSON.parse(jb(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Fe(){let e=cu().autoTag;if(!e)return{...Tn};let t=yn.safeParse({...Tn,...e});return t.success?t.data:{...Tn}}function lu(e){Pb();let t=cu(),s=yn.parse({...Tn,...t.autoTag??{},...e}),n={...t,autoTag:s},r=au();Mb(r,JSON.stringify(n,null,2));try{Db(r,384)}catch(o){console.error("[auto-tag-config] chmod 0600 failed (continuing):",o)}return s}function _o(e){let{apiKey:t,...s}=e;return{...s,apiKey:t?"sk-ant-\u2026":null,hasApiKey:!!t}}import{existsSync as uu,mkdirSync as Ub,readFileSync as $b,writeFileSync as Hb}from"node:fs";import{homedir as Bb}from"node:os";import{join as du}from"node:path";import{z as fo}from"zod";function pu(){return process.env.RECALL_HOME??du(Bb(),".recall")}function Wb(){let e=pu();uu(e)||Ub(e,{recursive:!0})}function mu(){return du(pu(),"config.json")}var Rn=fo.object({heuristicEnabled:fo.boolean().default(!0),agentEnabled:fo.boolean().default(!1)}),wn={heuristicEnabled:!0,agentEnabled:!1};function gu(){let e=mu();if(!uu(e))return{};try{return JSON.parse($b(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function it(){let e=gu().autoTitle;if(!e)return{...wn};let t=Rn.safeParse({...wn,...e});return t.success?t.data:{...wn}}function _u(e){Wb();let t=gu(),s=Rn.parse({...wn,...t.autoTitle??{},...e}),n={...t,autoTitle:s};return Hb(mu(),JSON.stringify(n,null,2)),s}U();var kn="claude-haiku-4-5-20251001",fu=80,qb=2e3,Rt=class extends Error{sessionId;constructor(t){super(`no neighborhood context available for session ${t}`),this.name="NoContextAvailableError",this.sessionId=t}},hu=null;async function Xb(e,t){if(hu)return hu(e,t);let{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ye(),tt));return n()?s(e,[],{model:t}):{success:!1,stdout:"",stderr:"claude CLI not found on PATH",exitCode:null}}function Jb(e){let s=f().prepare(`SELECT s.id,
|
|
1443
|
+
${s}`}function dS(e){return Le(e)?.auto_title_source??null}async function pS(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(ye(),tt));if(!s())throw new Error("claude CLI not found on PATH");let n=await t(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let r=mS(n.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,cS)}function mS(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return wu(n)}}catch{}return wu(t)}function wu(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function ku(e,t={}){let s=oe(e);if(!s)throw new Error(`thread not found: ${e}`);let n=lS(s.edges),r=n.length,o=t.force??!1,a=t.signal,c=[],u=[],d=[];for(let m=0;m<n.length;m++){let h=n[m],b=m+1;if(a?.aborted){let S={sessionId:h,reason:"cancelled"};u.push(S),t.onSkipped?.(S);continue}if(!o&&dS(h)==="agent"){let S={sessionId:h,reason:"already-titled"};u.push(S),t.onSkipped?.(S);continue}let T;try{if(Ru)T=await Ru({sessionId:h,current:b,total:r});else{let S=uS({sessionId:h,current:b,total:r});T=await pS({prompt:S,model:t.model})}}catch(S){let w=S instanceof Error?S.message:String(S??"unknown error"),R={sessionId:h,error:w};d.push(R),t.onFailed?.(R);continue}try{Ee(h,T,"agent")}catch(S){let w=S instanceof Error?S.message:String(S??"unknown error"),R={sessionId:h,error:`setAutoTitle failed: ${w}`};d.push(R),t.onFailed?.(R);continue}c.push(h),t.onProgress?.({current:b,total:r,sessionId:h,title:T})}return{generated:c,skipped:u,failed:d}}var Ru=null;var as=new Map,_S=300*1e3;function is(e,t,s){let n=e.events.length+1;e.events.push({id:n,kind:t,data:s});for(let r of e.waiters)r.resolve();e.waiters.clear()}function fS(e){e.cleanupTimer&&clearTimeout(e.cleanupTimer),e.cleanupTimer=setTimeout(()=>{as.delete(e.jobId)},_S)}function hS(e){let t=0,s=0,n=0;for(let r of e.events)r.kind==="progress"&&(t+=1),r.kind==="skipped"&&(s+=1),r.kind==="error"&&(n+=1);return{jobId:e.jobId,threadId:e.threadId,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.total,done:t,skipped:s,failed:n,result:e.result}}function Au(e){let t=gS(),s=new AbortController,n={jobId:t,threadId:e.threadId,status:"running",startedAt:new Date().toISOString(),endedAt:null,total:0,events:[],waiters:new Set,controller:s,result:null,cleanupTimer:null};return as.set(t,n),(async()=>{await Promise.resolve();try{let r=await ku(e.threadId,{force:e.force??!1,signal:s.signal,model:e.model,onProgress:o=>{n.total=o.total,is(n,"progress",o)},onSkipped:o=>{is(n,"skipped",o)},onFailed:o=>{is(n,"error",o)}});n.result=r,n.status=s.signal.aborted?"cancelled":"done",n.endedAt=new Date().toISOString(),is(n,"done",r)}catch(r){let o=r instanceof Error?r.message:String(r??"unknown error");n.result={generated:[],skipped:[],failed:[{sessionId:e.threadId,error:o}]},n.status="failed",n.endedAt=new Date().toISOString(),is(n,"done",n.result)}finally{fS(n)}})(),t}async function*xu(e,t=0){let s=as.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(n+=1,yield r,r.kind==="done")return}if(s.endedAt)return;await new Promise(r=>{s.waiters.add({resolve:r})})}}function Nu(e){let t=as.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Ao(e){let t=as.get(e);return t?hS(t):null}import{existsSync as Ou,mkdirSync as ES,readFileSync as bS,writeFileSync as SS,chmodSync as TS}from"node:fs";import{homedir as yS}from"node:os";import{join as Lu}from"node:path";import{z as Be}from"zod";function Cu(){return process.env.RECALL_HOME??Lu(yS(),".recall")}function wS(){let e=Cu();Ou(e)||ES(e,{recursive:!0})}function Iu(){return Lu(Cu(),"config.json")}var On=Be.object({enabled:Be.boolean().default(!1),backend:Be.enum(["api","mcp"]).default("api"),apiKey:Be.string().optional(),model:Be.string().default("claude-opus-4-7"),maxTagsPerSession:Be.number().int().min(1).max(10).default(4),minTagsPerSession:Be.number().int().min(1).max(10).default(2),autopilot:Be.boolean().default(!1)}),Nn={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function vu(){let e=Iu();if(!Ou(e))return{};try{return JSON.parse(bS(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Fe(){let e=vu().autoTag;if(!e)return{...Nn};let t=On.safeParse({...Nn,...e});return t.success?t.data:{...Nn}}function ju(e){wS();let t=vu(),s=On.parse({...Nn,...t.autoTag??{},...e}),n={...t,autoTag:s},r=Iu();SS(r,JSON.stringify(n,null,2));try{TS(r,384)}catch(o){console.error("[auto-tag-config] chmod 0600 failed (continuing):",o)}return s}function xo(e){let{apiKey:t,...s}=e;return{...s,apiKey:t?"sk-ant-\u2026":null,hasApiKey:!!t}}H();var Ln="claude-haiku-4-5-20251001",Mu=80,RS=2e3,kt=class extends Error{sessionId;constructor(t){super(`no neighborhood context available for session ${t}`),this.name="NoContextAvailableError",this.sessionId=t}},Du=null;async function kS(e,t){if(Du)return Du(e,t);let{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ye(),tt));return n()?s(e,[],{model:t}):{success:!1,stdout:"",stderr:"claude CLI not found on PATH",exitCode:null}}function AS(e){let s=f().prepare(`SELECT s.id,
|
|
1346
1444
|
s.auto_title,
|
|
1347
1445
|
s.auto_title_source,
|
|
1348
1446
|
CASE WHEN sa.alias IS NOT NULL AND sa.alias != ''
|
|
1349
1447
|
THEN 1 ELSE 0 END AS has_alias_int
|
|
1350
1448
|
FROM sessions s
|
|
1351
1449
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1352
|
-
WHERE s.id = ?`).get(e);return s?{id:s.id,auto_title:s.auto_title,auto_title_source:s.auto_title_source??null,has_alias:s.has_alias_int===1}:null}function
|
|
1353
|
-
`)}function
|
|
1450
|
+
WHERE s.id = ?`).get(e);return s?{id:s.id,auto_title:s.auto_title,auto_title_source:s.auto_title_source??null,has_alias:s.has_alias_int===1}:null}function xS(e){return e.parents.length===0&&e.children.length===0&&e.citations.length===0&&e.similar.length===0&&e.cousins.length===0&&e.wikiLinks.length===0}function NS(e,t){if(e.has_alias)return{eligible:!1,reason:"manual_alias"};let s=Qt({auto_title:e.auto_title,auto_title_source:e.auto_title_source,has_alias:e.has_alias});if(t)return{eligible:!0};switch(s){case"low_signal":case"recursive_meta":case"programmatic":return{eligible:!0};case"agent":return{eligible:!1,reason:"agent_titled"};case"manual_alias":return{eligible:!1,reason:"manual_alias"};default:return{eligible:!1,reason:"clean"}}}function OS(e){return["You are renaming a Claude Code session. Below is its NEIGHBORHOOD: parent","sessions, child sessions, related sessions, and citations \u2014 assembled by","Claude Recall's cog-graph. The session itself has a low-signal or","self-referential title that needs replacement.","","Read the neighborhood, then mint a single descriptive title that","reflects this session's role in the surrounding work. Constraints:","","- Maximum 80 characters.","- No quotes, no trailing punctuation.","- Output ONLY a JSON object with two fields:",' { "title": "<\u226480 char title>", "evidence": "<one sentence why>" }',"- No markdown, no preface, no commentary outside the JSON.","","--- NEIGHBORHOOD BUNDLE ---",e,"--- END NEIGHBORHOOD BUNDLE ---"].join(`
|
|
1451
|
+
`)}function LS(e){let t=e.trim();if(!t)return null;let s=t;try{let o=JSON.parse(t);if(o&&typeof o=="object"){let a=o;if(typeof a.result=="string")s=a.result.trim();else if(typeof a.title=="string")return Fu(a)}}catch{}let n=s.match(/```(?:json)?\s*\n([\s\S]*?)\n?```/i);n&&(s=n[1].trim());let r=null;try{r=JSON.parse(s)}catch{let o=s.indexOf("{"),a=s.lastIndexOf("}");if(o>=0&&a>o)try{r=JSON.parse(s.slice(o,a+1))}catch{return null}else return null}return!r||typeof r!="object"?null:Fu(r)}function Fu(e){let t=e.title;if(typeof t!="string")return null;let s=CS(t).trim();if(!s)return null;let n=e.evidence,r=typeof n=="string"?n.trim():"";return{title:s,evidence:r}}function CS(e){return e.replace(/^["'`]+|["'`]+$/g,"")}function IS(e){let t=e.replace(/\s+/g," ").trim().replace(/[.!?]+$/g,"").trim();return t.length<=Mu?t:t.slice(0,Mu)}function vS(e,t){let s=e.auto_title??"";return!s&&e.has_alias&&(s=f().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e.id)?.alias??""),{session_id:e.id,title:s,source:e.auto_title_source,confidence:0,evidence:`skipped: ${t}`,written:!1,skipped:t}}async function No(e,t={}){if(t.signal?.aborted)return{session_id:e,title:"",source:null,confidence:0,evidence:"aborted before start",written:!1,skipped:"aborted"};let s=AS(e);if(!s)throw new Error(`session not found: ${e}`);let n=NS(s,t.force===!0);if(!n.eligible)return vS(s,n.reason);let r=t.budget??RS,o=xn(e,{budget:r});if(xS(o))throw new kt(e);if(t.signal?.aborted)return{session_id:e,title:s.auto_title??"",source:s.auto_title_source,confidence:0,evidence:"aborted before CLI call",written:!1,skipped:"aborted"};let a=OS(o.bundle),c=t.model??Ln,u=await kS(a,c);if(!u.success){let T=u.stderr.slice(-300);throw new Error(`claude CLI exited ${u.exitCode}: ${T||"no stderr captured"}`)}let d=LS(u.stdout);if(!d)throw new Error("failed to parse regeneration output: not valid JSON {title, evidence}");if(!d.title||!d.title.trim())throw new Error("regeneration produced empty title");let m=IS(d.title);if(!m)throw new Error("regeneration produced empty title after clamp");Ee(e,m,"agent");let b=Le(e)?.auto_title??m;return{session_id:e,title:b,source:"agent",confidence:MS(o),evidence:d.evidence||`regenerated from neighborhood (${jS(o)})`,written:!0}}function jS(e){let t=[];return e.parents.length&&t.push(`${e.parents.length} parents`),e.children.length&&t.push(`${e.children.length} children`),e.citations.length&&t.push(`${e.citations.length} citations`),e.similar.length&&t.push(`${e.similar.length} similar`),e.cousins.length&&t.push(`${e.cousins.length} cousins`),e.wikiLinks.length&&t.push(`${e.wikiLinks.length} wiki-links`),t.join(", ")}function MS(e){let t=e.parents.length,s=e.children.length,n=e.citations.length,r=e.similar.length,o=e.cousins.length,a=e.wikiLinks.length,c=.25*Math.min(1,t)+.15*Math.min(1,s)+.2*Math.min(1,n/2)+.15*Math.min(1,r/3)+.1*Math.min(1,o/3)+.15*Math.min(1,a);return Math.min(.95,c)}import{streamSSE as Ye}from"hono/streaming";import{bodyLimit as cw}from"hono/body-limit";import{z as M}from"zod";H();H();Ve();function cs(e){return f().prepare("SELECT id, name FROM projects WHERE name = ? LIMIT 1").get(e)??null}H();function Pu(e){let t=f(),s=new Date().toISOString();return t.prepare(`INSERT INTO bug_signature_resolutions
|
|
1354
1452
|
(message_hash, resolved_in_session_id, fix_summary, resolved_at, unresolved_at)
|
|
1355
1453
|
VALUES (?, ?, ?, ?, NULL)
|
|
1356
1454
|
ON CONFLICT(message_hash) DO UPDATE SET
|
|
1357
1455
|
resolved_in_session_id = excluded.resolved_in_session_id,
|
|
1358
1456
|
fix_summary = excluded.fix_summary,
|
|
1359
1457
|
resolved_at = excluded.resolved_at,
|
|
1360
|
-
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,s),
|
|
1458
|
+
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,s),DS(e.messageHash)}function $u(e){f().prepare(`UPDATE bug_signature_resolutions
|
|
1361
1459
|
SET unresolved_at = ?
|
|
1362
|
-
WHERE message_hash = ?`).run(new Date().toISOString(),e)}function
|
|
1363
|
-
WHERE message_hash IN (${s})`).all(...e),r=new Map;for(let o of n)r.set(o.message_hash,o);return r}function
|
|
1460
|
+
WHERE message_hash = ?`).run(new Date().toISOString(),e)}function DS(e){return f().prepare("SELECT * FROM bug_signature_resolutions WHERE message_hash = ?").get(e)??null}function Oo(e){if(e.length===0)return new Map;let t=f(),s=e.map(()=>"?").join(","),n=t.prepare(`SELECT * FROM bug_signature_resolutions
|
|
1461
|
+
WHERE message_hash IN (${s})`).all(...e),r=new Map;for(let o of n)r.set(o.message_hash,o);return r}function Lo(e){return!!e&&e.unresolved_at===null}H();function ls(){return new Date().toISOString()}function Co(){let e=f(),t=e.prepare(`SELECT id, name, description, created_at, updated_at
|
|
1364
1462
|
FROM macro_repos
|
|
1365
1463
|
ORDER BY name COLLATE NOCASE`).all();if(t.length===0)return[];let s=t.map(a=>a.id),n=s.map(()=>"?").join(","),r=e.prepare(`SELECT m.macro_repo_id, m.project_id, p.name AS project_name
|
|
1366
1464
|
FROM macro_repo_members m
|
|
1367
1465
|
JOIN projects p ON p.id = m.project_id
|
|
1368
1466
|
WHERE m.macro_repo_id IN (${n})
|
|
1369
|
-
ORDER BY p.name COLLATE NOCASE`).all(...s),o=new Map;for(let a of t)o.set(a.id,{...a,member_project_ids:[],member_project_names:[]});for(let a of r){let c=o.get(a.macro_repo_id);c&&(c.member_project_ids.push(a.project_id),c.member_project_names.push(a.project_name))}return Array.from(o.values())}function
|
|
1467
|
+
ORDER BY p.name COLLATE NOCASE`).all(...s),o=new Map;for(let a of t)o.set(a.id,{...a,member_project_ids:[],member_project_names:[]});for(let a of r){let c=o.get(a.macro_repo_id);c&&(c.member_project_ids.push(a.project_id),c.member_project_names.push(a.project_name))}return Array.from(o.values())}function At(e){return Co().find(s=>s.id===e)??null}function Uu(){return f().prepare(`SELECT p.id, p.name
|
|
1370
1468
|
FROM projects p
|
|
1371
1469
|
LEFT JOIN macro_repo_members m ON m.project_id = p.id
|
|
1372
1470
|
WHERE m.project_id IS NULL
|
|
1373
|
-
ORDER BY p.name COLLATE NOCASE`).all()}function
|
|
1374
|
-
VALUES (?, ?, ?, ?)`).run(t,e.description??null,n,n),o=Number(r.lastInsertRowid);return
|
|
1471
|
+
ORDER BY p.name COLLATE NOCASE`).all()}function Hu(e){let t=e.name.trim();if(!t)throw new Error("macro repo name is required");let s=f(),n=ls(),r=s.prepare(`INSERT INTO macro_repos (name, description, created_at, updated_at)
|
|
1472
|
+
VALUES (?, ?, ?, ?)`).run(t,e.description??null,n,n),o=Number(r.lastInsertRowid);return At(o)}function Bu(e,t){let s=At(e);if(!s)throw new Error(`macro repo ${e} not found`);let n=t.name!==void 0?t.name.trim():s.name;if(!n)throw new Error("macro repo name cannot be empty");let r=t.description!==void 0?t.description:s.description;return f().prepare(`UPDATE macro_repos
|
|
1375
1473
|
SET name = ?, description = ?, updated_at = ?
|
|
1376
|
-
WHERE id = ?`).run(n,r,
|
|
1377
|
-
VALUES (?, ?, ?)`).run(e,t,
|
|
1378
|
-
WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(
|
|
1474
|
+
WHERE id = ?`).run(n,r,ls(),e),At(e)}function Wu(e){f().prepare("DELETE FROM macro_repos WHERE id = ?").run(e)}function qu(e,t){let s=f();if(!s.prepare("SELECT 1 FROM macro_repos WHERE id = ?").get(e))throw new Error(`macro repo ${e} not found`);if(!s.prepare("SELECT 1 FROM projects WHERE id = ?").get(t))throw new Error(`project ${t} not found`);s.prepare(`INSERT OR IGNORE INTO macro_repo_members (macro_repo_id, project_id, added_at)
|
|
1475
|
+
VALUES (?, ?, ?)`).run(e,t,ls()),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(ls(),e)}function Xu(e,t){let s=f();s.prepare(`DELETE FROM macro_repo_members
|
|
1476
|
+
WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(ls(),e)}H();function Ju(e){let t=f(),s=new Date().toISOString(),n=t.prepare(`INSERT INTO bug_synthesis_results
|
|
1379
1477
|
(scope, target_id, mode, model, output_markdown, input_tokens, output_tokens, context_summary, created_at, job_id)
|
|
1380
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.scope,e.target_id,e.mode,e.model,e.output_markdown,e.input_tokens,e.output_tokens,JSON.stringify(e.context_summary??{}),s,e.job_id??null),r=Number(n.lastInsertRowid);return
|
|
1478
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.scope,e.target_id,e.mode,e.model,e.output_markdown,e.input_tokens,e.output_tokens,JSON.stringify(e.context_summary??{}),s,e.job_id??null),r=Number(n.lastInsertRowid);return Io(r)}function Gu(e){let t={};try{t=JSON.parse(e.context_summary)}catch{t={}}return{id:e.id,scope:e.scope,target_id:e.target_id,mode:e.mode,model:e.model,output_markdown:e.output_markdown,input_tokens:e.input_tokens,output_tokens:e.output_tokens,context_summary:t,created_at:e.created_at,job_id:e.job_id}}function Io(e){let s=f().prepare("SELECT * FROM bug_synthesis_results WHERE id = ?").get(e);return s?Gu(s):null}function Yu(e={}){let t=f(),s=[],n=[];e.scope&&(s.push("scope = ?"),n.push(e.scope)),e.target_id&&(s.push("target_id = ?"),n.push(e.target_id));let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.min(Math.max(1,e.limit??50),500);return t.prepare(`SELECT * FROM bug_synthesis_results
|
|
1381
1479
|
${r}
|
|
1382
1480
|
ORDER BY created_at DESC
|
|
1383
|
-
LIMIT ?`).all(...n,o).map(
|
|
1481
|
+
LIMIT ?`).all(...n,o).map(Gu)}function zu(e){let s=f().prepare(`SELECT target_id, COUNT(*) AS n
|
|
1384
1482
|
FROM bug_synthesis_results
|
|
1385
1483
|
WHERE scope = ?
|
|
1386
|
-
GROUP BY target_id`).all(e),n=new Map;for(let r of s)n.set(r.target_id,r.n);return n}function
|
|
1387
|
-
`,"utf8")}catch(s){console.error("[launcher-audit] failed to append:",s)}}function
|
|
1388
|
-
`)){if(!c.trim())continue;let u;try{u=JSON.parse(c)}catch{continue}if(u.kind!=="run-completed"&&u.kind!=="synth-completed")continue;let d=Date.parse(u.ts);!Number.isFinite(d)||d<s||(n+=Number(u.input_tokens??0),r+=Number(u.output_tokens??0),o+=1)}return{input_tokens:n,output_tokens:r,records_counted:o}}var
|
|
1484
|
+
GROUP BY target_id`).all(e),n=new Map;for(let r of s)n.set(r.target_id,r.n);return n}function Ku(e){f().prepare("DELETE FROM bug_synthesis_results WHERE id = ?").run(e)}import{randomBytes as FS,timingSafeEqual as PS}from"node:crypto";var $S=6e4,US=new Set(["127.0.0.1","localhost"]),us=new Map;function vo(){return Date.now()}function Vu(){let e=vo();for(let[t,s]of us)(s.expiresAt<=e||s.used)&&us.delete(t)}function Ce(e){let t=e.req.header("origin")??"";if(t)try{let r=new URL(t);if(!US.has(r.hostname))return e.json({error:"forbidden: cross-origin launcher request rejected"},403)}catch{return e.json({error:"forbidden: invalid Origin header"},403)}let s=e.req.header("sec-fetch-site");return s&&s!=="same-origin"&&s!=="none"?e.json({error:"forbidden: Sec-Fetch-Site indicates cross-origin"},403):e.req.header("x-recall-launcher")!=="1"?e.json({error:"forbidden: missing X-Recall-Launcher header (this endpoint is callable only from the Recall web UI)"},403):null}function jo(e){Vu();let t=FS(32).toString("hex"),s=vo()+$S;return us.set(t,{token:t,intent:e,expiresAt:s,used:!1}),{token:t,expiresAt:s}}function Mo(e){if(Vu(),typeof e!="string"||e.length!==64)return null;let t;try{t=Buffer.from(e,"hex")}catch{return null}if(t.length!==32)return null;for(let s of us.values()){if(s.used||s.expiresAt<=vo())continue;let n;try{n=Buffer.from(s.token,"hex")}catch{continue}if(n.length===t.length&&PS(n,t))return s.used=!0,us.delete(s.token),s.intent}return null}import{existsSync as JS,mkdirSync as UN,readFileSync as GS,writeFileSync as HN}from"node:fs";import{homedir as YS}from"node:os";import{join as rd}from"node:path";import{z as Do}from"zod";import{appendFileSync as HS,existsSync as Zu,mkdirSync as BS,readFileSync as WS}from"node:fs";import{homedir as qS}from"node:os";import{join as Qu}from"node:path";function ed(){return process.env.RECALL_HOME??Qu(qS(),".recall")}function XS(){let e=ed();Zu(e)||BS(e,{recursive:!0})}function td(){return Qu(ed(),"launcher-audit.log")}function ne(e){XS();let t={ts:new Date().toISOString(),...e};try{HS(td(),JSON.stringify(t)+`
|
|
1485
|
+
`,"utf8")}catch(s){console.error("[launcher-audit] failed to append:",s)}}function sd(e){let t=td();if(!Zu(t))return{input_tokens:0,output_tokens:0,records_counted:0};let s=Date.now()-e,n=0,r=0,o=0,a;try{a=WS(t,"utf8")}catch{return{input_tokens:0,output_tokens:0,records_counted:0}}for(let c of a.split(`
|
|
1486
|
+
`)){if(!c.trim())continue;let u;try{u=JSON.parse(c)}catch{continue}if(u.kind!=="run-completed"&&u.kind!=="synth-completed")continue;let d=Date.parse(u.ts);!Number.isFinite(d)||d<s||(n+=Number(u.input_tokens??0),r+=Number(u.output_tokens??0),o+=1)}return{input_tokens:n,output_tokens:r,records_counted:o}}var zS=1440*60*1e3,KS=Do.object({dailyTokenBudget:Do.number().int().nonnegative().default(1e6),sessionCeiling:Do.number().int().positive().max(1e4).default(500)}),Fo={dailyTokenBudget:1e6,sessionCeiling:500};function VS(){return process.env.RECALL_HOME??rd(YS(),".recall")}function ZS(){return rd(VS(),"config.json")}function QS(){let e=ZS();if(!JS(e))return{};try{return JSON.parse(GS(e,"utf8"))}catch(t){return console.error("[launcher-budget] failed to parse config.json, using defaults:",t),{}}}function Po(){let e=QS().launcher;if(!e)return{...Fo};let t=KS.safeParse({...Fo,...e});return t.success?t.data:{...Fo}}function xt(){let e=Po(),t=sd(zS),s=t.input_tokens+t.output_tokens,n=Math.max(0,e.dailyTokenBudget-s);return{daily_token_budget:e.dailyTokenBudget,session_ceiling:e.sessionCeiling,spent_input_tokens_24h:t.input_tokens,spent_output_tokens_24h:t.output_tokens,spent_total_tokens_24h:s,remaining_tokens_24h:n}}function $o(e){return{estimated_input_tokens_max:e*2e4,estimated_output_tokens_max:e*1e3}}function Uo(e){let t=e.mode==="root_cause"?800:2e3;if(e.scope==="cluster")return{estimated_input_tokens_max:5e3+Math.min(8,Math.max(1,e.member_session_count??1))*3e3,estimated_output_tokens_max:t};let s=Math.max(1,e.cluster_count??1);return{estimated_input_tokens_max:Math.min(5e4,1e3*s),estimated_output_tokens_max:t}}var nd={pro:45,"max-5x":225,"max-20x":900};function eT(e){let t=e.toLowerCase();return t.includes("haiku")?1:t.includes("sonnet")?5:t.includes("opus")?10:5}var tT=4e3;function od(e){return Math.max(1,Math.ceil(e/tT))}function We(e,t){let s=eT(t),n=e*s,r=Object.keys(nd).map(o=>{let a=n/nd[o];return{plan:o,fraction:a,pct:Math.round(a*1e3)/10,would_exhaust_window:a>1}});return{model:t,model_multiplier:s,per_plan:r,caveat:"Estimates use Anthropic\u2019s public approximate caps and assume a multiplier of ~1x for Haiku, ~5x for Sonnet, ~10x for Opus. Anthropic adjusts these limits without notice; actual consumption depends on session size. Treat as planning guidance, not a contract."}}import{randomUUID as sT}from"node:crypto";var Nt=new Map,ds=new Map,nT=300*1e3;function Cn(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function rT(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{Nt.delete(e.jobId),ds.get(e.project)===e.jobId&&ds.delete(e.project)},nT),e.cleanupTimer.unref?.())}function Ho(e){return{jobId:e.jobId,project:e.project,model:e.model,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.progress.total,processed:e.progress.processed,ok:e.progress.ok,failed:e.progress.failed,skipped:e.progress.skipped,total_input_tokens:e.progress.total_input_tokens,total_output_tokens:e.progress.total_output_tokens,current_session_id:e.progress.current_session_id,error:e.error}}function id(e){let t=cs(e.project);if(!t)return{error:`project "${e.project}" not found`};let s=ds.get(t.name);if(s){let m=Nt.get(s);if(m&&m.status==="running")return{jobId:s,reused:!0};ds.delete(t.name)}let n=sT(),r=e.model??st,o=Math.max(1,e.limit??200),a=e.force??!1,c=new AbortController,u=new Date().toISOString(),d={jobId:n,project:t.name,projectId:t.id,model:r,limit:o,force:a,status:"running",startedAt:u,endedAt:null,events:[],waiters:new Set,controller:c,progress:{total:0,processed:0,ok:0,failed:0,skipped:0,current_session_id:null,total_input_tokens:0,total_output_tokens:0},error:null,cleanupTimer:null};return Nt.set(n,d),ds.set(t.name,n),ne({kind:"run-launched",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null}),(async()=>{await Promise.resolve();try{await Ga({projectId:t.id,limit:o,force:a,model:r,signal:c.signal,onProgress:m=>{d.progress=m,Cn(d,"progress",m)},onResult:m=>{!m.ok&&!m.skipped&&Cn(d,"error",{session_id:m.session_id,reason:m.failed??"unknown"})}}),d.status=c.signal.aborted?"cancelled":"done",d.endedAt=new Date().toISOString(),Cn(d,"done",Ho(d)),ne({kind:d.status==="cancelled"?"run-cancelled":"run-completed",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null,input_tokens:d.progress.total_input_tokens,output_tokens:d.progress.total_output_tokens,sessions_processed:d.progress.processed})}catch(m){let h=m instanceof Error?m.message:String(m??"unknown error");d.status="failed",d.endedAt=new Date().toISOString(),d.error=h,Cn(d,"done",Ho(d)),ne({kind:"run-failed",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null,reason:h,input_tokens:d.progress.total_input_tokens,output_tokens:d.progress.total_output_tokens})}finally{rT(d)}})(),{jobId:n,reused:!1}}async function*ad(e,t=0){let s=Nt.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function cd(e){let t=Nt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Bo(e){let t=Nt.get(e);return t?Ho(t):null}H();import{randomUUID as oT}from"node:crypto";import{spawn as iT}from"node:child_process";import{execSync as aT}from"node:child_process";var ld="claude-haiku-4-5-20251001",Lt=new Map,gs=new Map,cT=300*1e3;function ud(e){return`${e.scope}:${e.target_id}:${e.mode}`}function ps(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function lT(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{Lt.delete(e.jobId);let t=ud(e.intent);gs.get(t)===e.jobId&&gs.delete(t)},cT),e.cleanupTimer.unref?.())}function Ot(e){return{jobId:e.jobId,scope:e.intent.scope,target_id:e.intent.target_id,mode:e.intent.mode,model:e.intent.model,status:e.status,output_markdown:e.output_markdown,input_tokens:e.input_tokens,output_tokens:e.output_tokens,startedAt:e.startedAt,endedAt:e.endedAt,error:e.error,context_summary:e.context_summary}}function dd(){return f().prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
|
|
1389
1487
|
oi.bug_signatures
|
|
1390
1488
|
FROM session_output_index oi
|
|
1391
1489
|
JOIN sessions s ON s.id = oi.session_id
|
|
1392
1490
|
JOIN projects p ON p.id = s.project_id
|
|
1393
1491
|
WHERE oi.bug_signatures IS NOT NULL
|
|
1394
|
-
AND oi.bug_signatures != '[]'`).all()}function
|
|
1492
|
+
AND oi.bug_signatures != '[]'`).all()}function pd(e){try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function uT(e){let t=dd(),s=[];for(let a of t)for(let c of pd(a.bug_signatures)){let u=c.message_hash??`nohash:${(c.snippet??"").slice(0,64)}`;(c.message_hash===e||u===e)&&s.push({sig:c,session_id:a.session_id,project:a.project,auto_title:a.auto_title})}if(s.length===0)return null;let n=s[0],r=Array.from(new Set(s.map(a=>a.session_id))),o=Array.from(new Set(s.map(a=>a.project))).sort();return{message_hash:n.sig.message_hash??null,error_type:n.sig.error_type??null,snippet:(n.sig.snippet??"").slice(0,400),file:n.sig.file??null,occurrence_count:s.length,projects:o,member_session_ids:r}}function dT(e){let t=dd().filter(o=>o.project===e),s=new Map;for(let o of t)for(let a of pd(o.bug_signatures)){let c=a.message_hash??`nohash:${(a.snippet??"").slice(0,64)}`,u=s.get(c);u?(u.sigs.push(a),u.sessions.add(o.session_id)):s.set(c,{sigs:[a],first:a,sessions:new Set([o.session_id])})}let n=new Map;try{let o=Array.from(s.keys()).filter(a=>!a.startsWith("nohash:"));if(o.length>0){let a=f(),c=o.map(()=>"?").join(","),u=a.prepare(`SELECT message_hash, fix_summary
|
|
1395
1493
|
FROM bug_signature_resolutions
|
|
1396
1494
|
WHERE message_hash IN (${c})
|
|
1397
|
-
AND unresolved_at IS NULL`).all(...o);for(let d of u)n.set(d.message_hash,{fix_summary:d.fix_summary})}}catch{}let r=[];for(let[o,a]of s){let c=a.first.message_hash??null,u=c?n.get(c)??null:null;r.push({cluster_id:c??o,error_type:a.first.error_type??null,snippet:(a.first.snippet??"").slice(0,200),file:a.first.file??null,occurrence_count:a.sessions.size,resolved:u!==null,fix_summary:u?.fix_summary??null})}return r.sort((o,a)=>a.occurrence_count-o.occurrence_count),r}function
|
|
1495
|
+
AND unresolved_at IS NULL`).all(...o);for(let d of u)n.set(d.message_hash,{fix_summary:d.fix_summary})}}catch{}let r=[];for(let[o,a]of s){let c=a.first.message_hash??null,u=c?n.get(c)??null:null;r.push({cluster_id:c??o,error_type:a.first.error_type??null,snippet:(a.first.snippet??"").slice(0,200),file:a.first.file??null,occurrence_count:a.sessions.size,resolved:u!==null,fix_summary:u?.fix_summary??null})}return r.sort((o,a)=>a.occurrence_count-o.occurrence_count),r}function pT(e){let t=e.replace(/\s+/g," ").trim();return t.length===0?"":t.slice(0,Math.min(30,t.length))}function mT(e,t){let s=f(),n=pT(t),r=[];for(let o of e.slice(0,8)){let a=s.prepare(`SELECT s.id, p.name AS project, s.auto_title
|
|
1398
1496
|
FROM sessions s
|
|
1399
1497
|
JOIN projects p ON p.id = s.project_id
|
|
1400
1498
|
WHERE s.id = ?`).get(o);if(!a)continue;let c=[];if(n.length>0){let u=s.prepare(`SELECT rowid FROM messages
|
|
@@ -1406,9 +1504,9 @@ ${s}`}function WE(e){return Le(e)?.auto_title_source??null}async function qE(e){
|
|
|
1406
1504
|
WHERE session_id = ?
|
|
1407
1505
|
AND is_sidechain = 0
|
|
1408
1506
|
AND rowid BETWEEN ? AND ?
|
|
1409
|
-
ORDER BY rowid`).all(o,h.rowid-2,h.rowid+2);for(let T of b){if(d.has(T.rowid))continue;d.add(T.rowid);let S=(T.content_text??"").slice(0,800);if(m+S.length>4e3)break;m+=S.length,c.push({role:T.role??"unknown",content:S})}if(m>=4e3)break}}r.push({session_id:o,short_id:o.slice(0,8),project:a.project,auto_title:a.auto_title,excerpts:c})}return r}var
|
|
1410
|
-
`)}function
|
|
1411
|
-
`)}var
|
|
1507
|
+
ORDER BY rowid`).all(o,h.rowid-2,h.rowid+2);for(let T of b){if(d.has(T.rowid))continue;d.add(T.rowid);let S=(T.content_text??"").slice(0,800);if(m+S.length>4e3)break;m+=S.length,c.push({role:T.role??"unknown",content:S})}if(m>=4e3)break}}r.push({session_id:o,short_id:o.slice(0,8),project:a.project,auto_title:a.auto_title,excerpts:c})}return r}var gT="You are analyzing extracted bug findings from a developer's past Claude Code sessions. You are NOT being asked to fix code. You are being asked to synthesize patterns, identify likely root causes, or prioritize concerns based ONLY on the structured findings you are given. Output Markdown only, no preamble, no apologies, no questions.";function _T(e,t){let s=[];s.push("[CONTEXT]"),s.push("Bug fingerprint:"),s.push(`- error_type: ${e.error_type??"unknown"}`),s.push(`- description: ${e.snippet||"(no snippet)"}`),s.push(`- file: ${e.file??"(no file recorded)"}`),s.push(`- occurrences: ${e.occurrence_count} sessions`),s.push(`- projects: ${e.projects.join(", ")}`),s.push(`- finding_id: ${e.message_hash??"(no hash)"}`),s.push(""),s.push("Member session excerpts:");for(let n of t){let r=n.auto_title??"(untitled)";if(s.push(`=== Session ${n.short_id} | ${n.project} | "${r}" ===`),n.excerpts.length===0)s.push("(no surrounding messages found for this snippet)");else for(let o of n.excerpts)s.push(`${o.role}: ${o.content}`);s.push("")}return s.join(`
|
|
1508
|
+
`)}function fT(e,t,s){let n=[];n.push("[CONTEXT]"),n.push(`Project: ${e}`),n.push(`Total clusters: ${s}`),n.push(""),n.push("Clusters (sorted by occurrence_count desc):");for(let r of t)n.push(`- cluster_id: ${r.cluster_id}`),n.push(` error_type: ${r.error_type??"unknown"}`),n.push(` snippet: ${r.snippet||"(none)"}`),r.file&&n.push(` file: ${r.file}`),n.push(` occurrence_count: ${r.occurrence_count}`),n.push(` resolved: ${r.resolved?"true":"false"}`),r.fix_summary&&n.push(` fix_summary: ${r.fix_summary}`),n.push("");return t.length<s&&n.push(`(Showing top ${t.length} of ${s} clusters by occurrence.)`),n.join(`
|
|
1509
|
+
`)}var hT=`[TASK]
|
|
1412
1510
|
Output a Markdown synopsis with these sections:
|
|
1413
1511
|
|
|
1414
1512
|
## What this bug is
|
|
@@ -1430,7 +1528,7 @@ prefer "consider <pattern>" over "do <action>" unless the evidence
|
|
|
1430
1528
|
is overwhelming.
|
|
1431
1529
|
|
|
1432
1530
|
## Confidence
|
|
1433
|
-
One of: high / medium / low. With one sentence justifying the level.`,
|
|
1531
|
+
One of: high / medium / low. With one sentence justifying the level.`,ET=`[TASK]
|
|
1434
1532
|
Output a Markdown response with these sections:
|
|
1435
1533
|
|
|
1436
1534
|
## Most likely root cause
|
|
@@ -1447,7 +1545,7 @@ list is acceptable but explicitly say "(none found)".
|
|
|
1447
1545
|
At most 2 alternatives, each with one sentence why it is less likely.
|
|
1448
1546
|
|
|
1449
1547
|
## Confidence
|
|
1450
|
-
high / medium / low + one-sentence justification.`,
|
|
1548
|
+
high / medium / low + one-sentence justification.`,bT=`[TASK]
|
|
1451
1549
|
Output a Markdown response with these sections:
|
|
1452
1550
|
|
|
1453
1551
|
## Top 5 recurring concerns
|
|
@@ -1465,20 +1563,20 @@ Clusters that look small + high-confidence-fix. Empty list is fine.
|
|
|
1465
1563
|
Clusters that suggest deeper issues (multiple files, repeated patterns).
|
|
1466
1564
|
|
|
1467
1565
|
## Confidence
|
|
1468
|
-
high / medium / low.`;function
|
|
1469
|
-
`)}var
|
|
1566
|
+
high / medium / low.`;function ST(e,t){let s;return e.scope==="cluster"?s=e.mode==="root_cause"?ET:hT:s=bT,[gT,"",t,"",s].join(`
|
|
1567
|
+
`)}var TT=null;var ms;function yT(){if(ms)return ms;try{ms=aT("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{ms="claude"}return ms}function wT(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
|
|
1470
1568
|
`);for(;n!==-1;){let r=t.slice(0,n);t=t.slice(n+1),r.length>0&&e(r),n=t.indexOf(`
|
|
1471
|
-
`)}}}function
|
|
1472
|
-
`);if(t.length<4)return!1;let s=0,n=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?s++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&n++;return s/t.length>.7||n/t.length>.5}function
|
|
1569
|
+
`)}}}function RT(e){return new Promise(t=>{let s=["-p",e.prompt,"--output-format","stream-json","--verbose","--allowedTools","","--permission-mode","bypassPermissions","--model",e.model],n="",r=0,o=0,a=null,c=iT(yT(),s,{stdio:["ignore","pipe","pipe"]}),u=()=>{try{c.kill("SIGTERM")}catch{}};e.signal.addEventListener("abort",u,{once:!0});let m=wT(b=>{let T=b.trim();if(!T.startsWith("{"))return;let S;try{S=JSON.parse(T)}catch{return}if(!S||typeof S!="object")return;let w=S;if(w.type==="assistant"&&w.message?.content){for(let D of w.message.content)D?.type==="text"&&typeof D.text=="string"&&(n+=D.text,e.onPartial(D.text,n));let R=w.message?.usage;R&&(typeof R.input_tokens=="number"&&(r=Math.max(r,R.input_tokens)),typeof R.output_tokens=="number"&&(o=Math.max(o,R.output_tokens)))}});c.stdout.on("data",b=>m(b)),c.stderr.on("data",b=>{let T=b.toString("utf8");T.trim()&&(a=(a??"")+T)});let h=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}},1800*1e3);c.on("close",b=>{clearTimeout(h),e.signal.removeEventListener("abort",u);let T=b===0?null:a&&a.trim()||(e.signal.aborted?null:`claude CLI exited with code ${b}`);t({output_markdown:n,input_tokens:r,output_tokens:o,error:T})}),c.on("error",b=>{clearTimeout(h),e.signal.removeEventListener("abort",u),t({output_markdown:n,input_tokens:r,output_tokens:o,error:b instanceof Error?b.message:String(b)})})})}function In(e){if(e.scope==="cluster"){let r=uT(e.target_id);return r?{cluster:r,context_summary:{cluster_count:1,session_count:r.member_session_ids.length,findings_count:r.occurrence_count}}:null}let t=dT(e.target_id);if(t.length===0)return null;let s=t.slice(0,30),n=t.reduce((r,o)=>r+o.occurrence_count,0);return{project_clusters:s,total_project_clusters:t.length,context_summary:{cluster_count:t.length,session_count:0,findings_count:n}}}function md(e){let t=In(e.intent);if(!t)return{error:e.intent.scope==="cluster"?`cluster "${e.intent.target_id}" not found in any extracted findings`:`project "${e.intent.target_id}" has no extracted findings to synthesize`};let s=ud(e.intent),n=gs.get(s);if(n){let u=Lt.get(n);if(u&&u.status==="running")return{jobId:n,reused:!0};gs.delete(s)}let r=oT(),o=new AbortController,a=new Date().toISOString(),c={jobId:r,intent:e.intent,status:"running",startedAt:a,endedAt:null,events:[],waiters:new Set,controller:o,output_markdown:"",input_tokens:0,output_tokens:0,error:null,context_summary:t.context_summary,cleanupTimer:null};return Lt.set(r,c),gs.set(s,r),ne({kind:"synth-launched",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:`${e.intent.scope}/${e.intent.mode}/${e.intent.target_id}`}),(async()=>{await Promise.resolve();try{let u;if(e.intent.scope==="cluster"&&t.cluster){let b=mT(t.cluster.member_session_ids,t.cluster.snippet);u=_T(t.cluster,b)}else if(e.intent.scope==="project"&&t.project_clusters)u=fT(e.intent.target_id,t.project_clusters,t.total_project_clusters??t.project_clusters.length);else throw new Error("inconsistent prepared context");let d=ST(e.intent,u),h=await(TT??RT)({prompt:d,model:e.intent.model,signal:o.signal,onPartial:(b,T)=>{c.output_markdown=T,ps(c,"partial",Ot(c))}});if(c.output_markdown=h.output_markdown,c.input_tokens=h.input_tokens,c.output_tokens=h.output_tokens,o.signal.aborted)c.status="cancelled",c.endedAt=new Date().toISOString(),ps(c,"done",Ot(c)),ne({kind:"synth-cancelled",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,input_tokens:c.input_tokens,output_tokens:c.output_tokens});else if(h.error)c.status="failed",c.endedAt=new Date().toISOString(),c.error=h.error,ps(c,"done",Ot(c)),ne({kind:"synth-failed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:h.error,input_tokens:c.input_tokens,output_tokens:c.output_tokens});else{c.status="done",c.endedAt=new Date().toISOString(),ps(c,"done",Ot(c)),ne({kind:"synth-completed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,input_tokens:c.input_tokens,output_tokens:c.output_tokens});try{Ju({scope:e.intent.scope,target_id:e.intent.target_id,mode:e.intent.mode,model:e.intent.model,output_markdown:c.output_markdown,input_tokens:c.input_tokens,output_tokens:c.output_tokens,job_id:r})}catch(b){console.error("[synthesize-jobs] failed to persist synthesis result:",b)}}}catch(u){let d=u instanceof Error?u.message:String(u??"unknown error");c.status="failed",c.endedAt=new Date().toISOString(),c.error=d,ps(c,"done",Ot(c)),ne({kind:"synth-failed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:d,input_tokens:c.input_tokens,output_tokens:c.output_tokens})}finally{lT(c)}})(),{jobId:r,reused:!1}}async function*gd(e,t=0){let s=Lt.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function _d(e){let t=Lt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Wo(e){let t=Lt.get(e);return t?Ot(t):null}import{randomUUID as FT}from"node:crypto";H();qs();import{randomUUID as vT}from"node:crypto";H();var fd=10,hd=20;function kT(e){if(!e)return!1;let t=e.split(`
|
|
1570
|
+
`);if(t.length<4)return!1;let s=0,n=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?s++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&n++;return s/t.length>.7||n/t.length>.5}function AT(e){let t=e.byteLength/4;if(!Number.isInteger(t)||t<=0)return null;let s=new Float32Array(t),n=new Float32Array(e.buffer,e.byteOffset,t);return s.set(n),s}function Ed(e){let t=0;for(let n=0;n<e.length;n++)t+=e[n]*e[n];if(t<=0)return!1;let s=1/Math.sqrt(t);for(let n=0;n<e.length;n++)e[n]*=s;return!0}function qo(e){if(e.length===0)return null;let t=e[0].length,s=new Float32Array(t);for(let n of e)if(n.length===t)for(let r=0;r<t;r++)s[r]+=n[r];for(let n=0;n<t;n++)s[n]/=e.length;return Ed(s)?s:null}function bd(e){let t=new Map;if(e.length===0)return t;let s=f(),n=e.map(()=>"?").join(","),r=[];try{r=s.prepare(`SELECT cm.rowid AS rowid, cm.session_id AS session_id,
|
|
1473
1571
|
cm.text AS text, v.embedding AS embedding
|
|
1474
1572
|
FROM chunk_meta cm
|
|
1475
1573
|
JOIN vec_chunks v ON v.rowid = cm.rowid
|
|
1476
1574
|
WHERE cm.stale = 0
|
|
1477
1575
|
AND cm.session_id IN (${n})
|
|
1478
|
-
ORDER BY cm.session_id, cm.rowid ASC`).all(...e)}catch{return t}if(r.length===0)return t;let o=new Map;for(let a of r){let c=o.get(a.session_id);c||(c=[],o.set(a.session_id,c)),c.push(a)}for(let[a,c]of o){let u=c.filter(
|
|
1479
|
-
`),u=
|
|
1576
|
+
ORDER BY cm.session_id, cm.rowid ASC`).all(...e)}catch{return t}if(r.length===0)return t;let o=new Map;for(let a of r){let c=o.get(a.session_id);c||(c=[],o.set(a.session_id,c)),c.push(a)}for(let[a,c]of o){let u=c.filter(j=>!kT(j.text)),d=u.length>0?u:c,m=[];for(let j of d){let U=AT(j.embedding);U&&Ed(U)&&m.push(U)}if(m.length===0)continue;let h=Math.min(fd,m.length),b=Math.max(0,m.length-fd),T=m.slice(0,h),S=m.slice(b),w=qo(T),R=qo(S),D=qo(m),F=new Map;for(let j=0;j<T.length&&F.size<hd;j++)F.set(j,T[j]);for(let j=0;j<S.length&&F.size<hd;j++)F.set(b+j,S[j]);let L=Array.from(F.values());t.set(a,{session_id:a,full_mean:D,head_pool:w,tail_pool:R,sample_chunks:L})}return t}function xT(e,t){if(e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function Sd(e,t=.8){let s=new Map,n=[],r=[];for(let[d,m]of e)m.full_mean&&(n.push(d),r.push(m.full_mean));if(n.length===0)return s;let o=new Int32Array(n.length);for(let d=0;d<o.length;d++)o[d]=d;let a=d=>{let m=d;for(;o[m]!==m;)m=o[m];let h=d;for(;o[h]!==m;){let b=o[h];o[h]=m,h=b}return m},c=(d,m)=>{let h=a(d),b=a(m);h!==b&&(o[h]=b)};for(let d=0;d<n.length;d++)for(let m=d+1;m<n.length;m++)xT(r[d],r[m])>=t&&c(d,m);let u=new Map;for(let d=0;d<n.length;d++){let m=a(d),h=u.get(m);h===void 0&&(h=u.size,u.set(m,h)),s.set(n[d],h)}return s}var Pe={lo:.4,hi:.7},Ct="claude-haiku-4-5-20251001",It=50,Td=1500,yd=50,NT={same_workflow:.15,unrelated:-.2,unsure:0};function wd(e,t,s=Pe){if(s.lo>s.hi)throw new Error(`borderline band invalid: lo=${s.lo} > hi=${s.hi}`);let n=[];for(let r of e){if(r.confidence<s.lo||r.confidence>s.hi)continue;let o=t.get(r.parent_id),a=t.get(r.child_id);!o||!a||n.push({parent:o,child:a,step1:r})}return n.sort((r,o)=>r.child.started_at_ms-o.child.started_at_ms),n}function Rd(e,t=Pe){let s=0;for(let n of e)n.confidence>=t.lo&&n.confidence<=t.hi&&s++;return s}function OT(e,t){let s=e.replace(/\s+/g," ").trim();return s.length>t?s.slice(0,t-1)+"\u2026":s}function LT(e,t){if(e===null)return"unknown";let s=t-e;if(s<0)return"overlap";let n=Math.round(s/6e4);if(n<60)return`${n}m`;let r=Math.round(s/36e5);return r<24?`${r}h`:`${Math.round(s/864e5)}d`}function CT(e){let s=e.parent.recent_user_messages,n=e.child.recent_user_messages,r=s.slice(0,3),o=s.length>3?s.slice(-3):[],a=n.slice(0,3),c=d=>d.length===0?" (none captured)":d.map(m=>` - ${OT(m,500)}`).join(`
|
|
1577
|
+
`),u=LT(e.parent.ended_at_ms??e.parent.started_at_ms,e.child.started_at_ms);return["You are evaluating whether two Claude Code sessions belong to the same continuous workflow.","","A deterministic scorer rated this pair in the borderline band. Its signals:",e.step1.reasons.length===0?" (no signals fired strongly)":e.step1.reasons.map(d=>` - ${d}`).join(`
|
|
1480
1578
|
`),` step1_confidence: ${e.step1.confidence.toFixed(2)}`,"","PARENT SESSION","First user messages:",c(r),"Last user messages:",c(o),"",`CHILD SESSION (gap from parent: ${u})`,"First user messages:",c(a),"","Reply with EXACTLY one JSON object on a single line, no prose, no markdown:",'{"verdict":"same_workflow"|"unrelated"|"unsure","reason":"<one short sentence>"}',"","Guidance:",'- "same_workflow" = the child is clearly continuing what the parent was doing.','- "unrelated" = different problem, different files, different intent.','- "unsure" = could go either way; do not force a verdict.'].join(`
|
|
1481
|
-
`)}function
|
|
1579
|
+
`)}function IT(e){if(!e)return null;let t=e.trim();if(!t)return null;let s=t;try{let u=JSON.parse(t);typeof u.result=="string"&&(s=u.result.trim())}catch{}let n=s.match(/\{[\s\S]*\}/);if(!n)return null;let r;try{r=JSON.parse(n[0])}catch{return null}if(!r||typeof r!="object")return null;let o=r,a=typeof o.verdict=="string"?o.verdict.toLowerCase():"";if(a!=="same_workflow"&&a!=="unrelated"&&a!=="unsure")return null;let c=typeof o.reason=="string"&&o.reason.trim()?o.reason.trim():"";return{verdict:a,reason:c,delta:NT[a]}}async function kd(e,t={}){if(t.signal?.aborted)return null;let s=t.model??Ct,n=CT(e),r=t.spawn;if(!r)try{let a=await Promise.resolve().then(()=>(ye(),tt));if(!a.isClaudeCliAvailable())return null;r=(c,u)=>a.spawnClaudePrompt(c,[],u)}catch{return null}let o;try{o=await Promise.race([r(n,{model:s}),new Promise(a=>{t.signal&&t.signal.addEventListener("abort",()=>a({success:!1,stdout:""}),{once:!0})})])}catch{return null}return!o.success||!o.stdout?null:IT(o.stdout)}function Ad(e){let t=[];for(let s of e.proposals){let n=`${s.parent_id}::${s.child_id}`,r=e.rescored.get(n);if(!r){t.push(s);continue}let o=Math.max(0,Math.min(1,s.confidence+r.delta));if(o<e.applyThreshold)continue;let a=`LLM: ${r.verdict}${r.reason?` \u2014 ${r.reason}`:""} (${r.delta>=0?"+":""}${r.delta.toFixed(2)})`;t.push({...s,confidence:o,reasons:[...s.reasons,a]})}return t}function xd(e){return`${e.parent_id}::${e.child_id}`}async function jT(e){if(e.signal?.aborted)return null;let t,s;try{({spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(ye(),tt)))}catch{return null}if(!s())return null;let n=[];for(let o of e.sessionIds){let a=e.rowById.get(o);if(!a)continue;let c=a.alias||a.auto_title||(a.first_user_message?a.first_user_message.slice(0,120).replace(/\n/g," "):"(no title)");n.push(`- ${c}`)}let r=`Below is a chronological list of related Claude Code sessions that form one continuous workflow. Generate a single short descriptive name for the workflow as a whole. Requirements:
|
|
1482
1580
|
- 4 to 8 words
|
|
1483
1581
|
- Title-case, no trailing punctuation
|
|
1484
1582
|
- Capture the WORKFLOW (e.g., "Update Remotion deps + lint cleanup"), not any one session
|
|
@@ -1489,12 +1587,12 @@ Sessions:
|
|
|
1489
1587
|
`+n.join(`
|
|
1490
1588
|
`)+`
|
|
1491
1589
|
|
|
1492
|
-
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),a=null,c=new Promise(h=>{e.signal&&(a=()=>h(null),e.signal.addEventListener("abort",a,{once:!0}))}),u=await Promise.race([o,c]);if(a&&e.signal&&e.signal.removeEventListener("abort",a),!u||!u.success)return null;let d=u.stdout.trim();if(!d)return null;let m;try{let h=JSON.parse(d);m=typeof h.result=="string"?h.result:d}catch{m=d}return m=m.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),m?m.length>80?m.slice(0,77)+"...":m:null}catch{return null}}function
|
|
1590
|
+
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),a=null,c=new Promise(h=>{e.signal&&(a=()=>h(null),e.signal.addEventListener("abort",a,{once:!0}))}),u=await Promise.race([o,c]);if(a&&e.signal&&e.signal.removeEventListener("abort",a),!u||!u.success)return null;let d=u.stdout.trim();if(!d)return null;let m;try{let h=JSON.parse(d);m=typeof h.result=="string"?h.result:d}catch{m=d}return m=m.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),m?m.length>80?m.slice(0,77)+"...":m:null}catch{return null}}function Xo(e){return MT(e)}function Jo(e){let t=new Map;for(let o of e){let a=t.get(o.project);a||(a=[],t.set(o.project,a)),a.push(o)}let s=e.map(o=>o.id),n=new Map;try{n=bd(s)}catch{n=new Map}let r=new Map;for(let[o,a]of t){let c=new Map;for(let m of a){let h=n.get(m.id);h&&c.set(m.id,h)}let u=Sd(c,.8),d=[];for(let m of a){let h=DT(m,n,u);h&&d.push(h)}r.set(o,d)}return{byProject:t,scannablesByProject:r}}function Od(e){return f().prepare(`SELECT COUNT(DISTINCT t.id) AS n
|
|
1493
1591
|
FROM threads t
|
|
1494
1592
|
JOIN thread_edges te ON te.thread_id = t.id
|
|
1495
1593
|
JOIN sessions s ON s.id = te.session_id
|
|
1496
1594
|
JOIN projects p ON p.id = s.project_id
|
|
1497
|
-
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function
|
|
1595
|
+
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function MT(e){let t=f(),s={},n="1=1 AND s.message_count > 2";return n+=" AND COALESCE(s.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'",e.project&&(n+=" AND p.name = @project",s.project=e.project),t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1498
1596
|
s.first_user_message, s.auto_title,
|
|
1499
1597
|
NULLIF(sa.alias, '') AS alias,
|
|
1500
1598
|
s.file_path
|
|
@@ -1502,66 +1600,21 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1502
1600
|
JOIN projects p ON p.id = s.project_id
|
|
1503
1601
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1504
1602
|
WHERE ${n}
|
|
1505
|
-
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function
|
|
1603
|
+
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function DT(e,t,s){if(!e.started_at)return null;let n=Date.parse(e.started_at);if(!Number.isFinite(n))return null;let r=e.ended_at?Date.parse(e.ended_at):null,o=wn(e.file_path,{maxUserMessages:7}),a=t?.get(e.id);return{id:e.id,started_at_ms:n,ended_at_ms:Number.isFinite(r)?r:null,first_user_message:e.first_user_message,recent_user_messages:o.recent_user_messages,auto_title:e.auto_title,touched_files:o.touched_files,mean_embedding:a?.full_mean??null,head_pool:a?.head_pool??null,tail_pool:a?.tail_pool??null,sample_chunks:a?.sample_chunks??[],cluster_id:s?.get(e.id)??null,authored_paths:o.authored_paths,authored_content:o.authored_content}}function Nd(e){let t=e.alias||e.auto_title||(e.first_user_message?e.first_user_message.slice(0,60).replace(/\n/g," ").trim():`thread from ${e.id.slice(0,8)}`);return t.length>80?t.slice(0,77)+"...":t}async function Ld(e){if(!e.rescore.enabled)return{proposals:e.proposals,ran:!1,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};let t=e.rescore.band??Pe,s=e.rescore.cap??It,n=e.rescore.model??Ct,r=new Map(e.scannables.map(b=>[b.id,b])),o=wd(e.proposals,r,t);if(o.length===0)return{proposals:e.proposals,ran:!0,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};if(o.length>s)return{proposals:e.proposals,ran:!1,considered:o.length,promoted:0,demoted:0,unsure:0,failed:0,capped:!0};let a=new Map,c=0,u=0,d=0,m=0;for(let b=0;b<o.length&&!e.signal?.aborted;b++){let T=o[b],S=await kd(T,{model:n,signal:e.signal});S?(a.set(xd(T.step1),S),S.verdict==="same_workflow"?c++:S.verdict==="unrelated"?u++:d++):m++,e.onProgress?.({phase:"rescoring",current:b+1,total:o.length,verdict:S?.verdict??"failed"})}return{proposals:Ad({proposals:e.proposals,rescored:a,applyThreshold:e.applyThreshold}),ran:!0,considered:o.length,promoted:c,demoted:u,unsure:d,failed:m,capped:!1}}async function Cd(e){let t=f(),s=new Map(e.rows.map(d=>[d.id,d])),n=Ql(e.edges,e.scannables),r=new Date().toISOString(),o=[],a=new Map,c=0;for(let d of n){if(c++,e.signal?.aborted)break;let m=s.get(d.rootId),h=Nd(m);if(!e.llmNames){a.set(d.rootId,h),e.onProgress?.({phase:"naming",current:c,total:n.length,thread_name:h});continue}let T=await jT({rootRow:m,sessionIds:d.sessionIds,rowById:s,signal:e.signal,model:e.model})??h;a.set(d.rootId,T),e.onProgress?.({phase:"naming",current:c,total:n.length,thread_name:T})}return t.transaction(()=>{for(let d of n){if(!a.has(d.rootId))continue;let m=s.get(d.rootId),h=`auto-scan-${vT()}`,b=a.get(d.rootId)??Nd(m),T=`Auto-detected workflow chain (${d.sessionIds.length} sessions). Source: auto-scan-v1.`;t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(h,b,T,r),t.prepare(`INSERT INTO thread_edges
|
|
1506
1604
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1507
1605
|
VALUES (?, ?, NULL, 'origin', 1.0, 'auto-scan-v1', ?)`).run(h,d.rootId,r);let S=new Map;for(let w of e.edges)d.sessionIds.includes(w.child_id)&&S.set(w.child_id,w);for(let w of d.sessionIds){if(w===d.rootId)continue;let R=S.get(w);R&&t.prepare(`INSERT INTO thread_edges
|
|
1508
1606
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1509
|
-
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(h,w,R.parent_id,R.confidence,r)}o.push({thread_id:h,name:b,session_count:d.sessionIds.length})}})(),{project:e.project,threads:o}}var
|
|
1510
|
-
`)}function
|
|
1607
|
+
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(h,w,R.parent_id,R.confidence,r)}o.push({thread_id:h,name:b,session_count:d.sessionIds.length})}})(),{project:e.project,threads:o}}var jt=new Map,_s=new Map,PT=300*1e3;function vt(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function $T(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{jt.delete(e.jobId),_s.get(e.project)===e.jobId&&_s.delete(e.project)},PT),e.cleanupTimer.unref?.())}function vn(e){return{jobId:e.jobId,project:e.project,threshold:e.threshold,llm_names:e.llmNames,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total_threads:e.totalThreads,threads_named:e.threadsNamed,edges_written:e.edgesWritten,threads_created:e.threadsCreated,current_thread_name:e.currentThreadName,error:e.error,rescore:e.rescore}}var UT=500,HT=30,Go="claude-haiku-4-5-20251001",Id={"claude-haiku-4-5-20251001":{in:1,out:5,label:"Haiku 4.5"},"claude-sonnet-4-6":{in:3,out:15,label:"Sonnet 4.6"},"claude-opus-4-7":{in:15,out:75,label:"Opus 4.7"}};function vd(e){return Id[e]??Id[Go]}function jd(e){let t=typeof e.threshold=="number"?e.threshold:Tn;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let s=e.model??Go,n="",r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Pe.lo,hi:e.llm_rescore?.band_hi??Pe.hi};if(r&&(!Number.isFinite(o.lo)||!Number.isFinite(o.hi)||o.lo<0||o.hi>1||o.lo>o.hi))return{error:"rescore band must satisfy 0 \u2264 lo \u2264 hi \u2264 1"};let a=e.llm_rescore?.model??Ct,c=Xo({project:e.project});if(c.length===0)return{eligible_sessions:0,proposed_edges:0,estimated_threads:0,estimated_llm_calls:0,estimated_input_tokens_max:0,estimated_output_tokens_max:0,estimated_cost_usd_max:0,existing_auto_scan_threads:0,plan_window_estimate:We(0,s),accuracy_caveat:n,model:s,llm_rescore:r?{enabled:!0,band:o,cap:It,estimated_borderline_pairs:0,estimated_input_tokens_max:0,estimated_output_tokens_max:0,estimated_cost_usd_max:0,plan_window_estimate:We(0,a),exceeds_cap:!1,model:a}:null};let{byProject:u,scannablesByProject:d}=Jo(c),m=u.get(e.project)??[],h=d.get(e.project)??[],b=r?Math.min(t,o.lo):t,T=wt(h,b),S=r?T.filter(i=>i.confidence>=t):T,w=Md(S),R=w*UT,D=w*HT,F=vd(s),L=R/1e6*F.in,j=D/1e6*F.out,U=L+j,ie=We(w,s),B=Od(e.project),se=null;if(r){let i=Rd(T,o),l=vd(a),p=i*Td,g=i*yd,_=p/1e6*l.in+g/1e6*l.out;se={enabled:!0,band:o,cap:It,estimated_borderline_pairs:i,estimated_input_tokens_max:p,estimated_output_tokens_max:g,estimated_cost_usd_max:Math.round(_*1e4)/1e4,plan_window_estimate:We(i,a),exceeds_cap:i>It,model:a}}return{eligible_sessions:m.length,proposed_edges:S.length,estimated_threads:w,estimated_llm_calls:w,estimated_input_tokens_max:R,estimated_output_tokens_max:D,estimated_cost_usd_max:Math.round(U*1e4)/1e4,existing_auto_scan_threads:B,plan_window_estimate:ie,accuracy_caveat:n,model:s,llm_rescore:se}}function Md(e){let t=new Map,s=o=>{let a=o;for(;t.get(a)!==a;)a=t.get(a);return a},n=(o,a)=>{let c=s(o),u=s(a);c!==u&&t.set(c,u)};for(let o of e)t.has(o.parent_id)||t.set(o.parent_id,o.parent_id),t.has(o.child_id)||t.set(o.child_id,o.child_id),n(o.parent_id,o.child_id);let r=new Set;for(let o of t.keys())r.add(s(o));return r.size}function Dd(e){let t=typeof e.threshold=="number"?e.threshold:Tn;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let s=e.llm_names??!0,n=e.model??Go,r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Pe.lo,hi:e.llm_rescore?.band_hi??Pe.hi};if(r&&(!Number.isFinite(o.lo)||!Number.isFinite(o.hi)||o.lo<0||o.hi>1||o.lo>o.hi))return{error:"rescore band must satisfy 0 \u2264 lo \u2264 hi \u2264 1"};let a=e.llm_rescore?.model??Ct,c=_s.get(e.project);if(c){let U=jt.get(c);if(U&&U.status==="running")return{jobId:c,reused:!0};_s.delete(e.project)}let u=Xo({project:e.project});if(u.length===0)return{error:`no eligible sessions in project "${e.project}"`};let{byProject:d,scannablesByProject:m}=Jo(u),h=d.get(e.project),b=m.get(e.project);if(!h||!b)return{error:`project "${e.project}" not found`};let T=r?Math.min(t,o.lo):t,S=wt(b,T),w=r?S.filter(U=>U.confidence>=t):S;if(S.length===0||w.length===0&&!r)return{error:"no edges above threshold; nothing to apply"};let R=FT(),D=new AbortController,F=Md(w),L=new Date().toISOString(),j={jobId:R,project:e.project,threshold:t,llmNames:s,status:"running",startedAt:L,endedAt:null,events:[],waiters:new Set,controller:D,totalThreads:F,threadsNamed:0,edgesWritten:0,threadsCreated:0,currentThreadName:null,error:null,cleanupTimer:null,rescore:r?{enabled:!0,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1}:null};return jt.set(R,j),_s.set(e.project,R),(async()=>{await Promise.resolve();try{vt(j,"progress",{phase:"linking",edges_proposed:S.length,estimated_threads:F});let U=S;if(r){let B=await Ld({proposals:S,scannables:b,applyThreshold:t,rescore:{enabled:!0,band:o,model:a},signal:D.signal,onProgress:se=>{se.phase==="rescoring"&&vt(j,"progress",{phase:"rescoring",current:se.current,total:se.total,verdict:se.verdict})}});U=B.proposals,j.rescore={enabled:!0,considered:B.considered,promoted:B.promoted,demoted:B.demoted,unsure:B.unsure,failed:B.failed,capped:B.capped}}else U=S.filter(B=>B.confidence>=t);if(U.length===0){j.status=D.signal.aborted?"cancelled":"done",j.endedAt=new Date().toISOString(),vt(j,"done",{...vn(j),threads:[]});return}let ie=await Cd({project:e.project,rows:h,edges:U,scannables:b,llmNames:s,model:n,signal:D.signal,onProgress:B=>{B.phase==="naming"&&(j.threadsNamed=B.current,j.currentThreadName=B.thread_name??null,vt(j,"progress",{phase:"naming",current:B.current,total:B.total,thread_name:B.thread_name}))}});j.threadsCreated=ie.threads.length,j.edgesWritten=U.length+ie.threads.length,j.status=D.signal.aborted?"cancelled":"done",j.endedAt=new Date().toISOString(),vt(j,"done",{...vn(j),threads:ie.threads})}catch(U){j.status="failed",j.endedAt=new Date().toISOString(),j.error=U instanceof Error?U.message:String(U??"unknown error"),vt(j,"done",vn(j))}finally{$T(j)}})(),{jobId:R,reused:!1}}async function*Fd(e,t=0){let s=jt.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function Pd(e){let t=jt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Yo(e){let t=jt.get(e);return t?vn(t):null}vs();import{randomUUID as BT}from"node:crypto";var jn=new Map;function $d(e){let t={id:BT(),status:"pending",createdAt:new Date().toISOString(),finishedAt:null,total:e,completed:0,results:[],error:null,controller:new AbortController,listeners:new Set};return jn.set(t.id,t),t}function Mn(e){return jn.get(e)}function Mt(e,t){for(let s of e.listeners)s(t)}function Ud(e,t){return e.listeners.add(t),()=>{e.listeners.delete(t)}}function Hd(e){let t=jn.get(e);return t?(t.controller.abort(),t.status="cancelled",t.finishedAt=new Date().toISOString(),Mt(t,{type:"status",status:"cancelled"}),!0):!1}function Bd(e){return jn.delete(e)}pt();function Dn(e){let{session:t,knownTags:s,minTags:n,maxTags:r}=e,o=s.slice(0,50).map(c=>c.tag).join(", "),a=t.current_tags.length>0?`already applied, do not repeat: [${t.current_tags.join(", ")}]`:"currently has no tags";return[`You are tagging a software engineering session. Produce ${n}-${r} concise, lowercase, hyphen-separated tags that describe:`," - the domain or subsystem touched (auth, db, frontend, ...)"," - the kind of work (bugfix, feature, refactor, research, ...)"," - any specific tool, library, or product if prominent","","Prefer tags from the known-tags list below when applicable \u2014 consistency over creativity. Never invent marketing-sounding labels. Never tag based on speculation; only on observed work.","",`known tags (most used first, up to 50): ${o||"(none yet)"}`,"","Session:",` project: ${t.project}`,t.alias?` alias: ${JSON.stringify(t.alias)}`:" alias: (none)",t.git_branch?` git_branch: ${t.git_branch}`:"",` ${a}`," first_user_message:",Wd(t.first_user_message," ")," message_sample:",Wd(t.message_sample," "),"","Return a single JSON object matching this schema exactly, with no prose before or after:",`{"tags": string[] length ${n}-${r}, "confidence": number 0-1, "rationale": string one short sentence}`].filter(Boolean).join(`
|
|
1608
|
+
`)}function Wd(e,t){return e.split(`
|
|
1511
1609
|
`).map(s=>t+s).join(`
|
|
1512
|
-
`)}pt();import{z as
|
|
1513
|
-
|
|
1514
|
-
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function CT(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function IT(e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content)){let t=[];for(let s of e.content)if(s&&typeof s=="object"){let n=s;n.type==="text"&&typeof n.text=="string"?t.push(n.text):n.type==="image"&&t.push("[image]")}return t.join(`
|
|
1515
|
-
`)}return""}function vT(e){if(!e)return{text:"",toolNames:[]};if(typeof e.content=="string")return{text:Wo(e.content),toolNames:[]};if(!Array.isArray(e.content))return{text:"",toolNames:[]};let t=[],s=[];for(let n of e.content)if(!(!n||typeof n!="object")){if(n.type==="text"&&typeof n.text=="string"){t.push(Wo(n.text));continue}if(n.type==="tool_use"&&typeof n.name=="string"){s.push(n.name);let r=n.input!=null?CT(n.input):"",o=Ud(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${n.name}\`**
|
|
1516
|
-
|
|
1517
|
-
\`\`\`json
|
|
1518
|
-
${o}
|
|
1519
|
-
\`\`\``);continue}if(n.type==="tool_result"){let r=Wo(IT(n));if(r){let o=Ud(r,"tool result");t.push(`**Tool result**
|
|
1520
|
-
|
|
1521
|
-
\`\`\`
|
|
1522
|
-
${o}
|
|
1523
|
-
\`\`\``)}else t.push("_(tool result was empty or image-only)_");continue}if(n.type==="image"){t.push("_(image)_");continue}t.push(`_(unknown block: ${n.type})_`)}return{text:t.join(`
|
|
1524
|
-
|
|
1525
|
-
`),toolNames:s}}async function*$d(e){let t=NT(e,{encoding:"utf8"}),s=OT({input:t,crlfDelay:1/0});for await(let n of s){if(!n.trim())continue;let r;try{r=JSON.parse(n)}catch{continue}if(!r.uuid||!r.sessionId)continue;let{text:o,toolNames:a}=vT(r.message);yield{uuid:r.uuid,parentUuid:r.parentUuid??null,sessionId:r.sessionId,type:r.type??"unknown",role:r.message?.role??null,timestamp:r.timestamp??null,isSidechain:r.isSidechain===!0,cwd:r.cwd??null,gitBranch:r.gitBranch??null,version:r.version??null,contentText:o,toolNames:a,raw:n,usage:Xo(r.message),model:r.message?.model??null}}}function Hd(e,t,s){e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let n=e.prepare(`
|
|
1526
|
-
INSERT INTO message_usage (
|
|
1527
|
-
message_uuid, session_id, model,
|
|
1528
|
-
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
1529
|
-
timestamp
|
|
1530
|
-
) VALUES (
|
|
1531
|
-
@uuid, @session_id, @model,
|
|
1532
|
-
@input, @output, @cc, @cr,
|
|
1533
|
-
@ts
|
|
1534
|
-
)
|
|
1535
|
-
ON CONFLICT(message_uuid) DO UPDATE SET
|
|
1536
|
-
model = excluded.model,
|
|
1537
|
-
input_tokens = excluded.input_tokens,
|
|
1538
|
-
output_tokens = excluded.output_tokens,
|
|
1539
|
-
cache_create_tokens = excluded.cache_create_tokens,
|
|
1540
|
-
cache_read_tokens = excluded.cache_read_tokens,
|
|
1541
|
-
timestamp = excluded.timestamp
|
|
1542
|
-
`);for(let r of s)r.usage&&r.role==="assistant"&&n.run({uuid:r.uuid,session_id:t,model:r.model,input:r.usage.inputTokens,output:r.usage.outputTokens,cc:r.usage.cacheCreateTokens,cr:r.usage.cacheReadTokens,ts:r.timestamp})}function Un(e,t){let s=e.prepare(`SELECT
|
|
1543
|
-
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
1544
|
-
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
1545
|
-
COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
|
|
1546
|
-
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens
|
|
1547
|
-
FROM message_usage WHERE session_id = ?`).get(t),n=e.prepare(`SELECT model, SUM(output_tokens) AS out
|
|
1548
|
-
FROM message_usage
|
|
1549
|
-
WHERE session_id = ? AND model IS NOT NULL
|
|
1550
|
-
GROUP BY model
|
|
1551
|
-
ORDER BY out DESC LIMIT 1`).get(t);e.prepare(`UPDATE sessions SET
|
|
1552
|
-
total_input_tokens = @input,
|
|
1553
|
-
total_output_tokens = @output,
|
|
1554
|
-
total_cache_create_tokens = @cc,
|
|
1555
|
-
total_cache_read_tokens = @cr,
|
|
1556
|
-
primary_model = @model
|
|
1557
|
-
WHERE id = @id`).run({id:t,input:s.input_tokens,output:s.output_tokens,cc:s.cache_create_tokens,cr:s.cache_read_tokens,model:n?.model??null})}var jT=500,$n=new Set,Bd=null,Hn=!1;function Wd(){return $n.size}function MT(){return`
|
|
1610
|
+
`)}pt();import{z as fs}from"zod";var WT=fs.object({tags:fs.array(fs.string()).min(1),confidence:fs.number().min(0).max(1),rationale:fs.string().min(1).max(500)});function qT(e){let t=e.trim();return t.startsWith("```")?t.replace(/^```(?:json)?\s*/i,"").replace(/```$/i,"").trim():t}function Fn(e,t={}){let s=t.maxTags??10,n;try{n=JSON.parse(qT(e))}catch{return{ok:!1,reason:"not valid JSON"}}let r=WT.safeParse(n);if(!r.success)return{ok:!1,reason:r.error.issues.map(c=>c.message).join("; ")};let o=r.data.tags.map(c=>Qe(c)).filter(c=>c.length>0),a=Array.from(new Set(o)).slice(0,s);return a.length===0?{ok:!1,reason:"no usable tags after normalization"}:{ok:!0,data:{tags:a,confidence:r.data.confidence,rationale:r.data.rationale}}}import XT from"@anthropic-ai/sdk";async function Pn(e){let n=(await new XT({apiKey:e.apiKey}).messages.create({model:e.model,max_tokens:512,temperature:.2,messages:[{role:"user",content:e.prompt}]},e.signal?{signal:e.signal}:void 0)).content.find(r=>r.type==="text");if(!n||n.type!=="text")throw new Error("Anthropic response contained no text block");return n.text}function JT(e){if(!(e instanceof Error))return!1;let t=e;return t.status===429||t.status===502||t.status===503||t.status===504}async function $n(e,t={}){let s=t.maxAttempts??3,n=t.baseDelayMs??500,r;for(let o=0;o<s;o++)try{return await e()}catch(a){if(r=a,!JT(a)||o===s-1)throw a;await new Promise(c=>setTimeout(c,n*Math.pow(2,o)))}throw r}async function qd(e,t){e.status="running",Mt(e,{type:"status",status:"running"});let s=dt();for(let n of t.sessions){if(e.controller.signal.aborted)break;let r=Dn({session:n,knownTags:s,minTags:t.minTags,maxTags:t.maxTags}),o={sessionId:n.id,project:n.project,alias:n.alias,first_user_message:n.first_user_message,current_tags:n.current_tags,suggestion:null,error:null,applied:!1};try{let a=await $n(()=>Pn({apiKey:t.apiKey,model:t.model,prompt:r,signal:e.controller.signal})),c=Fn(a,{maxTags:t.maxTags});c.ok?o.suggestion=c.data:o.error=c.reason}catch(a){o.error=a instanceof Error?a.message:String(a)}e.results.push(o),e.completed+=1,Mt(e,{type:"result",result:o}),Mt(e,{type:"progress",completed:e.completed,total:e.total})}if(!e.controller.signal.aborted){e.status="completed",e.finishedAt=new Date().toISOString();let n=e.results.filter(o=>o.suggestion&&!o.error).length,r=e.results.filter(o=>o.error).length;Mt(e,{type:"done",summary:{ok:n,failed:r}})}}function Xd(e,t){let s=0,n=0;for(let r of t){let o=e.results.find(a=>a.sessionId===r.sessionId);if(o){for(let a of r.tags)try{let{added:c}=ut(r.sessionId,a);c&&(s+=1)}catch(c){console.error("[scanner] apply failed:",r.sessionId,a,c),n+=1}o.applied=!0}}return{applied:s,failed:n}}pt();vs();var Z={running:!1,status:"idle",processed:0,total:0,currentSessionId:null,lastError:null,lastRunAt:null,controller:null},zo=new Set;function Es(){return{status:Z.status,processed:Z.processed,total:Z.total,currentSessionId:Z.currentSessionId,lastError:Z.lastError,lastRunAt:Z.lastRunAt}}function Jd(e){return zo.add(e),()=>{zo.delete(e)}}function hs(){let e=Es();for(let t of zo)t(e)}async function Un(){let e=Fe();if(!(!e.enabled||!e.autopilot)&&!(e.backend!=="api"||!e.apiKey)&&!Z.running){Z.running=!0,Z.status="scanning",Z.processed=0,Z.total=0,Z.currentSessionId=null,Z.lastError=null,Z.controller=new AbortController,hs();try{let t=mt({untaggedOnly:!0,limit:200});if(Z.total=t.length,hs(),t.length===0){Z.status="idle",Z.lastRunAt=new Date().toISOString();return}let s=dt();for(let n of t){if(Z.controller.signal.aborted)break;let r=Fe();if(!r.autopilot||!r.enabled||r.backend!=="api"||!r.apiKey)break;Z.currentSessionId=n.id,hs();let o=Dn({session:n,knownTags:s,minTags:r.minTagsPerSession,maxTags:r.maxTagsPerSession});try{let a=await $n(()=>Pn({apiKey:r.apiKey,model:r.model,prompt:o,signal:Z.controller.signal})),c=Fn(a,{maxTags:r.maxTagsPerSession});if(c.ok)for(let u of c.data.tags)try{ut(n.id,u)}catch(d){console.error("[autopilot] addTag failed:",n.id,u,d)}}catch(a){console.error("[autopilot] scan failed for",n.id,a)}Z.processed+=1,hs(),await new Promise(a=>setTimeout(a,1500))}Z.status="idle",Z.lastRunAt=new Date().toISOString()}catch(t){Z.status="error",Z.lastError=t instanceof Error?t.message:String(t),console.error("[autopilot] fatal:",t)}finally{Z.running=!1,Z.currentSessionId=null,Z.controller=null,hs()}}}import{execFileSync as GT}from"node:child_process";import{existsSync as Gd,readFileSync as YT,writeFileSync as Yd}from"node:fs";import{homedir as zT}from"node:os";import{dirname as KT,join as VT,resolve as ZT}from"node:path";import{fileURLToPath as QT}from"node:url";var Dt=VT(zT(),".claude.json"),ey=KT(QT(import.meta.url)),ty=ZT(ey,"..","mcp","server.js");function Vo(){if(!Gd(Dt))return{};try{let e=YT(Dt,"utf8"),t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}catch(e){return console.error("[mcp-installer] failed to parse ~/.claude.json:",e),{}}}var Ko=new Map;function sy(e){let t=Ko.get(e);if(t!==void 0)return t;try{return GT("command",["-v",e],{stdio:"ignore"}),Ko.set(e,!0),!0}catch{return Ko.set(e,!1),!1}}function ny(){return sy("claude-recall-mcp")?{command:"claude-recall-mcp",args:[]}:{command:process.execPath,args:[ty]}}function qe(){let t=Vo().mcpServers?.recall;return{configPath:Dt,configExists:Gd(Dt),installed:!!(t&&typeof t.command=="string"),command:t?.command??null,args:t?.args??null}}function zd(){let e=Vo(),t=e.mcpServers??{},s=ny(),n=t.recall;if(n?.command===s.command&&JSON.stringify(n?.args??[])===JSON.stringify(s.args))return qe();let o={command:s.command};s.args.length>0&&(o.args=s.args);let a={...e,mcpServers:{...t,recall:o}};return Yd(Dt,JSON.stringify(a,null,2)),qe()}function Kd(){let e=Vo(),t=e.mcpServers??{};if(!t.recall)return qe();let{recall:s,...n}=t,r={...e,mcpServers:n};return Yd(Dt,JSON.stringify(r,null,2)),qe()}import{existsSync as Vd,mkdirSync as ry,readFileSync as oy,writeFileSync as Zd}from"node:fs";import{homedir as iy}from"node:os";import{join as Qd}from"node:path";import{z as Xe}from"zod";function ep(){return process.env.RECALL_HOME??Qd(iy(),".recall")}function tp(){let e=ep();Vd(e)||ry(e,{recursive:!0})}function Qo(){return Qd(ep(),"onboarding.json")}var Hn=Xe.object({version:Xe.literal(1).default(1),completed:Xe.boolean().default(!1),skipped:Xe.boolean().default(!1),finishedAt:Xe.string().nullable().default(null),completedSteps:Xe.array(Xe.string().max(100)).max(50).default([]),threadsIntroSeen:Xe.boolean().default(!1)}),Zo={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:[],threadsIntroSeen:!1};function Bn(){let e=Qo();if(!Vd(e))return{...Zo};try{let t=JSON.parse(oy(e,"utf8")),s=Hn.safeParse(t);return s.success?s.data:{...Zo}}catch(t){return console.error("[onboarding-state] failed to parse onboarding.json, using defaults:",t),{...Zo}}}function sp(e){tp();let t=Bn(),s=Hn.parse({...t,...e,completedSteps:ay([...t.completedSteps??[],...e.completedSteps??[]]),version:1});return Zd(Qo(),JSON.stringify(s,null,2)),s}function np(){tp();let e=Bn(),t={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:e.completedSteps,threadsIntroSeen:e.threadsIntroSeen};return Zd(Qo(),JSON.stringify(t,null,2)),t}function ay(e){let t=new Set,s=[];for(let n of e)t.has(n)||(t.add(n),s.push(n));return s}ye();br();Er();H();H();var cy=500,Wn=new Set,rp=null,qn=!1;function op(){return Wn.size}function ly(){return`
|
|
1558
1611
|
SELECT m.uuid, m.session_id, m.timestamp, m.raw_json
|
|
1559
1612
|
FROM messages m
|
|
1560
1613
|
LEFT JOIN message_usage mu ON mu.message_uuid = m.uuid
|
|
1561
1614
|
WHERE m.role = 'assistant' AND mu.message_uuid IS NULL
|
|
1562
1615
|
AND m.uuid NOT IN (SELECT value FROM json_each(?))
|
|
1563
1616
|
LIMIT ?
|
|
1564
|
-
`}function
|
|
1617
|
+
`}function ip(e,t){let s=t.limit??Number.MAX_SAFE_INTEGER,n=Math.max(1,t.chunkSize??cy),r=e.prepare(ly()),o=e.prepare(`
|
|
1565
1618
|
INSERT INTO message_usage (
|
|
1566
1619
|
message_uuid, session_id, model,
|
|
1567
1620
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1571,7 +1624,7 @@ ${o}
|
|
|
1571
1624
|
@input, @output, @cc, @cr, @ts
|
|
1572
1625
|
)
|
|
1573
1626
|
ON CONFLICT(message_uuid) DO NOTHING
|
|
1574
|
-
`),a=0,c=0,u=new Set;for(;a<s;){let d=Math.min(n,s-a),m=JSON.stringify([
|
|
1627
|
+
`),a=0,c=0,u=new Set;for(;a<s;){let d=Math.min(n,s-a),m=JSON.stringify([...Wn]),h=r.all(m,d);if(h.length===0)break;let b=new Set;if(e.transaction(()=>{for(let S of h){let w;try{w=JSON.parse(S.raw_json)}catch{Wn.add(S.uuid);continue}let R=Wr(w.message);if(!R){Wn.add(S.uuid);continue}o.run({uuid:S.uuid,session_id:S.session_id,model:w.message?.model??null,input:R.inputTokens,output:R.outputTokens,cc:R.cacheCreateTokens,cr:R.cacheReadTokens,ts:S.timestamp}),c+=1,b.add(S.session_id)}for(let S of b)Qs(e,S),u.add(S)})(),a+=h.length,t.onProgress?.({scanned:a,inserted:c,sessionsTouched:u.size,done:h.length<d}),h.length<d)break}return{scanned:a,inserted:c,sessionsTouched:u.size,done:!0}}function ap(e={}){return ip(f(),e)}function cp(e={}){return qn?!1:(qn=!0,queueMicrotask(()=>{try{let t=ip(f(),e);rp={scanned:t.scanned,inserted:t.inserted,sessionsTouched:t.sessionsTouched,finishedAt:new Date().toISOString()}}catch(t){console.error("[stats.backfill] failed:",t)}finally{qn=!1}}),!0)}function lp(){return qn}function ei(){return rp}var uy=[[/opus[-_ ]?4[-_. ]?7/i,{label:"Opus 4.7",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/opus[-_ ]?4[-_. ]?6/i,{label:"Opus 4.6",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4[-_. ]?6/i,{label:"Sonnet 4.6",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/sonnet[-_ ]?4[-_. ]?5/i,{label:"Sonnet 4.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku[-_ ]?4[-_. ]?5/i,{label:"Haiku 4.5",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}],[/opus[-_ ]?4(?!.*[5-9])/i,{label:"Opus 4",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4(?!.*[5-9])/i,{label:"Sonnet 4",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?7[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?7/i,{label:"Sonnet 3.7",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?5/i,{label:"Sonnet 3.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?haiku|haiku[-_ ]?3[-_. ]?5/i,{label:"Haiku 3.5",inputCentsPerMtok:80,outputCentsPerMtok:400,cacheCreateCentsPerMtok:100,cacheReadCentsPerMtok:8}],[/3[-_ ]?opus|opus[-_ ]?3/i,{label:"Opus 3",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/3[-_ ]?haiku|haiku(?!.*3[-_. ]?5)/i,{label:"Haiku 3",inputCentsPerMtok:25,outputCentsPerMtok:125,cacheCreateCentsPerMtok:30,cacheReadCentsPerMtok:3}],[/opus/i,{label:"Opus",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet/i,{label:"Sonnet",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku/i,{label:"Haiku",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}]],up={label:"unknown",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30};function Je(e){if(!e)return up;for(let[t,s]of uy)if(t.test(e))return s;return up}function Ae(e,t){if(e.byModel&&Object.keys(e.byModel).length>0){let a={input:0,output:0,cacheCreate:0,cacheRead:0},c=0;for(let[d,m]of Object.entries(e.byModel)){let h=Je(d);a.input+=m.inputTokens/1e6*h.inputCentsPerMtok,a.output+=m.outputTokens/1e6*h.outputCentsPerMtok,a.cacheCreate+=m.cacheCreateTokens/1e6*h.cacheCreateCentsPerMtok,a.cacheRead+=m.cacheReadTokens/1e6*h.cacheReadCentsPerMtok,c+=m.inputTokens+m.outputTokens+m.cacheCreateTokens+m.cacheReadTokens}let u=a.input+a.output+a.cacheCreate+a.cacheRead;return{cents:u,dollars:u/100,totalTokens:c,parts:a}}let s=Je(t),n={input:e.inputTokens/1e6*s.inputCentsPerMtok,output:e.outputTokens/1e6*s.outputCentsPerMtok,cacheCreate:e.cacheCreateTokens/1e6*s.cacheCreateCentsPerMtok,cacheRead:e.cacheReadTokens/1e6*s.cacheReadCentsPerMtok},r=n.input+n.output+n.cacheCreate+n.cacheRead,o=e.inputTokens+e.outputTokens+e.cacheCreateTokens+e.cacheReadTokens;return{cents:r,dollars:r/100,totalTokens:o,parts:n}}function Ft(e){let t=e/100;return t===0?"$0.00":t<.01?"<$0.01":t<1?`$${t.toFixed(2)}`:t<100?`$${t.toFixed(2)}`:t<1e4?`$${t.toFixed(0)}`:`$${(t/1e3).toFixed(1)}k`}function Pt(e){return!Number.isFinite(e)||e<0?"0":e<1e3?String(Math.round(e)):e<1e6?`${(e/1e3).toFixed(1)}k`:e<1e9?`${(e/1e6).toFixed(2)}M`:e<1e12?`${(e/1e9).toFixed(2)}B`:`${(e/1e12).toFixed(2)}T`}function ti(e){let t=new Map;for(let n of e){let r=n.model??null,o=t.get(r)??{inputTokens:0,outputTokens:0,cacheCreateTokens:0,cacheReadTokens:0,messageCount:0};o.inputTokens+=n.input_tokens,o.outputTokens+=n.output_tokens,o.cacheCreateTokens+=n.cache_create_tokens,o.cacheReadTokens+=n.cache_read_tokens,o.messageCount+=n.n,t.set(r,o)}let s=[];for(let[n,r]of t.entries()){let o=Ae({inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens},n);s.push({model:n,modelLabel:Je(n).label,inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens,messageCount:r.messageCount,cost:o})}return s.sort((n,r)=>r.cost.cents-n.cost.cents)}function si(e){let t={};for(let s of e)t[s.model??"__unknown__"]={inputTokens:s.inputTokens,outputTokens:s.outputTokens,cacheCreateTokens:s.cacheCreateTokens,cacheReadTokens:s.cacheReadTokens};return{byModel:t}}function dp(e){let t=f(),s=t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1575
1628
|
s.message_count,
|
|
1576
1629
|
s.total_input_tokens, s.total_output_tokens,
|
|
1577
1630
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
@@ -1586,7 +1639,7 @@ ${o}
|
|
|
1586
1639
|
COUNT(*) AS n
|
|
1587
1640
|
FROM message_usage
|
|
1588
1641
|
WHERE session_id = ?
|
|
1589
|
-
GROUP BY model`).all(e),r=
|
|
1642
|
+
GROUP BY model`).all(e),r=ti(n),o=s.total_input_tokens??0,a=s.total_output_tokens??0,c=s.total_cache_create_tokens??0,u=s.total_cache_read_tokens??0,d=Ae({inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:u,...si(r)},s.primary_model);return{sessionId:s.id,project:s.project,startedAt:s.started_at,endedAt:s.ended_at,messageCount:s.message_count,primaryModel:s.primary_model,primaryModelLabel:Je(s.primary_model).label,inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:u,totalTokens:d.totalTokens,cost:d,byModel:r,display:{dollars:Ft(d.cents),tokens:Pt(d.totalTokens),model:Je(s.primary_model).label}}}function pp(e){let t=f(),s=t.prepare("SELECT id, name FROM projects WHERE name = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT mu.model,
|
|
1590
1643
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1591
1644
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1592
1645
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1595,12 +1648,12 @@ ${o}
|
|
|
1595
1648
|
FROM message_usage mu
|
|
1596
1649
|
JOIN sessions s ON s.id = mu.session_id
|
|
1597
1650
|
WHERE s.project_id = ?
|
|
1598
|
-
GROUP BY mu.model`).all(s.id),r=
|
|
1651
|
+
GROUP BY mu.model`).all(s.id),r=ti(n),o=t.prepare(`SELECT COALESCE(SUM(total_input_tokens), 0) AS input_tokens,
|
|
1599
1652
|
COALESCE(SUM(total_output_tokens), 0) AS output_tokens,
|
|
1600
1653
|
COALESCE(SUM(total_cache_create_tokens), 0) AS cache_create_tokens,
|
|
1601
1654
|
COALESCE(SUM(total_cache_read_tokens), 0) AS cache_read_tokens,
|
|
1602
1655
|
COUNT(*) AS session_count
|
|
1603
|
-
FROM sessions WHERE project_id = ?`).get(s.id),a=Ae({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...
|
|
1656
|
+
FROM sessions WHERE project_id = ?`).get(s.id),a=Ae({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...si(r)},null),u=t.prepare(`SELECT s.id, sa.alias, s.started_at,
|
|
1604
1657
|
s.total_input_tokens, s.total_output_tokens,
|
|
1605
1658
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
1606
1659
|
s.primary_model
|
|
@@ -1611,7 +1664,7 @@ ${o}
|
|
|
1611
1664
|
+ COALESCE(s.total_output_tokens,0)
|
|
1612
1665
|
+ COALESCE(s.total_cache_create_tokens,0)
|
|
1613
1666
|
+ COALESCE(s.total_cache_read_tokens,0)) DESC
|
|
1614
|
-
LIMIT 10`).all(s.id).map(d=>{let m=Ae({inputTokens:d.total_input_tokens??0,outputTokens:d.total_output_tokens??0,cacheCreateTokens:d.total_cache_create_tokens??0,cacheReadTokens:d.total_cache_read_tokens??0},d.primary_model);return{sessionId:d.id,alias:d.alias,startedAt:d.started_at,totalTokens:m.totalTokens,cost:m}});return{project:s.name,sessionCount:o.session_count,inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,totalTokens:a.totalTokens,cost:a,byModel:r,topSessions:u,display:{dollars:
|
|
1667
|
+
LIMIT 10`).all(s.id).map(d=>{let m=Ae({inputTokens:d.total_input_tokens??0,outputTokens:d.total_output_tokens??0,cacheCreateTokens:d.total_cache_create_tokens??0,cacheReadTokens:d.total_cache_read_tokens??0},d.primary_model);return{sessionId:d.id,alias:d.alias,startedAt:d.started_at,totalTokens:m.totalTokens,cost:m}});return{project:s.name,sessionCount:o.session_count,inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,totalTokens:a.totalTokens,cost:a,byModel:r,topSessions:u,display:{dollars:Ft(a.cents),tokens:Pt(a.totalTokens)}}}function mp(e="all"){let t=f(),s=e==="7d"?new Date(Date.now()-7*864e5).toISOString():e==="30d"?new Date(Date.now()-30*864e5).toISOString():null,n=s?"WHERE mu.timestamp >= @since OR (mu.timestamp IS NULL AND s.started_at >= @since)":"",r=s?"WHERE m.timestamp >= @since OR (m.timestamp IS NULL AND s2.started_at >= @since)":"",o=s?{since:s}:{},a=l=>s?t.prepare(l).get(o):t.prepare(l).get(),c=l=>s?t.prepare(l).all(o):t.prepare(l).all(),u=c(`SELECT mu.model,
|
|
1615
1668
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1616
1669
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1617
1670
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1620,7 +1673,7 @@ ${o}
|
|
|
1620
1673
|
FROM message_usage mu
|
|
1621
1674
|
JOIN sessions s ON s.id = mu.session_id
|
|
1622
1675
|
${n}
|
|
1623
|
-
GROUP BY mu.model`),d=
|
|
1676
|
+
GROUP BY mu.model`),d=ti(u),m=0,h=0,b=0,T=0;for(let l of d)m+=l.inputTokens,h+=l.outputTokens,b+=l.cacheCreateTokens,T+=l.cacheReadTokens;let S=Ae({inputTokens:m,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:T,...si(d)},null),w=s?a(`SELECT
|
|
1624
1677
|
(SELECT COUNT(DISTINCT m.session_id)
|
|
1625
1678
|
FROM messages m
|
|
1626
1679
|
JOIN sessions s2 ON s2.id = m.session_id
|
|
@@ -1644,7 +1697,7 @@ ${o}
|
|
|
1644
1697
|
JOIN sessions s ON s.id = mu.session_id
|
|
1645
1698
|
${n}
|
|
1646
1699
|
GROUP BY day, mu.model
|
|
1647
|
-
ORDER BY day ASC`),
|
|
1700
|
+
ORDER BY day ASC`),D=new Map;for(let l of R){if(!l.day)continue;let p=Ae({inputTokens:l.input_tokens,outputTokens:l.output_tokens,cacheCreateTokens:l.cache_create_tokens,cacheReadTokens:l.cache_read_tokens},l.model),g=D.get(l.day)??{tokens:0,cents:0};g.tokens+=p.totalTokens,g.cents+=p.cents,D.set(l.day,g)}let F=[...D.entries()].map(([l,p])=>({day:l,tokens:p.tokens,cents:p.cents})).sort((l,p)=>l.day.localeCompare(p.day)),j=c(`SELECT s.id, p.name AS project, sa.alias, s.started_at,
|
|
1648
1701
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1649
1702
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1650
1703
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1660,7 +1713,7 @@ ${o}
|
|
|
1660
1713
|
+ COALESCE(SUM(mu.output_tokens),0)
|
|
1661
1714
|
+ COALESCE(SUM(mu.cache_create_tokens),0)
|
|
1662
1715
|
+ COALESCE(SUM(mu.cache_read_tokens),0)) DESC
|
|
1663
|
-
LIMIT 10`).map(l=>{let p=Ae({inputTokens:l.input_tokens,outputTokens:l.output_tokens,cacheCreateTokens:l.cache_create_tokens,cacheReadTokens:l.cache_read_tokens},l.primary_model);return{sessionId:l.id,project:l.project,alias:l.alias,startedAt:l.started_at,totalTokens:p.totalTokens,cost:p}}),
|
|
1716
|
+
LIMIT 10`).map(l=>{let p=Ae({inputTokens:l.input_tokens,outputTokens:l.output_tokens,cacheCreateTokens:l.cache_create_tokens,cacheReadTokens:l.cache_read_tokens},l.primary_model);return{sessionId:l.id,project:l.project,alias:l.alias,startedAt:l.started_at,totalTokens:p.totalTokens,cost:p}}),U=c(`SELECT p.id AS project_id,
|
|
1664
1717
|
p.name AS project,
|
|
1665
1718
|
mu.model,
|
|
1666
1719
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
@@ -1672,51 +1725,29 @@ ${o}
|
|
|
1672
1725
|
JOIN sessions s ON s.id = mu.session_id
|
|
1673
1726
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1674
1727
|
${n}
|
|
1675
|
-
GROUP BY p.id, mu.model`),ie=new Map;for(let l of
|
|
1728
|
+
GROUP BY p.id, mu.model`),ie=new Map;for(let l of U){let p=l.project_id??"__none__",g=ie.get(p);g||(g={project:l.project??"(no project)",sessionIds:new Set,sessionsApprox:0,byModel:{}},ie.set(p,g)),l.sessions>g.sessionsApprox&&(g.sessionsApprox=l.sessions),g.byModel[l.model??"__unknown__"]={inputTokens:l.input_tokens,outputTokens:l.output_tokens,cacheCreateTokens:l.cache_create_tokens,cacheReadTokens:l.cache_read_tokens}}let B=[...ie.values()].map(l=>{let p=0,g=0,_=0,E=0;for(let k of Object.values(l.byModel))p+=k.inputTokens,g+=k.outputTokens,_+=k.cacheCreateTokens,E+=k.cacheReadTokens;let y=Ae({inputTokens:p,outputTokens:g,cacheCreateTokens:_,cacheReadTokens:E,byModel:l.byModel},null);return{project:l.project,sessions:l.sessionsApprox,totalTokens:y.totalTokens,cost:y}});B.sort((l,p)=>p.totalTokens-l.totalTokens);let se=B.slice(0,20),i=t.prepare(`SELECT
|
|
1676
1729
|
(SELECT COUNT(*) FROM messages WHERE role='assistant') AS assistant_messages,
|
|
1677
|
-
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:w.total_sessions,sessionsWithUsage:w.sessions_with_usage,inputTokens:m,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:T,totalTokens:S.totalTokens,cost:S,daily:
|
|
1730
|
+
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:w.total_sessions,sessionsWithUsage:w.sessions_with_usage,inputTokens:m,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:T,totalTokens:S.totalTokens,cost:S,daily:F,byModel:d,topSessions:j,topRepos:se,backfill:{assistantMessages:i.assistant_messages,messagesWithUsage:i.messages_with_usage,pending:Math.max(0,i.assistant_messages-i.messages_with_usage),unrecoverable:Math.min(op(),Math.max(0,i.assistant_messages-i.messages_with_usage))},display:{dollars:Ft(S.cents),tokens:Pt(S.totalTokens)}}}H();function bs(e){return Math.max(0,Math.min(1,e))}function ni(e){let t=f(),s=t.prepare("SELECT id, name FROM projects WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT COUNT(*) AS cnt,
|
|
1678
1731
|
MAX(started_at) AS latest
|
|
1679
|
-
FROM sessions WHERE project_id = ?`).get(e),r=n.cnt;if(r===0)return{projectId:e,projectName:s.name,score:0,breakdown:{sessionCount:{raw:0,score:0,weight:.2},recency:{daysSinceLastSession:1/0,score:0,weight:.25},fragmentation:{avgMessages:0,score:0,weight:.15},searchCoverage:{ratio:0,score:0,weight:.2},tagCoverage:{ratio:0,score:0,weight:.2}}};let o=
|
|
1680
|
-
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,m=
|
|
1732
|
+
FROM sessions WHERE project_id = ?`).get(e),r=n.cnt;if(r===0)return{projectId:e,projectName:s.name,score:0,breakdown:{sessionCount:{raw:0,score:0,weight:.2},recency:{daysSinceLastSession:1/0,score:0,weight:.25},fragmentation:{avgMessages:0,score:0,weight:.15},searchCoverage:{ratio:0,score:0,weight:.2},tagCoverage:{ratio:0,score:0,weight:.2}}};let o=bs(r/10),a=n.latest?(Date.now()-new Date(n.latest).getTime())/(1e3*60*60*24):90,c=bs(1-a/90),d=t.prepare(`SELECT AVG(message_count) AS avg_msgs
|
|
1733
|
+
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,m=bs((d-2)/3),h=t.prepare(`SELECT COUNT(*) AS total,
|
|
1681
1734
|
SUM(CASE WHEN m.content_text IS NOT NULL AND m.content_text != '' THEN 1 ELSE 0 END) AS covered
|
|
1682
1735
|
FROM messages m
|
|
1683
1736
|
JOIN sessions s ON s.id = m.session_id
|
|
1684
|
-
WHERE s.project_id = ?`).get(e),b=h.total>0?h.covered/h.total:.5,T=
|
|
1737
|
+
WHERE s.project_id = ?`).get(e),b=h.total>0?h.covered/h.total:.5,T=bs(b),S=t.prepare(`SELECT COUNT(DISTINCT s.id) AS total,
|
|
1685
1738
|
COUNT(DISTINCT st.session_id) AS tagged
|
|
1686
1739
|
FROM sessions s
|
|
1687
1740
|
LEFT JOIN session_tags st ON st.session_id = s.id
|
|
1688
|
-
WHERE s.project_id = ?`).get(e),w=S.total>0?S.tagged/S.total:0,R=
|
|
1689
|
-
`)){if(!c)continue;let[u,d,...m]=c.split(" ");!u||a.has(u)||(a.add(u),o.push({commit_sha:u,committed_at:d??null,subject:m.join(" ")||null}))}return o}function WT(e){return f().prepare(`SELECT id, cwd, started_at, ended_at
|
|
1690
|
-
FROM sessions WHERE id = ?`).get(e)??null}function qT(e,t,s){if(s.length===0)return 0;let n=f(),r=new Date().toISOString(),o=n.prepare(`INSERT OR IGNORE INTO session_commits
|
|
1691
|
-
(session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
|
|
1692
|
-
VALUES (?, ?, ?, ?, ?, ?)`),a=0;return n.transaction(u=>{for(let d of u)o.run(e,d.commit_sha,d.committed_at,d.subject,t,r).changes>0&&(a+=1)})(s),a}async function Ko(e){let t=WT(e);if(!t)return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:"session not found"};if(!t.cwd)return{sessionId:e,status:"no-cwd",commitsFound:0,commitsInserted:0};if(!t.started_at||!t.ended_at)return{sessionId:e,status:"no-window",commitsFound:0,commitsInserted:0};let s=t.started_at,n=t.ended_at===t.started_at?new Date(Date.parse(t.ended_at)+1e3).toISOString():t.ended_at;try{if(!(await UT(t.cwd)).isDirectory())return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}catch{return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}if(!await HT(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await BT(t.cwd,s,n),a=qT(e,t.cwd,o);return{sessionId:e,status:"ok",commitsFound:o.length,commitsInserted:a}}catch(o){return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:o.message}}}function Bn(e){let t=f(),s=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(s))return[];let n=`${s.toLowerCase()}%`;return t.prepare(`SELECT sc.session_id AS sessionId,
|
|
1693
|
-
NULLIF(sa.alias, '') AS alias,
|
|
1694
|
-
p.name AS project,
|
|
1695
|
-
s.started_at AS startedAt,
|
|
1696
|
-
s.ended_at AS endedAt,
|
|
1697
|
-
sc.commit_sha AS commitSha,
|
|
1698
|
-
sc.committed_at AS committedAt,
|
|
1699
|
-
sc.subject AS subject
|
|
1700
|
-
FROM session_commits sc
|
|
1701
|
-
JOIN sessions s ON s.id = sc.session_id
|
|
1702
|
-
JOIN projects p ON p.id = s.project_id
|
|
1703
|
-
LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
|
|
1704
|
-
WHERE lower(sc.commit_sha) = lower(?)
|
|
1705
|
-
OR lower(sc.commit_sha) LIKE ?
|
|
1706
|
-
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function Vo(e){return f().prepare(`SELECT commit_sha, committed_at, subject, correlated_at
|
|
1707
|
-
FROM session_commits
|
|
1708
|
-
WHERE session_id = ?
|
|
1709
|
-
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var XT=3e4;function tp(e){try{let s=f().prepare(`SELECT MAX(correlated_at) AS last_at
|
|
1710
|
-
FROM session_commits WHERE session_id = ?`).get(e),n=s?.last_at?Date.parse(s.last_at):0;if(n&&Date.now()-n<XT)return}catch{}Ko(e).catch(t=>{console.error(`[git-correlator] ${e.slice(0,8)} failed:`,t)})}U();import{execFile as JT}from"node:child_process";import{promisify as GT}from"node:util";import{stat as YT}from"node:fs/promises";var zT=GT(JT),KT=60,VT=7,ZT=7,QT=5e3;function ey(){let e=f(),t=s=>{try{return!!e.prepare(s).get()}catch{return!1}};return{semantic:t("SELECT 1 FROM session_semantic LIMIT 1"),cost:t(`SELECT 1 FROM sessions
|
|
1741
|
+
WHERE s.project_id = ?`).get(e),w=S.total>0?S.tagged/S.total:0,R=bs(w),D=Math.round((o*.2+c*.25+m*.15+T*.2+R*.2)*100);return{projectId:e,projectName:s.name,score:D,breakdown:{sessionCount:{raw:r,score:o,weight:.2},recency:{daysSinceLastSession:Math.round(a),score:c,weight:.25},fragmentation:{avgMessages:Math.round(d*10)/10,score:m,weight:.15},searchCoverage:{ratio:Math.round(b*100)/100,score:T,weight:.2},tagCoverage:{ratio:Math.round(w*100)/100,score:R,weight:.2}}}}function gp(){let t=f().prepare("SELECT id FROM projects ORDER BY name").all(),s=[];for(let n of t){let r=ni(n.id);r&&s.push(r)}return s}H();import{execFile as dy}from"node:child_process";import{promisify as py}from"node:util";import{stat as my}from"node:fs/promises";var gy=py(dy),_y=60,fy=7,hy=7,Ey=5e3;function by(){let e=f(),t=s=>{try{return!!e.prepare(s).get()}catch{return!1}};return{semantic:t("SELECT 1 FROM session_semantic LIMIT 1"),cost:t(`SELECT 1 FROM sessions
|
|
1711
1742
|
WHERE (COALESCE(total_input_tokens,0)
|
|
1712
1743
|
+ COALESCE(total_output_tokens,0)
|
|
1713
1744
|
+ COALESCE(total_cache_create_tokens,0)
|
|
1714
1745
|
+ COALESCE(total_cache_read_tokens,0)) > 0
|
|
1715
|
-
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function
|
|
1746
|
+
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function ri(e){if(!e)return[];let t=new Set,s=[];for(let n of e.split(",")){let r=n.trim().toLowerCase();!r||t.has(r)||(t.add(r),s.push(r))}return s}function _p(e){return{sessionId:e.session_id,project:e.project,alias:e.alias,startedAt:e.started_at,endedAt:e.ended_at,firstUserMessage:e.first_user_message}}function Sy(){let e=f(),t=e.prepare(`SELECT ss.keywords
|
|
1716
1747
|
FROM session_semantic ss
|
|
1717
1748
|
JOIN sessions s ON s.id = ss.session_id
|
|
1718
1749
|
WHERE s.started_at IS NOT NULL
|
|
1719
|
-
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:
|
|
1750
|
+
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:fy});if(t.length===0)return null;let s=new Set;for(let o of t)for(let a of ri(o.keywords))s.add(a);if(s.size===0)return null;let n=e.prepare(`SELECT ss.session_id AS session_id,
|
|
1720
1751
|
ss.summary AS summary,
|
|
1721
1752
|
ss.keywords AS keywords,
|
|
1722
1753
|
p.name AS project,
|
|
@@ -1732,7 +1763,7 @@ ${o}
|
|
|
1732
1763
|
WHERE s.started_at IS NOT NULL
|
|
1733
1764
|
AND s.message_count > 2
|
|
1734
1765
|
AND julianday('now') - julianday(s.started_at) >= @ageDays
|
|
1735
|
-
ORDER BY s.started_at ASC`).all({ageDays:
|
|
1766
|
+
ORDER BY s.started_at ASC`).all({ageDays:_y});if(n.length===0)return null;let r=null;for(let o of n){let c=ri(o.keywords).filter(u=>s.has(u));c.length!==0&&(!r||c.length>r.overlap.length)&&(r={row:o,overlap:c})}return r?{..._p(r.row),summary:r.row.summary,keywords:ri(r.row.keywords),matchedKeywords:r.overlap,daysAgo:Math.max(0,Math.round(r.row.days_old))}:null}function Ty(){let t=f().prepare(`SELECT s.id AS session_id,
|
|
1736
1767
|
p.name AS project,
|
|
1737
1768
|
NULLIF(sa.alias, '') AS alias,
|
|
1738
1769
|
s.started_at AS started_at,
|
|
@@ -1751,28 +1782,28 @@ ${o}
|
|
|
1751
1782
|
AND (COALESCE(s.total_input_tokens, 0)
|
|
1752
1783
|
+ COALESCE(s.total_output_tokens, 0)
|
|
1753
1784
|
+ COALESCE(s.total_cache_create_tokens, 0)
|
|
1754
|
-
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:
|
|
1785
|
+
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:hy});if(t.length===0)return null;let s=null;for(let n of t){let r=Ae({inputTokens:n.input_tokens,outputTokens:n.output_tokens,cacheCreateTokens:n.cache_create_tokens,cacheReadTokens:n.cache_read_tokens},n.primary_model);r.cents<=0||(!s||r.cents>s.cents)&&(s={row:n,cents:r.cents,totalTokens:r.totalTokens})}return s?{..._p(s.row),totalTokens:s.totalTokens,costCents:s.cents,costDisplay:Ft(s.cents),tokensDisplay:Pt(s.totalTokens),primaryModel:s.row.primary_model,primaryModelLabel:Je(s.row.primary_model).label}:null}async function yy(e){try{if(!(await my(e)).isDirectory())return null}catch{return null}try{let{stdout:t}=await gy("git",["rev-parse","HEAD"],{cwd:e,timeout:Ey}),s=t.trim();return/^[0-9a-f]{40}$/.test(s)?s:null}catch{return null}}async function wy(){let e=f(),t=e.prepare(`SELECT s.id AS id, s.cwd AS cwd
|
|
1755
1786
|
FROM sessions s
|
|
1756
1787
|
WHERE s.cwd IS NOT NULL AND s.started_at IS NOT NULL
|
|
1757
1788
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1758
|
-
LIMIT 1`).get();if(!t?.cwd)return null;let s=await
|
|
1789
|
+
LIMIT 1`).get();if(!t?.cwd)return null;let s=await yy(t.cwd);if(!s)return null;let n=en(s);if(n.length===0)return null;let r=n[0],o=e.prepare(`SELECT s.first_user_message AS first_user_message, s.ended_at AS ended_at
|
|
1759
1790
|
FROM sessions s
|
|
1760
|
-
WHERE s.id = ?`).get(r.sessionId);return{sessionId:r.sessionId,project:r.project,alias:r.alias,startedAt:r.startedAt,endedAt:o?.ended_at??r.endedAt,firstUserMessage:o?.first_user_message??null,commitSha:r.commitSha,shortSha:r.commitSha.slice(0,7),subject:r.subject,committedAt:r.committedAt,cwd:t.cwd}}async function
|
|
1791
|
+
WHERE s.id = ?`).get(r.sessionId);return{sessionId:r.sessionId,project:r.project,alias:r.alias,startedAt:r.startedAt,endedAt:o?.ended_at??r.endedAt,firstUserMessage:o?.first_user_message??null,commitSha:r.commitSha,shortSha:r.commitSha.slice(0,7),subject:r.subject,committedAt:r.committedAt,cwd:t.cwd}}async function fp(){let e=by(),t=e.semantic?Promise.resolve().then(()=>{try{return Sy()}catch(c){return console.error("[discover.rediscovered]",c),null}}):Promise.resolve(null),s=e.cost?Promise.resolve().then(()=>{try{return Ty()}catch(c){return console.error("[discover.expensive]",c),null}}):Promise.resolve(null),n=e.git?wy().catch(c=>(console.error("[discover.authored]",c),null)):Promise.resolve(null),[r,o,a]=await Promise.all([t,s,n]);return{rediscovered:r,expensive:o,authored:a,availability:e,generatedAt:new Date().toISOString()}}Ve();H();Ve();async function hp(e,t=50){let s=await ir(e),n=f(),r=Buffer.from(s.buffer,s.byteOffset,s.byteLength);return n.prepare(`SELECT v.rowid, v.distance, cm.session_id, cm.text, cm.message_uuids
|
|
1761
1792
|
FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
|
|
1762
|
-
WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r,t).map(a=>({sessionId:a.session_id,chunkRowid:a.rowid,distance:a.distance,text:a.text,messageUuids:JSON.parse(a.message_uuids)}))}async function
|
|
1763
|
-
WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(o.embedding,t*5),c=new Map;for(let d of a){if(d.session_id===e)continue;let m=c.get(d.session_id);(m===void 0||d.distance<m)&&c.set(d.session_id,d.distance)}let u=[];for(let[d,m]of c){let h=1-m;h>=s&&u.push({sessionId:d,similarity:h})}return u.sort((d,m)=>m.similarity-d.similarity),u.slice(0,t)}function
|
|
1793
|
+
WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r,t).map(a=>({sessionId:a.session_id,chunkRowid:a.rowid,distance:a.distance,text:a.text,messageUuids:JSON.parse(a.message_uuids)}))}async function Ep(e,t=10,s=.65){let n=f(),r=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!r)return[];let o=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(r.rowid);if(!o)return[];let a=n.prepare(`SELECT v.rowid, v.distance, cm.session_id FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
|
|
1794
|
+
WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(o.embedding,t*5),c=new Map;for(let d of a){if(d.session_id===e)continue;let m=c.get(d.session_id);(m===void 0||d.distance<m)&&c.set(d.session_id,d.distance)}let u=[];for(let[d,m]of c){let h=1-m;h>=s&&u.push({sessionId:d,similarity:h})}return u.sort((d,m)=>m.similarity-d.similarity),u.slice(0,t)}function Ry(){let e=process.env.RECALL_RRF_K;if(e){let t=parseInt(e,10);if(!isNaN(t)&&t>=1&&t<=1e3)return t}return 60}function bp(e){let t=Ry(),s=new Map;for(let r of e)for(let o=0;o<r.length;o++){let a=r[o],c=1/(t+o+1),u=s.get(a.id);u?(u.score+=c,u.lanes.push(a.lane),o+1<u.bestRank&&(u.bestRank=o+1,u.bestData=a.data)):s.set(a.id,{score:c,lanes:[a.lane],bestRank:o+1,bestData:a.data})}let n=[];for(let[r,o]of s)n.push({id:r,score:o.score,lanes:o.lanes,data:o.bestData});return n.sort((r,o)=>o.score-r.score),n}H();Ve();H();function Sp(e){return e.replace(/```json[\s\S]*?```/g,"[tool-call]").replace(/\{[\s\S]{200,}?\}/g,"[json-object]")}function ky(e){let t=[],o=0;for(;o<e.length;){let a=[],c=0;for(;a.length<5&&o<e.length;){let d=e[o],m=Sp(d.content_text??"");if(c+m.length>2e3&&a.length>=3)break;a.push(d),c+=m.length,o++}if(a.length===0)break;let u=a.map(d=>{let m=d.role??"system",h=Sp(d.content_text??"");return`[${m}] ${h}`}).join(`
|
|
1764
1795
|
|
|
1765
|
-
`);t.push({messageUuids:a.map(d=>d.uuid),text:u}),o<e.length&&a.length>=3&&(o=Math.max(o-1,o-1))}return t}function
|
|
1796
|
+
`);t.push({messageUuids:a.map(d=>d.uuid),text:u}),o<e.length&&a.length>=3&&(o=Math.max(o-1,o-1))}return t}function Tp(e){let s=f().prepare("SELECT uuid, role, content_text FROM messages WHERE session_id = ? AND is_sidechain = 0 AND content_text IS NOT NULL ORDER BY rowid").all(e);return ky(s)}var Ap=2e3,Ay=1e4,at=null,Xn=!1,xp=null,oi=0;function Np(e){oi=Math.max(0,Math.floor(e))}var Ie=new Set,Op=-1;function Lp(e){Ie.add(e)}function Cp(){Ie.add(Op)}function yp(e){if(Ie.has(Op))return!0;if(Ie.size===0)return!1;let t=f().prepare("SELECT project_id FROM sessions WHERE id = ?").get(e);return t?Ie.has(t.project_id):!1}var wp=10,xy=200,Ny=2e3;function Oy(){let e=Number(process.env.RECALL_VECTOR_HARD_CAP??"");return Number.isFinite(e)&&e>0?Math.min(Ny,Math.floor(e)):xy}var Ly=3,Rp=1e3,Ss=new Map,ii=new Set;function kp(e,t,s,n){if(e.size>=Rp&&(e instanceof Map,!e.has(t))){let r=e.keys().next();r.done||(e.delete(r.value),console.warn(`[vector-worker] ${n??"tracking-map"} hit ${Rp}-entry cap; evicted oldest entry`))}e instanceof Map?e.set(t,s):e.add(t)}var Ts=[],Cy=50;function ai(){if(Ts.length<8)return null;let e=Ts.slice(-30),t=e.reduce((n,r)=>n+r.chunks,0),s=e.reduce((n,r)=>n+r.durationMs,0);return s<=0||t===0?null:{samples:e.length,chunksPerSec:t/(s/1e3),sessionsPerSec:e.length/(s/1e3),avgChunksPerSession:t/e.length}}function Iy(){return f().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}async function vy(){let e=f(),t=e.prepare("SELECT DISTINCT session_id FROM chunk_queue ORDER BY id LIMIT 1").get();if(!t)return Ie.size>0&&Ie.clear(),!1;if(yp(t.session_id)||ii.has(t.session_id))return e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(t.session_id),!0;let s=t.session_id,n=Date.now();try{e.prepare("DELETE FROM vec_chunks WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(s),e.prepare("DELETE FROM chunk_meta WHERE session_id = ?").run(s);let r=Tp(s),o=oi>0?oi:1/0,a=Math.min(o,Oy()),c=a<r.length?r.slice(0,a):r;if(c.length===0)return e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s),!0;let u=e.prepare("INSERT INTO chunk_meta(session_id, message_uuids, text, embedding_model_id, embedding_dim, stale, generated_at) VALUES (?, ?, ?, 'bge-base-en-v1.5', 768, 0, datetime('now'))"),d=e.prepare("INSERT INTO vec_chunks(rowid, embedding) VALUES (?, ?)"),m=0,h=!1;for(let b=0;b<c.length;b+=wp){if(yp(s)){h=!0;break}let T=Math.min(c.length,b+wp),S=c.slice(b,T),w=S.map(F=>F.text),R=await ct(w);e.transaction(()=>{for(let F=0;F<S.length;F++){let L=u.run(s,JSON.stringify(S[F].messageUuids),S[F].text),j=Buffer.from(R[F].buffer,R[F].byteOffset,R[F].byteLength);d.run(BigInt(L.lastInsertRowid),j)}})(),m+=S.length,await new Promise(F=>setImmediate(F))}h&&(e.prepare("DELETE FROM vec_chunks WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(s),e.prepare("DELETE FROM chunk_meta WHERE session_id = ?").run(s)),e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s),xp=new Date().toISOString(),Ts.push({ts:Date.now(),chunks:m,durationMs:Date.now()-n}),Ts.length>Cy&&Ts.shift(),Ss.has(s)&&Ss.delete(s)}catch(r){console.error("[vector-worker] failed for session",s,r);let o=(Ss.get(s)??0)+1;kp(Ss,s,o,"failureCounts"),o>=Ly&&(kp(ii,s,void 0,"blacklist"),Ss.delete(s),console.error(`[vector-worker] blacklisted session ${s} after ${o} failures \u2014 will not retry until daemon restart`)),e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s)}finally{if(Ie.size>0)try{let r=e.prepare("SELECT project_id FROM sessions WHERE id = ?").get(s);r!==void 0&&Ie.has(r.project_id)&&(e.prepare("SELECT 1 FROM chunk_queue cq JOIN sessions s ON s.id = cq.session_id WHERE s.project_id = ? LIMIT 1").get(r.project_id)||Ie.delete(r.project_id))}catch(r){console.error("[vector-worker] cancelledProjects cleanup failed:",r)}}return!0}async function Ip(){if(!_e().loaded)return;let e=await vy();at!==null&&clearTimeout(at),at=setTimeout(()=>{Ip()},e?Ap:Ay)}function ys(){if(!Xn){if(!_e().loaded){console.error("[vector-worker] cannot start: embedder not loaded");return}Xn=!0,at=setTimeout(()=>{Ip()},Ap)}}function vp(){Xn=!1,at!==null&&(clearTimeout(at),at=null)}function ve(){return{running:Xn,queueDepth:Iy(),lastProcessedAt:xp,blacklistedCount:ii.size}}ee();import{existsSync as Dp,mkdirSync as jp,rmSync as Mp,createWriteStream as jy,statSync as My}from"node:fs";import{join as Jn}from"node:path";import{createHash as Dy}from"node:crypto";import{readFile as Fy}from"node:fs/promises";var Py="https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/",Fp=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Pp(){return Jn(W,"models","BAAI","bge-base-en-v1.5")}function Ge(){let e=Pp();return Fp.every(t=>Dp(Jn(e,t.path)))}async function $p(e){let t=Pp();jp(t,{recursive:!0}),jp(Jn(t,"onnx"),{recursive:!0});for(let s of Fp){let n=Jn(t,s.path),r=Py+s.path,o=0;Dp(n)&&(o=My(n).size);let a={};o>0&&(a.Range=`bytes=${o}-`);let c=await fetch(r,{headers:a});if(!c.ok&&c.status!==206)throw new Error(`Failed to download ${s.path}: HTTP ${c.status}`);let u=c.headers.get("content-length"),d=u?o+Number(u):0,m=c.body;if(!m)throw new Error(`No response body for ${s.path}`);let h=jy(n,{flags:o>0?"a":"w"}),b=m.getReader(),T=o;for(;;){let{done:R,value:D}=await b.read();if(R)break;h.write(Buffer.from(D)),T+=D.byteLength,e?.(s.path,T,d)}if(h.end(),await new Promise((R,D)=>{h.on("finish",R),h.on("error",D)}),s.sha256==="TODO_PLACEHOLDER")throw Mp(n),new Error(`Refusing to install: SHA-256 not pinned for ${s.path}. Update model-download.ts.`);let S=await Fy(n);if(Dy("sha256").update(S).digest("hex")!==s.sha256)throw Mp(n),new Error(`SHA-256 mismatch for ${s.path}`)}}H();var $y=[/\btask\s+complete/i,/\bdone\b/i,/\bfinished\b/i,/\bimplemented\b/i,/\bcompleted\b/i,/\bshipped\b/i,/\ball\s+(?:tests?\s+)?pass/i,/\bsuccessfully\b/i],Uy=1440*60*1e3;function Hy(e){let t=f(),s=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
1766
1797
|
WHERE session_id = ? AND role = 'assistant'
|
|
1767
|
-
ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let u of s)if(u.content_text
|
|
1768
|
-
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let u of r){let d=u.tool_names??"",m=u.content_text??"";/\bWrite\b|\bEdit\b/.test(d)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(m)&&/pass|ok|✓/i.test(m)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(m)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(m)||/tsc.*(?:0 errors|no errors)/i.test(m))&&(o.buildSuccess=!0)}return n?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:n}:{status:"neutral",evidence:o,claimFound:n}}function
|
|
1769
|
-
`,"utf-8"),
|
|
1798
|
+
ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let u of s)if(u.content_text&&$y.some(d=>d.test(u.content_text))){n=!0;break}let r=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
1799
|
+
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let u of r){let d=u.tool_names??"",m=u.content_text??"";/\bWrite\b|\bEdit\b/.test(d)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(m)&&/pass|ok|✓/i.test(m)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(m)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(m)||/tsc.*(?:0 errors|no errors)/i.test(m))&&(o.buildSuccess=!0)}return n?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:n}:{status:"neutral",evidence:o,claimFound:n}}function By(e){let t=Hy(e);return f().prepare("UPDATE sessions SET verification_status = ?, verification_computed_at = ? WHERE id = ?").run(t.status,Date.now(),e),t}function Up(e){let s=f().prepare("SELECT verification_status, verification_computed_at FROM sessions WHERE id = ?").get(e);if(s?.verification_status&&s.verification_computed_at&&Date.now()-s.verification_computed_at<Uy){let n={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};return{status:s.verification_status,evidence:n,claimFound:s.verification_status!=="neutral"}}return By(e)}import{readFileSync as Wy,writeFileSync as qy,mkdirSync as Xy,chmodSync as Jy}from"node:fs";import{join as Hp}from"node:path";import{homedir as Bp}from"node:os";function Wp(){return Hp(Bp(),".recall","config.json")}function qp(){try{return JSON.parse(Wy(Wp(),"utf-8"))}catch{return{}}}function Gy(e){let t=Wp();Xy(Hp(Bp(),".recall"),{recursive:!0}),qy(t,JSON.stringify(e,null,2)+`
|
|
1800
|
+
`,"utf-8"),Jy(t,384)}function ci(){let t=qp().verification;return typeof t=="object"&&t!==null&&"enabled"in t?!!t.enabled:!1}function Xp(e){let t=qp();t.verification={...typeof t.verification=="object"&&t.verification!==null?t.verification:{},enabled:e},Gy(t)}var sw=5e3,fi={scanned:0,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0},li=0,ui=fi,di=null;async function nw(){return Date.now()-li>=sw&&!di&&(di=Zs().then(s=>(ui=s,li=Date.now(),s)).catch(()=>(li=Date.now(),ui=fi,fi)).finally(()=>{di=null})),ui}var rw=2e3,ow=6,zn=new Map;function ws(e){return e.replace(/[\\%_]/g,t=>"\\"+t)}function iw(e,t){let s=Date.now(),n=(zn.get(e)??[]).filter(a=>s-a.ts<rw);return zn.set(e,n),n.length<2||n[n.length-1].name===t?!1:n.slice(0,-1).some(a=>a.name===t)}function aw(e,t){let s=zn.get(e)??[];for(s.push({name:t,ts:Date.now()});s.length>ow;)s.shift();zn.set(e,s)}function Gp(e,t){let s=t.trim();if(!s)return 0;if(le(s)){let o=Et(s);if(!o)return 0;s=o}if(de(s))return 0;if(iw(e,s))return console.log(`[terminal] dropping rename of pid ${e} \u2192 "${s}", flap signature (competing editor sync sources)`),0;aw(e,s);let n=v.sessionsFor(e),r=0;for(let o of n)try{if(Te(o)===s)continue;he(o,s),r++}catch{}return r>0&&console.log(`[terminal] rename of pid ${e} \u2192 "${s}" propagated to ${r} session(s)`),r}var Yp=(()=>{try{let e=Kn(Ei(bi(import.meta.url)),"..","..","package.json");return JSON.parse(_i(e,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})(),pi=!1,mi=!1,gi=!1,lw=Ei(bi(import.meta.url)),hi=Kn(lw,"..","web"),Vp=Kn(hi,"index.html"),uw=Ky(Vp);function zp(){return f().prepare(`SELECT
|
|
1770
1801
|
(SELECT COUNT(*) FROM projects) AS projects,
|
|
1771
1802
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
1772
1803
|
(SELECT COUNT(*) FROM messages) AS messages,
|
|
1773
1804
|
(SELECT MIN(started_at) FROM sessions WHERE started_at IS NOT NULL) AS earliest,
|
|
1774
|
-
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var
|
|
1775
|
-
WHERE session_id = ? AND tool_names IS NOT NULL AND tool_names != ''`).all(l),_=g.length,E=new Set;for(let y of g){if(!/Read|Write|Edit/.test(y.tool_names))continue;let k=y.raw_json.match(/"(?:file_path|path)":\s*"([^"]+)"/g);if(k)for(let
|
|
1805
|
+
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var dw=/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i,pw=/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i;async function je(e,t){if((await Ke()).tier!=="pro")return e.json({error:"pro_required",message:"This feature requires a Claude Recall Pro license.",upgrade_url:"https://clauderecall.com/pricing",activate_command:"recall activate <license-key>"},402);await t()}var Gn=new Map,Zp=0,Kp=0,Qp=null,mw=6e4;function gw(){Zp+=1;let e=Date.now();e-Kp<mw||(Kp=e,console.warn("[auth] /api/terminal/* request rejected without a valid X-Recall-Token. The VS Code / Cursor extension is likely outdated relative to the daemon \u2014 tab names will not flow through, and session titles will fall back to the heuristic first-message snippet. Reinstall: `code --install-extension extensions/vscode/clauderecall-vscode-*.vsix` and restart the extension host. Run `recall doctor` for a full pipeline view."))}function _w(){Qp=new Date().toISOString()}function fw(){let e=mc();return{silentTerminalRejections:Zp,lastTerminalSyncAt:Qp,autoExtract:{circuitBroken:e.broken,brokenAt:e.brokenAt,reason:e.reason,consecutiveZeroTokenRuns:e.consecutiveZeroTokenRuns}}}function Yn(e,t){if(t)return"";let s=e;return` AND COALESCE(${s}.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(${s}.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(${s}.auto_title, '') NOT LIKE '[skill]%' AND COALESCE(${s}.auto_title, '') NOT LIKE 'You are summarizing a Claude Code session%' AND COALESCE(${s}.auto_title, '') NOT LIKE 'You are extracting a structured Output Index%' AND COALESCE(${s}.title_quality, '') != 'programmatic'`}function hw(e){let t=new Yy;if(t.use("*",cw({maxSize:1*1024*1024})),t.use("*",async(i,l)=>{let p=i.req.raw.headers.get("host")??"";if(!dw.test(p))return i.text("Forbidden: invalid Host header",403);let g=i.req.raw.headers.get("origin");if(g&&!pw.test(g))return i.text("Forbidden: cross-origin request rejected",403);await l()}),e){let i=Buffer.from(e,"utf8");t.use("/api/*",async(l,p)=>{if(l.req.method==="GET"&&l.req.path==="/api/health")return p();let g=l.req.raw.headers.get("x-recall-token")??"";!g&&l.req.method==="GET"&&(g=new URL(l.req.url).searchParams.get("token")??"");let _=!1;if(g.length===e.length)try{_=ew(Buffer.from(g,"utf8"),i)}catch{_=!1}return _?p():(l.req.path.startsWith("/api/terminal/")&&gw(),l.json({error:"unauthorized"},401))})}t.use("*",async(i,l)=>{await l(),i.res.headers.set("Content-Security-Policy","default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://clauderecall.com; font-src 'self' data:; connect-src 'self' data: https://clauderecall.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"),i.res.headers.set("X-Content-Type-Options","nosniff"),i.res.headers.set("X-Frame-Options","DENY"),i.res.headers.set("Referrer-Policy","no-referrer"),i.res.headers.set("Cross-Origin-Resource-Policy","same-origin")}),t.get("/api/health",i=>i.json({status:"ok",version:Yp,uptimeSeconds:Math.round(process.uptime()),pipeline:fw()})),t.get("/api/stats",i=>i.json(zp())),t.get("/api/stats/session/:id",i=>{let l=dp(i.req.param("id"));return l?i.json(l):i.json({error:"session not found"},404)}),t.get("/api/stats/project/:name",i=>{let l=pp(i.req.param("name"));return l?i.json(l):i.json({error:"project not found"},404)}),t.get("/api/stats/overview",i=>{let l=i.req.query("range"),p=l==="7d"?"7d":l==="30d"?"30d":"all";return i.json(mp(p))}),t.post("/api/stats/backfill",async i=>{let l=await i.req.json().catch(()=>({})),p=l.limit?Math.max(1,Math.min(1e5,Number(l.limit))):5e3;if(p>5e3){let _=cp({limit:p});return i.json({mode:"background",started:_,alreadyRunning:!_&&lp(),limit:p,lastRun:ei()})}let g=ap({limit:p});return i.json({mode:"sync",started:!1,alreadyRunning:!1,limit:p,result:g,lastRun:ei()})}),t.get("/api/stats/health",i=>i.json(gp())),t.get("/api/stats/health/:projectId",i=>{let l=Number(i.req.param("projectId")),p=ni(l);return p?i.json(p):i.json({error:"project not found"},404)}),t.get("/api/config/verification",i=>i.json({enabled:ci()})),t.put("/api/config/verification",async i=>{let l=await i.req.json();return typeof l.enabled=="boolean"&&Xp(l.enabled),i.json({enabled:ci()})}),t.get("/api/sessions/:id/verification",i=>{let l=i.req.param("id"),p=Up(l);return i.json(p)}),t.get("/api/sessions/:id/share-stats",i=>{let l=i.req.param("id"),g=f().prepare(`SELECT tool_names, raw_json FROM messages
|
|
1806
|
+
WHERE session_id = ? AND tool_names IS NOT NULL AND tool_names != ''`).all(l),_=g.length,E=new Set;for(let y of g){if(!/Read|Write|Edit/.test(y.tool_names))continue;let k=y.raw_json.match(/"(?:file_path|path)":\s*"([^"]+)"/g);if(k)for(let x of k){let A=x.match(/":\s*"([^"]+)"/);A&&E.add(A[1])}}return i.json({filesReferenced:E.size,toolCallCount:_})}),t.get("/api/sessions/:id/commits",async i=>{let l=i.req.param("id"),p=zr(l);if(p.length>0||i.req.query("refresh")!=="1")return i.json({commits:p});let g=await Yr(l);return i.json({commits:zr(l),status:g.status})}),t.get("/api/commits/:sha/session",i=>{let l=i.req.param("sha");return/^[0-9a-fA-F]{4,40}$/.test(l)?i.json({sessions:en(l)}):i.json({error:"invalid sha format"},400)}),t.get("/api/license/status",async i=>{let l=await Ke();return i.json(l)}),t.post("/api/feedback",async i=>{let l;try{l=await i.req.json()}catch{return i.json({error:"invalid json"},400)}if(!l||typeof l!="object")return i.json({error:"invalid body"},400);let p=process.env.RECALL_FEEDBACK_API??"https://clauderecall.com/api/feedback",g=p==="https://clauderecall.com/api/feedback",_=await Ke(),E=Ut(),y=g&&_.tier==="pro"&&E?E.license_jwt:null,k=(()=>{try{let N=Kn(Ei(bi(import.meta.url)),"..","..","package.json");return JSON.parse(_i(N,"utf8")).version}catch{return"unknown"}})(),x=l,A={score:x.score,comment:x.comment??null,surface:"web",version:typeof x.version=="string"?x.version:k,os:typeof x.os=="string"?x.os:process.platform,trigger_kind:typeof x.trigger_kind=="string"?x.trigger_kind:"manual",license_jwt:y};try{let N=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(A)}),C=await N.json().catch(()=>({}));return i.json(C,N.status)}catch(N){let C=N instanceof Error?N.message:"network error";return i.json({error:"upstream_unreachable",detail:C},502)}}),t.get("/api/discover/today",je,async i=>{try{return i.json(await fp())}catch(l){return console.error("[discover.today]",l),i.json({rediscovered:null,expensive:null,authored:null,availability:{semantic:!1,cost:!1,git:!1},generatedAt:new Date().toISOString(),error:l.message},500)}}),t.get("/api/macro-repos",i=>i.json({macro_repos:Co(),orphan_projects:Uu()})),t.get("/api/bug-synthesis",i=>{let l=i.req.query("scope"),p=i.req.query("target_id"),g=i.req.query("limit"),_=l==="cluster"||l==="project"?l:void 0,E=g?Math.max(1,Number(g)):50,y=Yu({scope:_,target_id:p??void 0,limit:E});return i.json({results:y})}),t.get("/api/bug-synthesis/counts",i=>{let l=i.req.query("scope");if(l!=="cluster"&&l!=="project")return i.json({error:'scope must be "cluster" or "project"'},400);let p=zu(l);return i.json({counts:Array.from(p.entries()).map(([g,_])=>({target_id:g,count:_}))})}),t.get("/api/bug-synthesis/:id",i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=Io(l);return p?i.json({result:p}):i.json({error:"not found"},404)}),t.delete("/api/bug-synthesis/:id",i=>{let l=Number(i.req.param("id"));return Number.isFinite(l)?(Ku(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let s=M.object({name:M.string().min(1).max(100),description:M.string().max(500).nullable().optional()});t.post("/api/macro-repos",async i=>{let l=await i.req.json().catch(()=>null),p=s.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let g=Hu({name:p.data.name,description:p.data.description??null});return i.json({macro_repo:g},201)}catch(g){let _=g instanceof Error?g.message:String(g);return _.includes("UNIQUE constraint")?i.json({error:`a macro repo named "${p.data.name}" already exists`},409):i.json({error:_},400)}});let n=M.object({name:M.string().min(1).max(100).optional(),description:M.string().max(500).nullable().optional()});t.patch("/api/macro-repos/:id",async i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=await i.req.json().catch(()=>null),g=n.safeParse(p);if(!g.success)return i.json({error:"invalid request body"},400);try{let _=Bu(l,g.data);return i.json({macro_repo:_})}catch(_){let E=_ instanceof Error?_.message:String(_);return E.includes("not found")?i.json({error:E},404):E.includes("UNIQUE constraint")?i.json({error:"another macro repo already has that name"},409):i.json({error:E},400)}}),t.delete("/api/macro-repos/:id",i=>{let l=Number(i.req.param("id"));return Number.isFinite(l)?(Wu(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let r=M.object({project_id:M.number().int().positive()});t.post("/api/macro-repos/:id/members",async i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=await i.req.json().catch(()=>null),g=r.safeParse(p);if(!g.success)return i.json({error:"invalid request body"},400);try{return qu(l,g.data.project_id),i.json({macro_repo:At(l)})}catch(_){let E=_ instanceof Error?_.message:String(_);return i.json({error:E},E.includes("not found")?404:400)}}),t.delete("/api/macro-repos/:id/members/:projectId",i=>{let l=Number(i.req.param("id")),p=Number(i.req.param("projectId"));return!Number.isFinite(l)||!Number.isFinite(p)?i.json({error:"invalid id"},400):(Xu(l,p),i.json({macro_repo:At(l)}))}),t.get("/api/projects",i=>{let l=f(),p=i.req.query("system")==="1"||i.req.query("system")==="true",g=Yn("s",p),_=l.prepare(`SELECT p.id, p.name, p.decoded_path,
|
|
1776
1807
|
COUNT(CASE WHEN s.id IS NOT NULL${g} THEN 1 END) AS session_count,
|
|
1777
1808
|
COALESCE(SUM(CASE WHEN s.id IS NOT NULL${g} THEN s.message_count ELSE 0 END), 0) AS message_count,
|
|
1778
1809
|
MAX(COALESCE(s.ended_at, s.started_at)) AS latest
|
|
@@ -1790,12 +1821,12 @@ ${o}
|
|
|
1790
1821
|
sa.alias AS alias
|
|
1791
1822
|
FROM sessions s
|
|
1792
1823
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1793
|
-
WHERE s.project_id = ?${
|
|
1824
|
+
WHERE s.project_id = ?${Yn("s",_)}
|
|
1794
1825
|
ORDER BY s.started_at`).all(g.id),y=E.map(A=>A.id),k=[];if(y.length>0){let A=y.map(()=>"?").join(",");k=l.prepare(`SELECT thread_id, session_id, parent_session_id, role
|
|
1795
1826
|
FROM thread_edges
|
|
1796
1827
|
WHERE session_id IN (${A})
|
|
1797
1828
|
AND (parent_session_id IS NULL
|
|
1798
|
-
OR parent_session_id IN (${A}))`).all(...y,...y)}let
|
|
1829
|
+
OR parent_session_id IN (${A}))`).all(...y,...y)}let x=E.map(A=>{let N=A.alias??A.auto_title??A.first_user_message??A.id.slice(0,8),C=null,I=null;if(A.auto_title?.startsWith("/")){let O=A.auto_title.split(" \xB7 ");C=O[0],I=O.length>1?O.slice(1).join(" \xB7 "):null}return{id:A.id.slice(0,8),full_id:A.id,title:N,alias:A.alias,auto_title:A.auto_title,auto_title_source:A.auto_title_source,title_quality:A.title_quality,started_at:A.started_at,msgs:A.message_count,skill:C,brand:I}});return i.json({project:g,sessions:x,thread_edges:k})});let o=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),a=new Set(["pending","approved","rejected"]),c=new Set(["L1","L2","L3","L4","user"]);t.get("/api/links",i=>{let l=i.req.query("source_id")??void 0,p=i.req.query("target_id")??void 0,g=i.req.query("type"),_=i.req.query("approved"),E=i.req.query("limit"),y;if(g){if(!o.has(g))return i.json({error:`invalid type: ${g}`},400);y=g}let k=_==="1"||_==="true",x=E?Number(E):void 0;if(x!==void 0&&(!Number.isFinite(x)||x<1))return i.json({error:"invalid limit"},400);try{let A=Cs({sourceSessionId:l,targetSessionId:p,linkType:y,approvedOnly:k,limit:x});return i.json({links:A})}catch(A){return i.json({error:A.message},400)}}),t.get("/api/links/suggestions",i=>{let l=i.req.query("status"),p=i.req.query("source_id")??void 0,g=i.req.query("target_id")??void 0,_=i.req.query("inferred_by"),E=i.req.query("limit"),y;if(l){if(!a.has(l))return i.json({error:`invalid status: ${l}`},400);y=l}let k;if(_){if(!c.has(_))return i.json({error:`invalid inferred_by: ${_}`},400);k=_}let x=E?Number(E):void 0;if(x!==void 0&&(!Number.isFinite(x)||x<1))return i.json({error:"invalid limit"},400);try{let A=lt({status:y,sourceSessionId:p,targetSessionId:g,inferredBy:k,limit:x}),N=new Set;for(let O of A)N.add(O.source_session_id),N.add(O.target_session_id);let C=new Map;if(N.size>0){let O=Array.from(N),P=O.map(()=>"?").join(","),K=f().prepare(`SELECT s.id,
|
|
1799
1830
|
NULLIF(sa.alias, '') AS alias,
|
|
1800
1831
|
s.auto_title,
|
|
1801
1832
|
s.first_user_message,
|
|
@@ -1803,7 +1834,7 @@ ${o}
|
|
|
1803
1834
|
FROM sessions s
|
|
1804
1835
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1805
1836
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1806
|
-
WHERE s.id IN (${
|
|
1837
|
+
WHERE s.id IN (${P})`).all(...O);for(let G of K){let $=G.first_user_message?G.first_user_message.slice(0,80):null,X=G.alias??G.auto_title??$??G.id.slice(0,8);C.set(G.id,{title:X,project:G.project})}}let I=A.map(O=>{let P=C.get(O.source_session_id),q=C.get(O.target_session_id);return{...O,source_title:P?.title??O.source_session_id.slice(0,8),source_project:P?.project??null,target_title:q?.title??O.target_session_id.slice(0,8),target_project:q?.project??null}});return i.json({suggestions:I})}catch(A){return i.json({error:A.message},400)}}),t.get("/api/output-index/:sessionId",i=>{let l=i.req.param("sessionId");if(!l)return i.json({error:"sessionId required"},400);let p=Ze(l);return p?i.json(p):i.json({error:`no output index for session ${l}`},404)});let u=new Set(["pagerank","embedding-rerank","hybrid"]);t.get("/api/neighborhood/:sessionId",i=>{let l=i.req.param("sessionId");if(!l)return i.json({error:"sessionId required"},400);let p=i.req.query("budget"),g=p!==void 0?Number(p):4e3;if(!Number.isFinite(g)||g<100)return i.json({error:"budget must be a number \u2265 100"},400);let _=i.req.query("scoring")??"hybrid";if(!u.has(_))return i.json({error:`invalid scoring: ${_}; valid: pagerank, embedding-rerank, hybrid`},400);let E=_,y=i.req.query("max_depth"),k=y!==void 0?Number(y):2;if(!Number.isFinite(k)||k<1)return i.json({error:"max_depth must be a number \u2265 1"},400);let x,A=i.req.query("edge_types");if(A){let P=A.split(",").map(q=>q.trim()).filter(Boolean);for(let q of P)if(!o.has(q))return i.json({error:`invalid edge_type: ${q}`},400);x=P}let N=i.req.query("include_wiki_links"),C=N===void 0?!0:!(N==="0"||N==="false"),I=i.req.query("include_suggestions"),O=I==="1"||I==="true";try{let P=xn(l,{budget:g,scoring:E,maxDepth:k,edgeTypes:x,includeWikiLinks:C,includeSuggestions:O});return i.json(P)}catch(P){let q=P instanceof Error?P.message:"unknown error",K=/not found/.test(q)?404:500;return i.json({error:q},K)}}),t.get("/api/bug-patterns",i=>{let l=i.req.query("min_count"),p=i.req.query("status"),g=i.req.query("project")??void 0,_=i.req.query("limit"),E=i.req.query("offset"),y=l?Number(l):void 0;if(y!==void 0&&(!Number.isFinite(y)||y<1))return i.json({error:"min_count must be a positive integer"},400);let k;if(p==="open")k=!1;else if(p==="resolved")k=!0;else if(p&&p!=="all")return i.json({error:`invalid status: ${p}; valid: open, resolved, all`},400);let x=_?Number(_):void 0;if(x!==void 0&&(!Number.isFinite(x)||x<1))return i.json({error:"invalid limit"},400);let A=E?Number(E):void 0;if(A!==void 0&&(!Number.isFinite(A)||A<0))return i.json({error:"invalid offset"},400);try{let N=Yi({minOccurrenceCount:y,hasResolved:k,project:g,limit:x,offset:A});return i.json(N)}catch(N){return i.json({error:N.message},400)}}),t.get("/api/bug-patterns/setup-status",i=>{let l=f(),g=l.prepare(`SELECT p.name AS project,
|
|
1807
1838
|
COUNT(s.id) AS total_sessions,
|
|
1808
1839
|
SUM(CASE WHEN oi.session_id IS NOT NULL THEN 1 ELSE 0 END) AS extracted_sessions,
|
|
1809
1840
|
MAX(oi.extracted_at) AS last_extracted_at
|
|
@@ -1811,20 +1842,20 @@ ${o}
|
|
|
1811
1842
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1812
1843
|
LEFT JOIN session_output_index oi ON oi.session_id = s.id
|
|
1813
1844
|
GROUP BY p.id
|
|
1814
|
-
ORDER BY total_sessions DESC`).all().map(y=>({project:y.project,total_sessions:y.total_sessions??0,extracted_sessions:y.extracted_sessions??0,remaining_sessions:(y.total_sessions??0)-(y.extracted_sessions??0),last_extracted_at:y.last_extracted_at})),_=g.reduce((y,k)=>(y.total_sessions+=k.total_sessions,y.extracted_sessions+=k.extracted_sessions,y.remaining_sessions+=k.remaining_sessions,y),{total_sessions:0,extracted_sessions:0,remaining_sessions:0}),E=l.prepare("SELECT COUNT(*) AS n FROM bug_pattern_clusters").get();return i.json({projects:g,totals:{..._,cluster_count:E.n}})});let d=M.object({project:M.string().min(1),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),limit:M.number().int().positive().optional(),force:M.boolean().optional()});t.post("/api/extract-outputs/preflight",async i=>{let l=Ce(i);if(l)return l;let p
|
|
1845
|
+
ORDER BY total_sessions DESC`).all().map(y=>({project:y.project,total_sessions:y.total_sessions??0,extracted_sessions:y.extracted_sessions??0,remaining_sessions:(y.total_sessions??0)-(y.extracted_sessions??0),last_extracted_at:y.last_extracted_at})),_=g.reduce((y,k)=>(y.total_sessions+=k.total_sessions,y.extracted_sessions+=k.extracted_sessions,y.remaining_sessions+=k.remaining_sessions,y),{total_sessions:0,extracted_sessions:0,remaining_sessions:0}),E=l.prepare("SELECT COUNT(*) AS n FROM bug_pattern_clusters").get();return i.json({projects:g,totals:{..._,cluster_count:E.n}})});let d=M.object({project:M.string().min(1),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),limit:M.number().int().positive().optional(),force:M.boolean().optional()});t.post("/api/extract-outputs/preflight",async i=>{let l=Ce(i);if(l)return l;let p=fo();if(p>0&&p<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(p/1024**3).toFixed(2)} GB free \u2014 extract-outputs needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:p},507);let g=await i.req.json().catch(()=>null),_=d.safeParse(g);if(!_.success)return i.json({error:"invalid request body",details:_.error.format()},400);let E={project:_.data.project,model:_.data.model??st,limit:_.data.limit??200,force:_.data.force??!1},y=Po();if(E.limit>y.sessionCeiling)return ne({kind:"run-rejected",job_id:null,project:E.project,model:E.model,limit:E.limit,origin:i.req.header("origin")??null,reason:`limit ${E.limit} exceeds session ceiling ${y.sessionCeiling}`}),i.json({error:`requested limit ${E.limit} exceeds session ceiling ${y.sessionCeiling}. Lower the limit or edit launcher.sessionCeiling in ~/.recall/config.json.`},400);let k=cs(E.project);if(!k)return i.json({error:`project "${E.project}" not found`},404);let A=_t({projectId:k.id,limit:E.limit,force:E.force}).eligible.length,N=$o(A),C=We(A,E.model),I=xt(),O=N.estimated_input_tokens_max+N.estimated_output_tokens_max>I.remaining_tokens_24h;if(ne({kind:"preflight",job_id:null,project:E.project,model:E.model,limit:E.limit,origin:i.req.header("origin")??null,sessions_eligible:A}),A===0)return i.json({eligible_session_count:0,...N,plan_window_estimate:C,budget:I,would_exceed_budget:!1,preflight_token:null,expires_at:null,message:"No eligible sessions to extract. Pass force=true to re-extract sessions already at the current extractor version."});let{token:P,expiresAt:q}=jo(E);return i.json({preflight_token:P,expires_at:new Date(q).toISOString(),eligible_session_count:A,...N,plan_window_estimate:C,budget:I,would_exceed_budget:O})});let m=M.object({preflight_token:M.string().length(64)});t.post("/api/extract-outputs/run",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=m.safeParse(p);if(!g.success)return i.json({error:"invalid request body"},400);let _=Mo(g.data.preflight_token);if(!_)return ne({kind:"run-rejected",job_id:null,project:null,model:null,limit:null,origin:i.req.header("origin")??null,reason:"preflight token invalid, expired, or already used"}),i.json({error:"preflight token invalid, expired, or already used"},400);let E=xt(),k=(()=>{let C=cs(_.project);return C?_t({projectId:C.id,limit:_.limit,force:_.force}):null})()?.eligible.length??0,x=$o(k),A=x.estimated_input_tokens_max+x.estimated_output_tokens_max;if(A>E.remaining_tokens_24h)return ne({kind:"run-rejected",job_id:null,project:_.project,model:_.model,limit:_.limit,origin:i.req.header("origin")??null,reason:`projected spend ${A} exceeds remaining 24h budget ${E.remaining_tokens_24h}`}),i.json({error:"daily token budget would be exceeded. Wait for the rolling 24h window to clear, or raise launcher.dailyTokenBudget in ~/.recall/config.json.",budget:E,projected_spend:A},429);let N=id({project:_.project,model:_.model,limit:_.limit,force:_.force,origin:i.req.header("origin")??null});return"error"in N?i.json({error:N.error},400):i.json({jobId:N.jobId,reused:N.reused},N.reused?409:200)}),t.get("/api/extract-outputs/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Bo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of ad(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/extract-outputs/jobs/:jobId",i=>{let l=Bo(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/extract-outputs/jobs/:jobId",i=>{let l=Ce(i);return l||(cd(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))}),t.get("/api/extract-outputs/budget",i=>i.json(xt()));let h=M.object({scope:M.enum(["cluster","project"]),target_id:M.string().min(1),mode:M.enum(["synopsis","priorities","root_cause"]),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()});t.post("/api/bug-patterns/synthesize/preflight",async i=>{{let q=fo();if(q>0&&q<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(q/1024**3).toFixed(2)} GB free \u2014 bug synthesis needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:q},507)}let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=h.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _={scope:g.data.scope,target_id:g.data.target_id,mode:g.data.mode,model:g.data.model??ld},E=In(_);if(!E)return ne({kind:"synth-rejected",job_id:null,project:_.scope==="project"?_.target_id:null,model:_.model,limit:null,origin:i.req.header("origin")??null,reason:"target not found"}),i.json({error:_.scope==="cluster"?`cluster "${_.target_id}" not found in any extracted findings`:`project "${_.target_id}" has no extracted findings to synthesize`},404);let y=Uo({scope:_.scope,mode:_.mode,member_session_count:E.context_summary.session_count,cluster_count:E.context_summary.cluster_count}),k=y.estimated_input_tokens_max+y.estimated_output_tokens_max,x=od(k),A=We(x,_.model),N=xt(),I=y.estimated_input_tokens_max+y.estimated_output_tokens_max>N.remaining_tokens_24h;ne({kind:"synth-preflight",job_id:null,project:_.scope==="project"?_.target_id:null,model:_.model,limit:null,origin:i.req.header("origin")??null,reason:`${_.scope}/${_.mode}/${_.target_id}`});let{token:O,expiresAt:P}=jo({project:_.target_id,model:_.model,limit:1,force:!1});return Gn.set(O,_),setTimeout(()=>Gn.delete(O),9e4).unref?.(),i.json({preflight_token:O,expires_at:new Date(P).toISOString(),estimated_input_tokens_max:y.estimated_input_tokens_max,estimated_output_tokens_max:y.estimated_output_tokens_max,plan_window_estimate:A,budget:N,would_exceed_budget:I,context_summary:E.context_summary})});let b=M.object({preflight_token:M.string().length(64)});t.post("/api/bug-patterns/synthesize/run",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=b.safeParse(p);if(!g.success)return i.json({error:"invalid request body"},400);let _=Mo(g.data.preflight_token),E=Gn.get(g.data.preflight_token)??null;if(Gn.delete(g.data.preflight_token),!E||!_)return ne({kind:"synth-rejected",job_id:null,project:null,model:null,limit:null,origin:i.req.header("origin")??null,reason:"preflight token invalid, expired, or already used"}),i.json({error:"preflight token invalid, expired, or already used"},400);let y=xt(),k=In(E);if(!k)return i.json({error:E.scope==="cluster"?`cluster "${E.target_id}" no longer exists`:`project "${E.target_id}" has no findings`},404);let x=Uo({scope:E.scope,mode:E.mode,member_session_count:k.context_summary.session_count,cluster_count:k.context_summary.cluster_count}),A=x.estimated_input_tokens_max+x.estimated_output_tokens_max;if(A>y.remaining_tokens_24h)return ne({kind:"synth-rejected",job_id:null,project:E.scope==="project"?E.target_id:null,model:E.model,limit:null,origin:i.req.header("origin")??null,reason:`projected spend ${A} exceeds remaining 24h budget ${y.remaining_tokens_24h}`}),i.json({error:"daily token budget would be exceeded. Wait for the rolling 24h window to clear, or raise launcher.dailyTokenBudget in ~/.recall/config.json.",budget:y,projected_spend:A},429);let N=md({intent:E,origin:i.req.header("origin")??null});return"error"in N?i.json({error:N.error},400):i.json({jobId:N.jobId,reused:N.reused},N.reused?409:200)}),t.get("/api/bug-patterns/synthesize/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Wo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of gd(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/bug-patterns/synthesize/jobs/:jobId",i=>{let l=Wo(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/bug-patterns/synthesize/jobs/:jobId",i=>{let l=Ce(i);return l||(_d(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))}),t.get("/api/bug-signatures",i=>{let l=i.req.query("project")??null,p=Math.min(Math.max(1,Number(i.req.query("limit")??100)),500),g=f(),_=["oi.bug_signatures IS NOT NULL"],E=[];l&&(_.push("p.name = ?"),E.push(l));let k=g.prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
|
|
1815
1846
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1816
1847
|
FROM session_output_index oi
|
|
1817
1848
|
JOIN sessions s ON s.id = oi.session_id
|
|
1818
1849
|
JOIN projects p ON p.id = s.project_id
|
|
1819
1850
|
WHERE ${_.join(" AND ")}
|
|
1820
1851
|
ORDER BY oi.extracted_at DESC
|
|
1821
|
-
LIMIT ?`).all(...E,p).map(I=>{let O=[];try{let
|
|
1852
|
+
LIMIT ?`).all(...E,p).map(I=>{let O=[];try{let P=JSON.parse(I.bug_signatures);Array.isArray(P)&&(O=P)}catch{O=[]}return{session_id:I.session_id,project:I.project,auto_title:I.auto_title,started_at:I.started_at,extracted_at:I.extracted_at,rawSignatures:O}}),x=k.flatMap(I=>I.rawSignatures.map(O=>O.message_hash).filter(O=>typeof O=="string")),A=Oo(x),N=k.map(I=>({session_id:I.session_id,project:I.project,auto_title:I.auto_title,started_at:I.started_at,extracted_at:I.extracted_at,signatures:I.rawSignatures.map(O=>{let P=O.message_hash?A.get(O.message_hash)??null:null;return{...O,resolved:Lo(P),resolution:P}}),signature_count:I.rawSignatures.length})),C=N.reduce((I,O)=>(I.sessions_total+=1,O.signature_count>0?(I.sessions_with_findings+=1,I.total_findings+=O.signature_count):I.sessions_empty+=1,I),{sessions_total:0,sessions_with_findings:0,sessions_empty:0,total_findings:0});return i.json({sessions:N,totals:C})}),t.post("/api/bug-signatures/:hash/resolve",async i=>{let l=i.req.param("hash");if(!l||l.length<4)return i.json({error:"invalid message hash"},400);let p=await i.req.json().catch(()=>({})),g=Pu({messageHash:l,resolvedInSessionId:p.resolved_in_session_id??null,fixSummary:p.fix_summary??null});return i.json({resolution:g})}),t.post("/api/bug-signatures/:hash/unresolve",i=>{let l=i.req.param("hash");return!l||l.length<4?i.json({error:"invalid message hash"},400):($u(l),i.json({ok:!0}))}),t.get("/api/bug-patterns/graph",i=>{let l=i.req.query("project")??null,p=i.req.query("include_resolved")!=="0",g=f(),_=["oi.bug_signatures IS NOT NULL","oi.bug_signatures != '[]'"],E=[];l&&(_.push("p.name = ?"),E.push(l));let y=g.prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
|
|
1822
1853
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1823
1854
|
FROM session_output_index oi
|
|
1824
1855
|
JOIN sessions s ON s.id = oi.session_id
|
|
1825
1856
|
JOIN projects p ON p.id = s.project_id
|
|
1826
1857
|
WHERE ${_.join(" AND ")}
|
|
1827
|
-
ORDER BY oi.extracted_at DESC`).all(...E),k=[];for(let
|
|
1858
|
+
ORDER BY oi.extracted_at DESC`).all(...E),k=[];for(let $ of y){let X=[];try{let Y=JSON.parse($.bug_signatures);Array.isArray(Y)&&(X=Y)}catch{continue}for(let Y of X)k.push({sig:Y,session_id:$.session_id,project:$.project,auto_title:$.auto_title})}let x=new Map;for(let $ of k){let X=$.sig.message_hash??`nohash:${($.sig.snippet??"").slice(0,64)}`,Y=x.get(X);Y?Y.push($):x.set(X,[$])}let A=Array.from(x.keys()).filter($=>!$.startsWith("nohash:")),N=Oo(A),C=[],I=new Map,O=[];for(let[$,X]of x){let Y=X[0],Q=Y.sig.message_hash??null,V=Q?N.get(Q)??null:null,J=Lo(V);if(!p&&J)continue;let re=Array.from(new Set(X.map(me=>me.project))).sort(),ze=Array.from(new Set(X.map(me=>me.session_id))),Me={id:Q??$,message_hash:Q,error_type:Y.sig.error_type??null,snippet:(Y.sig.snippet??"").slice(0,200),file:Y.sig.file??null,occurrence_count:X.length,projects:re,resolved:J,fix_summary:V?.fix_summary??null,member_session_ids:ze};C.push(Me);for(let me of X)I.has(me.session_id)||I.set(me.session_id,{session_id:me.session_id,project:me.project,auto_title:me.auto_title}),O.push({cluster_id:Me.id,session_id:me.session_id})}let P=[],q=4,K=new Map;function G($){let X=K.get($)??0;return X>=q?!1:(K.set($,X+1),!0)}for(let $=0;$<C.length;$+=1)for(let X=$+1;X<C.length;X+=1){let Y=C[$],Q=C[X],V=null;Y.error_type&&Y.error_type!=="unknown"&&Y.error_type===Q.error_type?V="same_error_type":Y.file&&Q.file&&Y.file===Q.file&&(V="same_file"),V&&(!G(Y.id)||!G(Q.id)||P.push({a:Y.id,b:Q.id,reason:V}))}return i.json({clusters:C,sessions:Array.from(I.values()),member_edges:O,related_edges:P,totals:{cluster_count:C.length,singleton_count:C.filter($=>$.occurrence_count===1).length,recurring_count:C.filter($=>$.occurrence_count>1).length,session_count:I.size,resolved_count:C.filter($=>$.resolved).length}})}),t.get("/api/bug-patterns/:clusterId",i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=zi(l);return p?i.json(p):i.json({error:`cluster ${l} not found`},404)}),t.post("/api/bug-patterns/:clusterId/resolve",async i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);let g=p.resolved_in_session_id,_=p.fix_summary;if(typeof g!="string"||g.length===0)return i.json({error:"resolved_in_session_id required"},400);if(typeof _!="string"||_.trim().length===0)return i.json({error:"fix_summary required"},400);try{let E=Ki(l,g,_);return i.json(E)}catch(E){let y=E instanceof Error?E.message:"unknown error",k=/not found/.test(y)?404:(/not a member/.test(y),400);return i.json({error:y},k)}}),t.post("/api/bug-patterns/:clusterId/split",async i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);let g=p.member_session_ids;if(!Array.isArray(g)||g.length===0)return i.json({error:"member_session_ids must be a non-empty array of strings"},400);let _=[];for(let E of g){if(typeof E!="string"||E.length===0)return i.json({error:"member_session_ids must contain only non-empty strings"},400);_.push(E)}try{let E=Vi(l,_);return i.json(E)}catch(E){let y=E instanceof Error?E.message:"unknown error",k=/not found/.test(y)?404:(/cannot split|none of the supplied/.test(y),400);return i.json({error:y},k)}}),t.post("/api/links",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=l.source_session_id,g=l.target_session_id,_=l.link_type;if(typeof p!="string"||p.length===0)return i.json({error:"source_session_id required"},400);if(typeof g!="string"||g.length===0)return i.json({error:"target_session_id required"},400);if(typeof _!="string")return i.json({error:"link_type required"},400);if(!o.has(_))return i.json({error:`invalid link_type: ${_}`},400);if(_!=="wiki_link")return i.json({error:`link_type '${_}' is not user-writable; only wiki_link is exposed via this endpoint. Other types must go through the suggestions-queue review flow.`},403);if(p===g)return i.json({error:"a session cannot link to itself"},400);let E=f();if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(p))return i.json({error:`source session not found: ${p}`},404);if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(g))return i.json({error:`target session not found: ${g}`},404);let x=Cs({sourceSessionId:g,targetSessionId:p,linkType:"wiki_link"});if(x.length>0)return i.json({link:x[0]});try{let A=ha({source_session_id:p,target_session_id:g,link_type:"wiki_link",confidence:1,source:"manual",evidence:l.evidence??{created_via:"context_menu"},approved:!0});return i.json({link:A})}catch(A){return i.json({error:A.message},400)}}),t.delete("/api/links/:id",i=>{let l=i.req.param("id"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"id must be a positive integer"},400);let g=Ea(p);return g.removed===0?i.json({error:`link ${p} not found`},404):i.json(g)}),t.get("/api/sessions/:id/links",i=>{let l=i.req.param("id");if(!l)return i.json({error:"sessionId required"},400);let p=f();if(!p.prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:`session not found: ${l}`},404);let _=i.req.query("type")??"wiki_link";if(!o.has(_))return i.json({error:`invalid type: ${_}`},400);let E=_,y=qt(l).filter(I=>I.link_type===E),k=gu(l,y);if(k.length===0)return i.json({links:[]});let x=k.map(I=>I.otherSessionId),A=x.map(()=>"?").join(","),N=p.prepare(`SELECT s.id,
|
|
1828
1859
|
NULLIF(sa.alias, '') AS alias,
|
|
1829
1860
|
s.auto_title,
|
|
1830
1861
|
s.first_user_message,
|
|
@@ -1832,7 +1863,7 @@ ${o}
|
|
|
1832
1863
|
FROM sessions s
|
|
1833
1864
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1834
1865
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1835
|
-
WHERE s.id IN (${A})`).all(...
|
|
1866
|
+
WHERE s.id IN (${A})`).all(...x),C=new Map(N.map(I=>[I.id,I]));return i.json({links:k.map(I=>{let O=C.get(I.otherSessionId),P=O?.alias?.trim()||O?.auto_title?.trim()||(O?.first_user_message?O.first_user_message.slice(0,80):"")||I.otherSessionId.slice(0,8);return{linkId:I.linkId,otherSessionId:I.otherSessionId,direction:I.direction,updatedAt:I.updatedAt,title:P,project:O?.project??null}})})}),t.patch("/api/links/suggestions/:id",async i=>{let l=i.req.param("id"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"id must be a positive integer"},400);let g=await i.req.json().catch(()=>null);if(!g||typeof g.status!="string")return i.json({error:"status required (approved|rejected)"},400);if(g.status!=="approved"&&g.status!=="rejected")return i.json({error:`invalid status: ${g.status}`},400);try{let _=dr(p,g.status);return i.json(_)}catch(_){let E=_.message,y=/already decided/.test(E)?409:/not found/.test(E)?404:400;return i.json({error:E},y)}}),t.post("/api/links/suggestions/bulk-decide",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=Array.isArray(l.ids)?l.ids:null;if(!p||p.length===0)return i.json({error:"ids must be a non-empty array"},400);if(p.length>1e3)return i.json({error:"bulk-decide capped at 1000 ids per call"},400);if(l.status!=="approved"&&l.status!=="rejected")return i.json({error:`invalid status: ${l.status}`},400);let g=l.status,_=0,E=0,y=[];for(let k of p){let x=Number(k);if(!Number.isInteger(x)||x<=0){y.push({id:Number.isFinite(Number(k))?Number(k):-1,error:"invalid id"});continue}try{dr(x,g),_+=1}catch(A){let N=A.message;/already decided/.test(N)?E+=1:y.push({id:x,error:N})}}return i.json({decided:_,skipped:E,errors:y})}),t.get("/api/sessions",i=>{let l=f(),p=i.req.query("project"),g=i.req.query("since"),_=i.req.query("until"),E=i.req.queries("tag")??[],y=i.req.query("collection"),k=Math.max(1,Math.min(500,Number(i.req.query("limit")??100))),x=i.req.query("cursor"),A=null;if(x)try{let K=Buffer.from(x,"base64url").toString("utf8"),G=JSON.parse(K);typeof G.ts=="string"&&typeof G.id=="string"&&(A={ts:G.ts,id:G.id})}catch{}let N=i.req.query("system")==="1"||i.req.query("system")==="true",C={limit:k},I="s.message_count > 2"+Yn("s",N);if(p&&(I+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",C.proj=`%${ws(p)}%`),g&&(I+=" AND s.started_at >= @since",C.since=g),_&&(I+=" AND s.started_at <= @until",/^\d{4}-\d{2}-\d{2}$/.test(_)?C.until=`${_}T23:59:59.999Z`:C.until=_),E.length>0&&E.map(G=>Qe(G)).filter(Boolean).forEach((G,$)=>{I+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${$})`,C[`tag_${$}`]=G}),y){let K=no(y);if(K.length===0)return i.json({items:[],nextCursor:null});let G=K.map(($,X)=>`@col_${X}`).join(",");I+=` AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id IN (${G}))`,K.forEach(($,X)=>{C[`col_${X}`]=$})}A&&(I+=" AND (COALESCE(s.ended_at, s.started_at, '') < @cursor_ts OR (COALESCE(s.ended_at, s.started_at, '') = @cursor_ts AND s.id < @cursor_id))",C.cursor_ts=A.ts,C.cursor_id=A.id);let O=l.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1836
1867
|
s.message_count, s.first_user_message, s.git_branch,
|
|
1837
1868
|
s.auto_title, s.auto_title_source, s.verification_status,
|
|
1838
1869
|
COALESCE(s.ended_at, s.started_at, '') AS _cursor_ts,
|
|
@@ -1853,15 +1884,15 @@ ${o}
|
|
|
1853
1884
|
LEFT JOIN session_notes sn ON sn.session_id = s.id
|
|
1854
1885
|
WHERE ${I}
|
|
1855
1886
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC, s.id DESC
|
|
1856
|
-
LIMIT @limit`).all(C),
|
|
1887
|
+
LIMIT @limit`).all(C),P=O.map(({tags_csv:K,_cursor_ts:G,...$})=>{let X=$.id,Y=v.getOrigin(X),Q=$.alias,V=Q==null?null:v.isSessionAutoLinked(X)?"auto":"manual",J=Qt({auto_title:$.auto_title,auto_title_source:$.auto_title_source??null,has_alias:Q!=null&&V==="manual"});return{...$,tags:K?K.split(","):[],origin:Y?{editor:Y.editor,label:Y.label}:null,alias_source:V,title_quality:J}}),q=null;if(O.length===k&&O.length>0){let K=O[O.length-1],G=JSON.stringify({ts:K._cursor_ts??"",id:K.id});q=Buffer.from(G,"utf8").toString("base64url")}return i.json({items:P,nextCursor:q})}),t.get("/api/sessions/:id",i=>{let l=f(),p=i.req.param("id"),g=l.prepare(`SELECT s.*, p.name AS project_name, p.decoded_path,
|
|
1857
1888
|
NULLIF(sa.alias, '') AS alias
|
|
1858
1889
|
FROM sessions s
|
|
1859
1890
|
JOIN projects p ON p.id = s.project_id
|
|
1860
1891
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1861
|
-
WHERE s.id = ?`).get(p);if(!g)return i.json({error:"not found"},404);let _=
|
|
1892
|
+
WHERE s.id = ?`).get(p);if(!g)return i.json({error:"not found"},404);let _=Gt(p),E=v.getOrigin(p),y=E?{editor:E.editor,label:E.label}:null,k=g.alias==null?null:v.isSessionAutoLinked(p)?"auto":"manual",x=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
1862
1893
|
FROM messages
|
|
1863
1894
|
WHERE session_id = ?
|
|
1864
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(p);return i.json({session:{...g,tags:_,origin:y,alias_source:k},messages:
|
|
1895
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(p);return i.json({session:{...g,tags:_,origin:y,alias_source:k},messages:x})}),t.get("/api/tags",i=>i.json(dt())),t.get("/api/sessions/:id/tags",i=>i.json({tags:Gt(i.req.param("id"))})),t.post("/api/sessions/:id/tags",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.tag!="string")return i.json({error:"tag required"},400);try{let g=ut(l,p.tag);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/sessions/:id/tags/:tag",i=>{let l=i.req.param("id"),p=i.req.param("tag");return i.json(Na(l,p))}),t.get("/api/config/auto-tag",i=>i.json(xo(Fe()))),t.put("/api/config/auto-tag",async i=>{let l=await i.req.json().catch(()=>({})),p=On.partial().safeParse(l);if(!p.success)return i.json({error:"invalid config",issues:p.error.issues},400);let g=p.data;g.apiKey===void 0&&delete g.apiKey;let _=ju(g);return _.autopilot&&_.enabled&&_.backend==="api"&&_.apiKey&&Un(),i.json(xo(_))}),t.get("/api/onboarding",i=>{let p=f().prepare(`SELECT s.id,
|
|
1865
1896
|
p.name AS project,
|
|
1866
1897
|
s.started_at,
|
|
1867
1898
|
s.ended_at,
|
|
@@ -1873,7 +1904,7 @@ ${o}
|
|
|
1873
1904
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1874
1905
|
WHERE s.message_count > 2
|
|
1875
1906
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1876
|
-
LIMIT 1`).get();return i.json({state:Fn(),mostRecentSession:p??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),p=Dn.partial().safeParse(l);return p.success?i.json(Fd(p.data)):i.json({error:"invalid onboarding state",issues:p.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(Pd())),t.get("/api/config/mcp-install",i=>i.json({...qe(),claudeCliAvailable:pe()})),t.post("/api/config/mcp-install",i=>i.json({...Ld(),claudeCliAvailable:pe()})),t.delete("/api/config/mcp-install",i=>i.json({...Cd(),claudeCliAvailable:pe()}));let T=M.object({scope:M.object({untaggedOnly:M.boolean().optional(),project:M.string().optional(),collectionId:M.string().optional(),sessionIds:M.array(M.string()).optional(),limit:M.number().int().min(1).max(500).optional()}).default({}),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:M.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(ii)return i.json({error:"a scan is already running"},409);if(!pe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!qe().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),g=T.safeParse(p);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=g.data.scope,E=Fe(),y=g.data.model??E.model,k=f(),N=()=>k.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,A=N();ii=!0;let L;try{let C=g.data.scanId;L=await br(_,{model:y,scanId:C});let I=N(),O=Math.max(0,I-A);return C&&Is(C,{type:"done",result:{success:L.success,exitCode:L.exitCode,tagsAdded:O}}),i.json({success:L.success,exitCode:L.exitCode,tagsAdded:O,model:y,stdout:Se(L.stdout.slice(0,2e3)).redacted,stderrTail:Se(L.stderr.slice(-2e3)).redacted})}finally{ii=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return Ye(i,async p=>{let g=[],_={resolve:()=>{}},E=new Promise(A=>{_.resolve=A}),y=Na(l,A=>{g.push(A);let L=_.resolve;E=new Promise(C=>{_.resolve=C}),L()}),k=!1,N=setInterval(()=>{k||p.writeSSE({event:"heartbeat",data:""}).catch(()=>{k=!0})},15e3);try{for(;!k;){g.length===0&&await E;let A=g.shift();if(A&&(await p.writeSSE({event:A.type,data:JSON.stringify(A)}),A.type==="done"))break}}finally{k=!0,clearInterval(N),y()}})}),t.get("/api/prompts",i=>i.json({prompts:fr.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:pe()})),t.post("/api/prompts/run",async i=>{if(!pe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!qe().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),_=M.object({name:M.string(),args:M.record(M.string(),M.unknown()).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).safeParse(p);if(!_.success)return i.json({error:"invalid request",issues:_.error.issues},400);let E=xa(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let y=E.build(_.data.args??{}),k=Fe(),N=_.data.model??k.model,A=await et(y,E.allowedTools,{model:N});return i.json({success:A.success,exitCode:A.exitCode,promptName:E.name,model:N,stdout:A.stdout,stderrTail:A.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(fs())),t.get("/api/autopilot/events",i=>Ye(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(fs())});let p=[],g=()=>{},_=new Promise(y=>g=y),E=xd(y=>{p.push(y);let k=g;_=new Promise(N=>g=N),k()});try{for(;;){if(p.length===0){let k=new Promise(A=>setTimeout(()=>A("tick"),3e4));if(await Promise.race([_.then(()=>"event"),k])==="tick"){await l.writeSSE({event:"heartbeat",data:"1"});continue}}let y=p.shift();y&&await l.writeSSE({event:"state",data:JSON.stringify(y)})}}finally{E()}})),t.post("/api/autopilot/kick",i=>(Mn(),i.json({ok:!0,snapshot:fs()})));let S=M.object({scope:M.object({untaggedOnly:M.boolean().optional(),project:M.string().optional(),collectionId:M.string().optional(),sessionIds:M.array(M.string()).optional(),limit:M.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=Fe();if(!l.enabled)return i.json({error:"auto-tagging is disabled"},403);if(l.backend!=="api")return i.json({error:"api-backend scan requires backend=api in config"},400);if(!l.apiKey)return i.json({error:"no api key configured"},400);let p=await i.req.json().catch(()=>({})),g=S.safeParse(p);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=mt(g.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=Sd(_.length);return kd(E,{apiKey:l.apiKey,model:l.model,minTags:l.minTagsPerSession,maxTags:l.maxTagsPerSession,sessions:_}),i.json({scanId:E.id,total:E.total})}),t.get("/api/tags/scan/:id",i=>{let l=Ln(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let{controller:p,listeners:g,..._}=l;return i.json(_)}),t.get("/api/tags/scan/:id/events",i=>{let l=Ln(i.req.param("id"));return l?Ye(i,async p=>{await p.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let k of l.results)await p.writeSSE({event:"result",data:JSON.stringify(k)});let g=[],_={resolve:()=>{}},E=new Promise(k=>{_.resolve=k}),y=Td(l,k=>{g.push(k);let N=_.resolve;E=new Promise(A=>{_.resolve=A}),N()});try{for(;l.status==="running"||l.status==="pending";){g.length===0&&await E;let k=g.shift();if(k&&(await p.writeSSE({event:k.type,data:JSON.stringify(k)}),k.type==="done"||k.type==="status"&&(k.status==="cancelled"||k.status==="failed")))break}}finally{y()}}):i.json({error:"scan not found"},404)});let w=M.object({selection:M.array(M.object({sessionId:M.string(),tags:M.array(M.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=Ln(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let p=await i.req.json().catch(()=>({})),g=w.safeParse(p);if(!g.success)return i.json({error:"invalid selection"},400);let _=Ad(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return yd(l),wd(l),i.json({ok:!0})}),t.put("/api/sessions/:id/alias",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.alias!="string")return i.json({error:"alias required"},400);try{let g=he(l,p.alias);if(p.pin===!0)v.unlinkSession(l);else{let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l),E=_?.cwd?_.cwd.replace(/\/+$/,""):null,y=!1;if(E&&_?.started_at){let k=Date.parse(_.started_at),N=_.started_at,A=v.all().filter(L=>L.cwd&&L.cwd.replace(/\/+$/,"")===E&&Kt({sessionStartedAt:N,terminalOpenedAt:L.opened_at??null}).allowed);if(Number.isFinite(k)&&A.length>0){let C=A.map(I=>({t:I,gap:k-Date.parse(I.opened_at??"")})).filter(I=>Number.isFinite(I.gap)).sort((I,O)=>I.gap-O.gap)[0];C&&(v.linkSession(l,C.t.shell_pid),y=!0)}}y||v.unlinkSession(l)}return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return Xs(l),v.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:Te(l)})}),t.get("/api/config/auto-title",i=>i.json(it())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),p=Rn.partial().safeParse(l);return p.success?i.json(_u(p.data)):i.json({error:"invalid config",issues:p.error.issues},400)}),t.get("/api/sessions/:id/auto-title",i=>{let l=i.req.param("id"),p=Le(l);return p?i.json(p):i.json({error:"session not found"},404)}),t.post("/api/sessions/:id/auto-title",async i=>{let l=i.req.param("id");if(!it().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);if(!f().prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:"session not found"},404);try{let E=await Dl(l);return Ee(l,E,"agent"),i.json(Le(l))}catch(E){return i.json({error:E.message,code:"agent-title-failed"},500)}}),t.post("/api/sessions/:id/auto-title/revert",i=>{let l=i.req.param("id"),p=Le(l);if(!p)return i.json({error:"session not found"},404);let g=p.auto_title_history;if(!g||g.length===0)return i.json({error:"no prior title to revert to",code:"no-history"},422);let _=g[g.length-1];return Ee(l,_.title,"agent"),i.json(Le(l))}),t.post("/api/sessions/:id/regenerate-title",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),g=p.model??kn;try{let _=await ho(l,{model:g,force:p.force===!0,budget:typeof p.budget=="number"?p.budget:void 0,signal:i.req.raw.signal}),E=Le(l),y=E?.auto_title_history&&E.auto_title_history.length>0?E.auto_title_history[E.auto_title_history.length-1].title:null;return i.json({..._,previous_title:y})}catch(_){if(_ instanceof Rt)return i.json({error:_.message,code:"no-context-available",session_id:_.sessionId},422);let E=_ instanceof Error?_.message:"unknown error",y=/not found|unknown/i.test(E)?404:500;return i.json({error:E,code:"regenerate-failed"},y)}}),t.post("/api/sessions/regenerate-titles-batch",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=l.project;if(typeof p!="string"||p.length===0)return i.json({error:"project (string) required"},400);let g=l.quality_filter;if(!Array.isArray(g)||g.length===0)return i.json({error:"quality_filter (non-empty array) required"},400);let _=new Set(["low_signal","recursive_meta","programmatic"]),E=[];for(let O of g){if(typeof O!="string")return i.json({error:`invalid quality_filter entry: ${O}`},400);if(!_.has(O))return i.json({error:`quality_filter must be a subset of ${[..._].join(",")}; got ${O}`},400);E.push(O)}let y=typeof l.model=="string"&&l.model.length>0?l.model:kn,k=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,N=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,L=f().prepare(`SELECT s.id,
|
|
1907
|
+
LIMIT 1`).get();return i.json({state:Bn(),mostRecentSession:p??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),p=Hn.partial().safeParse(l);return p.success?i.json(sp(p.data)):i.json({error:"invalid onboarding state",issues:p.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(np())),t.get("/api/config/mcp-install",i=>i.json({...qe(),claudeCliAvailable:pe()})),t.post("/api/config/mcp-install",i=>i.json({...zd(),claudeCliAvailable:pe()})),t.delete("/api/config/mcp-install",i=>i.json({...Kd(),claudeCliAvailable:pe()}));let T=M.object({scope:M.object({untaggedOnly:M.boolean().optional(),project:M.string().optional(),collectionId:M.string().optional(),sessionIds:M.array(M.string()).optional(),limit:M.number().int().min(1).max(500).optional()}).default({}),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:M.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(pi)return i.json({error:"a scan is already running"},409);if(!pe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!qe().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),g=T.safeParse(p);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=g.data.scope,E=Fe(),y=g.data.model??E.model,k=f(),x=()=>k.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,A=x();pi=!0;let N;try{let C=g.data.scanId;N=await Sr(_,{model:y,scanId:C});let I=x(),O=Math.max(0,I-A);return C&&js(C,{type:"done",result:{success:N.success,exitCode:N.exitCode,tagsAdded:O}}),i.json({success:N.success,exitCode:N.exitCode,tagsAdded:O,model:y,stdout:Se(N.stdout.slice(0,2e3)).redacted,stderrTail:Se(N.stderr.slice(-2e3)).redacted})}finally{pi=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return Ye(i,async p=>{let g=[],_={resolve:()=>{}},E=new Promise(A=>{_.resolve=A}),y=Ca(l,A=>{g.push(A);let N=_.resolve;E=new Promise(C=>{_.resolve=C}),N()}),k=!1,x=setInterval(()=>{k||p.writeSSE({event:"heartbeat",data:""}).catch(()=>{k=!0})},15e3);try{for(;!k;){g.length===0&&await E;let A=g.shift();if(A&&(await p.writeSSE({event:A.type,data:JSON.stringify(A)}),A.type==="done"))break}}finally{k=!0,clearInterval(x),y()}})}),t.get("/api/prompts",i=>i.json({prompts:hr.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:pe()})),t.post("/api/prompts/run",async i=>{if(!pe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!qe().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),_=M.object({name:M.string(),args:M.record(M.string(),M.unknown()).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).safeParse(p);if(!_.success)return i.json({error:"invalid request",issues:_.error.issues},400);let E=La(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let y=E.build(_.data.args??{}),k=Fe(),x=_.data.model??k.model,A=await et(y,E.allowedTools,{model:x});return i.json({success:A.success,exitCode:A.exitCode,promptName:E.name,model:x,stdout:A.stdout,stderrTail:A.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(Es())),t.get("/api/autopilot/events",i=>Ye(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(Es())});let p=[],g=()=>{},_=new Promise(y=>g=y),E=Jd(y=>{p.push(y);let k=g;_=new Promise(x=>g=x),k()});try{for(;;){if(p.length===0){let k=new Promise(A=>setTimeout(()=>A("tick"),3e4));if(await Promise.race([_.then(()=>"event"),k])==="tick"){await l.writeSSE({event:"heartbeat",data:"1"});continue}}let y=p.shift();y&&await l.writeSSE({event:"state",data:JSON.stringify(y)})}}finally{E()}})),t.post("/api/autopilot/kick",i=>(Un(),i.json({ok:!0,snapshot:Es()})));let S=M.object({scope:M.object({untaggedOnly:M.boolean().optional(),project:M.string().optional(),collectionId:M.string().optional(),sessionIds:M.array(M.string()).optional(),limit:M.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=Fe();if(!l.enabled)return i.json({error:"auto-tagging is disabled"},403);if(l.backend!=="api")return i.json({error:"api-backend scan requires backend=api in config"},400);if(!l.apiKey)return i.json({error:"no api key configured"},400);let p=await i.req.json().catch(()=>({})),g=S.safeParse(p);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=mt(g.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=$d(_.length);return qd(E,{apiKey:l.apiKey,model:l.model,minTags:l.minTagsPerSession,maxTags:l.maxTagsPerSession,sessions:_}),i.json({scanId:E.id,total:E.total})}),t.get("/api/tags/scan/:id",i=>{let l=Mn(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let{controller:p,listeners:g,..._}=l;return i.json(_)}),t.get("/api/tags/scan/:id/events",i=>{let l=Mn(i.req.param("id"));return l?Ye(i,async p=>{await p.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let k of l.results)await p.writeSSE({event:"result",data:JSON.stringify(k)});let g=[],_={resolve:()=>{}},E=new Promise(k=>{_.resolve=k}),y=Ud(l,k=>{g.push(k);let x=_.resolve;E=new Promise(A=>{_.resolve=A}),x()});try{for(;l.status==="running"||l.status==="pending";){g.length===0&&await E;let k=g.shift();if(k&&(await p.writeSSE({event:k.type,data:JSON.stringify(k)}),k.type==="done"||k.type==="status"&&(k.status==="cancelled"||k.status==="failed")))break}}finally{y()}}):i.json({error:"scan not found"},404)});let w=M.object({selection:M.array(M.object({sessionId:M.string(),tags:M.array(M.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=Mn(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let p=await i.req.json().catch(()=>({})),g=w.safeParse(p);if(!g.success)return i.json({error:"invalid selection"},400);let _=Xd(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return Hd(l),Bd(l),i.json({ok:!0})}),t.put("/api/sessions/:id/alias",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.alias!="string")return i.json({error:"alias required"},400);try{let g=he(l,p.alias);if(p.pin===!0)v.unlinkSession(l);else{let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l),E=_?.cwd?_.cwd.replace(/\/+$/,""):null,y=!1;if(E&&_?.started_at){let k=Date.parse(_.started_at),x=_.started_at,A=v.all().filter(N=>N.cwd&&N.cwd.replace(/\/+$/,"")===E&&ns({sessionStartedAt:x,terminalOpenedAt:N.opened_at??null}).allowed);if(Number.isFinite(k)&&A.length>0){let C=A.map(I=>({t:I,gap:k-Date.parse(I.opened_at??"")})).filter(I=>Number.isFinite(I.gap)).sort((I,O)=>I.gap-O.gap)[0];C&&(v.linkSession(l,C.t.shell_pid),y=!0)}}y||v.unlinkSession(l)}return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return Ys(l),v.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:Te(l)})}),t.get("/api/config/auto-title",i=>i.json(rt())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),p=on.partial().safeParse(l);return p.success?i.json(el(p.data)):i.json({error:"invalid config",issues:p.error.issues},400)}),t.get("/api/sessions/:id/auto-title",i=>{let l=i.req.param("id"),p=Le(l);return p?i.json(p):i.json({error:"session not found"},404)}),t.post("/api/sessions/:id/auto-title",async i=>{let l=i.req.param("id");if(!rt().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);if(!f().prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:"session not found"},404);try{let E=await qc(l);return Ee(l,E,"agent"),i.json(Le(l))}catch(E){return i.json({error:E.message,code:"agent-title-failed"},500)}}),t.post("/api/sessions/:id/auto-title/revert",i=>{let l=i.req.param("id"),p=Le(l);if(!p)return i.json({error:"session not found"},404);let g=p.auto_title_history;if(!g||g.length===0)return i.json({error:"no prior title to revert to",code:"no-history"},422);let _=g[g.length-1];return Ee(l,_.title,"agent"),i.json(Le(l))}),t.post("/api/sessions/:id/regenerate-title",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),g=p.model??Ln;try{let _=await No(l,{model:g,force:p.force===!0,budget:typeof p.budget=="number"?p.budget:void 0,signal:i.req.raw.signal}),E=Le(l),y=E?.auto_title_history&&E.auto_title_history.length>0?E.auto_title_history[E.auto_title_history.length-1].title:null;return i.json({..._,previous_title:y})}catch(_){if(_ instanceof kt)return i.json({error:_.message,code:"no-context-available",session_id:_.sessionId},422);let E=_ instanceof Error?_.message:"unknown error",y=/not found|unknown/i.test(E)?404:500;return i.json({error:E,code:"regenerate-failed"},y)}}),t.post("/api/sessions/regenerate-titles-batch",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=l.project;if(typeof p!="string"||p.length===0)return i.json({error:"project (string) required"},400);let g=l.quality_filter;if(!Array.isArray(g)||g.length===0)return i.json({error:"quality_filter (non-empty array) required"},400);let _=new Set(["low_signal","recursive_meta","programmatic"]),E=[];for(let O of g){if(typeof O!="string")return i.json({error:`invalid quality_filter entry: ${O}`},400);if(!_.has(O))return i.json({error:`quality_filter must be a subset of ${[..._].join(",")}; got ${O}`},400);E.push(O)}let y=typeof l.model=="string"&&l.model.length>0?l.model:Ln,k=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,x=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,N=f().prepare(`SELECT s.id,
|
|
1877
1908
|
s.auto_title,
|
|
1878
1909
|
s.auto_title_source,
|
|
1879
1910
|
NULLIF(sa.alias, '') AS alias
|
|
@@ -1882,14 +1913,14 @@ ${o}
|
|
|
1882
1913
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1883
1914
|
WHERE p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\'
|
|
1884
1915
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1885
|
-
LIMIT @limit`).all({proj:`%${
|
|
1916
|
+
LIMIT @limit`).all({proj:`%${ws(p)}%`,limit:k}),C=new Set(E),I=N.filter(O=>{let P=O.alias==null?null:v.isSessionAutoLinked(O.id)?"auto":"manual",q=Qt({auto_title:O.auto_title,auto_title_source:O.auto_title_source??null,has_alias:O.alias!=null&&P==="manual"});return C.has(q)});return Ye(i,async O=>{let P=I.length,q=[],K=[],G=[],$=0,X=async(Q,V)=>{$+=1;try{await O.writeSSE({id:String($),event:Q,data:JSON.stringify(V)})}catch{}};await X("start",{total:P,model:y});let Y=0;for(let Q of I){if(i.req.raw.signal.aborted)break;Y+=1;try{let V=await No(Q.id,{model:y,budget:x,signal:i.req.raw.signal});V.written?(q.push(Q.id),await X("progress",{sessionId:Q.id,title:V.title,evidence:V.evidence,confidence:V.confidence,current:Y,total:P})):(K.push({sessionId:Q.id,reason:V.skipped??"unknown"}),await X("skipped",{sessionId:Q.id,reason:V.skipped??"unknown",current:Y,total:P}))}catch(V){let J=V instanceof Error?V.message:String(V),re=V instanceof kt?"no-context-available":"failed";G.push({sessionId:Q.id,error:J}),await X("error",{sessionId:Q.id,error:J,code:re,current:Y,total:P})}}await X("done",{generated:q,skipped:K,failed:G,cancelled:i.req.raw.signal.aborted})})}),t.get("/api/sessions/:id/notes",i=>{let l=i.req.param("id"),p=fn(l);return p?i.json(p):i.body(null,204)}),t.put("/api/sessions/:id/notes",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.content!="string")return i.json({error:"content required (string)"},400);try{let g=Il(l,p.content);return i.json(g)}catch(g){return console.error("[notes] failed to save note for session",l,g),i.json({error:"failed to save note"},500)}}),t.post("/api/sessions/:id/generate-note",async i=>{let l=i.req.param("id");if(!rt().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);try{let g=await vl(l),_=jl(l,g);return i.json(_)}catch(g){let _=g.message,E=/no messages available/i.test(_)?404:500;return i.json({error:_},E)}}),t.get("/api/semantic/status",i=>i.json(wr())),t.put("/api/semantic/config",async i=>{let l=await i.req.json().catch(()=>({})),p=Ds.partial().safeParse(l);return p.success?(Fs(p.data),i.json(wr())):i.json({error:"invalid semantic config",issues:p.error.issues},400)}),t.get("/api/semantic/config",i=>i.json(ce())),t.post("/api/semantic/backfill",je,async i=>{if(gi)return i.json({error:"a scan is already running"},409);if(!ce().enabled)return i.json({error:"semantic search is disabled"},400);let p=await i.req.json().catch(()=>({})),g=Math.max(1,Math.min(5e3,Number(p.limit??200)));return gi=!0,Us({limit:g,force:!!p.force}).catch(_=>console.error("[semantic.backfill] error:",_)).finally(()=>{gi=!1}),i.json({ok:!0,limit:g})}),t.post("/api/semantic/sessions/:id",je,async i=>{if(!ce().enabled)return i.json({error:"semantic search is disabled"},400);let p=i.req.param("id");if(!p)return i.json({error:"session id required"},400);let g=await $s(p);return i.json(g)}),t.get("/api/semantic/vector-status",i=>{let l=_e(),p=ve(),g=Ge(),_=(i.req.query("project")??"").trim(),E=_.length>512?"":_,y=null,k=0;if(E){let C=f(),I=C.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(E,E);I?y=C.prepare("SELECT COUNT(*) AS n FROM chunk_queue WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)").get(I.id).n:y=0}let x=ai(),A=x?.chunksPerSec??0,N=A>0?Math.ceil(p.queueDepth/A):0;return A>0&&y!==null&&(k=Math.ceil(y/A)),i.json({embedder:l,worker:p,modelInstalled:g,project:E||null,queueDepthForProject:y,etaForProject:k,throughput:{chunksPerSec:A,samples:x?.samples??0,source:x?"local-measured":"no-samples-yet"},etaSeconds:N})}),t.post("/api/semantic/install",je,async i=>{if(Ge())return i.json({ok:!0,status:"already_installed"});if(mi)return i.json({error:"a scan is already running"},409);mi=!0;try{return await $p(),await Ue(),ys(),i.json({ok:!0,status:"installed"})}catch(l){let p=l instanceof Error?l.message:"unknown error";return i.json({ok:!1,error:p},500)}finally{mi=!1}}),t.get("/api/semantic/reindex-preview",je,async i=>{let l=(i.req.query("project")??"").trim();if(!l)return i.json({error:"project name required"},400);let p=f(),g=p.prepare("SELECT id, name FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(l,l);if(!g)return i.json({error:`project not found: ${l}`},404);let _=p.prepare(`SELECT
|
|
1886
1917
|
COUNT(*) AS total,
|
|
1887
1918
|
SUM(CASE WHEN s.message_count >= 3 THEN 1 ELSE 0 END) AS eligible,
|
|
1888
1919
|
SUM(CASE WHEN s.message_count >= 3 AND EXISTS
|
|
1889
1920
|
(SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id) THEN 1 ELSE 0 END)
|
|
1890
1921
|
AS indexed
|
|
1891
|
-
FROM sessions s WHERE s.project_id = ?`).get(g.id),E=_.eligible??0,y=_.indexed??0,k=Math.max(0,E-y),
|
|
1892
|
-
WHERE session_id NOT IN (SELECT id FROM sessions WHERE project_id = ?)`).get(g.id).n,A=
|
|
1922
|
+
FROM sessions s WHERE s.project_id = ?`).get(g.id),E=_.eligible??0,y=_.indexed??0,k=Math.max(0,E-y),x=p.prepare(`SELECT COUNT(*) AS n FROM chunk_queue
|
|
1923
|
+
WHERE session_id NOT IN (SELECT id FROM sessions WHERE project_id = ?)`).get(g.id).n,A=ai(),N=2,C=30,I=N,O=C,P=N*C,q="fallback-baseline",K=0;if(A)I=A.sessionsPerSec,O=A.avgChunksPerSession,P=A.chunksPerSec,q="local-measured",K=A.samples;else if(Ge()){if(!_e().loaded)try{await Ue()}catch{}try{let Y=["[user] benchmark probe one \u2014 typical session opening turn","[assistant] benchmark probe two \u2014 typical assistant response with code reference","[user] benchmark probe three \u2014 typical follow-up clarification"],Q=Date.now();await ct(Y);let V=Date.now()-Q;V>0&&(P=Y.length*1e3/V,I=P/C,q="live-benchmark")}catch{}}let G=Y=>p.prepare(`SELECT
|
|
1893
1924
|
COALESCE(SUM(CASE WHEN ec > 5 THEN 5 ELSE ec END), 0) AS total_quick,
|
|
1894
1925
|
COALESCE(SUM(CASE WHEN ec > 80 THEN 80 ELSE ec END), 0) AS total_standard,
|
|
1895
1926
|
COALESCE(SUM(CASE WHEN ec > 200 THEN 200 ELSE ec END), 0) AS total_full,
|
|
@@ -1902,9 +1933,9 @@ ${o}
|
|
|
1902
1933
|
END AS ec
|
|
1903
1934
|
FROM sessions s
|
|
1904
1935
|
WHERE s.project_id = ? AND s.message_count >= 3 ${Y}
|
|
1905
|
-
)`).get(g.id)
|
|
1936
|
+
)`).get(g.id),$=G("AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)"),X=G("");return i.json({project:g.name,total:_.total??0,eligible:E,indexed:y,pendingNew:k,pendingForce:E,modelInstalled:Ge(),modelName:"BAAI/bge-base-en-v1.5",embedderLoaded:_e().loaded,workerRunning:ve().running,queueDepthOther:x,sessionsPerSec:I,avgChunksPerSession:O,chunksPerSec:P,throughputSource:q,throughputSamples:K,estimatedChunksByDepth:{new:{quick:$.total_quick,standard:$.total_standard,full:$.total_full,uncapped:$.total_uncapped},force:{quick:X.total_quick,standard:X.total_standard,full:X.total_full,uncapped:X.total_uncapped}}})}),t.post("/api/semantic/cancel-reindex",je,async i=>{let l={};try{l=await i.req.json()}catch{}let p=(l.project??"").trim(),g=f(),_=0,E=null;if(p){let y=g.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(p,p);if(!y)return i.json({error:`project not found: ${p}`},404);let k=g.prepare("SELECT COUNT(*) AS n FROM chunk_queue WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)").get(y.id).n;g.prepare("DELETE FROM chunk_queue WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)").run(y.id),_=k,E=y.id}else{let y=g.prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n;g.prepare("DELETE FROM chunk_queue").run(),_=y}return E!==null?Lp(E):Cp(),ve().queueDepth===0&&vp(),i.json({cleared:_,project:p||null,queueDepth:ve().queueDepth})}),t.post("/api/semantic/reindex-project",je,async i=>{if(!Ge())return i.json({error:"embedder not installed \u2014 run `recall semantic install` first"},503);let l={};try{l=await i.req.json()}catch{}let p=(l.project??"").trim();if(!p)return i.json({error:"project name required"},400);let g=!!l.force,_=Math.max(0,Math.floor(Number(l.maxChunks??0)));Np(_);let E=f(),y=E.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(p,p);if(!y)return i.json({error:`project not found: ${p}`},404);let k=g?"SELECT id FROM sessions WHERE project_id = ? AND message_count >= 3":`SELECT s.id FROM sessions s
|
|
1906
1937
|
WHERE s.project_id = ? AND s.message_count >= 3
|
|
1907
|
-
AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)`,
|
|
1938
|
+
AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)`,x=E.prepare(k).all(y.id);if(x.length===0)return i.json({enqueued:0,queueDepth:ve().queueDepth,message:"nothing to do \u2014 every session in this project is already vectorized (use force:true to re-embed)"});let A=E.prepare("INSERT INTO chunk_queue(session_id, action) VALUES (?, 'embed')");if(E.transaction(()=>{for(let C of x)A.run(C.id)})(),!_e().loaded)try{await Ue()}catch(C){let I=C instanceof Error?C.message:"unknown error";return i.json({error:`embedder load failed: ${I}`},500)}return ve().running||ys(),i.json({enqueued:x.length,queueDepth:ve().queueDepth,project:p,appliedMaxChunks:_})}),t.get("/api/sessions/:id/similar",je,async i=>{if(!_e().loaded)return i.json({error:"vector model not loaded"},503);let l=i.req.param("id"),p=Math.max(1,Math.min(50,Number(i.req.query("limit")??10)));try{let g=await Ep(l,p);return i.json({sessionId:l,similar:g})}catch(g){let _=g instanceof Error?g.message:"unknown error";return i.json({error:_},500)}}),t.get("/api/search",je,async i=>{let l=f(),p=i.req.query("q")?.trim();if(!p)return i.json({query:"",hits:[],tags:[]});if(p.length>500)return i.json({error:"query too long (max 500 chars)"},400);let g=i.req.query("project"),_=p.split(/\s+/).filter(J=>J.length>0),E=_.filter(J=>J.startsWith("#")).map(J=>Qe(J)).filter(Boolean),y=_.filter(J=>!J.startsWith("#")),k=y.length>20,A=(k?y.slice(0,20):y).map(J=>`"${J.replace(/"/g,"")}"`),N=A.join(" "),C=Math.max(1,Math.min(200,Number(i.req.query("limit")??30))),I=i.req.query("system")==="1"||i.req.query("system")==="true",O=Yn("s",I);if(A.length===0&&E.length>0){let J=`
|
|
1908
1939
|
SELECT s.id AS session_id,
|
|
1909
1940
|
s.id AS message_uuid,
|
|
1910
1941
|
p.name AS project,
|
|
@@ -1917,7 +1948,7 @@ ${o}
|
|
|
1917
1948
|
JOIN projects p ON p.id = s.project_id
|
|
1918
1949
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1919
1950
|
WHERE 1=1${O}
|
|
1920
|
-
`,re={limit:C};g&&(J+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",re.proj=`%${
|
|
1951
|
+
`,re={limit:C};g&&(J+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",re.proj=`%${ws(g)}%`),E.forEach((Me,me)=>{J+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${me})`,re[`tag_${me}`]=Me}),J+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit";let ze=l.prepare(J).all(re);return i.json({query:p,hits:ze,tags:E,truncated:k})}let P=`
|
|
1921
1952
|
SELECT m.session_id AS session_id,
|
|
1922
1953
|
m.uuid AS message_uuid,
|
|
1923
1954
|
p.name AS project,
|
|
@@ -1932,7 +1963,7 @@ ${o}
|
|
|
1932
1963
|
JOIN projects p ON p.id = s.project_id
|
|
1933
1964
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1934
1965
|
WHERE messages_fts MATCH @fts${O}
|
|
1935
|
-
`,
|
|
1966
|
+
`,q={fts:N,limit:C};g&&(P+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",q.proj=`%${ws(g)}%`),E.forEach((J,re)=>{P+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${re})`,q[`tag_${re}`]=J}),P+=" ORDER BY bm25(messages_fts) LIMIT @limit";let G=l.prepare(P).all(q).map(J=>({...J,matched_via:"fts"}));if(i.req.query("mode")!=="semantic")return i.json({query:p,hits:G,tags:E,truncated:k});let X=[];try{let J=`
|
|
1936
1967
|
SELECT s.id AS session_id,
|
|
1937
1968
|
s.id AS message_uuid,
|
|
1938
1969
|
p.name AS project,
|
|
@@ -1948,15 +1979,15 @@ ${o}
|
|
|
1948
1979
|
JOIN projects p ON p.id = s.project_id
|
|
1949
1980
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1950
1981
|
WHERE sessions_fts MATCH @fts${O}
|
|
1951
|
-
`,re={fts:
|
|
1982
|
+
`,re={fts:N,limit:C};g&&(J+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",re.proj=`%${ws(g)}%`),E.forEach((ze,Me)=>{J+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${Me})`,re[`tag_${Me}`]=ze}),J+=" ORDER BY rank LIMIT @limit",X=l.prepare(J).all(re)}catch(J){console.error("[search.semantic] failed:",J)}if(_e().loaded)try{let J=await hp(p,C),re=G.map(ue=>({id:String(ue.session_id),data:ue,lane:"bm25"})),ze=X.map(ue=>({id:String(ue.session_id),data:ue,lane:"summary"})),Me=J.map(ue=>({id:ue.sessionId,data:{session_id:ue.sessionId,snippet:ue.text,matched_via:"vector"},lane:"vector"})),um=bp([re,ze,Me]).slice(0,C).map(ue=>({...ue.data,session_id:ue.id,rrf_score:ue.score,lanes:ue.lanes,matched_via:ue.lanes.length>1?"fused":ue.lanes[0]}));return i.json({query:p,hits:um,tags:E,mode:"semantic",fusion:"rrf",truncated:k})}catch(J){console.error("[search.vector] failed, falling back:",J)}let Y=new Set(G.map(J=>String(J.session_id))),Q=X.filter(J=>!Y.has(String(J.session_id))).map(({rank:J,...re})=>({...re,matched_via:"semantic"})),V=[...G,...Q].slice(0,C);return i.json({query:p,hits:V,tags:E,mode:"semantic",truncated:k})}),t.get("/api/sessions/:id/context",je,i=>{let l=f(),p=i.req.param("id"),g=i.req.query("mode")==="full"?"full":"condensed",_=i.req.query("subagents")==="1",E=i.req.query("prelude")??null,y=l.prepare(`SELECT s.id, p.name AS project_name, p.decoded_path,
|
|
1952
1983
|
s.started_at, s.ended_at, s.message_count, s.git_branch
|
|
1953
1984
|
FROM sessions s JOIN projects p ON p.id = s.project_id
|
|
1954
1985
|
WHERE s.id = ?`).get(p);if(!y)return i.json({error:"not found"},404);let k=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
1955
1986
|
FROM messages
|
|
1956
1987
|
WHERE session_id = ?
|
|
1957
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(p),N=uc(y,k,{mode:g,includeSidechain:_,prelude:E});return i.text(N)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:Sc(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),p=He(l);if(!p)return i.json({error:"not found"},404);let g=Tc(l,!0);return i.json({collection:p,members:g})}),t.post("/api/collections",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=Vt({name:l.name,description:l.description??null,icon:l.icon??null,color:l.color??null,parent_id:l.parent_id??null,sort_key:l.sort_key});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/collections/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let g=wc(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.post("/api/collections/:id/archive",i=>{let l=i.req.param("id");try{let p=Rc(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/restore",i=>{let l=i.req.param("id");try{let p=kc(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/members",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.session_id!="string")return i.json({error:"session_id required"},400);try{let g=Zt(l,p.session_id,p.note??null);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/collections/:id/members/:sid",i=>{let l=i.req.param("id"),p=i.req.param("sid");try{let g=Ac(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.get("/api/sessions/:id/collections",i=>{let l=i.req.param("id");return i.json({collections:yc(l)})});let R=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:Cc()})),t.post("/api/auto-collections/rules",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string"||typeof l.pattern!="string"||!l.type||!R.includes(l.type))return i.json({error:"name, type, pattern required (type must be a known matcher)"},400);try{let p=zr({name:l.name,type:l.type,pattern:l.pattern,collection_id:l.collection_id,parent_collection_id:l.parent_collection_id,priority:l.priority,enabled:l.enabled});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/auto-collections/rules/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let g=Ic(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/auto-collections/rules/:id",i=>{let l=i.req.param("id");try{let p=vc(l);return i.json(p)}catch(p){return i.json({error:p.message},400)}}),t.get("/api/auto-collections/suggestions",i=>{let l=i.req.query("dismissed")==="1";return i.json({suggestions:en({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let p=Mc(l);return i.json({rule:p})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/suggestions/:id/dismiss",i=>{let l=i.req.param("id");try{return jc(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=tn();return i.json({suggestions:l})}),t.get("/api/auto-collections/suggestions/:id/preview",i=>{let l=i.req.param("id"),p=Math.max(1,Math.min(20,Number(i.req.query("limit"))||3)),_=en({includeDismissed:!1}).find(y=>y.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=Oc(_.type,_.pattern,p);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(Dc());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:Qr({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),p=oe(l);if(!p)return i.json({error:"thread not found"},404);let g=p.edges.map(_=>({..._,alias_source:_.alias==null?null:v.isSessionAutoLinked(_.session_id)?"auto":"manual"}));return i.json({thread:{...p,edges:g}})}),t.post("/api/threads",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name)return i.json({error:"name required"},400);try{let p=nn({name:l.name,summary:l.summary??null,originSessionId:l.originSessionId});return i.json({thread:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/threads/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{p.name&&Jc(l,p.name),p.close&&Gc(l),p.reopen&&Yc(l),p.archive&&zc(l),"folder_id"in p&&_l(l,p.folder_id??null);let g=oe(l);return g?i.json({thread:g}):i.json({error:"thread not found"},404)}catch(g){return i.json({error:g.message},400)}}),t.get("/api/thread-folders",i=>i.json({folders:no()})),t.post("/api/thread-folders",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=ll({name:l.name,parentFolderId:l.parent_folder_id??null,projectScope:l.project_scope??null});return i.json({folder:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/thread-folders/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{let g;return p.name&&(g=dl(l,p.name)),"parent_folder_id"in p&&(g=pl(l,p.parent_folder_id??null)),g?i.json({folder:g}):i.json({error:"no patch fields"},400)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/thread-folders/:id",i=>{let l=i.req.param("id");try{return gl(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/thread-folders/reorder",async i=>{let l=await i.req.json().catch(()=>({})),p=l.ordered_ids;if(!Array.isArray(p))return i.json({error:"ordered_ids must be an array"},400);try{return ml(l.parent_folder_id??null,l.project_scope??null,p),i.json({ok:!0})}catch(g){return i.json({error:g.message},400)}}),t.post("/api/threads/:id/sessions",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionId)return i.json({error:"sessionId required"},400);try{let g=rn({threadId:l,sessionId:p.sessionId,parentSessionId:p.parentSessionId??null,role:p.role});return i.json({edge:g})}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/threads/:id/sessions/:sessionId",i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),g=Xc(l,p);return i.json(g)}),t.patch("/api/threads/:id/sessions/:sessionId",async i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),g=await i.req.json().catch(()=>({}));try{let _=Qt(l,p,g.parentSessionId??null);return i.json({edge:_})}catch(_){return i.json({error:_.message},400)}}),t.post("/api/threads/:id/merge",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sourceId)return i.json({error:"sourceId required"},400);try{let g=Kc(p.sourceId,l);return i.json({thread:g})}catch(g){return i.json({error:g.message},400)}}),t.post("/api/threads/:id/split",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionIds?.length||!p.newThreadName)return i.json({error:"sessionIds and newThreadName required"},400);try{let g=Vc({threadId:l,sessionIds:p.sessionIds,newThreadName:p.newThreadName});return i.json({thread:g})}catch(g){return i.json({error:g.message},400)}}),t.get("/api/sessions/:id/threads",i=>{let l=i.req.param("id");return i.json({threads:qc(l)})});let j=M.object({enabled:M.boolean(),band_lo:M.number().min(0).max(1).optional(),band_hi:M.number().min(0).max(1).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),x=M.object({project:M.string().min(1),threshold:M.number().min(0).max(1).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:j});t.post("/api/threads/scan/preflight",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=x.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=_d({project:g.data.project,threshold:g.data.threshold,model:g.data.model,llm_rescore:g.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json(_)});let q=M.object({project:M.string().min(1),threshold:M.number().min(0).max(1).optional(),llm_names:M.boolean().optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:j});t.post("/api/threads/scan/apply",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=q.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=hd({project:g.data.project,threshold:g.data.threshold,llm_names:g.data.llm_names,model:g.data.model,llm_rescore:g.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json({jobId:_.jobId,reused:_.reused},_.reused?409:200)}),t.get("/api/threads/scan/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Fo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Ed(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/threads/scan/jobs/:jobId",i=>{let l=Fo(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/threads/scan/jobs/:jobId",i=>{let l=Ce(i);return l||(bd(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let D=M.object({project_id:M.number().int().positive(),mode:M.enum(["preflight","apply"]),window_hours:M.number().min(.5).max(168).optional(),score_threshold:M.number().min(0).max(1).optional(),use_live_pids:M.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),p=D.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let g=await il(p.data.project_id,{windowHours:p.data.window_hours,scoreThreshold:p.data.score_threshold,useLivePids:p.data.use_live_pids});if(p.data.mode==="preflight")return i.json({plan:g});let _=al(g);return i.json({plan:g,result:_})}catch(g){return i.json({error:g.message},400)}}),t.get("/api/threads/:id/titles/preflight",i=>{let l=i.req.param("id"),p=oe(l);if(!p)return i.json({error:"thread not found"},404);let g=f(),_=0;for(let E of p.edges)g.prepare("SELECT auto_title_source FROM sessions WHERE id = ?").get(E.session_id)?.auto_title_source==="agent"&&(_+=1);return i.json({total:p.edges.length,alreadyTitled:_,untitled:p.edges.length-_})}),t.post("/api/threads/:id/titles/generate",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),g=oe(l);if(!g)return i.json({error:"thread not found"},404);if(g.edges.length===0)return i.json({error:"thread has no sessions"},400);let _=Fe(),E=p.model??_.model,y=ql({threadId:l,force:p.force??!1,model:E});return i.json({jobId:y})}),t.get("/api/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!uo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Xl(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/jobs/:jobId",i=>{let l=uo(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>Jl(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404)),t.post("/api/terminal/opened",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=v.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance",count:v.size()});let g=v.upsert({shell_pid:l.shell_pid,tab_name:l.tab_name,cwd:l.cwd??null,opened_at:l.opened_at??new Date().toISOString()});return i.json({ok:!0,ownership:p,count:v.size(),entry:g})}),t.post("/api/terminal/renamed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=v.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance"});let g=v.rename(l.shell_pid,l.tab_name);if(!g)return i.json({error:"unknown shell_pid"},404);let _=jp(l.shell_pid,l.tab_name);return i.json({ok:!0,ownership:p,entry:g,propagated:_})}),t.post("/api/terminal/closed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number")return i.json({error:"shell_pid required"},400);let p=v.remove(l.shell_pid);return i.json({ok:!0,removed:p,count:v.size()})}),t.post("/api/terminal/claude-started",async i=>{let l=await i.req.json().catch(()=>null);return!l||typeof l.shell_pid!="number"?i.json({error:"shell_pid required"},400):(v.pushPending({shell_pid:l.shell_pid,tab_name:typeof l.tab_name=="string"?l.tab_name:"",cwd:typeof l.cwd=="string"?l.cwd:null,started_at:typeof l.started_at=="string"?l.started_at:new Date().toISOString()}),i.json({ok:!0,pending:v.pendingSize()}))}),t.post("/api/terminal/output",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.text!="string")return i.json({error:"shell_pid and text required"},400);let p=l.text.length>8192?l.text.slice(-8192):l.text,g=typeof l.captured_at=="string"?l.captured_at:new Date().toISOString();return v.setOutputTail(l.shell_pid,p,g),i.json({ok:!0})}),t.post("/api/terminal/sync",async i=>{let l=await i.req.json().catch(()=>null);if(!l||!Array.isArray(l.terminals))return i.json({error:"terminals array required"},400);let p=new Map;for(let O of v.all())p.set(O.shell_pid,O.tab_name);let g=l.terminals.filter(O=>!!O&&typeof O.shell_pid=="number"&&typeof O.tab_name=="string").map(O=>({shell_pid:O.shell_pid,tab_name:O.tab_name,cwd:O.cwd??null,opened_at:O.opened_at??new Date().toISOString()})),_=l.extension_instance_id??null,E=[],y=g.filter(O=>{let F=v.claimPidOwnership(O.shell_pid,_);return E.push({shell_pid:O.shell_pid,ownership:F}),F!=="rejected"}),k=v.sync(y),N=0;for(let O of y){let F=p.get(O.shell_pid),W=v.get(O.shell_pid)?.tab_name??O.tab_name,G=!!W&&!de(W)&&!ce(W)?W:O.tab_name;F!==void 0&&F!==G&&(N+=jp(O.shell_pid,G))}let A=E.filter(O=>O.ownership==="rejected").length;A>0&&console.log(`[terminal/sync] dropped ${A} tab_name update(s), pid(s) owned by a different extension instance`);let L=await Py(),C={resolved:0,expired:0};try{C=nu()}catch{}let I={rebound:0,ghosts:0,ambiguous:0};try{I=su()}catch{}return Ky(),i.json({ok:!0,count:v.size(),diff:k,propagated:N,live_sweep:L,deferred_resolved:C,rebound:I})}),t.get("/api/terminal/registry",i=>i.json({terminals:v.all(),count:v.size()})),t.get("/api/terminal/sessions/:shellPid",i=>{let l=i.req.param("shellPid"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"shellPid must be a positive integer"},400);let g=v.sessionsFor(p);return i.json({shell_pid:p,sessions:g})}),t.get("/api/sessions/:id/linked-terminal",i=>{let l=i.req.param("id"),p=v.all().find(_=>v.sessionsFor(_.shell_pid).includes(l)),g=[];if(!p){let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l);if(_?.cwd&&_.started_at){let E=Date.parse(_.started_at);if(Number.isFinite(E)){let y=_.cwd.replace(/\/+$/,""),k=300*1e3;for(let N of v.all()){if(!N.cwd||N.cwd.replace(/\/+$/,"")!==y||de(N.tab_name))continue;let A=Date.parse(N.opened_at),L=Date.parse(N.last_seen_at);!Number.isFinite(A)||!Number.isFinite(L)||A>E||L+k<E||g.push({shell_pid:N.shell_pid,tab_name:N.tab_name,cwd:N.cwd,opened_at:N.opened_at,last_seen_at:N.last_seen_at,reason:"time-overlap"})}g.sort((N,A)=>Date.parse(A.last_seen_at)-Date.parse(N.last_seen_at))}}}return p?i.json({linked:{shell_pid:p.shell_pid,tab_name:p.tab_name,cwd:p.cwd},suggested:[]}):i.json({linked:null,suggested:g})}),t.post("/api/sessions/:id/auto-relink",async i=>{let l=i.req.param("id");if(Te(l))return i.json({applied:!1,reason:"has-alias"});if(v.all().some(y=>v.sessionsFor(y.shell_pid).includes(l)))return i.json({applied:!1,reason:"already-linked"});let g=f().prepare("SELECT cwd, git_branch, started_at FROM sessions WHERE id = ?").get(l);if(!g?.cwd)return i.json({applied:!1,reason:"no-cwd"});let _=g.cwd.replace(/\/+$/,""),E=v.all().filter(y=>y.cwd&&y.cwd.replace(/\/+$/,"")===_&&!de(y.tab_name));if(E.length===1){let y=E[0],k=Kt({sessionStartedAt:g.started_at??null,terminalOpenedAt:y.opened_at??null});if(!k.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:k.reason});let N=v.getOrigin(l),A=wt({tabName:y.tab_name,origin:N??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return A?(he(l,A),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:A,linked_pid:y.shell_pid,linked_tab_name:y.tab_name,method:"cwd-singleton"})):i.json({applied:!1,reason:"no-usable-name"})}if(E.length>1){let y=await tu(l);if(y){let N=v.get(y.shell_pid),A=Kt({sessionStartedAt:g.started_at??null,terminalOpenedAt:N?.opened_at??null});if(!A.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:A.reason});let L=v.getOrigin(l),C=wt({tabName:y.tab_name,origin:L??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(C)return he(l,C),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:C,linked_pid:y.shell_pid,linked_tab_name:y.tab_name,matched_fingerprints:y.matched_fingerprints,method:"content-match"})}let k=6e4;if(g.started_at){let N=Date.parse(g.started_at);if(Number.isFinite(N)){let A=E.filter(C=>Kt({sessionStartedAt:g.started_at,terminalOpenedAt:C.opened_at??null}).allowed).map(C=>({t:C,gap:N-Date.parse(C.opened_at??"")})).filter(C=>Number.isFinite(C.gap)&&C.gap>=0&&C.gap<=k);if(A.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:A.length});let L=A[0];if(L){let C=v.getOrigin(l),I=wt({tabName:L.t.tab_name,origin:C??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(I)return he(l,I),v.linkSession(l,L.t.shell_pid),i.json({applied:!0,alias:I,linked_pid:L.t.shell_pid,linked_tab_name:L.t.tab_name,method:"closest-before-temporal",gap_ms:L.gap})}}}return i.json({applied:!1,reason:"ambiguous",candidate_count:E.length})}return i.json({applied:!1,reason:"no-candidates"})}),t.post("/api/sessions/:id/relink",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(p?.clear)return v.unlinkSession(l),Xs(l),i.json({ok:!0,alias:null,linked_pid:null});if(!p||typeof p.shell_pid!="number")return i.json({error:"shell_pid required"},400);let g=v.get(p.shell_pid);if(!g)return i.json({error:"terminal not registered"},404);let _=v.getOrigin(l),E=f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(l),y=null,k=g.tab_name?.trim()??"";if(k&&!de(k)&&!ce(k))y=k;else if(k&&ce(k)){let N=yt(k);N&&!de(N)&&(y=N)}return y?(v.unlinkSession(l),he(l,y),i.json({ok:!0,alias:y,linked_pid:p.shell_pid,linked_tab_name:g.tab_name})):i.json({error:"terminal has no usable name, name the tab in your editor first, then retry the relink"},422)}),t.post("/api/sessions/:id/recorrelate",async i=>{let l=i.req.param("id"),p=f().prepare("SELECT file_path FROM sessions WHERE id = ?").get(l);if(!p?.file_path)return i.json({error:"session not found"},404);v.unlinkSession(l),Xs(l),await bn(p.file_path);let g=Te(l);return i.json({ok:!0,alias:g,linked_pid:v.all().find(_=>v.sessionsFor(_.shell_pid).includes(l))?.shell_pid??null})}),t.get("/api/paste-expand",async i=>{let l=i.req.query("session"),p=i.req.query("message"),g=i.req.query("path");if(!l||!p||!g)return i.json({error:"session, message and path are required"},400);let _=f(),E=_.prepare("SELECT rowid, content_text FROM messages WHERE uuid = ? AND session_id = ?").get(p,l);if(!E)return i.json({error:"message not found in session"},404);let y=g.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");if(!new RegExp(`\\[Pasted text #\\d+ \\+\\d+ lines\\]\\s*${y}`).test(E.content_text??""))return i.json({error:"path not referenced by this message"},403);let N=_.prepare(`SELECT content_text FROM messages
|
|
1988
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(p),x=xl(y,k,{mode:g,includeSidechain:_,prelude:E});return i.text(x)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:sl(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),p=He(l);if(!p)return i.json({error:"not found"},404);let g=nl(l,!0);return i.json({collection:p,members:g})}),t.post("/api/collections",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=ts({name:l.name,description:l.description??null,icon:l.icon??null,color:l.color??null,parent_id:l.parent_id??null,sort_key:l.sort_key});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/collections/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let g=ol(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.post("/api/collections/:id/archive",i=>{let l=i.req.param("id");try{let p=il(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/restore",i=>{let l=i.req.param("id");try{let p=al(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/members",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.session_id!="string")return i.json({error:"session_id required"},400);try{let g=ss(l,p.session_id,p.note??null);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/collections/:id/members/:sid",i=>{let l=i.req.param("id"),p=i.req.param("sid");try{let g=cl(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.get("/api/sessions/:id/collections",i=>{let l=i.req.param("id");return i.json({collections:rl(l)})});let R=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:ml()})),t.post("/api/auto-collections/rules",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string"||typeof l.pattern!="string"||!l.type||!R.includes(l.type))return i.json({error:"name, type, pattern required (type must be a known matcher)"},400);try{let p=lo({name:l.name,type:l.type,pattern:l.pattern,collection_id:l.collection_id,parent_collection_id:l.parent_collection_id,priority:l.priority,enabled:l.enabled});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/auto-collections/rules/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let g=gl(l,p);return i.json(g)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/auto-collections/rules/:id",i=>{let l=i.req.param("id");try{let p=_l(l);return i.json(p)}catch(p){return i.json({error:p.message},400)}}),t.get("/api/auto-collections/suggestions",i=>{let l=i.req.query("dismissed")==="1";return i.json({suggestions:gn({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let p=hl(l);return i.json({rule:p})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/suggestions/:id/dismiss",i=>{let l=i.req.param("id");try{return fl(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=_n();return i.json({suggestions:l})}),t.get("/api/auto-collections/suggestions/:id/preview",i=>{let l=i.req.param("id"),p=Math.max(1,Math.min(20,Number(i.req.query("limit"))||3)),_=gn({includeDismissed:!1}).find(y=>y.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=dl(_.type,_.pattern,p);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(El());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:To({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),p=oe(l);if(!p)return i.json({error:"thread not found"},404);let g=p.edges.map(_=>({..._,alias_source:_.alias==null?null:v.isSessionAutoLinked(_.session_id)?"auto":"manual"}));return i.json({thread:{...p,edges:g}})}),t.post("/api/threads",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name)return i.json({error:"name required"},400);try{let p=En({name:l.name,summary:l.summary??null,originSessionId:l.originSessionId});return i.json({thread:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/threads/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{p.name&&ql(l,p.name),p.close&&Xl(l),p.reopen&&Jl(l),p.archive&&Gl(l),"folder_id"in p&&mu(l,p.folder_id??null);let g=oe(l);return g?i.json({thread:g}):i.json({error:"thread not found"},404)}catch(g){return i.json({error:g.message},400)}}),t.get("/api/thread-folders",i=>i.json({folders:ko()})),t.post("/api/thread-folders",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=au({name:l.name,parentFolderId:l.parent_folder_id??null,projectScope:l.project_scope??null});return i.json({folder:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/thread-folders/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{let g;return p.name&&(g=lu(l,p.name)),"parent_folder_id"in p&&(g=uu(l,p.parent_folder_id??null)),g?i.json({folder:g}):i.json({error:"no patch fields"},400)}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/thread-folders/:id",i=>{let l=i.req.param("id");try{return pu(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/thread-folders/reorder",async i=>{let l=await i.req.json().catch(()=>({})),p=l.ordered_ids;if(!Array.isArray(p))return i.json({error:"ordered_ids must be an array"},400);try{return du(l.parent_folder_id??null,l.project_scope??null,p),i.json({ok:!0})}catch(g){return i.json({error:g.message},400)}}),t.post("/api/threads/:id/sessions",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionId)return i.json({error:"sessionId required"},400);try{let g=bn({threadId:l,sessionId:p.sessionId,parentSessionId:p.parentSessionId??null,role:p.role});return i.json({edge:g})}catch(g){return i.json({error:g.message},400)}}),t.delete("/api/threads/:id/sessions/:sessionId",i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),g=Wl(l,p);return i.json(g)}),t.patch("/api/threads/:id/sessions/:sessionId",async i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),g=await i.req.json().catch(()=>({}));try{let _=rs(l,p,g.parentSessionId??null);return i.json({edge:_})}catch(_){return i.json({error:_.message},400)}}),t.post("/api/threads/:id/merge",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sourceId)return i.json({error:"sourceId required"},400);try{let g=Yl(p.sourceId,l);return i.json({thread:g})}catch(g){return i.json({error:g.message},400)}}),t.post("/api/threads/:id/split",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionIds?.length||!p.newThreadName)return i.json({error:"sessionIds and newThreadName required"},400);try{let g=zl({threadId:l,sessionIds:p.sessionIds,newThreadName:p.newThreadName});return i.json({thread:g})}catch(g){return i.json({error:g.message},400)}}),t.get("/api/sessions/:id/threads",i=>{let l=i.req.param("id");return i.json({threads:Bl(l)})});let D=M.object({enabled:M.boolean(),band_lo:M.number().min(0).max(1).optional(),band_hi:M.number().min(0).max(1).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),F=M.object({project:M.string().min(1),threshold:M.number().min(0).max(1).optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/preflight",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=F.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=jd({project:g.data.project,threshold:g.data.threshold,model:g.data.model,llm_rescore:g.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json(_)});let L=M.object({project:M.string().min(1),threshold:M.number().min(0).max(1).optional(),llm_names:M.boolean().optional(),model:M.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/apply",async i=>{let l=Ce(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=L.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=Dd({project:g.data.project,threshold:g.data.threshold,llm_names:g.data.llm_names,model:g.data.model,llm_rescore:g.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json({jobId:_.jobId,reused:_.reused},_.reused?409:200)}),t.get("/api/threads/scan/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Yo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Fd(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/threads/scan/jobs/:jobId",i=>{let l=Yo(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/threads/scan/jobs/:jobId",i=>{let l=Ce(i);return l||(Pd(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let j=M.object({project_id:M.number().int().positive(),mode:M.enum(["preflight","apply"]),window_hours:M.number().min(.5).max(168).optional(),score_threshold:M.number().min(0).max(1).optional(),use_live_pids:M.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),p=j.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let g=await ru(p.data.project_id,{windowHours:p.data.window_hours,scoreThreshold:p.data.score_threshold,useLivePids:p.data.use_live_pids});if(p.data.mode==="preflight")return i.json({plan:g});let _=ou(g);return i.json({plan:g,result:_})}catch(g){return i.json({error:g.message},400)}}),t.get("/api/threads/:id/titles/preflight",i=>{let l=i.req.param("id"),p=oe(l);if(!p)return i.json({error:"thread not found"},404);let g=f(),_=0;for(let E of p.edges)g.prepare("SELECT auto_title_source FROM sessions WHERE id = ?").get(E.session_id)?.auto_title_source==="agent"&&(_+=1);return i.json({total:p.edges.length,alreadyTitled:_,untitled:p.edges.length-_})}),t.post("/api/threads/:id/titles/generate",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),g=oe(l);if(!g)return i.json({error:"thread not found"},404);if(g.edges.length===0)return i.json({error:"thread has no sessions"},400);let _=Fe(),E=p.model??_.model,y=Au({threadId:l,force:p.force??!1,model:E});return i.json({jobId:y})}),t.get("/api/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Ao(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ye(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of xu(l,g))if(E||(await _.writeSSE({id:String(k.id),event:k.kind,data:JSON.stringify(k.data)}),k.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/jobs/:jobId",i=>{let l=Ao(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>Nu(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404)),t.post("/api/terminal/opened",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=v.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance",count:v.size()});let g=v.upsert({shell_pid:l.shell_pid,tab_name:l.tab_name,cwd:l.cwd??null,opened_at:l.opened_at??new Date().toISOString()});return i.json({ok:!0,ownership:p,count:v.size(),entry:g})}),t.post("/api/terminal/renamed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=v.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance"});let g=v.rename(l.shell_pid,l.tab_name);if(!g)return i.json({error:"unknown shell_pid"},404);let _=Gp(l.shell_pid,l.tab_name);return i.json({ok:!0,ownership:p,entry:g,propagated:_})}),t.post("/api/terminal/closed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number")return i.json({error:"shell_pid required"},400);let p=v.remove(l.shell_pid);return i.json({ok:!0,removed:p,count:v.size()})}),t.post("/api/terminal/claude-started",async i=>{let l=await i.req.json().catch(()=>null);return!l||typeof l.shell_pid!="number"?i.json({error:"shell_pid required"},400):(v.pushPending({shell_pid:l.shell_pid,tab_name:typeof l.tab_name=="string"?l.tab_name:"",cwd:typeof l.cwd=="string"?l.cwd:null,started_at:typeof l.started_at=="string"?l.started_at:new Date().toISOString()}),i.json({ok:!0,pending:v.pendingSize()}))}),t.post("/api/terminal/output",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.text!="string")return i.json({error:"shell_pid and text required"},400);let p=l.text.length>8192?l.text.slice(-8192):l.text,g=typeof l.captured_at=="string"?l.captured_at:new Date().toISOString();return v.setOutputTail(l.shell_pid,p,g),i.json({ok:!0})}),t.post("/api/terminal/sync",async i=>{let l=await i.req.json().catch(()=>null);if(!l||!Array.isArray(l.terminals))return i.json({error:"terminals array required"},400);let p=new Map;for(let O of v.all())p.set(O.shell_pid,O.tab_name);let g=l.terminals.filter(O=>!!O&&typeof O.shell_pid=="number"&&typeof O.tab_name=="string").map(O=>({shell_pid:O.shell_pid,tab_name:O.tab_name,cwd:O.cwd??null,opened_at:O.opened_at??new Date().toISOString()})),_=l.extension_instance_id??null,E=[],y=g.filter(O=>{let P=v.claimPidOwnership(O.shell_pid,_);return E.push({shell_pid:O.shell_pid,ownership:P}),P!=="rejected"}),k=v.sync(y),x=0;for(let O of y){let P=p.get(O.shell_pid),q=v.get(O.shell_pid)?.tab_name??O.tab_name,G=!!q&&!de(q)&&!le(q)?q:O.tab_name;P!==void 0&&P!==G&&(x+=Gp(O.shell_pid,G))}let A=E.filter(O=>O.ownership==="rejected").length;A>0&&console.log(`[terminal/sync] dropped ${A} tab_name update(s), pid(s) owned by a different extension instance`);let N=await nw(),C={resolved:0,expired:0};try{C=Nc()}catch{}let I={rebound:0,ghosts:0,ambiguous:0};try{I=xc()}catch{}return _w(),i.json({ok:!0,count:v.size(),diff:k,propagated:x,live_sweep:N,deferred_resolved:C,rebound:I})}),t.get("/api/terminal/registry",i=>i.json({terminals:v.all(),count:v.size()})),t.get("/api/terminal/sessions/:shellPid",i=>{let l=i.req.param("shellPid"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"shellPid must be a positive integer"},400);let g=v.sessionsFor(p);return i.json({shell_pid:p,sessions:g})}),t.get("/api/sessions/:id/linked-terminal",i=>{let l=i.req.param("id"),p=v.all().find(_=>v.sessionsFor(_.shell_pid).includes(l)),g=[];if(!p){let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l);if(_?.cwd&&_.started_at){let E=Date.parse(_.started_at);if(Number.isFinite(E)){let y=_.cwd.replace(/\/+$/,""),k=300*1e3;for(let x of v.all()){if(!x.cwd||x.cwd.replace(/\/+$/,"")!==y||de(x.tab_name))continue;let A=Date.parse(x.opened_at),N=Date.parse(x.last_seen_at);!Number.isFinite(A)||!Number.isFinite(N)||A>E||N+k<E||g.push({shell_pid:x.shell_pid,tab_name:x.tab_name,cwd:x.cwd,opened_at:x.opened_at,last_seen_at:x.last_seen_at,reason:"time-overlap"})}g.sort((x,A)=>Date.parse(A.last_seen_at)-Date.parse(x.last_seen_at))}}}return p?i.json({linked:{shell_pid:p.shell_pid,tab_name:p.tab_name,cwd:p.cwd},suggested:[]}):i.json({linked:null,suggested:g})}),t.post("/api/sessions/:id/auto-relink",async i=>{let l=i.req.param("id");if(Te(l))return i.json({applied:!1,reason:"has-alias"});if(v.all().some(y=>v.sessionsFor(y.shell_pid).includes(l)))return i.json({applied:!1,reason:"already-linked"});let g=f().prepare("SELECT cwd, git_branch, started_at FROM sessions WHERE id = ?").get(l);if(!g?.cwd)return i.json({applied:!1,reason:"no-cwd"});let _=g.cwd.replace(/\/+$/,""),E=v.all().filter(y=>y.cwd&&y.cwd.replace(/\/+$/,"")===_&&!de(y.tab_name));if(E.length===1){let y=E[0],k=ns({sessionStartedAt:g.started_at??null,terminalOpenedAt:y.opened_at??null});if(!k.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:k.reason});let x=v.getOrigin(l),A=bt({tabName:y.tab_name,origin:x??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return A?(he(l,A),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:A,linked_pid:y.shell_pid,linked_tab_name:y.tab_name,method:"cwd-singleton"})):i.json({applied:!1,reason:"no-usable-name"})}if(E.length>1){let y=await Ac(l);if(y){let x=v.get(y.shell_pid),A=ns({sessionStartedAt:g.started_at??null,terminalOpenedAt:x?.opened_at??null});if(!A.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:A.reason});let N=v.getOrigin(l),C=bt({tabName:y.tab_name,origin:N??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(C)return he(l,C),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:C,linked_pid:y.shell_pid,linked_tab_name:y.tab_name,matched_fingerprints:y.matched_fingerprints,method:"content-match"})}let k=6e4;if(g.started_at){let x=Date.parse(g.started_at);if(Number.isFinite(x)){let A=E.filter(C=>ns({sessionStartedAt:g.started_at,terminalOpenedAt:C.opened_at??null}).allowed).map(C=>({t:C,gap:x-Date.parse(C.opened_at??"")})).filter(C=>Number.isFinite(C.gap)&&C.gap>=0&&C.gap<=k);if(A.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:A.length});let N=A[0];if(N){let C=v.getOrigin(l),I=bt({tabName:N.t.tab_name,origin:C??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(I)return he(l,I),v.linkSession(l,N.t.shell_pid),i.json({applied:!0,alias:I,linked_pid:N.t.shell_pid,linked_tab_name:N.t.tab_name,method:"closest-before-temporal",gap_ms:N.gap})}}}return i.json({applied:!1,reason:"ambiguous",candidate_count:E.length})}return i.json({applied:!1,reason:"no-candidates"})}),t.post("/api/sessions/:id/relink",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(p?.clear)return v.unlinkSession(l),Ys(l),i.json({ok:!0,alias:null,linked_pid:null});if(!p||typeof p.shell_pid!="number")return i.json({error:"shell_pid required"},400);let g=v.get(p.shell_pid);if(!g)return i.json({error:"terminal not registered"},404);let _=v.getOrigin(l),E=f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(l),y=null,k=g.tab_name?.trim()??"";if(k&&!de(k)&&!le(k))y=k;else if(k&&le(k)){let x=Et(k);x&&!de(x)&&(y=x)}return y?(v.unlinkSession(l),he(l,y),i.json({ok:!0,alias:y,linked_pid:p.shell_pid,linked_tab_name:g.tab_name})):i.json({error:"terminal has no usable name, name the tab in your editor first, then retry the relink"},422)}),t.post("/api/sessions/:id/recorrelate",async i=>{let l=i.req.param("id"),p=f().prepare("SELECT file_path FROM sessions WHERE id = ?").get(l);if(!p?.file_path)return i.json({error:"session not found"},404);v.unlinkSession(l),Ys(l),await Vs(p.file_path);let g=Te(l);return i.json({ok:!0,alias:g,linked_pid:v.all().find(_=>v.sessionsFor(_.shell_pid).includes(l))?.shell_pid??null})}),t.get("/api/paste-expand",async i=>{let l=i.req.query("session"),p=i.req.query("message"),g=i.req.query("path");if(!l||!p||!g)return i.json({error:"session, message and path are required"},400);let _=f(),E=_.prepare("SELECT rowid, content_text FROM messages WHERE uuid = ? AND session_id = ?").get(p,l);if(!E)return i.json({error:"message not found in session"},404);let y=g.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");if(!new RegExp(`\\[Pasted text #\\d+ \\+\\d+ lines\\]\\s*${y}`).test(E.content_text??""))return i.json({error:"path not referenced by this message"},403);let x=_.prepare(`SELECT content_text FROM messages
|
|
1958
1989
|
WHERE session_id = ? AND rowid > ?
|
|
1959
|
-
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let A of
|
|
1990
|
+
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let A of x){let N=A.content_text??"";if(N.includes("**Tool result**")&&N.includes(g))return i.json({source:"tool-result",content:N});if(/^\s*1\t/.test(N)&&N.length>200)return i.json({source:"tool-result",content:N})}try{let A=await Qy(g),N=tw();if(!A.startsWith(N+"/")&&!A.startsWith(N+"\\"))return i.json({error:"path outside allowed root"},403);let C=[".ssh",".gnupg",".gpg",".aws",".kube",".docker",".password-store"],O=A.slice(N.length+1).split("/")[0].split("\\")[0];if(C.includes(O))return i.json({error:"path inside sensitive directory"},403);let P=await Vy(A),q=2*1024*1024;if(P.size>q)return i.json({error:"file too large",size:P.size,max:q},413);let K=await Zy(A,"utf8");return i.json({source:"disk",content:K})}catch(A){return i.json({source:"missing",error:A.message})}}),t.get("/api/projects/:name/stats",i=>{let l=f(),p=i.req.param("name"),g=l.prepare(`SELECT
|
|
1960
1991
|
(SELECT COUNT(*) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.message_count > 2) AS sessions,
|
|
1961
1992
|
(SELECT COALESCE(SUM(s.message_count), 0) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=?) AS messages,
|
|
1962
1993
|
(SELECT MIN(s.started_at) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.started_at IS NOT NULL) AS earliest,
|
|
@@ -1964,35 +1995,4 @@ ${o}
|
|
|
1964
1995
|
JOIN projects p ON p.id = s.project_id
|
|
1965
1996
|
WHERE p.name = ? AND s.git_branch IS NOT NULL
|
|
1966
1997
|
ORDER BY s.git_branch
|
|
1967
|
-
LIMIT 20`).all(p).map(E=>E.git_branch);return i.json({...g,branches:_})});function
|
|
1968
|
-
VALUES (?, ?, ?)
|
|
1969
|
-
ON CONFLICT(encoded_path) DO UPDATE SET decoded_path = excluded.decoded_path, name = excluded.name
|
|
1970
|
-
RETURNING id`),m=n.prepare(`
|
|
1971
|
-
INSERT INTO sessions (
|
|
1972
|
-
id, project_id, file_path, file_mtime,
|
|
1973
|
-
started_at, ended_at, message_count,
|
|
1974
|
-
user_message_count, assistant_message_count,
|
|
1975
|
-
first_user_message, cwd, git_branch, version, indexed_at
|
|
1976
|
-
) VALUES (@id, @project_id, @file_path, @file_mtime, @started_at, @ended_at, @message_count,
|
|
1977
|
-
@user_message_count, @assistant_message_count, @first_user_message,
|
|
1978
|
-
@cwd, @git_branch, @version, @indexed_at)
|
|
1979
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1980
|
-
file_path = excluded.file_path,
|
|
1981
|
-
file_mtime = excluded.file_mtime,
|
|
1982
|
-
started_at = excluded.started_at,
|
|
1983
|
-
ended_at = excluded.ended_at,
|
|
1984
|
-
message_count = excluded.message_count,
|
|
1985
|
-
user_message_count = excluded.user_message_count,
|
|
1986
|
-
assistant_message_count = excluded.assistant_message_count,
|
|
1987
|
-
first_user_message = excluded.first_user_message,
|
|
1988
|
-
cwd = excluded.cwd,
|
|
1989
|
-
git_branch = excluded.git_branch,
|
|
1990
|
-
version = excluded.version,
|
|
1991
|
-
indexed_at = excluded.indexed_at
|
|
1992
|
-
`),h=n.prepare(`
|
|
1993
|
-
INSERT INTO messages (uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1994
|
-
is_sidechain, content_text, tool_names, raw_json)
|
|
1995
|
-
VALUES (@uuid, @session_id, @parent_uuid, @type, @role, @timestamp,
|
|
1996
|
-
@is_sidechain, @content_text, @tool_names, @raw_json)
|
|
1997
|
-
ON CONFLICT(uuid) DO NOTHING
|
|
1998
|
-
`),b=n.prepare("DELETE FROM messages WHERE session_id = ?"),T=new Date().toISOString();if(n.transaction(()=>{let{id:w}=d.get(s,u,c);for(let R of o.values()){if(Ol(R.firstUser)){console.log(`[watcher] skipping daemon-spawn phantom ${R.sessionId} (first message matches autonomous-spawn pattern)`);continue}let j=gi(R.firstUser),x=j.alias?j.stripped:R.firstUser;if(m.run({id:R.sessionId,project_id:w,file_path:e,file_mtime:t,started_at:R.earliest,ended_at:R.latest,message_count:R.entries.length,user_message_count:R.users,assistant_message_count:R.assistants,first_user_message:x,cwd:R.cwd,git_branch:R.branch,version:R.version,indexed_at:T}),j.alias&&!Te(R.sessionId))try{he(R.sessionId,j.alias)}catch(q){console.error(`[watcher] header-alias setAlias failed for ${R.sessionId}:`,q)}b.run(R.sessionId);for(let q of R.entries)h.run(aw(q));Hd(n,R.sessionId,R.entries),Un(n,R.sessionId)}})(),it().heuristicEnabled)for(let w of o.values()){let R=gi(w.firstUser).stripped,j=Tt(R);j&&Ee(w.sessionId,j,"heuristic")}}function aw(e){let t=Se(e.contentText).redacted,s=Se(e.raw).redacted;return{uuid:e.uuid,session_id:e.sessionId,parent_uuid:e.parentUuid,type:e.type,role:e.role,timestamp:e.timestamp,is_sidechain:e.isSidechain?1:0,content_text:t,tool_names:e.toolNames.join(","),raw_json:s}}function Bp(e){let t=_i.get(e);t?.timer&&clearTimeout(t.timer);let s={timer:null};s.timer=setTimeout(()=>{_i.delete(e),Wp(e).then(async()=>{bn(e);try{let r=f().prepare("SELECT id FROM sessions WHERE file_path = ?").all(e);for(let o of r){Pa(o.id),tp(o.id);try{Lc(o.id)}catch(a){console.error("[watcher] auto-collections apply failed:",a)}}}catch(n){console.error("[watcher] semantic dispatch failed:",n)}}).catch(n=>{console.error(`[watcher] reindex failed for ${e}:`,n)})},ow),_i.set(e,s)}function qp(){let e=tw(Kn,{depth:4,ignoreInitial:!0,persistent:!0,awaitWriteFinish:{stabilityThreshold:500,pollInterval:200},ignored:t=>{if(t.endsWith(".jsonl"))return!1;try{if(fi(t).isDirectory())return!1}catch{}return!0}});return e.on("add",t=>t.endsWith(".jsonl")&&Bp(t)),e.on("change",t=>t.endsWith(".jsonl")&&Bp(t)),e}var cw=4;function*Xp(e){let t;try{t=sw(e,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(let s of t){let n=rw(e,s.name);s.isDirectory()?yield*Xp(n):s.isFile()&&s.name.endsWith(".jsonl")&&(yield n)}}async function Jp(){let e=Date.now(),t={scanned:0,reindexed:0,upToDate:0,skipped:0,errors:0,durationMs:0},s=f(),n=[],r=s.prepare("SELECT file_mtime FROM sessions WHERE file_path = ? LIMIT 1");for(let d of Xp(Kn)){if(t.scanned+=1,d.includes("/subagents/")){t.skipped+=1;continue}let m;try{m=fi(d).mtimeMs}catch{t.errors+=1;continue}let h=r.get(d);if(h&&h.file_mtime>=m){t.upToDate+=1;continue}n.push(d)}if(n.length===0)return t.durationMs=Date.now()-e,t;let o=n.slice(),a=async()=>{for(;o.length>0;){let d=o.shift();if(!d)break;try{await Wp(d),t.reindexed+=1}catch(m){t.errors+=1;let h=m instanceof Error?m.message:String(m);console.error(`[ingestion-sweep] failed for ${d}: ${h}`)}}},c=Math.min(cw,n.length),u=[];for(let d=0;d<c;d+=1)u.push(a());return await Promise.all(u),t.durationMs=Date.now()-e,t}import{createServer as Yp}from"node:net";function Gp(e){return new Promise(t=>{let s=Yp();s.once("error",()=>t(!1)),s.once("listening",()=>{s.close(()=>t(!0))}),s.listen(e,"127.0.0.1")})}async function zp(){let e=new Set([3e3,3001,4200,5e3,5173,8e3,8080,8888,9e3]),t=51370;if(!e.has(t)&&await Gp(t))return t;for(let s=0;s<50;s++){let n=49152+Math.floor(Math.random()*16383);if(!e.has(n)&&await Gp(n))return n}return new Promise((s,n)=>{let r=Yp();r.once("error",n),r.listen(0,"127.0.0.1",()=>{let o=r.address();if(o&&typeof o=="object"){let a=o.port;r.close(()=>s(a))}else r.close(),n(new Error("could not determine a free port"))})})}ee();import{existsSync as pw,readFileSync as nI,writeFileSync as Vp,unlinkSync as mw}from"node:fs";import{join as Ei}from"node:path";ee();import{randomBytes as lw}from"node:crypto";import{writeFileSync as uw,readFileSync as VC,existsSync as ZC}from"node:fs";import{join as dw}from"node:path";var hi=dw(B,"daemon.token");function Kp(){z();let e=lw(32).toString("hex");return uw(hi,e,{encoding:"utf8",mode:384}),e}var Zp=Ei(B,"daemon.pid"),Qp=Ei(B,"daemon.port"),aI=Ei(B,"daemon.log");function em(e){z(),Vp(Zp,JSON.stringify(e),{encoding:"utf8",mode:384}),Vp(Qp,String(e.port),{encoding:"utf8",mode:384})}function bi(){for(let e of[Zp,Qp,hi])if(pw(e))try{mw(e)}catch{}}Lr();U();Ve();var _w=Math.max(1,gw().length),tm=String(Math.max(2,Math.floor(_w/2)));process.env.OMP_NUM_THREADS||(process.env.OMP_NUM_THREADS=tm);process.env.ORT_NUM_THREADS||(process.env.ORT_NUM_THREADS=tm);var fw=360*60*1e3,hw=60*1e3,Ew=1440*60*1e3,bw=300*1e3,Sw=300*1e3,Tw=10*1e3,yw=1440*60*1e3,ww=30*1e3,Rw=500,kw=1500;async function Aw(){let e=await zp(),t=Kp();(!t||t.length<32)&&(console.error("[daemon] FATAL: daemon token init returned empty or undersized \u2014 refusing to start"),process.exit(1));let s=await Hp(e,t);em({pid:process.pid,port:e,startedAt:new Date().toISOString()}),Sr(ae().enabled);try{zt();let x=za();x>0&&console.log(`[daemon] archive: migrated ${x} hot row(s) into archive.sqlite`)}catch(x){let q=x instanceof Error?x.message:String(x);console.error(`[daemon] archive migration failed: ${q}`)}let n=qp(),r=()=>{try{tn()}catch(x){console.error("[daemon] suggestion scan failed:",x)}},o=setTimeout(r,hw),a=setInterval(r,fw),c=()=>{try{let x=v.reapStaleLinks();(x.pruned_pids||x.pruned_sessions)&&console.log(`[daemon] reaper: pruned ${x.pruned_pids} pid${x.pruned_pids===1?"":"s"}, ${x.pruned_sessions} session link${x.pruned_sessions===1?"":"s"}`)}catch(x){console.error("[daemon] stale-link reaper failed:",x)}},u=setTimeout(c,bw),d=setInterval(c,Ew),m=()=>{try{let x=v.gcDeadPids();(x.pruned_pids||x.pruned_sessions)&&console.log(`[daemon] dead-pid gc: pruned ${x.pruned_pids} pid${x.pruned_pids===1?"":"s"}, ${x.pruned_sessions} session link${x.pruned_sessions===1?"":"s"}`)}catch(x){console.error("[daemon] dead-pid gc failed:",x)}},h=setTimeout(m,Tw),b=setInterval(m,Sw),T=()=>{Ci().then(x=>{x.ran&&x.revoked&&console.log(`[daemon] license check: REVOKED${x.reason?` (${x.reason})`:""}`)}).catch(x=>{console.error("[daemon] license check failed:",x)})},S=setTimeout(T,ww),w=setInterval(T,yw),R=cc();(async()=>{try{if(!Ge())return;let x=f().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get();if(x.n===0)return;if(!ae().autoResumeWorker){console.log(`[daemon] vector-worker dormant: ${x.n} chunk(s) pending in queue. Run \`recall vectorize\` or click Start in the web UI to drain. To restore auto-resume, set semantic.autoResumeWorker=true in ~/.recall/config.json.`);return}let D=await Ke();if(D.tier!=="pro"){console.log(`[daemon] vector-worker auto-resume skipped: ${x.n} chunk(s) pending but license tier is ${D.tier}`);return}_e().loaded||await $e(),ve().running||(Ss(),console.log(`[daemon] vector-worker auto-resumed: ${x.n} chunk(s) pending`))}catch(x){let q=x instanceof Error?x.message:String(x);console.error(`[daemon] vector-worker auto-resume failed: ${q}`)}})();let j=x=>{console.log(`[daemon] received ${x}, shutting down`),clearTimeout(o),clearInterval(a),clearTimeout(u),clearInterval(d),clearTimeout(h),clearInterval(b),clearTimeout(S),clearInterval(w),R.stop(),n.close(),s.close(),bi(),process.exit(0)};process.on("SIGTERM",()=>j("SIGTERM")),process.on("SIGINT",()=>j("SIGINT")),process.on("SIGHUP",()=>j("SIGHUP")),console.log(`[daemon] ready on http://127.0.0.1:${e} pid=${process.pid}`),setTimeout(()=>{Sn().then(x=>{console.log(`[daemon] boot sweep: scanned ${x.scanned} live claude(s), linked ${x.linked}, renamed ${x.renamed}, ambiguous_cwd ${x.ambiguous_cwd}`)}).catch(x=>{console.error("[daemon] boot sweep failed:",x)})},Rw),setTimeout(()=>{Jp().then(x=>{console.log(`[daemon] ingestion sweep: scanned=${x.scanned} reindexed=${x.reindexed} up-to-date=${x.upToDate} skipped=${x.skipped} errors=${x.errors} (${x.durationMs}ms)`)}).catch(x=>{console.error("[daemon] ingestion sweep failed:",x)})},kw)}Aw().catch(e=>{console.error("[daemon] fatal:",e),bi(),process.exit(1)});
|
|
1998
|
+
LIMIT 20`).all(p).map(E=>E.git_branch);return i.json({...g,branches:_})});function U(i){return i.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function ie(i){if(!e)return i;let l=`<meta name="recall-token" content="${U(e)}" />`,p=i.indexOf("</head>");return p!==-1?i.slice(0,p)+l+i.slice(p):l+i}function B(i){let l=zp();return i.html(Al({projects:l.projects,sessions:l.sessions,messages:l.messages,port:Number(i.req.raw.headers.get("host")?.split(":")[1]??0),version:Yp}))}function se(){try{return _i(Vp,"utf8")}catch{return null}}return uw?(t.use("/assets/*",Jp({root:hi})),t.get("/favicon.svg",Jp({root:hi})),t.get("/",i=>{i.header("cache-control","no-cache, no-store, must-revalidate"),i.header("pragma","no-cache"),i.header("expires","0");let l=se();return l===null?B(i):i.html(ie(l))}),t.get("*",i=>{if(i.req.path.startsWith("/api/"))return i.notFound();i.header("cache-control","no-cache, no-store, must-revalidate"),i.header("pragma","no-cache"),i.header("expires","0");let l=se();return l===null?B(i):i.html(ie(l))})):t.get("/",i=>B(i)),t}function Ew(){if(Un(),!!rt().heuristicEnabled){try{let{updated:e}=Xc();e>0&&console.log(`[auto-title] backfilled heuristic title on ${e} sessions`)}catch(e){console.error("[auto-title] backfill failed:",e)}try{let{scanned:e,updated:t}=Jc();t>0&&console.log(`[auto-title] refreshed templated heuristic title on ${t}/${e} sessions`)}catch(e){console.error("[auto-title] templated-title refresh failed:",e)}try{let{scanned:e,updated:t}=Gc();t>0&&console.log(`[auto-title] refreshed recursive_meta title on ${t}/${e} sessions`)}catch(e){console.error("[auto-title] recursive_meta refresh failed:",e)}try{let{scanned:e,updated:t}=Yc();t>0&&console.log(`[auto-title] canonicalized brand on ${t}/${e} session titles`)}catch(e){console.error("[auto-title] brand canonicalization failed:",e)}}}async function em(e,t){let s=hw(t);return new Promise((n,r)=>{try{let o=zy({fetch:s.fetch,port:e,hostname:"127.0.0.1"},()=>{n(o),setImmediate(()=>{try{Ew()}catch(a){console.error("[daemon] startup maintenance crashed:",a)}})})}catch(o){r(o)}})}import{createServer as sm}from"node:net";function tm(e){return new Promise(t=>{let s=sm();s.once("error",()=>t(!1)),s.once("listening",()=>{s.close(()=>t(!0))}),s.listen(e,"127.0.0.1")})}async function nm(){let e=new Set([3e3,3001,4200,5e3,5173,8e3,8080,8888,9e3]),t=51370;if(!e.has(t)&&await tm(t))return t;for(let s=0;s<50;s++){let n=49152+Math.floor(Math.random()*16383);if(!e.has(n)&&await tm(n))return n}return new Promise((s,n)=>{let r=sm();r.once("error",n),r.listen(0,"127.0.0.1",()=>{let o=r.address();if(o&&typeof o=="object"){let a=o.port;r.close(()=>s(a))}else r.close(),n(new Error("could not determine a free port"))})})}ee();import{existsSync as yw,readFileSync as bI,writeFileSync as om,unlinkSync as ww}from"node:fs";import{join as Ti}from"node:path";ee();import{randomBytes as bw}from"node:crypto";import{writeFileSync as Sw,readFileSync as mI,existsSync as gI}from"node:fs";import{join as Tw}from"node:path";var Si=Tw(W,"daemon.token");function rm(){z();let e=bw(32).toString("hex");return Sw(Si,e,{encoding:"utf8",mode:384}),e}var im=Ti(W,"daemon.pid"),am=Ti(W,"daemon.port"),wI=Ti(W,"daemon.log");function cm(e){z(),om(im,JSON.stringify(e),{encoding:"utf8",mode:384}),om(am,String(e.port),{encoding:"utf8",mode:384})}function yi(){for(let e of[im,am,Si])if(yw(e))try{ww(e)}catch{}}Cr();H();Ve();var kw=Math.max(1,Rw().length),lm=String(Math.max(2,Math.floor(kw/2)));process.env.OMP_NUM_THREADS||(process.env.OMP_NUM_THREADS=lm);process.env.ORT_NUM_THREADS||(process.env.ORT_NUM_THREADS=lm);var Aw=360*60*1e3,xw=60*1e3,Nw=1440*60*1e3,Ow=300*1e3,Lw=300*1e3,Cw=10*1e3,Iw=1440*60*1e3,vw=30*1e3,jw=500,Mw=1500,Dw=6e4;async function Fw(){let e=await nm(),t=rm();(!t||t.length<32)&&(console.error("[daemon] FATAL: daemon token init returned empty or undersized \u2014 refusing to start"),process.exit(1));let s=await em(e,t);cm({pid:process.pid,port:e,startedAt:new Date().toISOString()}),Tr(ce().enabled);try{Vt();let L=tc();L>0&&console.log(`[daemon] archive: migrated ${L} hot row(s) into archive.sqlite`)}catch(L){let j=L instanceof Error?L.message:String(L);console.error(`[daemon] archive migration failed: ${j}`)}let n=Rl(),r=setInterval(()=>{kl()},Dw),o=()=>{try{_n()}catch(L){console.error("[daemon] suggestion scan failed:",L)}},a=setTimeout(o,xw),c=setInterval(o,Aw),u=()=>{try{let L=v.reapStaleLinks();(L.pruned_pids||L.pruned_sessions)&&console.log(`[daemon] reaper: pruned ${L.pruned_pids} pid${L.pruned_pids===1?"":"s"}, ${L.pruned_sessions} session link${L.pruned_sessions===1?"":"s"}`)}catch(L){console.error("[daemon] stale-link reaper failed:",L)}},d=setTimeout(u,Ow),m=setInterval(u,Nw),h=()=>{try{let L=v.gcDeadPids();(L.pruned_pids||L.pruned_sessions)&&console.log(`[daemon] dead-pid gc: pruned ${L.pruned_pids} pid${L.pruned_pids===1?"":"s"}, ${L.pruned_sessions} session link${L.pruned_sessions===1?"":"s"}`)}catch(L){console.error("[daemon] dead-pid gc failed:",L)}},b=setTimeout(h,Cw),T=setInterval(h,Lw),S=()=>{ji().then(L=>{L.ran&&L.revoked&&console.log(`[daemon] license check: REVOKED${L.reason?` (${L.reason})`:""}`)}).catch(L=>{console.error("[daemon] license check failed:",L)})},w=setTimeout(S,vw),R=setInterval(S,Iw),D=gc();(async()=>{try{if(!Ge())return;let L=f().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get();if(L.n===0)return;if(!ce().autoResumeWorker){console.log(`[daemon] vector-worker dormant: ${L.n} chunk(s) pending in queue. Run \`recall vectorize\` or click Start in the web UI to drain. To restore auto-resume, set semantic.autoResumeWorker=true in ~/.recall/config.json.`);return}let U=await Ke();if(U.tier!=="pro"){console.log(`[daemon] vector-worker auto-resume skipped: ${L.n} chunk(s) pending but license tier is ${U.tier}`);return}_e().loaded||await Ue(),ve().running||(ys(),console.log(`[daemon] vector-worker auto-resumed: ${L.n} chunk(s) pending`))}catch(L){let j=L instanceof Error?L.message:String(L);console.error(`[daemon] vector-worker auto-resume failed: ${j}`)}})();let F=L=>{console.log(`[daemon] received ${L}, shutting down`),clearTimeout(a),clearInterval(c),clearTimeout(d),clearInterval(m),clearTimeout(b),clearInterval(T),clearTimeout(w),clearInterval(R),clearInterval(r),D.stop(),n.close(),s.close(),yi(),process.exit(0)};process.on("SIGTERM",()=>F("SIGTERM")),process.on("SIGINT",()=>F("SIGINT")),process.on("SIGHUP",()=>F("SIGHUP")),console.log(`[daemon] ready on http://127.0.0.1:${e} pid=${process.pid}`),setTimeout(()=>{Zs().then(L=>{console.log(`[daemon] boot sweep: scanned ${L.scanned} live claude(s), linked ${L.linked}, renamed ${L.renamed}, ambiguous_cwd ${L.ambiguous_cwd}`)}).catch(L=>{console.error("[daemon] boot sweep failed:",L)})},jw),setTimeout(()=>{_o().then(L=>{console.log(`[daemon] ingestion sweep: scanned=${L.scanned} reindexed=${L.reindexed} up-to-date=${L.upToDate} skipped=${L.skipped} errors=${L.errors} (${L.durationMs}ms)`)}).catch(L=>{console.error("[daemon] ingestion sweep failed:",L)})},Mw)}Fw().catch(e=>{console.error("[daemon] fatal:",e),yi(),process.exit(1)});
|