@clauderecallhq/cli 0.68.3 → 0.71.2
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 +7 -0
- package/dist/cli.js +269 -257
- package/dist/daemon/embedder-worker.js +3 -0
- package/dist/daemon/entrypoint.js +268 -245
- package/dist/mcp-server.js +90 -89
- package/dist/web/assets/{dist-CPal3OGL.js → dist-ghoqZUE3.js} +1 -1
- package/dist/web/assets/index-BZhPn6ht.js +657 -0
- package/dist/web/assets/index-BdyfKGwx.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -6
- package/dist/web/assets/index-B-HrjaDy.css +0 -1
- package/dist/web/assets/index-BxxoX8lK.js +0 -645
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* Claude Recall (proprietary). See LICENSE for terms. */
|
|
3
|
-
var
|
|
3
|
+
var sm=Object.defineProperty;var ue=(e,t)=>()=>(e&&(t=e(e=0)),t);var ys=(e,t)=>{for(var s in t)sm(e,s,{get:t[s],enumerable:!0})};import{homedir as bi}from"node:os";import{join as zn,basename as Nw}from"node:path";import{existsSync as nm,mkdirSync as rm,chmodSync as om,readdirSync as Lw,statSync as Cw}from"node:fs";function z(){nm(H)||rm(H,{recursive:!0,mode:448}),process.platform!=="win32"&&om(H,448)}var Kn,H,Vn,ee=ue(()=>{"use strict";Kn=process.env.CLAUDE_PROJECTS_DIR?process.env.CLAUDE_PROJECTS_DIR:zn(bi(),".claude","projects"),H=process.env.RECALL_HOME?process.env.RECALL_HOME:zn(bi(),".recall"),Vn=zn(H,"db.sqlite")});import{createRequire as Om}from"node:module";var Lm,Cm,vm,er,tr,vi,Ii=ue(()=>{"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)}}Lm=Om(import.meta.url),Cm=["node","sqlite"].join(":"),vm=Lm(Cm),er=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)}},tr=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,s={}){this.inner=new vm.DatabaseSync(t,{readOnly:s.readonly??!1,allowExtension:!0})}prepare(t){return new er(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)}},vi=tr});function Mi(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 xp=Object.defineProperty;var ue=(e,t)=>()=>(e&&(t=e(e=0)),t);var ds=(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 ji,Di=ue(()=>{"use strict";ji=`
|
|
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 Fi from"sqlite-vec";function f(){if(ge)return ge;z(),ge=new vi(Vn),Fi.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(ji),Mi(ge);try{ge.exec("PRAGMA optimize")}catch{}return ge}var ge,U=ue(()=>{"use strict";Ii();ee();Di();ge=null});import{existsSync as Pm}from"node:fs";import{dirname as Ki}from"node:path";import{fileURLToPath as Um}from"node:url";function Vi(){if(Rs)return Rs;let e=Ki(Um(import.meta.url));for(;!Pm(`${e}/package.json`);){let t=Ki(e);if(t===e)throw new Error(`package.json not found walking up from ${import.meta.url}`);e=t}return Rs=e,Rs}var Rs,Zi=ue(()=>{"use strict";Rs=null});var Qi,ea=ue(()=>{"use strict";Qi="BAAI/bge-base-en-v1.5"});var sa={};ys(sa,{EmbedderUnavailableError:()=>Bt,embed:()=>ct,embedQuery:()=>or,getEmbedderStatus:()=>_e,loadEmbedder:()=>$e,unloadEmbedder:()=>Jm});import{Worker as Bm}from"node:worker_threads";import{join as Hm}from"node:path";import{existsSync as Wm}from"node:fs";function qm(){return Hm(Vi(),"dist","daemon","embedder-worker.js")}function ta(e){for(let t of $t.values())t.reject(e);$t.clear()}function Xm(){if(De)return De;let e=qm();if(!Wm(e))throw new Bt(new Error(`embedder-worker bundle not found at ${e}. Run \`npm run build:cli\` to emit it.`));let t=new Bm(e);return t.on("message",s=>{let n=$t.get(s.id);n&&($t.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));ta(n),De=null,Ae=!1}),t.on("exit",s=>{s!==0&&(console.error(`[embedder-worker] exited with code ${s}`),ta(new Error(`embedder worker exited with code ${s}`))),De=null,Ae=!1}),De=t,t}function ks(e){return new Promise((t,s)=>{let n;try{n=Xm()}catch(r){s(r instanceof Error?r:new Error(String(r)));return}$t.set(e.id,{resolve:t,reject:s}),n.postMessage(e)})}function As(){return nr=nr+1>>>0,String(nr)}function rr(e){if(!e.ok)throw e.unavailable?new Bt(new Error(e.error)):new Error(e.error);return e}async function $e(){if(!(Ae&&De))try{rr(await ks({id:As(),type:"load"})),Ae=!0}catch(e){throw Ae=!1,e}}function _e(){return{loaded:Ae,modelId:Qi,dim:768}}async function ct(e){if(!Ae)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");return rr(await ks({id:As(),type:"embed",texts:e})).embeddings.map(s=>new Float32Array(s))}async function or(e){if(!Ae)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=rr(await ks({id:As(),type:"embedQuery",text:e}));return new Float32Array(t.embedding)}async function Jm(){if(!De){Ae=!1;return}try{await ks({id:As(),type:"unload"})}catch{}Ae=!1;let e=De;De=null;try{await e.terminate()}catch{}}var De,$t,nr,Ae,Bt,Ve=ue(()=>{"use strict";Zi();ea();De=null,$t=new Map,nr=0,Ae=!1,Bt=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 Yg}from"node:fs";import{join as Gg}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)})(),ka(),{tag:s,added:!0})}function Ra(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)})(),ka(),{tag:s,removed:!0}):{tag:s,removed:!1}}function Xt(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 ka(){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};Yg(zg,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var zg,pt=ue(()=>{"use strict";U();ee();zg=Gg(H,"tags.json")});function Kg(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=Kg(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:Xt(a.id)}})}var Cs=ue(()=>{"use strict";U();pt()});import{z as Ee}from"zod";function _r(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 Qg(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 t_(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 n_(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 Aa(e){return fr.find(t=>t.name===e)}var Vg,Zg,e_,s_,r_,o_,i_,a_,fr,hr=ue(()=>{"use strict";Vg={project:Ee.string().optional().describe("Exact project name match (optional)."),collectionId:Ee.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:Ee.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:Ee.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:Ee.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:Ee.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:Ee.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};Zg={sessionId:Ee.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:Ee.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};e_={sessionId:Ee.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};s_={sessionId:Ee.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:Ee.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};r_={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:Vg,build:_r,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},o_={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:Zg,build:Qg,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},i_={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:e_,build:t_,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},a_={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:s_,build:n_,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},fr=[r_,o_,i_,a_]});function vs(e,t){let s=Jt.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}function xa(e,t){let s=Jt.get(e);return s||(s=new Set,Jt.set(e,s)),s.add(t),()=>{let n=Jt.get(e);n&&(n.delete(t),n.size===0&&Jt.delete(e))}}var Jt,Er=ue(()=>{"use strict";Jt=new Map});var tt={};ys(tt,{buildScanPrompt:()=>Na,isClaudeCliAvailable:()=>pe,runClaudeCliScan:()=>br,spawnClaudePrompt:()=>et});import{execFileSync as c_,execSync as l_,spawn as u_}from"node:child_process";function p_(){if(Yt)return Yt;try{Yt=l_("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{Yt="claude"}return Yt}function pe(){try{return c_("command",["-v","claude"],{stdio:"ignore"}),!0}catch{return!1}}function Na(e){return _r({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 m_(e,t){let s=t.get(e);return s||e.slice(0,8)}function g_(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 __(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),vs(t,{type:"progress",current:r.size,total:s,sessionId:h,sessionLabel:m_(h,n)}))}}}function f_(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 br(e,t={},s){let n=!!t.scanId,r=n?g_(e):[],o=new Map(r.map(u=>[u.id,u.label])),a=r.length,c;return n&&t.scanId&&(c=__({scanId:t.scanId,total:a,labelTable:o})),Oa({prompt:Na(e),allowedTools:d_.split(","),opts:t,onProgress:s,onStdoutLine:c,outputFormat:n?"stream-json":"json"})}async function et(e,t,s={},n){return Oa({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function Oa(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=u_(p_(),c,{stdio:["ignore","pipe","pipe"]}),m=[],h=[],b=o?f_(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 d_,Yt,Se=ue(()=>{"use strict";Cs();hr();Er();d_=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});var Ja={};ys(Ja,{RetentionConfigSchema:()=>xr,readRetentionConfig:()=>Nr,writeRetentionConfig:()=>Bs});import{existsSync as Ba,mkdirSync as nf,readFileSync as rf,writeFileSync as of}from"node:fs";import{homedir as af}from"node:os";import{join as Ha}from"node:path";import{z as Us}from"zod";function Wa(){return process.env.RECALL_HOME??Ha(af(),".recall")}function cf(){let e=Wa();Ba(e)||nf(e,{recursive:!0})}function qa(){return Ha(Wa(),"config.json")}function Xa(){let e=qa();if(!Ba(e))return{};try{return JSON.parse(rf(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 Nr(){let e=Xa().retention;if(!e)return{...$s};let t=xr.safeParse({...$s,...e});return t.success?t.data:{...$s}}function Bs(e){cf();let t=Xa(),s=xr.parse({...$s,...t.retention??{},...e}),n={...t,retention:s};return of(qa(),JSON.stringify(n,null,2)),s}var xr,$s,Or=ue(()=>{"use strict";xr=Us.object({autoArchiveEnabled:Us.boolean().default(!1),autoArchiveAfterDays:Us.number().int().min(7).max(3650).default(90),lastRunAt:Us.string().nullable().default(null)}),$s={autoArchiveEnabled:!1,autoArchiveAfterDays:90,lastRunAt:null}});import Ne from"chalk";import{formatDistanceToNowStrict as KR,parseISO as VR}from"date-fns";var Te,Hs=ue(()=>{"use strict";Te={dim:Ne.gray,bold:Ne.bold,project:Ne.cyan,user:Ne.blue,assistant:Ne.green,tool:Ne.magenta,warn:Ne.yellow,err:Ne.red,ok:Ne.green,accent:Ne.hex("#f97316")}});import{existsSync as lf}from"node:fs";import{join as uf}from"node:path";function zt(){if(Ya&&lf(Gt))return;z();let e=f(),t=Gt.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")}Ya=!0}function Ga(){let e=f();if(e.prepare("SELECT COUNT(*) AS n FROM messages_archive").get().n===0)return 0;zt();let s=Gt.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 Gt,Ya,Lr=ue(()=>{"use strict";ee();U();Gt=uf(H,"archive.sqlite"),Ya=!1});var Ka={};ys(Ka,{runArchive:()=>Ef});function Cr(e){zt();let t=f(),s=Gt.replace(/'/g,"''");t.exec(`ATTACH DATABASE '${s}' AS archive`);try{return e(t)}finally{t.exec("DETACH DATABASE archive")}}function df(){return Cr(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 za(e){return e?e.slice(0,10):"\u2014"}function pf(){let e=df();return console.log(Te.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 ${za(e.oldestLiveTimestamp)}`),console.log(` Newest archived ${za(e.newestArchivedTimestamp)}`),console.log(""),console.log(Te.dim(" recall archive run --before YYYY-MM-DD")),console.log(Te.dim(" recall archive restore <session-id>")),0}function mf(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(
|
|
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(Te.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=Cr(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(
|
|
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(Te.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function gf(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=Cr(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 _f(){let e=Nr();return console.log(Te.dim("\u2014 Auto-archive \u2014")),console.log(` Enabled ${e.autoArchiveEnabled?Te.ok("YES"):"no"}`),console.log(` After ${e.autoArchiveAfterDays} days`),console.log(` Last run ${e.lastRunAt??"\u2014"}`),0}function ff(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=Bs({autoArchiveEnabled:!0,autoArchiveAfterDays:t});return console.log(`Auto-archive: ENABLED (after ${s.autoArchiveAfterDays} days).`),console.log(Te.dim(" The daemon will run a daily archive pass on the next tick.")),0}function hf(){return Bs({autoArchiveEnabled:!1}),console.log("Auto-archive: DISABLED. Existing archived sessions stay archived."),0}async function Ef(e){let t=e._action??"list";if(t==="list"||t==="stats")return pf();if(t==="run")return e.before?mf({before:e.before,dryRun:e.dryRun===!0}):(console.error("Usage: recall archive run --before YYYY-MM-DD [--dry-run]"),1);if(t==="restore")return gf(e._sessionId??"");if(t==="auto"){let s=e._subAction??"status";return s==="status"?_f():s==="on"||s==="enable"?ff(e.after):s==="off"||s==="disable"?hf():(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 Va=ue(()=>{"use strict";Hs();U();Lr();Or()});import{cpus as pw}from"node:os";import{Hono as Ny}from"hono";import{serve as Oy}from"@hono/node-server";ee();import{existsSync as im,readFileSync as am,writeFileSync as jw,unlinkSync as Mw}from"node:fs";import{join as cm}from"node:path";var Si=cm(H,"license.json");function Pt(){if(!im(Si))return null;try{let e=am(Si,"utf8"),t=JSON.parse(e);return typeof t.license_jwt!="string"||t.license_jwt.length===0?null:t}catch{return null}}import{jwtVerify as lm,importSPKI as um}from"jose";var Ti=`-----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
|
+
`,Zn="ES256",yi="clauderecall.com",wi="clauderecall-cli";var ws=null;async function dm(){return ws||(ws=await um(Ti,Zn),ws)}async function Ri(e){try{let t=await dm(),{payload:s}=await lm(e,t,{issuer:yi,audience:wi,algorithms:[Zn]});return{valid:!0,claims:s}}catch(t){return{valid:!1,reason:t instanceof Error?t.message:"verification failed"}}}import{createHash as pm}from"node:crypto";import{hostname as mm,userInfo as gm,platform as _m,arch as fm}from"node:os";function ki(){let e="unknown";try{e=gm().username}catch{}let t=[mm(),e,_m(),fm()];return pm("sha256").update(t.join("\0")).digest("hex")}ee();import{existsSync as hm,readFileSync as Em,writeFileSync as bm}from"node:fs";import{join as Sm}from"node:path";function Ai(){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 Qn=Sm(H,"license-check.json"),Tm=1440*60*1e3,ym=720*60*60*1e3,wm=1e4;function xi(){if(!hm(Qn))return null;try{let e=JSON.parse(Em(Qn,"utf8"));return typeof e.license_key!="string"||typeof e.last_checked_at!="string"||typeof e.revoked!="boolean"?null:e}catch{return null}}function Rm(e){z(),bm(Qn,JSON.stringify(e,null,2)+`
|
|
747
|
+
`,{mode:384})}async function km(e,t){let s=null,n=null;try{s=new AbortController,n=setTimeout(()=>s?.abort(),wm);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 Ni(e,t={}){let s=xi(),n=t.apiUrl??`${Ai()}/api/license/check`,r=s?.license_key===e,o=!s||!r||Date.now()-new Date(s.last_checked_at).getTime()>=Tm;if(!t.force&&!o)return s;let a=await km(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 Rm(c),c}function Oi(e){let t=xi();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()>ym?{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 Am=Date.UTC(2026,5,1,7,0,0);var xm=1440*60*1e3,e0=60*xm;async function Ke(){let e=Pt();if(!e)return{tier:"free"};let t=await Ri(e.license_jwt);if(!t.valid||!t.claims)return{tier:"free",invalid_reason:t.reason};if(t.claims.machine_fp&&t.claims.machine_fp!==ki())return{tier:"free",invalid_reason:"machine fingerprint mismatch \u2014 re-activate on this device"};let s=Oi(e.license_key);return s?.revoked?{tier:"free",invalid_reason:s.reason}:Nm(e,t.claims)}async function Li(e){let t=Pt();if(!t)return{ran:!1,revoked:!1,reason:null,last_checked_at:null};let s=await Ni(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 Nm(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 Ci(){return(await Ke()).tier==="pro"}U();U();import{createHash as Ym}from"node:crypto";U();ee();import{writeFileSync as Im,readFileSync as b0,existsSync as jm,mkdirSync as Mm,readdirSync as S0,unlinkSync as T0}from"node:fs";import{join as Pi}from"node:path";import{randomUUID as Ui}from"node:crypto";var sr=Pi(H,"bug-patterns");function Dm(){z(),jm(sr)||Mm(sr,{recursive:!0})}function Ue(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 $i(e){return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at}}function Bi(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??Ui(),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=Hi(n);if(!c)throw new Error("createCluster succeeded but read-back failed");return Ut(n),c}function Hi(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:Ue(s),members:n.map($i)}}function Wi(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&&Ut(e);let a=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Ue(a),added:o}}function qi(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(Ue),total:o.n}}function Fm(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 Xi(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:Ue(s),members:n.map(Fm)}}function Ji(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),Ut(e);let a=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Ue(a)}}function Yi(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=Ui(),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)})(),Ut(e),Ut(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:Ue(h),split:Ue(b),movedMembers:m.map($i)}}function Ut(e){try{Dm();let t=Hi(e);if(!t)return;let s=Pi(sr,`${e}.json`),n={schema:"claude-recall.bug-pattern-cluster.v1",cluster:t.cluster,members:t.members,backed_up_at:new Date().toISOString()};Im(s,JSON.stringify(n,null,2))}catch(t){console.error("[bug-patterns] backup failed:",t)}}function Gi(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(Ue):[]}function zi(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 Gm=/\b0x[0-9a-fA-F]+\b/g,zm=/\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,Km=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g,Vm=/:\d+:\d+/g,Zm=/\bline\s+\d+\b/gi,Qm=/\bcolumn\s+\d+\b/gi,eg=/\b(?:pid|PID|process(?:\s+id)?)\s*[:=]?\s*\d+\b/gi,tg=/\b(?:port|:)\s*[:=]?\s*\d{2,5}\b/gi,sg=/\b\d{4,}\b/g,ng=/(['"`])[^'"`\n]{1,128}\1/g;function rg(e){if(!e)return"";let t=String(e);return t=t.replace(Gm,"<hex>"),t=t.replace(zm,"<uuid>"),t=t.replace(Km,"<ts>"),t=t.replace(Vm,":<line>:<col>"),t=t.replace(Zm,"line <n>"),t=t.replace(Qm,"column <n>"),t=t.replace(eg,"pid <n>"),t=t.replace(tg,"port <n>"),t=t.replace(sg,"<num>"),t=t.replace(ng,"<arg>"),t=t.replace(/\s+/g," ").trim(),t.toLowerCase()}function og(e){let t=(e.error_type??"unknown").toLowerCase().trim(),s=rg(e.snippet??e.message_hash??""),n=`${t}|${s}`;return Ym("sha256").update(n).digest("hex").slice(0,16)}function ig(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=og(d);a.push({session_id:c.session_id,project:c.project,started_at:c.started_at,signature:d,fingerprint:h})}}return a}function ag(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 cg(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 lg(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-cg(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 ug(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=lg({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 x=R.started_at??"",J=D.started_at??"";return x<J?-1:x>J?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 na(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=Gi(n.fingerprint),o=zi(n.fingerprint),a=n.members.map(d=>d.session_id).filter(d=>!o.has(d));if(r.length===0){let d=Bi({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=Wi(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 ra(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=ig(e.project),a=new Set(o.map(x=>x.session_id)),c=ag(o),u=[],d=!1;if(e.semantic){let x=e.embedder??null;if(!x)try{x=await mg()}catch(J){let B=(J instanceof Error?J.message:String(J)).split(`
|
|
794
|
+
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${B}
|
|
795
|
+
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),d=!0}if(x){let J=[];for(let M of c)M.members.length===1&&J.push(M.members[0]);J.length>=2&&(u=await ug(J,x,n,r))}}let m=c.filter(x=>x.members.length>=t),h=u.filter(x=>x.members.length>=t),b=na(m,t),T=na(h,t),S=[...b.cluster_ids,...T.cluster_ids],w=Array.from(new Set(S)),R=[];if(w.length>0){let x=f(),J=w.map(()=>"?").join(","),M=x.prepare(`SELECT * FROM bug_pattern_clusters
|
|
796
|
+
WHERE id IN (${J})
|
|
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 B of M)R.push({id:B.id,signature_hash:B.signature_hash,example_message:B.example_message,occurrence_count:B.occurrence_count,first_seen_at:B.first_seen_at,last_seen_at:B.last_seen_at,resolved_in_session_id:B.resolved_in_session_id,fix_summary:B.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 dg=async()=>{let{embed:e,loadEmbedder:t,getEmbedderStatus:s}=await Promise.resolve().then(()=>(Ve(),sa));return s().loaded||await t(),e},pg=dg;async function mg(){return pg()}U();U();ee();import{writeFileSync as oa,readFileSync as H0,existsSync as ia,mkdirSync as aa,readdirSync as W0}from"node:fs";import{join as xs}from"node:path";var gg=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),ca=new Set(["regex","llm","embedding","manual","auto","citation","git","terminal-registry"]),_g=new Set(["pending","approved","rejected"]),fg=new Set(["L1","L2","L3","L4","user"]),ir=xs(H,"links"),ar=xs(H,"suggestions"),hg=xs(ar,"index.json");function Eg(){z(),ia(ir)||aa(ir,{recursive:!0})}function bg(){z(),ia(ar)||aa(ar,{recursive:!0})}function la(e){try{return JSON.parse(e)}catch{return e}}function Ns(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:la(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function cr(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:la(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function ua(e){if(!Number.isFinite(e)||e<0||e>1)throw new Error("confidence must be a number in [0, 1]")}function lr(e){if(!gg.has(e))throw new Error(`invalid link_type: ${e}`)}function Sg(e){if(!ca.has(e))throw new Error(`invalid source: ${e}`)}function da(e){if(!fg.has(e))throw new Error(`invalid inferred_by: ${e}`)}function pa(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 ma(e){pa(e.source_session_id,e.target_session_id),lr(e.link_type),Sg(e.source),ua(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 dr(e.source_session_id),Ns(o)}function Os(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&&(lr(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(Ns)}function Ht(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(Ns)}function ga(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&&dr(s.source_session_id),{removed:n.changes,sourceSessionId:s.source_session_id}}function Wt(e){pa(e.source_session_id,e.target_session_id),lr(e.link_type),ua(e.confidence),da(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 _a(),cr(r)}function lt(e={}){let t=f(),s=[],n=[];if(e.status){if(!_g.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&&(da(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(cr)}function ur(e,t,s={}){if(t!=="approved"&&t!=="rejected")throw new Error(`invalid decision: ${t}`);let n=s.source??"manual";if(!ca.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))})(),_a(),t==="approved"&&dr(o.source_session_id);let u=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);return{suggestion:cr(u),link:c?Ns(c):null}}function dr(e){try{Eg();let t=Os({sourceSessionId:e}),s=xs(ir,`${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};oa(s,JSON.stringify(n,null,2))}catch(t){console.error("[session-links] backup failed:",t)}}function _a(){try{bg();let e=lt({limit:5e3}),t={schema:"claude-recall.session-link-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:e};oa(hg,JSON.stringify(t,null,2))}catch(e){console.error("[session-links] suggestions backup failed:",e)}}U();ee();import{writeFileSync as Tg,readFileSync as z0,existsSync as yg,mkdirSync as wg,readdirSync as K0}from"node:fs";import{join as fa}from"node:path";var pr=fa(H,"output-index");function Rg(){z(),yg(pr)||wg(pr,{recursive:!0})}function qt(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function kg(e){if(!e)return null;try{return JSON.parse(e)}catch{return e}}function ha(e){return{session_id:e.session_id,files_written:qt(e.files_written),brands_mentioned:qt(e.brands_mentioned),terms_introduced:qt(e.terms_introduced),plan_ids_referenced:qt(e.plan_ids_referenced),bug_signatures:qt(e.bug_signatures),raw_extraction:kg(e.raw_extraction),extracted_at:e.extracted_at,extractor_version:e.extractor_version}}function Ea(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=ha(m);return Ag(e.session_id),h}function Ze(e){let s=f().prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e);return s?ha(s):null}function Ag(e){try{Rg();let t=Ze(e);if(!t)return;let s=fa(pr,`${e}.json`),n={schema:"claude-recall.session-output-index.v1",backed_up_at:new Date().toISOString(),...t};Tg(s,JSON.stringify(n,null,2))}catch(t){console.error("[output-index] backup failed:",t)}}var mr={citation:"same-project",similar:"same-project",skill_track:"same-project",bug_pattern:"cross-project",wiki_link:"cross-project",temporal_proximity:"same-project"},xg=2,Ng=.25,Og=5,Lg=60,Cg=25;function Ls(e){return e.trim().toLowerCase()}function vg(e){let t=new Set;for(let s of e.files_written)t.add(`file:${Ls(s)}`);for(let s of e.brands_mentioned)t.add(`brand:${Ls(s)}`);for(let s of e.terms_introduced)t.add(`term:${Ls(s)}`);for(let s of e.plan_ids_referenced)t.add(`plan:${Ls(s)}`);return t}function Ig(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Lg);return Math.max(.2,t)}function jg(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 Mg(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 Dg(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 Fg(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=vg(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 Pg(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<xg)continue;let d=t.startedAt.get(a)??null,m=jg(n,d),h=Ig(m),b=Math.min(1,u/Og*h);if(b<Ng)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,Cg)}async function ba(e){if(mr.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project \u2014 refusing to run inference");let t=Dg(e.projectId),s=new Map;for(let a of t){let c=Mg(a.id);c&&s.set(a.id,c)}let n=Fg(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=Pg(a.id,n);for(let u of c)try{let d=Wt({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}}U();var Ug=/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,$g=[/\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],Bg=.95,Hg=.85;var gr=50;function Wg(e){if(!e)return[];let t=new Set,s=[],n=e.match(Ug);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>=gr))break}return s}function qg(e){if(!e)return[];let t=new Set,s=[];for(let n of $g){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>=gr))break}if(s.length>=gr)break}}return s}function Sa(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 Ta(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 Xg(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 Jg(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>typeof s=="string")}catch{}return[]}function ya(e={}){if(mr.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project");let t=Sa(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 Ta(e.projectId)){if(e.signal?.aborted)break;let a=Wg(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{Wt({source_session_id:o.session_id,target_session_id:u,link_type:"citation",confidence:Bg,evidence:{matched_uuid:u,source_message_uuid:o.message_uuid,scanner:"uuid-ref"},inferred_by:"L1"}),r+=1}catch{}}}return{created:r}}function wa(e={}){let t=Xg(e.projectId);if(t.length===0)return{created:0};let s=new Map;for(let c of t){let u=Jg(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=Sa(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 Ta(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{Wt({source_session_id:c,target_session_id:d,link_type:"citation",confidence:Hg,evidence:{matched_plan_ids:Array.from(m).slice(0,12),scanner:"plan-ref"},inferred_by:"L1"}),o+=1}catch{}return{created:o}}U();Se();import{existsSync as y_,mkdirSync as w_,writeFileSync as R_}from"node:fs";import{homedir as k_}from"node:os";import{join as Tr}from"node:path";U();import{existsSync as La,mkdirSync as h_,readFileSync as E_,writeFileSync as b_}from"node:fs";import{homedir as S_}from"node:os";import{join as Ca}from"node:path";import{z as xe}from"zod";function va(){return process.env.RECALL_HOME??Ca(S_(),".recall")}function T_(){let e=va();La(e)||h_(e,{recursive:!0})}function Ia(){return Ca(va(),"config.json")}var js=xe.object({enabled:xe.boolean().default(!1),model:xe.string().optional(),ratePerMinute:xe.number().int().min(1).max(600).default(30),lastProcessedSessionId:xe.string().nullable().default(null),backfillPaused:xe.boolean().default(!1),autoExtractEnabled:xe.boolean().default(!1),autoExtractIntervalMinutes:xe.number().int().min(5).max(720).default(60),autoExtractBatchSize:xe.number().int().min(1).max(20).default(1),autoResumeWorker:xe.boolean().default(!1)}),Is={enabled:!1,ratePerMinute:30,lastProcessedSessionId:null,backfillPaused:!1,autoExtractEnabled:!1,autoExtractIntervalMinutes:60,autoExtractBatchSize:1,autoResumeWorker:!1};function ja(){let e=Ia();if(!La(e))return{};try{return JSON.parse(E_(e,"utf8"))}catch(t){return console.error("[semantic-config] failed to parse config.json, using defaults:",t),{}}}function ae(){let e=ja().semantic;if(!e)return{...Is};let t=js.safeParse({...Is,...e});return t.success?t.data:{...Is}}function Ms(e){T_();let t=ja(),s=js.parse({...Is,...t.semantic??{},...e}),n={...t,semantic:s};return b_(Ia(),JSON.stringify(n,null,2)),Sr(s.enabled),s}function Sr(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 A_=1,x_=12e3,Ma=3,N_=[];function O_(){return process.env.RECALL_HOME??Tr(k_(),".recall")}function Da(){return Tr(O_(),"semantic")}function L_(){let e=Da();y_(e)||w_(e,{recursive:!0})}function C_(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>x_)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 v_(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 I_(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}),L_();let n=Tr(Da(),`${e.sessionId}.json`);R_(n,JSON.stringify({version:A_,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 Ds=null;function M_(){let t=ae().ratePerMinute,s=t/6e4;return(!Ds||Ds.capacity!==t)&&(Ds={tokens:t,capacity:t,refillPerMs:s,lastRefill:Date.now()}),Ds}function D_(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 F_(e){for(;;){if(e?.aborted)throw new Error("aborted");let t=M_();if(D_(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 Fs(e,t={}){let s=ae();if(!s.enabled)return{sessionId:e,ok:!1,reason:"disabled"};if(!pe())return{sessionId:e,ok:!1,reason:"claude-cli-missing"};let n=C_(e);if(!n)return{sessionId:e,ok:!1,reason:"session-not-found"};if(n.messageCount<Ma)return{sessionId:e,ok:!1,reason:"too-short"};if(!n.excerpt.trim())return{sessionId:e,ok:!1,reason:"empty-excerpt"};await F_(t.signal);let r=v_(n),o=await et(r,N_,{model:s.model});if(!o.success)return{sessionId:e,ok:!1,reason:`claude-cli-exit-${o.exitCode??"null"}`,model:s.model??null};let a=I_(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 Ps(e={}){let t=ae();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||ae().backfillPaused)break;c.currentSessionId=u,e.onProgress?.({...c});try{(await
|
|
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||ae().backfillPaused)break;c.currentSessionId=u,e.onProgress?.({...c});try{(await Fs(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,Ms({lastProcessedSessionId:u}),e.onProgress?.({...c})}return c.currentSessionId=null,e.onProgress?.({...c}),c}async function Fa(e){if(!ae().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<Ma)&&!(n.generated_at&&n.source_message_count!=null&&n.source_message_count>=n.message_count))try{await Fs(e)}catch(r){console.error("[semantic] processSession error for",e,r)}}function yr(){let e=ae(),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}}U();import{createHash as B_}from"node:crypto";var P_=[{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 U_(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 $_(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 be(e){if(!e)return{redacted:e,count:0};let t=e,s=0,n=new Set;for(let r of P_)r.regex.lastIndex=0,t=t.replace(r.regex,o=>{let a=`${r.name}::${$_(o)}`;return n.has(a)||(n.add(a),s+=1),`[REDACTED ${r.name}: ${U_(o)}]`});return{redacted:t,count:s}}Se();var kr=1,st="claude-haiku-4-5-20251001",H_=3,W_=32e3,Pa=2e3,q_=30,X_=30,J_=30,Y_=30;function G_(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 Ua(e,t={}){if(e.message_count<H_)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>=kr)return{eligible:!1,reason:"already-extracted"}}return{eligible:!0}}function z_(e){let t=G_(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>Pa?u.slice(0,Pa)+"\u2026":u,m=`${c}: ${d}`;if(o+m.length>W_)break;r.push(m),o+=m.length}return{meta:t,excerpt:r.join(`
|
|
934
934
|
|
|
935
|
-
`)}}function
|
|
936
|
-
`)}function
|
|
935
|
+
`)}}function K_(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 V_(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function wr(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 Z_(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 Q_(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=B_("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 ef(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=wr(o.files_written,200),c=wr(o.brands_mentioned,X_),u=Z_(o.terms_introduced,q_),d=wr(o.plan_ids_referenced,J_),m=Q_(o.bug_signatures,Y_);return a.length===0&&c.length===0&&u.length===0&&d.length===0&&m.length===0&&!V_(o.files_written)?null:{files_written:a,brands_mentioned:c,terms_introduced:u,plan_ids_referenced:d,bug_signatures:m}}var Rr=null;async function tf(e,t){return Rr?Rr(e,t):et(e,[],{model:t})}async function Ar(e,t={}){if(t.signal?.aborted)return{session_id:e,ok:!1,failed:"aborted"};let s=z_(e);if(!s)return{session_id:e,ok:!1,skipped:"session-not-found"};let n=Ua(s.meta,{force:t.force});if(!n.eligible)return{session_id:e,ok:!1,skipped:n.reason};if(!Rr&&!pe())return{session_id:e,ok:!1,failed:"claude-cli-missing"};let r=K_(s),o=t.model??st,a=await tf(r,o);if(!a.success){let m=(a.stderr||a.stdout||"").slice(0,400),h=m?be(m).redacted:void 0;return{session_id:e,ok:!1,failed:"claude-cli-error",exit_code:a.exitCode,stderr_excerpt:h}}let c=ef(a.stdout);if(!c){let m=a.stdout.slice(0,400),h=m?be(m).redacted:void 0;return{session_id:e,ok:!1,failed:"parse-failed",exit_code:a.exitCode,stderr_excerpt:h}}let u=sf(a.stdout),d=Ea({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:kr});return{session_id:e,ok:!0,index:d,usage:u}}function sf(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 gt(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,7 @@ 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=Ua(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 $a(e={}){let t=gt({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 Ar(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}}Se();var te="[daemon:inference]",vr=!1,Ir=!1,jr=!1,Mr=!1,Dr=!1,Za=0,Fr=!1,Pr=!1,Ws=3,nt=0,_t=!1,qs=null,ft=null,Qa=!1;function bf(){return f().prepare("SELECT id, name FROM projects").all()}async function ec(){if(vr)return;vr=!0;let e=Date.now();try{let s=(await ra({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 tc(){if(Ir)return;Ir=!0;let e=Date.now();try{let t=0,s=0;for(let n of bf())try{let r=await ba({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{Ir=!1}}function sc(){if(jr)return;jr=!0;let e=Date.now();try{let t=ya({}),s=wa({});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{jr=!1}}async function nc(){if(Mr)return;let e=ae();if(!e.enabled||e.backfillPaused)return;Mr=!0;let t=Date.now();try{let s=await Ps({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{Mr=!1}}async function rc(){if(Dr)return;let e=ae();if(e.autoExtractEnabled&&!Qa&&_t&&(Sf(),console.log(`${te} auto-extract: circuit breaker reset (config toggled on).`)),Qa=e.autoExtractEnabled,!e.autoExtractEnabled||_t)return;let t=e.autoExtractIntervalMinutes*60*1e3;if(Date.now()-Za<t)return;if(!pe()){Fr||(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)`),Fr=!0);return}if(Fr=!1,!await Ci().catch(()=>!1)){Pr||(console.log(`${te} auto-extract: Pro license required \u2014 pausing nibbler (run \`recall semantic auto-extract off\` to silence; upgrade unlocks)`),Pr=!0);return}Pr=!1,Dr=!0,Za=Date.now();let n=Date.now();try{let r=e.autoExtractBatchSize,{eligible:o}=gt({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 Ar(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=be(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>=Ws&&(_t=!0,qs=Date.now(),ft=`${Ws} consecutive zero-token runs (claude CLI returning no usage)`,console.log(`${te} auto-extract: CIRCUIT BREAKER tripped \u2014 ${ft}. 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>=Ws&&(_t=!0,qs=Date.now(),ft=`${Ws} consecutive thrown errors`,console.log(`${te} auto-extract: CIRCUIT BREAKER tripped \u2014 ${ft}. Pausing nibbler.`))}finally{Dr=!1}}function ic(){return{broken:_t,brokenAt:qs,reason:ft,consecutiveZeroTokenRuns:nt}}function Sf(){_t=!1,qs=null,ft=null,nt=0}var Ur=!1;async function oc(){if(Ur)return;let{readRetentionConfig:e,writeRetentionConfig:t}=await Promise.resolve().then(()=>(Or(),Ja)),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(()=>(Va(),Ka)),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 ac(){let m=[],h=[];m.push(setTimeout(()=>{ec()},9e4)),h.push(setInterval(()=>{ec()},18e5)),m.push(setTimeout(()=>{tc()},18e4)),h.push(setInterval(()=>{tc()},36e5)),m.push(setTimeout(()=>sc(),12e4)),h.push(setInterval(()=>sc(),18e5)),m.push(setTimeout(()=>{nc()},24e4)),h.push(setInterval(()=>{nc()},9e5)),m.push(setTimeout(()=>{rc()},3e5)),h.push(setInterval(()=>{rc()},9e5));let b=3600*1e3,T=300*1e3;return m.push(setTimeout(()=>{oc()},T)),h.push(setInterval(()=>{oc()},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)}}}U();import{serveStatic as vp}from"@hono/node-server/serve-static";import{existsSync as Ly,readFileSync as li}from"node:fs";import{stat as Cy,readFile as vy,realpath as Iy}from"node:fs/promises";import{timingSafeEqual as jy}from"node:crypto";import{homedir as My}from"node:os";import{dirname as pi,join as Gn}from"node:path";import{fileURLToPath as mi}from"node:url";import{existsSync as Sk,readFileSync as Tk,statSync as yk,statfsSync as Tf}from"node:fs";Hs();U();ee();var yf=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],Ak=new RegExp(`^(${yf.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);var xk=5*6e4;function $r(){try{let e=Tf(H);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}function cc(e){let{projects:t,sessions:s,messages:n,port:r,version:o}=e;return`<!DOCTYPE html>
|
|
949
949
|
<html lang="en">
|
|
950
950
|
<head>
|
|
951
951
|
<meta charset="utf-8" />
|
|
@@ -1000,71 +1000,71 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1000
1000
|
</footer>
|
|
1001
1001
|
</main>
|
|
1002
1002
|
</body>
|
|
1003
|
-
</html>`}var
|
|
1003
|
+
</html>`}var wf=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,Rf=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,kf=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function Af(e){return e.replace(wf,"").trim()}function xf(e){let t=e.replace(Rf,"[tool call]");return t=t.replace(kf,"[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
1004
|
|
|
1005
|
-
`),t.trim()}function
|
|
1006
|
-
`)}
|
|
1005
|
+
`),t.trim()}function Nf(e){return e.role??e.type??"message"}function lc(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=Af(h);n==="condensed"&&(b=xf(b));let T=b.length>0,S=!!m.tool_names&&m.tool_names.length>0;if(!T&&!S){d+=1;continue}let w=Nf(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(`
|
|
1006
|
+
`)}U();ee();import{writeFileSync as Of}from"node:fs";import{join as Lf}from"node:path";var Cf=Lf(H,"aliases.json");function Br(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Oe(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:Br(t.previous_aliases)}))}function ye(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=Br(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)
|
|
1007
1007
|
VALUES (?, ?, ?, ?)
|
|
1008
1008
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
1009
1009
|
alias = excluded.alias,
|
|
1010
1010
|
updated_at = excluded.updated_at,
|
|
1011
|
-
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(a)),
|
|
1012
|
-
WHERE session_id = ?`).run(s,JSON.stringify(r),e),
|
|
1011
|
+
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(a)),uc(),{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=Br(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),uc()}function uc(){try{z();let e=vf(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};Of(Cf,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 If,mkdirSync as jf,existsSync as Mf}from"node:fs";import{join as pc}from"node:path";var Hr=pc(H,"notes"),dc=200,Df=12e3,Ff=800,Pf=8e3;function mc(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function gc(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 Uf(){z(),Mf(Hr)||jf(Hr,{recursive:!0})}function $f(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:mc(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:gc(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?$f(t):null}function _c(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=mc(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
1013
|
VALUES (?, ?, ?, ?)
|
|
1014
1014
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
1015
1015
|
content = excluded.content,
|
|
1016
1016
|
updated_at = excluded.updated_at,
|
|
1017
|
-
previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(o)),
|
|
1017
|
+
previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(o)),Wf(e,t,n),Js(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 fc(e){let s=f().prepare(`SELECT rowid AS rid, role, content_text
|
|
1018
1018
|
FROM messages
|
|
1019
1019
|
WHERE session_id = ? AND is_sidechain = 0
|
|
1020
1020
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1021
1021
|
AND role IN ('user', 'assistant')
|
|
1022
1022
|
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1023
|
-
LIMIT ?`).all(e,
|
|
1023
|
+
LIMIT ?`).all(e,dc);if(s.length===0)throw new Error("no messages available to summarise");let n=Df,r=[];for(let b of s){if(n<=0)break;let T=(b.content_text??"").slice(0,Ff);r.push({rid:b.rid,role:b.role,content:T}),n-=T.length}r.reverse();let o=s.length===dc||n<=0,a=r.map(b=>`**${b.role}**: ${b.content}`).join(`
|
|
1024
1024
|
|
|
1025
1025
|
`),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(()=>(
|
|
1026
|
+
`),{spawnClaudePrompt:u,isClaudeCliAvailable:d}=await Promise.resolve().then(()=>(Se(),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=Hf(m.stdout);if(!h)throw new Error("claude CLI returned an empty synopsis");return h.slice(0,Pf)}function Hf(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 hc(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=gc(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
1027
|
SET auto_synopsis = ?,
|
|
1028
1028
|
auto_synopsis_generated_at = ?,
|
|
1029
1029
|
auto_synopsis_history = ?
|
|
1030
1030
|
WHERE session_id = ?`).run(t,r,JSON.stringify(a),e):s.prepare(`INSERT INTO session_notes
|
|
1031
1031
|
(session_id, content, updated_at, previous_versions, auto_synopsis,
|
|
1032
1032
|
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
|
|
1033
|
+
VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,n,t,r,JSON.stringify(a)),Js(e)}function Wf(e,t,s){try{Uf();let n=pc(Hr,`${e}.md`),r=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${s} -->
|
|
1034
|
+
`;If(n,r+t)}catch(n){console.error("[notes] mirror write failed:",n)}}pt();U();ee();import{randomUUID as qf}from"node:crypto";import{writeFileSync as Xf,readFileSync as qk,existsSync as Xk}from"node:fs";import{join as Jf}from"node:path";var Yf=Jf(H,"collections.json"),Ys=8;function Gs(e){return{...e}}function we(e,t,s,n=null,r=new Date().toISOString()){f().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
|
|
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 Ec(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 Gf(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 bc(e=!1){return f().prepare(`SELECT c.*,
|
|
1036
1036
|
(SELECT COUNT(*) FROM collection_sessions cs WHERE cs.collection_id = c.id) AS session_count
|
|
1037
1037
|
FROM collections c
|
|
1038
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=>({...
|
|
1039
|
+
ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(n=>({...Gs(n),session_count:n.session_count}))}function Be(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?Gs(t):null}function Sc(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
1040
|
FROM collection_sessions
|
|
1041
1041
|
WHERE collection_id IN (${r})
|
|
1042
|
-
ORDER BY added_at DESC`).all(...n)}function
|
|
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 Tc(e){return f().prepare(`SELECT c.* FROM collections c
|
|
1043
1043
|
JOIN collection_sessions cs ON cs.collection_id = c.id
|
|
1044
1044
|
WHERE cs.session_id = ? AND c.archived_at IS NULL
|
|
1045
|
-
ORDER BY LOWER(c.name)`).all(e)}function
|
|
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=qf();if(e.parent_id){if(!Be(e.parent_id))throw new Error("parent collection not found");if(Ec(e.parent_id)>=Ys-1)throw new Error(`max collection depth is ${Ys}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
|
|
1046
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),
|
|
1047
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),we(r,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),rt(),Be(r)}function yc(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(!Be(t.parent_id))throw new Error("parent collection not found");if(Gf(t.parent_id,e))throw new Error("cannot move collection into one of its descendants");if(Ec(t.parent_id)>=Ys-1)throw new Error(`max collection depth is ${Ys}`)}return s.transaction(()=>{s.prepare(`UPDATE collections
|
|
1048
1048
|
SET name = ?, description = ?, icon = ?, color = ?,
|
|
1049
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&&
|
|
1051
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,d,s,c,u),
|
|
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)
|
|
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&&we(e,"rename",{from:n.name,to:o.name},null,r),t.description!==void 0&&t.description!==n.description&&we(e,"describe",{description:o.description},null,r),(t.icon!==void 0&&t.icon!==n.icon||t.color!==void 0&&t.color!==n.color)&&we(e,"recolor",{icon:o.icon,color:o.color},null,r),t.parent_id!==void 0&&t.parent_id!==n.parent_id&&we(e,"move",{from:n.parent_id,to:o.parent_id},null,r),t.sort_key!==void 0&&t.sort_key!==n.sort_key&&we(e,"reorder",{from:n.sort_key,to:o.sort_key},null,r)})(),rt(),Be(e)}function wc(e){let t=f(),s=zs(e);if(s.archived_at)return Gs(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = ?, updated_at = ? WHERE id = ?").run(n,n,e),we(e,"archive",{name:s.name},null,n)})(),rt(),Be(e)}function Rc(e){let t=f(),s=zs(e);if(!s.archived_at)return Gs(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = NULL, updated_at = ? WHERE id = ?").run(n,e),we(e,"restore",{name:s.name},null,n)})(),rt(),Be(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),we(e,"add",{note:s,source:c,rule_id:u},t,d)})(),rt(),{added:!0}}function kc(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),we(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)we(o.collection_id,"remove",{rule_id:e},o.session_id,n)})(),rt(),{removed:s.length}}function zf(){return f().prepare(`SELECT id, collection_id, session_id, action, payload, at
|
|
1053
1053
|
FROM collection_events
|
|
1054
|
-
ORDER BY at ASC, id ASC`).all()}function
|
|
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
1055
|
created_at, updated_at, archived_at
|
|
1056
1056
|
FROM collections
|
|
1057
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
1058
|
FROM collection_sessions
|
|
1059
|
-
ORDER BY collection_id, added_at`).all(),n=
|
|
1059
|
+
ORDER BY collection_id, added_at`).all(),n=zf(),r={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};Xf(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 Kf,mkdirSync as Vf,writeFileSync as Ac}from"node:fs";import{homedir as Zf}from"node:os";import{basename as Qf,join as Yr}from"node:path";var Vs=Yr(H,"auto-rules"),eh=Yr(Vs,"rules.json"),th=Yr(Vs,"suggestions.json"),qr="Repositories",sh="Topics",xc=3;var nh=5,rh=2,oh=[/\bROADMAP\.md\b/g,/\bPROJECT\.md\b/g,/\bdocs\/[A-Za-z0-9._-]+\.md\b/g,/\.planning\/[A-Za-z0-9._\-/]+/g];function Gr(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 ih(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 ah(e){switch(e){case"cwd-prefix":case"project-id":case"git-branch-prefix":return qr;case"tag":return sh;case"plan-file":return null}}function ch(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
1060
|
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1061
|
-
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(
|
|
1062
|
-
WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(n=>n.id)}}function
|
|
1061
|
+
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(Jr(),`seed:${e}`,o.id,new Date().toISOString()),o.id}function lh(e,t,s){let n;if(s!==void 0)n=s;else{let o=ah(t);n=o?ch(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?Gr(t):null}function uh(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 Nc(e,t,s=3){let n=f(),r=`s.id AS id, s.cwd AS cwd, s.started_at AS started_at,
|
|
1063
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
1064
|
WHERE s.cwd IS NOT NULL AND s.cwd LIKE ? ESCAPE '\\'
|
|
1065
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(
|
|
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
1066
|
WHERE s.git_branch IS NOT NULL AND s.git_branch LIKE ? ESCAPE '\\'
|
|
1067
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(
|
|
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
1068
|
WHERE s.project_id = ?
|
|
1069
1069
|
ORDER BY s.started_at DESC LIMIT ?`).all(c,s));break}case"tag":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1070
1070
|
JOIN session_tags st ON st.session_id = s.id
|
|
@@ -1072,19 +1072,19 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1072
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
1073
|
WHERE s.first_user_message IS NOT NULL
|
|
1074
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:
|
|
1076
|
-
`)[0].trim();return s.length>80?s.slice(0,80)+"\u2026":s}function
|
|
1075
|
+
ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",s);break}return a.map(c=>({id:c.id,title:dh(c),cwd:c.cwd,started_at:c.started_at}))}function dh(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 ph(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 Oc(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
1077
|
WHERE enabled = 1 AND created_by != 'seed'
|
|
1078
|
-
ORDER BY priority, created_at`).all(),r=0;for(let o of n){let a=
|
|
1078
|
+
ORDER BY priority, created_at`).all(),r=0;for(let o of n){let a=Gr(o);if(ph(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 uh(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=lh(t,e.type,e.parent_collection_id)),n.prepare(`INSERT INTO auto_collection_rules
|
|
1079
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
|
|
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 Lc(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(Gr)}function Cc(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
1081
|
SET name = ?, pattern = ?, enabled = ?, priority = ?
|
|
1082
|
-
WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o
|
|
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(ih)}function Ic(e){f().prepare("UPDATE auto_collection_suggestions SET dismissed = 1 WHERE id = ?").run(e),ht()}function jc(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=Zf(),n=new Set(e.prepare("SELECT decoded_path FROM projects").all().map(c=>c.decoded_path)),r=[...mh(s,t).filter(c=>!n.has(c.pattern)),...gh(t),..._h(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
1083
|
SET session_count = ?, detected_at = ?, suggested_name = ?, suggested_parent_collection_id = ?
|
|
1084
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
1085
|
(id, type, pattern, suggested_name, suggested_parent_collection_id, session_count, detected_at, dismissed)
|
|
1086
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(
|
|
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
|
|
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 mh(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<xc)continue;let u=!1;for(let[d,m]of r.entries())if(d!==a&&d.startsWith(a+"/")&&m.size>=xc){u=!0;break}u||o.push({type:"cwd-prefix",pattern:a,suggested_name:Qf(a)||a,suggested_parent_collection_id:null,session_count:c.size,detected_at:t,dismissed:!1})}return o}function gh(e){return f().prepare("SELECT tag, COUNT(*) AS n FROM session_tags GROUP BY tag HAVING n >= ?").all(nh).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 _h(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 oh){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<rh||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(),Kf(Vs)||Vf(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();Ac(eh,JSON.stringify({schema:"claude-recall.auto-rules.v1",backed_up_at:new Date().toISOString(),rules:t},null,2)),Ac(th,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 Mc(){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 Dc}from"node:crypto";import{writeFileSync as Fc,readFileSync as iA,existsSync as fh,mkdirSync as hh}from"node:fs";import{join as Kr}from"node:path";var sn=Kr(H,"threads"),Eh=Kr(sn,"index.json");function Pc(){z(),fh(sn)||hh(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 Uc(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
1088
|
p.name AS project,
|
|
1089
1089
|
COUNT(*) AS n,
|
|
1090
1090
|
SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
|
|
@@ -1092,7 +1092,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1092
1092
|
LEFT JOIN sessions s ON s.id = te.session_id
|
|
1093
1093
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1094
1094
|
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
|
|
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 $c(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 Bc(e){let s=f().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
|
|
1096
1096
|
s.auto_title AS auto_title,
|
|
1097
1097
|
s.auto_title_source AS auto_title_source,
|
|
1098
1098
|
s.first_user_message AS first_user_message,
|
|
@@ -1100,11 +1100,11 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1100
1100
|
FROM (SELECT ? AS sid) q
|
|
1101
1101
|
LEFT JOIN sessions s ON s.id = q.sid
|
|
1102
1102
|
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
|
|
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 Zr(e){let s=f().prepare(`SELECT
|
|
1104
1104
|
COUNT(*) AS session_count,
|
|
1105
1105
|
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
|
|
1107
|
-
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),
|
|
1106
|
+
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function Re(e){let t=oe(e);t&&(Pc(),Fc(Kr(sn,`${e}.json`),JSON.stringify(t,null,2)),Hc())}function Hc(){Pc();let e=Qr({includeArchived:!0});Fc(Eh,JSON.stringify({threads:e},null,2))}function nn(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=f(),n=Dc(),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)
|
|
1107
|
+
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),Re(n);let o=oe(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function Qr(e={}){let t=f(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),r=Uc(n.map(o=>o.id));return n.map(o=>Vr(o,Zr(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
1108
|
NULLIF(sa.alias, '') AS alias,
|
|
1109
1109
|
s.auto_title AS auto_title,
|
|
1110
1110
|
s.auto_title_source AS auto_title_source,
|
|
@@ -1115,10 +1115,10 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1115
1115
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1116
1116
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1117
1117
|
WHERE e.thread_id = ?
|
|
1118
|
-
ORDER BY e.added_at ASC`).all(e).map(
|
|
1118
|
+
ORDER BY e.added_at ASC`).all(e).map($c),r=Uc([e]).get(e);return{...Vr(s,Zr(s.id),r),edges:n}}function Wc(e){return f().prepare(`SELECT t.* FROM threads t
|
|
1119
1119
|
JOIN thread_edges e ON e.thread_id = t.id
|
|
1120
1120
|
WHERE e.session_id = ? AND t.archived = 0
|
|
1121
|
-
ORDER BY t.created_at DESC`).all(e).map(n=>
|
|
1121
|
+
ORDER BY t.created_at DESC`).all(e).map(n=>Vr(n,Zr(n.id)))}function rn(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
1122
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1123
1123
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1124
1124
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
@@ -1126,33 +1126,33 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1126
1126
|
role = excluded.role,
|
|
1127
1127
|
confidence = excluded.confidence,
|
|
1128
1128
|
source = excluded.source,
|
|
1129
|
-
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,n),
|
|
1129
|
+
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,n),Re(e.threadId);let u=Bc(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 qc(e,t){let n=f().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&Re(e),{removed:n.changes}}function Qt(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
1130
|
SET parent_session_id = ?, role = ?, added_at = ?
|
|
1131
|
-
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),
|
|
1131
|
+
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),Re(e);let a=Bc(t);return $c({...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 Xc(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),Re(e);let r=oe(e);if(!r)throw new Error(`thread ${e} not found`);return r}function Jc(e){f().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),Re(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Yc(e){f().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),Re(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Gc(e){f().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),Re(e);let s=oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function zc(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
1132
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1133
1133
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1134
1134
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
1135
1135
|
parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
|
|
1136
1136
|
role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
|
|
1137
1137
|
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)})(),
|
|
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)})(),Re(t),Hc();let r=oe(t);if(!r)throw new Error("merge destination disappeared");return r}function Kc(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=f(),s=new Date().toISOString(),n=Dc();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
1139
|
(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))}})(),
|
|
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 D=R;if(D.type!=="tool_use")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
|
|
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))}})(),Re(e.threadId),Re(n);let r=oe(n);if(!r)throw new Error("split destination disappeared");return r}U();import{execFile as Ih}from"node:child_process";import{promisify as jh}from"node:util";import{readlink as Mh,readFile as tl}from"node:fs/promises";import{platform as un}from"node:os";import{readFileSync as bh,statSync as Sh}from"node:fs";var Th=200*1024*1024,an=.7,cn=.5,Qc=cn,yh=[{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"}],wh=["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 Vc(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 Rh(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of yh)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function kh(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 wh)if(o.includes(a))return{weight:t[n],matched:a,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function eo(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 Ah(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=eo(n,r);o>s&&(s=o)}return s}function xh(e,t){let s=eo(e.mean_embedding,t.mean_embedding),n=eo(e.tail_pool,t.head_pool),r=Ah(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 Nh(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 Oh(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 Lh(e,t){let s=Vc(e),n=Vc(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function Zc(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function Ch(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(`
|
|
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=Zc(t.recent_user_messages[0]);if(o.length>=200)for(let a of e.authored_content){let c=Zc(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 vh(e,t,s=Qc){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=Rh(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=kh(o),c=Oh(e.touched_files,t.touched_files),u=Lh(e.auto_title,t.auto_title),d=xh(e,t),m=Nh(e,t),h=Ch(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 Et(e,t=Qc){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=vh(c,r,t);u&&(!o||u.confidence>o.confidence)&&(o=u)}o&&s.push(o)}return s}function el(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 ln(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,r=new Set,o=[],a=new Set,c=[],u;try{if(Sh(e).size>Th)return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c};u=bh(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(`
|
|
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 D=R;if(D.type!=="tool_use")continue;let x=D.input??{},J=typeof x.file_path=="string"?x.file_path:null;if(J){let M=on(J);M&&r.add(M)}if((D.name==="Write"||D.name==="Edit"||D.name==="MultiEdit")&&J){let M=on(J);M&&a.add(M);let B=typeof x.content=="string"?x.content:typeof x.new_string=="string"?x.new_string:null;B&&B.length>=200&&c.push(B.length>4096?B.slice(0,4096):B)}if(D.name==="Bash"&&typeof x.command=="string")for(let M of x.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let B=on(M[1]);B&&r.add(B)}if((D.name==="Glob"||D.name==="Grep")&&typeof x.pattern=="string"){let M=on(x.pattern);M&&!M.includes("*")&&r.add(M)}}}return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}function on(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var so=jh(Ih),Dh=6,sl="Active ",nl=" sessions \u2014 ",Fh=6e4;async function Ph(){if(un()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await so(t,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let r of s.split(`
|
|
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 Uh(e){let t=un();if(t==="linux")try{return(await Mh(`/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 so(s,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let r of n.split(`
|
|
1144
|
+
`))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function $h(e){let t=un();if(t==="linux")try{let s=await tl(`/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 tl("/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 so(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 Bh(e,t){let s=await Ph();if(s.length===0)return null;let n=e.replace(/\/+$/,""),r=[];for(let a of s){let c=await Uh(a);if(c&&(c===n||c.startsWith(n+"/"))){let u=await $h(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=Fh;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 Hh(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 Wh(e,t){let s=f(),n=`${sl}${t}${nl}%`,r=s.prepare(`SELECT t.id
|
|
1145
1145
|
FROM threads t
|
|
1146
1146
|
WHERE t.archived = 0
|
|
1147
1147
|
AND t.name LIKE ?
|
|
1148
|
-
ORDER BY t.created_at DESC`).all(n);for(let a of r){let c=
|
|
1148
|
+
ORDER BY t.created_at DESC`).all(n);for(let a of r){let c=oe(a.id);if(c&&c.project===t)return c}let o=s.prepare(`SELECT DISTINCT te.thread_id AS id
|
|
1149
1149
|
FROM thread_edges te
|
|
1150
1150
|
JOIN sessions s ON s.id = te.session_id
|
|
1151
1151
|
JOIN threads t ON t.id = te.thread_id
|
|
1152
1152
|
WHERE s.project_id = ?
|
|
1153
1153
|
AND t.archived = 0
|
|
1154
1154
|
AND t.name LIKE ?
|
|
1155
|
-
LIMIT 1`).get(e,n);return o?
|
|
1155
|
+
LIMIT 1`).get(e,n);return o?oe(o.id):null}function rl(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 to(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
1156
|
sa.alias AS alias,
|
|
1157
1157
|
s.auto_title AS auto_title,
|
|
1158
1158
|
s.first_user_message AS first_user_message,
|
|
@@ -1172,19 +1172,19 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1172
1172
|
FROM sessions s
|
|
1173
1173
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1174
1174
|
WHERE s.project_id = ?
|
|
1175
|
-
ORDER BY s.started_at ASC`).all(e)}function
|
|
1175
|
+
ORDER BY s.started_at ASC`).all(e)}function qh(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=ln(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 Xh(e){let s=f().prepare(`SELECT session_id, parent_session_id, source
|
|
1176
1176
|
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
|
|
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 ol(e,t={}){let s=Hh(e),n=t.windowHours??Dh,r=t.scoreThreshold??cn,o=t.useLivePids??!0,a=[],c=[];if(o&&s.decoded_path){let w=to(e,0),R=await Bh(s.decoded_path,w);if(R===null){let x=un()==="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(x),c=to(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=to(e,n);c.length===0&&!a.length&&a.push(`No active sessions in ${s.name} within the last ${n}h.`);let u=Wh(e,s.name),d=new Set(u?u.edges.map(w=>w.session_id):[]),m=c.filter(w=>!d.has(w.session_id)),h=qh(c);h.sort((w,R)=>w.started_at_ms-R.started_at_ms);let b=Et(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:`${sl}${s.name}${nl}${rl(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 il(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=nn({name:e.thread.name,summary:`Auto-captured by sync-active on ${rl()}. 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=Xh(s);for(let a of e.candidates)n.has(a.session_id)||(rn({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{Qt(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
1178
|
WHERE thread_id = ?
|
|
1179
1179
|
AND source = 'auto-active'
|
|
1180
1180
|
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),
|
|
1181
|
+
AND role = 'child'`).all(s);for(let a of o)try{Qt(s,a.session_id,null)}catch{}return t}U();ee();import{randomUUID as Jh}from"node:crypto";import{writeFileSync as Yh}from"node:fs";import{join as Gh}from"node:path";var zh=Gh(H,"thread-folders.json");function al(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 es(){try{z();let e=no({includeArchived:!0});Yh(zh,JSON.stringify({folders:e},null,2))}catch{}}function no(e={}){let t=e.includeArchived?"":"WHERE archived = 0";return f().prepare(`SELECT * FROM thread_folders ${t} ORDER BY sort_order, name`).all().map(al)}function ot(e){let t=f().prepare("SELECT * FROM thread_folders WHERE id = ?").get(e);return t?al(t):null}function cl(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=ot(s);if(!c)throw new Error(`parent folder ${s} not found`);n=c.project_scope}let r=Jh(),o=new Date().toISOString(),a=ll(s,n);return f().prepare(`INSERT INTO thread_folders (id, name, parent_folder_id, project_scope, created_at, archived, sort_order)
|
|
1182
|
+
VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,s,n,o,a),es(),{id:r,name:t,parent_folder_id:s,project_scope:n,created_at:o,archived:!1,sort_order:a}}function ll(e,t){return f().prepare(`SELECT COALESCE(MAX(sort_order), -100) + 100 AS next
|
|
1183
1183
|
FROM thread_folders
|
|
1184
1184
|
WHERE parent_folder_id IS ?
|
|
1185
|
-
AND project_scope IS ?`).get(e,t)?.next??0}function
|
|
1185
|
+
AND project_scope IS ?`).get(e,t)?.next??0}function ul(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=ot(e);if(!n)throw new Error(`folder ${e} not found`);return f().prepare("UPDATE thread_folders SET name = ? WHERE id = ?").run(s,e),es(),{...n,name:s}}function dl(e,t){let s=ot(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=ot(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=ot(a);if(!u)break;a=u.parent_folder_id,c++}n=o.project_scope}let r=ll(t,n);return f().prepare("UPDATE thread_folders SET parent_folder_id = ?, project_scope = ?, sort_order = ? WHERE id = ?").run(t,n,r,e),es(),{...s,parent_folder_id:t,project_scope:n,sort_order:r}}function pl(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
1186
|
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),
|
|
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),es()}function ml(e){if(!ot(e))throw new Error(`folder ${e} not found`);f().prepare("DELETE FROM thread_folders WHERE id = ?").run(e),es()}function gl(e,t){if(t!==null&&!ot(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 _l(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}U();var Kh=4e3,Vh=2,Zh=30,Qh=.2,eE={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function dn(e){return e?Math.ceil(e.length/4):0}function fl(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Zh);return Math.max(Qh,t)}function hl(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 El(e){return f().prepare(`SELECT s.id,
|
|
1188
1188
|
NULLIF(sa.alias, '') AS alias,
|
|
1189
1189
|
s.auto_title,
|
|
1190
1190
|
s.auto_title_source,
|
|
@@ -1195,25 +1195,25 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1195
1195
|
FROM sessions s
|
|
1196
1196
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1197
1197
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1198
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
1198
|
+
WHERE s.id = ?`).get(e)??null}function bl(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 Sl(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 tE(e){let s=f().prepare(`SELECT id, auto_title, started_at
|
|
1199
1199
|
FROM sessions
|
|
1200
1200
|
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
|
|
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 sE(e){return{table:e!==null?tE(e):null,originProjectId:e,cache:new Map}}function pn(e,t){let s=e.cache.get(t);if(s)return s;let n=El(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:Sl(n),decimal:r,summary:bl(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,o),o}function nE(e,t){let n=f().prepare(`SELECT DISTINCT te.parent_session_id AS pid
|
|
1202
1202
|
FROM thread_edges te
|
|
1203
1203
|
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=
|
|
1204
|
+
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let a=pn(e,o.pid);a&&r.push(a)}return r}function rE(e,t){let n=f().prepare(`SELECT DISTINCT te.session_id AS sid
|
|
1205
1205
|
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
|
|
1206
|
+
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let a=pn(e,o.sid);a&&r.push(a)}return r}function Tl(e){let t=eE[e.linkType]??.5,s=bt(e.confidence),n=t*s,r=fl(e.daysApart),o=e.embeddingCosine??.5,a=bt(e.pagerank);if(e.scoring==="pagerank")return bt(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?bt(n):bt(o);let c=.35*n+.2*r+.2*o+.25*a;return bt(c)}function bt(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function oE(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=Ht(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 iE(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 yl=240;function wl(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function aE(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=wl(e.summary,yl);return`${r}
|
|
1207
|
+
${o}`}return r}function cE(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+=dn(c),e.summary){let u=wl(e.summary,yl*4);n.push(u),o+=dn(u)}n.push("");for(let u of t){if(u.refs.length===0)continue;let d=`## ${u.heading}`,m=dn(d),h=[],b=0;for(let T of u.refs){let S=aE(T),w=dn(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
1208
|
`)+`
|
|
1209
|
-
`,budgetUsed:o,truncated:r}}function
|
|
1210
|
-
`)[0].trim();return s.length>
|
|
1209
|
+
`,budgetUsed:o,truncated:r}}function lE(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=pn(e,u);if(!d)continue;let m=hl(t.started_at,d.started_at),h=Tl({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 mn(e,t={}){let s=Math.max(100,Math.floor(t.budget??Kh)),n=t.scoring??"hybrid",r=Math.max(1,Math.min(5,t.maxDepth??Vh)),o=t.includeWikiLinks??!0,a=t.includeSuggestions??!1,c=t.edgeTypes?new Set(t.edgeTypes):null,u=El(e);if(!u)throw new Error(`session not found: ${e}`);let d=sE(u.project_id),m={session_id:u.id,title:Sl(u),decimal:d.table?.byId.get(u.id)??null,summary:bl(u.id),project:u.project,started_at:u.started_at};d.cache.set(u.id,m);let h=nE(d,e),b=rE(d,e),T=Ht(e).filter($=>$.approved).filter($=>!c||c.has($.link_type)).filter($=>o||$.link_type!=="wiki_link"),S=oE(e,T,h,b,r),w=iE(S),R=[],D=[],x=[],J=[];for(let $ of T){let se=$.source_session_id===e?$.target_session_id:$.source_session_id,i=pn(d,se);if(!i)continue;let l=hl(m.started_at,i.started_at),p=Tl({confidence:$.confidence,linkType:$.link_type,daysApart:l,embeddingCosine:null,pagerank:w.get(se)??0,scoring:n}),g=fl(l),_=`${$.link_type} confidence=${$.confidence.toFixed(2)} recency=${g.toFixed(2)} (${Math.round(l)}d apart)`,E={...i,score:p,evidence:_,link_type:$.link_type};$.link_type==="citation"?R.push(E):$.link_type==="similar"?D.push(E):$.link_type==="wiki_link"?J.push(E):x.push(E)}if(a){let $=lt({sourceSessionId:e,status:"pending",limit:100}),se=lt({targetSessionId:e,status:"pending",limit:100}),i=[...$,...se],l=new Set,p=i.filter(_=>l.has(_.id)?!1:(l.add(_.id),!0)),g=lE(d,m,p,c,n,w);for(let _ of g)_.link_type==="citation"?R.push(_):_.link_type==="similar"?D.push(_):_.link_type==="wiki_link"?J.push(_):x.push(_)}let M=($,se)=>se.score-$.score;R.sort(M),D.sort(M),x.sort(M),J.sort(M);let ie=cE(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:x},{heading:"Wiki links (manual)",refs:J}],s);return{origin:m,parents:h,children:b,citations:R,similar:D,cousins:x,wikiLinks:J,bundle:ie.bundle,budgetUsed:ie.budgetUsed,budgetRemaining:Math.max(0,s-ie.budgetUsed),truncated:ie.truncated}}import{randomUUID as XE}from"node:crypto";U();ee();import{writeFileSync as fE,mkdirSync as hE,existsSync as EE}from"node:fs";import{join as Il}from"node:path";U();var Rl=80;function kl(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(`
|
|
1210
|
+
`)[0].trim();return s.length>Rl?s.slice(0,Rl)+"\u2026":s}function uE(e){return f().prepare(`SELECT s.id AS id,
|
|
1211
1211
|
sa.alias AS alias,
|
|
1212
1212
|
s.auto_title AS auto_title,
|
|
1213
1213
|
s.first_user_message AS first_user_message
|
|
1214
1214
|
FROM sessions s
|
|
1215
1215
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1216
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
1216
|
+
WHERE s.id = ?`).get(e)??null}function dE(e){let t=uE(e);return t?kl(t):e.slice(0,8)}function Al(e){if(!e)return null;let t=f(),s=t.prepare(`SELECT e.thread_id AS thread_id,
|
|
1217
1217
|
t.name AS thread_name,
|
|
1218
1218
|
e.parent_session_id AS parent_session_id,
|
|
1219
1219
|
e.added_at AS added_at
|
|
@@ -1222,7 +1222,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1222
1222
|
WHERE e.session_id = ?
|
|
1223
1223
|
AND t.archived = 0
|
|
1224
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:
|
|
1225
|
+
LIMIT 1`).get(e);if(!s)return null;let n=s.parent_session_id?{id:s.parent_session_id,title:dE(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
1226
|
s.id AS id,
|
|
1227
1227
|
sa.alias AS alias,
|
|
1228
1228
|
s.auto_title AS auto_title,
|
|
@@ -1232,33 +1232,33 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1232
1232
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1233
1233
|
WHERE e.thread_id = ?
|
|
1234
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?
|
|
1235
|
+
ORDER BY e.added_at ASC`).all(s.thread_id,...r).map(u=>({id:u.session_id,title:u.id?kl(u):u.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:c}}var xl=[/^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 xl.some(t=>t.test(e))}var pE=[/^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 Nl(e){return e?pE.some(t=>t.test(e)):!1}var mE=[/^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],gE=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],_E=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<_E)return"low_signal";for(let t of xl)if(t.test(e.auto_title))return"recursive_meta";for(let t of mE)if(t.test(e.auto_title))return"programmatic";for(let t of gE)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 Ol(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=Il(H,"titles"),bE=80,SE=60,TE=100,yE=50,ss=5,_n=15,wE=500;function jl(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=RE(t,e);if(s)return s;let n=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(n&&n.length<=bE?n:t.slice(0,SE)).trim()||null}function RE(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 kE){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 NE){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 kE=[{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)=>xE(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)=>AE(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Ll(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)=>Ll(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 AE(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 Ll(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 xE(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 NE=[{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,TE).trim()}var OE=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],LE=[/^(?: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:OE.some(s=>s.test(t))}function CE(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return LE.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(CE(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
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
|
|
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
1238
|
FROM messages
|
|
1239
1239
|
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1240
1240
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1241
1241
|
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1242
|
-
LIMIT ?`).all(e,
|
|
1242
|
+
LIMIT ?`).all(e,ss),n=t.prepare(`SELECT rowid AS rid, content_text
|
|
1243
1243
|
FROM messages
|
|
1244
1244
|
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1245
1245
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1246
1246
|
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1247
|
-
LIMIT ?`).all(e,
|
|
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,wE);return a&&h===ss?`--- (middle of session omitted) ---
|
|
1248
1248
|
${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
|
|
1249
|
-
`),u=null;try{u=
|
|
1250
|
-
`)}var
|
|
1251
|
-
`)}async function
|
|
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=
|
|
1249
|
+
`),u=null;try{u=Al(e)}catch(m){console.error("[autoTitle] thread context resolution failed:",m),u=null}let d=[];return u&&(d.push(IE(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 IE(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 Ml(e){let t=lo(e),{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(Se(),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=jE(r.stdout);if(!o)throw new Error("claude CLI returned an empty title");return o.slice(0,yE)}function jE(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 Cl(n)}}catch{}return Cl(t)}function Cl(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}function he(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=jl(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
1253
|
SET auto_title = ?,
|
|
1254
1254
|
auto_title_source = ?,
|
|
1255
1255
|
auto_title_generated_at = ?,
|
|
1256
1256
|
auto_title_history = ?
|
|
1257
|
-
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(a),e),
|
|
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:
|
|
1257
|
+
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(a),e),PE(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:jl(t.auto_title_history)}:null}function Dl(){let t=f().prepare(`SELECT id, first_user_message
|
|
1259
1259
|
FROM sessions
|
|
1260
1260
|
WHERE auto_title IS NULL
|
|
1261
|
-
AND first_user_message IS NOT NULL`).all(),s=0;for(let n of t){let r=
|
|
1261
|
+
AND first_user_message IS NOT NULL`).all(),s=0;for(let n of t){let r=Tt(n.first_user_message);r&&(he(n.id,r,"heuristic"),s+=1)}return{updated:s}}function Fl(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
1262
|
SELECT auto_title, project_id
|
|
1263
1263
|
FROM sessions
|
|
1264
1264
|
WHERE auto_title IS NOT NULL
|
|
@@ -1311,7 +1311,7 @@ ${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
|
|
|
1311
1311
|
AND content_text IS NOT NULL
|
|
1312
1312
|
AND content_text != ''
|
|
1313
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=
|
|
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&&(he(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 Pl(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
1315
|
FROM sessions s
|
|
1316
1316
|
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1317
1317
|
AND (
|
|
@@ -1326,75 +1326,75 @@ ${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
|
|
|
1326
1326
|
AND content_text IS NOT NULL
|
|
1327
1327
|
AND content_text != ''
|
|
1328
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=
|
|
1329
|
+
LIMIT 10`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),m=ME(d,u.auto_title);m&&(he(u.id,m,"heuristic"),c+=1)}return{scanned:a,updated:c}}function ME(e,t){for(let s of e){let n=DE(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 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
|
|
1330
1330
|
FROM sessions s
|
|
1331
1331
|
WHERE s.auto_title_source = 'heuristic'${s}
|
|
1332
1332
|
AND s.auto_title IS NOT NULL
|
|
1333
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=
|
|
1335
|
-
`;
|
|
1336
|
-
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}var
|
|
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=Ol(c.auto_title);u!==c.auto_title&&(he(c.id,u,"heuristic"),a+=1)}return{scanned:o,updated:a}}function DE(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 FE(){z(),EE(ao)||hE(ao,{recursive:!0})}function PE(e,t,s,n){try{FE();let r=Il(ao,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
|
|
1335
|
+
`;fE(r,o+t+`
|
|
1336
|
+
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}var UE=50;function $E(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(`
|
|
1337
1337
|
`);return`${t}
|
|
1338
|
-
${s}`}function aE(e){return ke(e)?.auto_title_source??null}async function cE(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(he(),We));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=lE(n.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,rE)}function lE(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 Sl(n)}}catch{}return Sl(t)}function Sl(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function yl(e,t={}){let s=re(e);if(!s)throw new Error(`thread not found: ${e}`);let n=oE(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&&aE(h)==="agent"){let S={sessionId:h,reason:"already-titled"};u.push(S),t.onSkipped?.(S);continue}let T;try{if(Tl)T=await Tl({sessionId:h,current:b,total:r});else{let S=iE({sessionId:h,current:b,total:r});T=await cE({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{me(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 Tl=null;var Gt=new Map,dE=300*1e3;function Yt(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 pE(e){e.cleanupTimer&&clearTimeout(e.cleanupTimer),e.cleanupTimer=setTimeout(()=>{Gt.delete(e.jobId)},dE)}function mE(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 wl(e){let t=uE(),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 Gt.set(t,n),(async()=>{await Promise.resolve();try{let r=await yl(e.threadId,{force:e.force??!1,signal:s.signal,model:e.model,onProgress:o=>{n.total=o.total,Yt(n,"progress",o)},onSkipped:o=>{Yt(n,"skipped",o)},onFailed:o=>{Yt(n,"error",o)}});n.result=r,n.status=s.signal.aborted?"cancelled":"done",n.endedAt=new Date().toISOString(),Yt(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(),Yt(n,"done",n.result)}finally{pE(n)}})(),t}async function*Rl(e,t=0){let s=Gt.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 Al(e){let t=Gt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function zr(e){let t=Gt.get(e);return t?mE(t):null}Q();import{existsSync as gE,readFileSync as _E,writeFileSync as fE}from"node:fs";import{join as hE}from"node:path";import{z as pe}from"zod";var Kr=hE(U,"terminals.json"),kl=1440*60*1e3,EE=3e4,bE=6e4,SE=pe.object({shell_pid:pe.number(),tab_name:pe.string(),cwd:pe.string().nullable().optional(),opened_at:pe.string(),last_seen_at:pe.string()}),TE=pe.object({schema:pe.string().optional(),saved_at:pe.string().optional(),terminals:pe.array(SE).max(500).default([]),sessions_by_pid:pe.record(pe.string(),pe.array(pe.string()).max(50)).optional().default({})}),xl=/^[⠀-⣿✳\s]+/,Nl=/^\d+(\.\d+){1,3}$/;function ie(e){let t=e.trim();return!!(!t||xl.test(t)||Nl.test(t))}function lt(e){let t=e.trim();if(!t||Nl.test(t))return null;let s=t.replace(xl,"").trim();return s.length>0?s:null}function Vr(e,t){if(!ie(e))return e;let s=lt(e);return s||(t&&!ie(t)?t:e)}function yE(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 Zr=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,!!gE(Kr)))try{let t=_E(Kr,"utf8"),s=JSON.parse(t),n=TE.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{J();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)]))};fE(Kr,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let s=new Date().toISOString(),n=this.entries.get(t.shell_pid),r=Vr(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=Vr(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>bE?(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=Vr(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=yE({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()-EE;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()-kl;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()-kl;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 Zr;import{execFile as vE}from"node:child_process";import{promisify as IE}from"node:util";import{existsSync as jE}from"node:fs";import{basename as ME}from"node:path";H();import{execFile as wE}from"node:child_process";import{readFile as RE}from"node:fs/promises";import{promisify as AE}from"node:util";var kE=AE(wE),Ol=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function xE(e){let t={};for(let s of Ol){let n=e[s];typeof n=="string"&&n.length>0&&(t[s]=n)}return t}function NE(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 OE(e){if(process.platform==="linux")try{let t=await RE(`/proc/${e}/environ`,"utf8");return LE(t)}catch{return{}}try{let{stdout:t}=await kE("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return CE(t)}catch{return{}}}function LE(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 CE(e){let t={},s=new Set(Ol),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 Ll(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await OE(e),s=xE(t);return NE(s)}catch{return null}}var on=IE(vE),rn;function DE(){if(rn!==void 0)return rn;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(jE(t))return rn=t,t;return rn=null,null}var FE=3,PE=3600*1e3,zt=new Map;function Il(){let e=v.all(),t=e.map(s=>s.shell_pid).sort((s,n)=>s-n).join(",");return`${e.length}:${t}`}function UE(e){let t=zt.get(e);return t?Date.now()-t.lastAt>PE?(zt.delete(e),!1):t.refusals<FE?!1:t.fingerprint===Il():!1}function Cl(e){let t=Il(),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 $E(e){zt.delete(e)}async function BE(e){let t=DE();if(!t)return null;try{let{stdout:s}=await on(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
|
|
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
|
|
1338
|
+
${s}`}function HE(e){return Le(e)?.auto_title_source??null}async function WE(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(Se(),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=qE(n.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,UE)}function qE(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 $l(n)}}catch{}return $l(t)}function $l(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function Hl(e,t={}){let s=oe(e);if(!s)throw new Error(`thread not found: ${e}`);let n=$E(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&&HE(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 WE({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{he(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,JE=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)},JE)}function GE(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 Wl(e){let t=XE(),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 Hl(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*ql(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 Xl(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?GE(t):null}ee();import{existsSync as zE,readFileSync as KE,writeFileSync as VE}from"node:fs";import{join as ZE}from"node:path";import{z as fe}from"zod";var po=ZE(H,"terminals.json"),Jl=1440*60*1e3,QE=3e4,eb=6e4,tb=fe.object({shell_pid:fe.number(),tab_name:fe.string(),cwd:fe.string().nullable().optional(),opened_at:fe.string(),last_seen_at:fe.string()}),sb=fe.object({schema:fe.string().optional(),saved_at:fe.string().optional(),terminals:fe.array(tb).max(500).default([]),sessions_by_pid:fe.record(fe.string(),fe.array(fe.string()).max(50)).optional().default({})}),Yl=/^[⠀-⣿✳\s]+/,Gl=/^\d+(\.\d+){1,3}$/;function ce(e){let t=e.trim();return!!(!t||Yl.test(t)||Gl.test(t))}function yt(e){let t=e.trim();if(!t||Gl.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 nb(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,!!zE(po)))try{let t=KE(po,"utf8"),s=JSON.parse(t),n=sb.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)]))};VE(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>eb?(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=nb({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()-QE;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()-Jl;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()-Jl;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}}},I=new go;import{execFile as mb}from"node:child_process";import{promisify as gb}from"node:util";import{existsSync as _b}from"node:fs";import{basename as fb}from"node:path";U();import{execFile as rb}from"node:child_process";import{readFile as ob}from"node:fs/promises";import{promisify as ib}from"node:util";var ab=ib(rb),zl=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function cb(e){let t={};for(let s of zl){let n=e[s];typeof n=="string"&&n.length>0&&(t[s]=n)}return t}function lb(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 ub(e){if(process.platform==="linux")try{let t=await ob(`/proc/${e}/environ`,"utf8");return db(t)}catch{return{}}try{let{stdout:t}=await ab("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return pb(t)}catch{return{}}}function db(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 pb(e){let t={},s=new Set(zl),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 Kl(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await ub(e),s=cb(t);return lb(s)}catch{return null}}var En=gb(mb),hn;function hb(){if(hn!==void 0)return hn;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(_b(t))return hn=t,t;return hn=null,null}var Eb=3,bb=3600*1e3,os=new Map;function Ql(){let e=I.all(),t=e.map(s=>s.shell_pid).sort((s,n)=>s-n).join(",");return`${e.length}:${t}`}function Sb(e){let t=os.get(e);return t?Date.now()-t.lastAt>bb?(os.delete(e),!1):t.refusals<Eb?!1:t.fingerprint===Ql():!1}function Vl(e){let t=Ql(),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 Tb(e){os.delete(e)}async function yb(e){let t=hb();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 Zl(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 wb(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 Rb=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 Rb.has(s)}function wt(e){let t=e.tabName?.trim();return t&&!de(t)&&!ce(t)?t:null}function kb(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=fb(e,".jsonl");if(Oe(t)||Sb(t))return;let s=kb(t),n=await yb(e),r=n?await Zl(n):null,o=n?await Kl(n):null;o&&I.setOrigin(t,o);let a=null,c=null,u=null,d=I.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`),Vl(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=I.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 Zl(S);if(!R)break;let D=I.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=I.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`),Vl(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=I.all().filter(R=>R.shell_pid!==c&&R.cwd&&R.cwd.replace(/\/+$/,"")===S&&!de(R.tab_name)&&!ce(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=wt({tabName:h,origin:o,cwd:s?.cwd??null,gitBranch:s?.git_branch??null});if(m!=null&&!c&&I.deferSessionLink(t,m,s?.cwd??null,s?.git_branch??null),!!T)try{ye(t,T),Tb(t);let S=!!h&&!de(h)&&!ce(h)&&T===h.trim();c!=null&&I.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 Ab(){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 xb=9e4;function Nb(e){let t=(Date.now()-xb)/1e3,s=e.replace(/\/+$/,"");return f().prepare(`SELECT s.id, NULLIF(sa.alias, '') AS alias, s.started_at AS started_at
|
|
1341
1341
|
FROM sessions s
|
|
1342
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
|
|
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 Ob(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
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
|
|
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 eu(e){if(Oe(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 Ob(t.file_path);if(s.length===0)return null;let n=t.cwd.replace(/\/+$/,""),r=I.allOutputTails(),o=[];for(let[a,c]of r){let u=I.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 Lb=3e4;function tu(){let e=Date.now(),t=I.all(),s=o=>{let a=Date.parse(o.last_seen_at);return!Number.isFinite(a)||e-a>Lb},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=I.sessionsFor(o.shell_pid);if(a.length!==0){r.ghosts++;for(let c of a){let u=Oe(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&&(I.unlinkSession(c),I.linkSession(c,b.shell_pid),r.rebound++)}}}return r}function su(){let e={resolved:0,expired:0},t=I.allDeferredLinks();for(let s of t){let n=I.get(s.parent_shell_pid);if(!n||de(n.tab_name)||ce(n.tab_name))continue;let r=Oe(s.session_id);if(r&&!I.isSessionAutoLinked(s.session_id)){I.resolveDeferredLink(s.session_id);continue}let o=I.getOrigin(s.session_id),a=wt({tabName:n.tab_name,origin:o??null,cwd:s.cwd,gitBranch:s.git_branch});if(!a){I.resolveDeferredLink(s.session_id);continue}r!==a&&ye(s.session_id,a),I.linkSession(s.session_id,s.parent_shell_pid),I.resolveDeferredLink(s.session_id),e.resolved++}return e}var Cb=6e4;async function Sn(){let e=await Ab(),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=I.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 wb(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=Nb(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=Cb;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&&!I.isSessionAutoLinked(u.id)){t.skipped_manual++;continue}if(u.alias===a.target){I.linkSession(u.id,a.shellPid);continue}try{ye(u.id,a.target),I.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 nu,mkdirSync as vb,readFileSync as Ib,writeFileSync as jb,chmodSync as Mb}from"node:fs";import{homedir as Db}from"node:os";import{join as ru}from"node:path";import{z as He}from"zod";function ou(){return process.env.RECALL_HOME??ru(Db(),".recall")}function Fb(){let e=ou();nu(e)||vb(e,{recursive:!0})}function iu(){return ru(ou(),"config.json")}var yn=He.object({enabled:He.boolean().default(!1),backend:He.enum(["api","mcp"]).default("api"),apiKey:He.string().optional(),model:He.string().default("claude-opus-4-7"),maxTagsPerSession:He.number().int().min(1).max(10).default(4),minTagsPerSession:He.number().int().min(1).max(10).default(2),autopilot:He.boolean().default(!1)}),Tn={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function au(){let e=iu();if(!nu(e))return{};try{return JSON.parse(Ib(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Fe(){let e=au().autoTag;if(!e)return{...Tn};let t=yn.safeParse({...Tn,...e});return t.success?t.data:{...Tn}}function cu(e){Fb();let t=au(),s=yn.parse({...Tn,...t.autoTag??{},...e}),n={...t,autoTag:s},r=iu();jb(r,JSON.stringify(n,null,2));try{Mb(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 lu,mkdirSync as Pb,readFileSync as Ub,writeFileSync as $b}from"node:fs";import{homedir as Bb}from"node:os";import{join as uu}from"node:path";import{z as fo}from"zod";function du(){return process.env.RECALL_HOME??uu(Bb(),".recall")}function Hb(){let e=du();lu(e)||Pb(e,{recursive:!0})}function pu(){return uu(du(),"config.json")}var Rn=fo.object({heuristicEnabled:fo.boolean().default(!0),agentEnabled:fo.boolean().default(!1)}),wn={heuristicEnabled:!0,agentEnabled:!1};function mu(){let e=pu();if(!lu(e))return{};try{return JSON.parse(Ub(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function it(){let e=mu().autoTitle;if(!e)return{...wn};let t=Rn.safeParse({...wn,...e});return t.success?t.data:{...wn}}function gu(e){Hb();let t=mu(),s=Rn.parse({...wn,...t.autoTitle??{},...e}),n={...t,autoTitle:s};return $b(pu(),JSON.stringify(n,null,2)),s}U();var kn="claude-haiku-4-5-20251001",_u=80,Wb=2e3,Rt=class extends Error{sessionId;constructor(t){super(`no neighborhood context available for session ${t}`),this.name="NoContextAvailableError",this.sessionId=t}},fu=null;async function qb(e,t){if(fu)return fu(e,t);let{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(Se(),tt));return n()?s(e,[],{model:t}):{success:!1,stdout:"",stderr:"claude CLI not found on PATH",exitCode:null}}function Xb(e){let s=f().prepare(`SELECT s.id,
|
|
1346
1346
|
s.auto_title,
|
|
1347
1347
|
s.auto_title_source,
|
|
1348
1348
|
CASE WHEN sa.alias IS NOT NULL AND sa.alias != ''
|
|
1349
1349
|
THEN 1 ELSE 0 END AS has_alias_int
|
|
1350
1350
|
FROM sessions s
|
|
1351
1351
|
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
|
|
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 Jb(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 Yb(e,t){if(e.has_alias)return{eligible:!1,reason:"manual_alias"};let s=ts({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 Gb(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(`
|
|
1353
|
+
`)}function zb(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 hu(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:hu(r)}function hu(e){let t=e.title;if(typeof t!="string")return null;let s=Kb(t).trim();if(!s)return null;let n=e.evidence,r=typeof n=="string"?n.trim():"";return{title:s,evidence:r}}function Kb(e){return e.replace(/^["'`]+|["'`]+$/g,"")}function Vb(e){let t=e.replace(/\s+/g," ").trim().replace(/[.!?]+$/g,"").trim();return t.length<=_u?t:t.slice(0,_u)}function Zb(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 ho(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=Xb(e);if(!s)throw new Error(`session not found: ${e}`);let n=Yb(s,t.force===!0);if(!n.eligible)return Zb(s,n.reason);let r=t.budget??Wb,o=mn(e,{budget:r});if(Jb(o))throw new Rt(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=Gb(o.bundle),c=t.model??kn,u=await qb(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=zb(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=Vb(d.title);if(!m)throw new Error("regeneration produced empty title after clamp");he(e,m,"agent");let b=Le(e)?.auto_title??m;return{session_id:e,title:b,source:"agent",confidence:eS(o),evidence:d.evidence||`regenerated from neighborhood (${Qb(o)})`,written:!0}}function Qb(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 eS(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 Ge}from"hono/streaming";import{bodyLimit as Hy}from"hono/body-limit";import{z as j}from"zod";U();U();Ve();function is(e){return f().prepare("SELECT id, name FROM projects WHERE name = ? LIMIT 1").get(e)??null}U();function Eu(e){let t=f(),s=new Date().toISOString();return t.prepare(`INSERT INTO bug_signature_resolutions
|
|
1354
1354
|
(message_hash, resolved_in_session_id, fix_summary, resolved_at, unresolved_at)
|
|
1355
1355
|
VALUES (?, ?, ?, ?, NULL)
|
|
1356
1356
|
ON CONFLICT(message_hash) DO UPDATE SET
|
|
1357
1357
|
resolved_in_session_id = excluded.resolved_in_session_id,
|
|
1358
1358
|
fix_summary = excluded.fix_summary,
|
|
1359
1359
|
resolved_at = excluded.resolved_at,
|
|
1360
|
-
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,s),
|
|
1360
|
+
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,s),tS(e.messageHash)}function bu(e){f().prepare(`UPDATE bug_signature_resolutions
|
|
1361
1361
|
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
|
|
1362
|
+
WHERE message_hash = ?`).run(new Date().toISOString(),e)}function tS(e){return f().prepare("SELECT * FROM bug_signature_resolutions WHERE message_hash = ?").get(e)??null}function Eo(e){if(e.length===0)return new Map;let t=f(),s=e.map(()=>"?").join(","),n=t.prepare(`SELECT * FROM bug_signature_resolutions
|
|
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 bo(e){return!!e&&e.unresolved_at===null}U();function as(){return new Date().toISOString()}function So(){let e=f(),t=e.prepare(`SELECT id, name, description, created_at, updated_at
|
|
1364
1364
|
FROM macro_repos
|
|
1365
1365
|
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
1366
|
FROM macro_repo_members m
|
|
1367
1367
|
JOIN projects p ON p.id = m.project_id
|
|
1368
1368
|
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
|
|
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 kt(e){return So().find(s=>s.id===e)??null}function Su(){return f().prepare(`SELECT p.id, p.name
|
|
1370
1370
|
FROM projects p
|
|
1371
1371
|
LEFT JOIN macro_repo_members m ON m.project_id = p.id
|
|
1372
1372
|
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
|
|
1373
|
+
ORDER BY p.name COLLATE NOCASE`).all()}function Tu(e){let t=e.name.trim();if(!t)throw new Error("macro repo name is required");let s=f(),n=as(),r=s.prepare(`INSERT INTO macro_repos (name, description, created_at, updated_at)
|
|
1374
|
+
VALUES (?, ?, ?, ?)`).run(t,e.description??null,n,n),o=Number(r.lastInsertRowid);return kt(o)}function yu(e,t){let s=kt(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
1375
|
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(
|
|
1376
|
+
WHERE id = ?`).run(n,r,as(),e),kt(e)}function wu(e){f().prepare("DELETE FROM macro_repos WHERE id = ?").run(e)}function Ru(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)
|
|
1377
|
+
VALUES (?, ?, ?)`).run(e,t,as()),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(as(),e)}function ku(e,t){let s=f();s.prepare(`DELETE FROM macro_repo_members
|
|
1378
|
+
WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(as(),e)}U();function Au(e){let t=f(),s=new Date().toISOString(),n=t.prepare(`INSERT INTO bug_synthesis_results
|
|
1379
1379
|
(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
|
|
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 To(r)}function xu(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 To(e){let s=f().prepare("SELECT * FROM bug_synthesis_results WHERE id = ?").get(e);return s?xu(s):null}function Nu(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
1381
|
${r}
|
|
1382
1382
|
ORDER BY created_at DESC
|
|
1383
|
-
LIMIT ?`).all(...n,o).map(
|
|
1383
|
+
LIMIT ?`).all(...n,o).map(xu)}function Ou(e){let s=f().prepare(`SELECT target_id, COUNT(*) AS n
|
|
1384
1384
|
FROM bug_synthesis_results
|
|
1385
1385
|
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
|
|
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 Lu(e){f().prepare("DELETE FROM bug_synthesis_results WHERE id = ?").run(e)}import{randomBytes as sS,timingSafeEqual as nS}from"node:crypto";var rS=6e4,oS=new Set(["127.0.0.1","localhost"]),cs=new Map;function yo(){return Date.now()}function Cu(){let e=yo();for(let[t,s]of cs)(s.expiresAt<=e||s.used)&&cs.delete(t)}function Ce(e){let t=e.req.header("origin")??"";if(t)try{let r=new URL(t);if(!oS.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 wo(e){Cu();let t=sS(32).toString("hex"),s=yo()+rS;return cs.set(t,{token:t,intent:e,expiresAt:s,used:!1}),{token:t,expiresAt:s}}function Ro(e){if(Cu(),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 cs.values()){if(s.used||s.expiresAt<=yo())continue;let n;try{n=Buffer.from(s.token,"hex")}catch{continue}if(n.length===t.length&&nS(n,t))return s.used=!0,cs.delete(s.token),s.intent}return null}import{existsSync as dS,mkdirSync as Gx,readFileSync as pS,writeFileSync as zx}from"node:fs";import{homedir as mS}from"node:os";import{join as Pu}from"node:path";import{z as ko}from"zod";import{appendFileSync as iS,existsSync as vu,mkdirSync as aS,readFileSync as cS}from"node:fs";import{homedir as lS}from"node:os";import{join as Iu}from"node:path";function ju(){return process.env.RECALL_HOME??Iu(lS(),".recall")}function uS(){let e=ju();vu(e)||aS(e,{recursive:!0})}function Mu(){return Iu(ju(),"launcher-audit.log")}function ne(e){uS();let t={ts:new Date().toISOString(),...e};try{iS(Mu(),JSON.stringify(t)+`
|
|
1387
|
+
`,"utf8")}catch(s){console.error("[launcher-audit] failed to append:",s)}}function Du(e){let t=Mu();if(!vu(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=cS(t,"utf8")}catch{return{input_tokens:0,output_tokens:0,records_counted:0}}for(let c of a.split(`
|
|
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 gS=1440*60*1e3,_S=ko.object({dailyTokenBudget:ko.number().int().nonnegative().default(1e6),sessionCeiling:ko.number().int().positive().max(1e4).default(500)}),Ao={dailyTokenBudget:1e6,sessionCeiling:500};function fS(){return process.env.RECALL_HOME??Pu(mS(),".recall")}function hS(){return Pu(fS(),"config.json")}function ES(){let e=hS();if(!dS(e))return{};try{return JSON.parse(pS(e,"utf8"))}catch(t){return console.error("[launcher-budget] failed to parse config.json, using defaults:",t),{}}}function xo(){let e=ES().launcher;if(!e)return{...Ao};let t=_S.safeParse({...Ao,...e});return t.success?t.data:{...Ao}}function At(){let e=xo(),t=Du(gS),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 No(e){return{estimated_input_tokens_max:e*2e4,estimated_output_tokens_max:e*1e3}}function Oo(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 Fu={pro:45,"max-5x":225,"max-20x":900};function bS(e){let t=e.toLowerCase();return t.includes("haiku")?1:t.includes("sonnet")?5:t.includes("opus")?10:5}var SS=4e3;function Uu(e){return Math.max(1,Math.ceil(e/SS))}function We(e,t){let s=bS(t),n=e*s,r=Object.keys(Fu).map(o=>{let a=n/Fu[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 TS}from"node:crypto";var xt=new Map,ls=new Map,yS=300*1e3;function An(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 wS(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{xt.delete(e.jobId),ls.get(e.project)===e.jobId&&ls.delete(e.project)},yS),e.cleanupTimer.unref?.())}function Lo(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 $u(e){let t=is(e.project);if(!t)return{error:`project "${e.project}" not found`};let s=ls.get(t.name);if(s){let m=xt.get(s);if(m&&m.status==="running")return{jobId:s,reused:!0};ls.delete(t.name)}let n=TS(),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 xt.set(n,d),ls.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 $a({projectId:t.id,limit:o,force:a,model:r,signal:c.signal,onProgress:m=>{d.progress=m,An(d,"progress",m)},onResult:m=>{!m.ok&&!m.skipped&&An(d,"error",{session_id:m.session_id,reason:m.failed??"unknown"})}}),d.status=c.signal.aborted?"cancelled":"done",d.endedAt=new Date().toISOString(),An(d,"done",Lo(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,An(d,"done",Lo(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{wS(d)}})(),{jobId:n,reused:!1}}async function*Bu(e,t=0){let s=xt.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 Hu(e){let t=xt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Co(e){let t=xt.get(e);return t?Lo(t):null}U();import{randomUUID as RS}from"node:crypto";import{spawn as kS}from"node:child_process";import{execSync as AS}from"node:child_process";var Wu="claude-haiku-4-5-20251001",Ot=new Map,ps=new Map,xS=300*1e3;function qu(e){return`${e.scope}:${e.target_id}:${e.mode}`}function us(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 NS(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{Ot.delete(e.jobId);let t=qu(e.intent);ps.get(t)===e.jobId&&ps.delete(t)},xS),e.cleanupTimer.unref?.())}function Nt(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 Xu(){return f().prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
|
|
1389
1389
|
oi.bug_signatures
|
|
1390
1390
|
FROM session_output_index oi
|
|
1391
1391
|
JOIN sessions s ON s.id = oi.session_id
|
|
1392
1392
|
JOIN projects p ON p.id = s.project_id
|
|
1393
1393
|
WHERE oi.bug_signatures IS NOT NULL
|
|
1394
|
-
AND oi.bug_signatures != '[]'`).all()}function
|
|
1394
|
+
AND oi.bug_signatures != '[]'`).all()}function Ju(e){try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function OS(e){let t=Xu(),s=[];for(let a of t)for(let c of Ju(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 LS(e){let t=Xu().filter(o=>o.project===e),s=new Map;for(let o of t)for(let a of Ju(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
1395
|
FROM bug_signature_resolutions
|
|
1396
1396
|
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
|
|
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 CS(e){let t=e.replace(/\s+/g," ").trim();return t.length===0?"":t.slice(0,Math.min(30,t.length))}function vS(e,t){let s=f(),n=CS(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
1398
|
FROM sessions s
|
|
1399
1399
|
JOIN projects p ON p.id = s.project_id
|
|
1400
1400
|
WHERE s.id = ?`).get(o);if(!a)continue;let c=[];if(n.length>0){let u=s.prepare(`SELECT rowid FROM messages
|
|
@@ -1406,9 +1406,9 @@ ${s}`}function aE(e){return ke(e)?.auto_title_source??null}async function cE(e){
|
|
|
1406
1406
|
WHERE session_id = ?
|
|
1407
1407
|
AND is_sidechain = 0
|
|
1408
1408
|
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
|
|
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 IS="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 jS(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(`
|
|
1410
|
+
`)}function MS(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(`
|
|
1411
|
+
`)}var DS=`[TASK]
|
|
1412
1412
|
Output a Markdown synopsis with these sections:
|
|
1413
1413
|
|
|
1414
1414
|
## What this bug is
|
|
@@ -1430,7 +1430,7 @@ prefer "consider <pattern>" over "do <action>" unless the evidence
|
|
|
1430
1430
|
is overwhelming.
|
|
1431
1431
|
|
|
1432
1432
|
## Confidence
|
|
1433
|
-
One of: high / medium / low. With one sentence justifying the level.`,
|
|
1433
|
+
One of: high / medium / low. With one sentence justifying the level.`,FS=`[TASK]
|
|
1434
1434
|
Output a Markdown response with these sections:
|
|
1435
1435
|
|
|
1436
1436
|
## Most likely root cause
|
|
@@ -1447,7 +1447,7 @@ list is acceptable but explicitly say "(none found)".
|
|
|
1447
1447
|
At most 2 alternatives, each with one sentence why it is less likely.
|
|
1448
1448
|
|
|
1449
1449
|
## Confidence
|
|
1450
|
-
high / medium / low + one-sentence justification.`,
|
|
1450
|
+
high / medium / low + one-sentence justification.`,PS=`[TASK]
|
|
1451
1451
|
Output a Markdown response with these sections:
|
|
1452
1452
|
|
|
1453
1453
|
## Top 5 recurring concerns
|
|
@@ -1465,20 +1465,20 @@ Clusters that look small + high-confidence-fix. Empty list is fine.
|
|
|
1465
1465
|
Clusters that suggest deeper issues (multiple files, repeated patterns).
|
|
1466
1466
|
|
|
1467
1467
|
## Confidence
|
|
1468
|
-
high / medium / low.`;function
|
|
1469
|
-
`)}var
|
|
1468
|
+
high / medium / low.`;function US(e,t){let s;return e.scope==="cluster"?s=e.mode==="root_cause"?FS:DS:s=PS,[IS,"",t,"",s].join(`
|
|
1469
|
+
`)}var $S=null;var ds;function BS(){if(ds)return ds;try{ds=AS("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{ds="claude"}return ds}function HS(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
|
|
1470
1470
|
`);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
|
|
1471
|
+
`)}}}function WS(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=kS(BS(),s,{stdio:["ignore","pipe","pipe"]}),u=()=>{try{c.kill("SIGTERM")}catch{}};e.signal.addEventListener("abort",u,{once:!0});let m=HS(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 xn(e){if(e.scope==="cluster"){let r=OS(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=LS(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 Yu(e){let t=xn(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=qu(e.intent),n=ps.get(s);if(n){let u=Ot.get(n);if(u&&u.status==="running")return{jobId:n,reused:!0};ps.delete(s)}let r=RS(),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 Ot.set(r,c),ps.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=vS(t.cluster.member_session_ids,t.cluster.snippet);u=jS(t.cluster,b)}else if(e.intent.scope==="project"&&t.project_clusters)u=MS(e.intent.target_id,t.project_clusters,t.total_project_clusters??t.project_clusters.length);else throw new Error("inconsistent prepared context");let d=US(e.intent,u),h=await($S??WS)({prompt:d,model:e.intent.model,signal:o.signal,onPartial:(b,T)=>{c.output_markdown=T,us(c,"partial",Nt(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(),us(c,"done",Nt(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,us(c,"done",Nt(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(),us(c,"done",Nt(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{Au({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,us(c,"done",Nt(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{NS(c)}})(),{jobId:r,reused:!1}}async function*Gu(e,t=0){let s=Ot.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 zu(e){let t=Ot.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function vo(e){let t=Ot.get(e);return t?Nt(t):null}import{randomUUID as sT}from"node:crypto";U();Hs();import{randomUUID as ZS}from"node:crypto";U();var Ku=10,Vu=20;function qS(e){if(!e)return!1;let t=e.split(`
|
|
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 XS(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 Zu(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 Io(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 Zu(s)?s:null}function Qu(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
1473
|
cm.text AS text, v.embedding AS embedding
|
|
1474
1474
|
FROM chunk_meta cm
|
|
1475
1475
|
JOIN vec_chunks v ON v.rowid = cm.rowid
|
|
1476
1476
|
WHERE cm.stale = 0
|
|
1477
1477
|
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(M=>!
|
|
1479
|
-
`),u=
|
|
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(M=>!qS(M.text)),d=u.length>0?u:c,m=[];for(let M of d){let B=XS(M.embedding);B&&Zu(B)&&m.push(B)}if(m.length===0)continue;let h=Math.min(Ku,m.length),b=Math.max(0,m.length-Ku),T=m.slice(0,h),S=m.slice(b),w=Io(T),R=Io(S),D=Io(m),x=new Map;for(let M=0;M<T.length&&x.size<Vu;M++)x.set(M,T[M]);for(let M=0;M<S.length&&x.size<Vu;M++)x.set(b+M,S[M]);let J=Array.from(x.values());t.set(a,{session_id:a,full_mean:D,head_pool:w,tail_pool:R,sample_chunks:J})}return t}function JS(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 ed(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++)JS(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},Lt="claude-haiku-4-5-20251001",Ct=50,td=1500,sd=50,YS={same_workflow:.15,unrelated:-.2,unsure:0};function nd(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 GS(e,t){let s=e.replace(/\s+/g," ").trim();return s.length>t?s.slice(0,t-1)+"\u2026":s}function zS(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 KS(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=>` - ${GS(m,500)}`).join(`
|
|
1479
|
+
`),u=zS(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
1480
|
`),` 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
|
|
1481
|
+
`)}function VS(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:YS[a]}}async function od(e,t={}){if(t.signal?.aborted)return null;let s=t.model??Lt,n=KS(e),r=t.spawn;if(!r)try{let a=await Promise.resolve().then(()=>(Se(),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:VS(o.stdout)}function id(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 ad(e){return`${e.parent_id}::${e.child_id}`}async function QS(e){if(e.signal?.aborted)return null;let t,s;try{({spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(Se(),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
1482
|
- 4 to 8 words
|
|
1483
1483
|
- Title-case, no trailing punctuation
|
|
1484
1484
|
- Capture the WORKFLOW (e.g., "Update Remotion deps + lint cleanup"), not any one session
|
|
@@ -1489,12 +1489,12 @@ Sessions:
|
|
|
1489
1489
|
`+n.join(`
|
|
1490
1490
|
`)+`
|
|
1491
1491
|
|
|
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
|
|
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 jo(e){return eT(e)}function Mo(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=Qu(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=ed(c,.8),d=[];for(let m of a){let h=tT(m,n,u);h&&d.push(h)}r.set(o,d)}return{byProject:t,scannablesByProject:r}}function ld(e){return f().prepare(`SELECT COUNT(DISTINCT t.id) AS n
|
|
1493
1493
|
FROM threads t
|
|
1494
1494
|
JOIN thread_edges te ON te.thread_id = t.id
|
|
1495
1495
|
JOIN sessions s ON s.id = te.session_id
|
|
1496
1496
|
JOIN projects p ON p.id = s.project_id
|
|
1497
|
-
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function
|
|
1497
|
+
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function eT(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
1498
|
s.first_user_message, s.auto_title,
|
|
1499
1499
|
NULLIF(sa.alias, '') AS alias,
|
|
1500
1500
|
s.file_path
|
|
@@ -1502,27 +1502,27 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1502
1502
|
JOIN projects p ON p.id = s.project_id
|
|
1503
1503
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1504
1504
|
WHERE ${n}
|
|
1505
|
-
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function
|
|
1505
|
+
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function tT(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=ln(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 cd(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 ud(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??Ct,n=e.rescore.model??Lt,r=new Map(e.scannables.map(b=>[b.id,b])),o=nd(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 od(T,{model:n,signal:e.signal});S?(a.set(ad(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:id({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 dd(e){let t=f(),s=new Map(e.rows.map(d=>[d.id,d])),n=el(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=cd(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 QS({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-${ZS()}`,b=a.get(d.rootId)??cd(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
1506
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1507
1507
|
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
1508
|
(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
|
|
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 It=new Map,ms=new Map,nT=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 rT(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{It.delete(e.jobId),ms.get(e.project)===e.jobId&&ms.delete(e.project)},nT),e.cleanupTimer.unref?.())}function Nn(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 oT=500,iT=30,Do="claude-haiku-4-5-20251001",pd={"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 md(e){return pd[e]??pd[Do]}function gd(e){let t=typeof e.threshold=="number"?e.threshold:an;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let s=e.model??Do,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??Lt,c=jo({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:Ct,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}=Mo(c),m=u.get(e.project)??[],h=d.get(e.project)??[],b=r?Math.min(t,o.lo):t,T=Et(h,b),S=r?T.filter(i=>i.confidence>=t):T,w=_d(S),R=w*oT,D=w*iT,x=md(s),J=R/1e6*x.in,M=D/1e6*x.out,B=J+M,ie=We(w,s),$=ld(e.project),se=null;if(r){let i=rd(T,o),l=md(a),p=i*td,g=i*sd,_=p/1e6*l.in+g/1e6*l.out;se={enabled:!0,band:o,cap:Ct,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>Ct,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(B*1e4)/1e4,existing_auto_scan_threads:$,plan_window_estimate:ie,accuracy_caveat:n,model:s,llm_rescore:se}}function _d(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 fd(e){let t=typeof e.threshold=="number"?e.threshold:an;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??Do,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??Lt,c=ms.get(e.project);if(c){let B=It.get(c);if(B&&B.status==="running")return{jobId:c,reused:!0};ms.delete(e.project)}let u=jo({project:e.project});if(u.length===0)return{error:`no eligible sessions in project "${e.project}"`};let{byProject:d,scannablesByProject:m}=Mo(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=Et(b,T),w=r?S.filter(B=>B.confidence>=t):S;if(S.length===0||w.length===0&&!r)return{error:"no edges above threshold; nothing to apply"};let R=sT(),D=new AbortController,x=_d(w),J=new Date().toISOString(),M={jobId:R,project:e.project,threshold:t,llmNames:s,status:"running",startedAt:J,endedAt:null,events:[],waiters:new Set,controller:D,totalThreads:x,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 It.set(R,M),ms.set(e.project,R),(async()=>{await Promise.resolve();try{vt(M,"progress",{phase:"linking",edges_proposed:S.length,estimated_threads:x});let B=S;if(r){let $=await ud({proposals:S,scannables:b,applyThreshold:t,rescore:{enabled:!0,band:o,model:a},signal:D.signal,onProgress:se=>{se.phase==="rescoring"&&vt(M,"progress",{phase:"rescoring",current:se.current,total:se.total,verdict:se.verdict})}});B=$.proposals,M.rescore={enabled:!0,considered:$.considered,promoted:$.promoted,demoted:$.demoted,unsure:$.unsure,failed:$.failed,capped:$.capped}}else B=S.filter($=>$.confidence>=t);if(B.length===0){M.status=D.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),vt(M,"done",{...Nn(M),threads:[]});return}let ie=await dd({project:e.project,rows:h,edges:B,scannables:b,llmNames:s,model:n,signal:D.signal,onProgress:$=>{$.phase==="naming"&&(M.threadsNamed=$.current,M.currentThreadName=$.thread_name??null,vt(M,"progress",{phase:"naming",current:$.current,total:$.total,thread_name:$.thread_name}))}});M.threadsCreated=ie.threads.length,M.edgesWritten=B.length+ie.threads.length,M.status=D.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),vt(M,"done",{...Nn(M),threads:ie.threads})}catch(B){M.status="failed",M.endedAt=new Date().toISOString(),M.error=B instanceof Error?B.message:String(B??"unknown error"),vt(M,"done",Nn(M))}finally{rT(M)}})(),{jobId:R,reused:!1}}async function*hd(e,t=0){let s=It.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 Ed(e){let t=It.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Fo(e){let t=It.get(e);return t?Nn(t):null}Cs();import{randomUUID as aT}from"node:crypto";var On=new Map;function bd(e){let t={id:aT(),status:"pending",createdAt:new Date().toISOString(),finishedAt:null,total:e,completed:0,results:[],error:null,controller:new AbortController,listeners:new Set};return On.set(t.id,t),t}function Ln(e){return On.get(e)}function jt(e,t){for(let s of e.listeners)s(t)}function Sd(e,t){return e.listeners.add(t),()=>{e.listeners.delete(t)}}function Td(e){let t=On.get(e);return t?(t.controller.abort(),t.status="cancelled",t.finishedAt=new Date().toISOString(),jt(t,{type:"status",status:"cancelled"}),!0):!1}function yd(e){return On.delete(e)}pt();function Cn(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(`
|
|
1510
|
+
`)}function wd(e,t){return e.split(`
|
|
1511
1511
|
`).map(s=>t+s).join(`
|
|
1512
|
-
`)}
|
|
1512
|
+
`)}pt();import{z as gs}from"zod";var cT=gs.object({tags:gs.array(gs.string()).min(1),confidence:gs.number().min(0).max(1),rationale:gs.string().min(1).max(500)});function lT(e){let t=e.trim();return t.startsWith("```")?t.replace(/^```(?:json)?\s*/i,"").replace(/```$/i,"").trim():t}function vn(e,t={}){let s=t.maxTags??10,n;try{n=JSON.parse(lT(e))}catch{return{ok:!1,reason:"not valid JSON"}}let r=cT.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 uT from"@anthropic-ai/sdk";async function In(e){let n=(await new uT({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 dT(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 jn(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,!dT(a)||o===s-1)throw a;await new Promise(c=>setTimeout(c,n*Math.pow(2,o)))}throw r}async function Rd(e,t){e.status="running",jt(e,{type:"status",status:"running"});let s=dt();for(let n of t.sessions){if(e.controller.signal.aborted)break;let r=Cn({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 jn(()=>In({apiKey:t.apiKey,model:t.model,prompt:r,signal:e.controller.signal})),c=vn(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,jt(e,{type:"result",result:o}),jt(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;jt(e,{type:"done",summary:{ok:n,failed:r}})}}function kd(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();Cs();var Z={running:!1,status:"idle",processed:0,total:0,currentSessionId:null,lastError:null,lastRunAt:null,controller:null},Po=new Set;function fs(){return{status:Z.status,processed:Z.processed,total:Z.total,currentSessionId:Z.currentSessionId,lastError:Z.lastError,lastRunAt:Z.lastRunAt}}function Ad(e){return Po.add(e),()=>{Po.delete(e)}}function _s(){let e=fs();for(let t of Po)t(e)}async function Mn(){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,_s();try{let t=mt({untaggedOnly:!0,limit:200});if(Z.total=t.length,_s(),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,_s();let o=Cn({session:n,knownTags:s,minTags:r.minTagsPerSession,maxTags:r.maxTagsPerSession});try{let a=await jn(()=>In({apiKey:r.apiKey,model:r.model,prompt:o,signal:Z.controller.signal})),c=vn(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,_s(),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,_s()}}}import{execFileSync as pT}from"node:child_process";import{existsSync as xd,readFileSync as mT,writeFileSync as Nd}from"node:fs";import{homedir as gT}from"node:os";import{dirname as _T,join as fT,resolve as hT}from"node:path";import{fileURLToPath as ET}from"node:url";var Mt=fT(gT(),".claude.json"),bT=_T(ET(import.meta.url)),ST=hT(bT,"..","mcp","server.js");function $o(){if(!xd(Mt))return{};try{let e=mT(Mt,"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 Uo=new Map;function TT(e){let t=Uo.get(e);if(t!==void 0)return t;try{return pT("command",["-v",e],{stdio:"ignore"}),Uo.set(e,!0),!0}catch{return Uo.set(e,!1),!1}}function yT(){return TT("claude-recall-mcp")?{command:"claude-recall-mcp",args:[]}:{command:process.execPath,args:[ST]}}function qe(){let t=$o().mcpServers?.recall;return{configPath:Mt,configExists:xd(Mt),installed:!!(t&&typeof t.command=="string"),command:t?.command??null,args:t?.args??null}}function Od(){let e=$o(),t=e.mcpServers??{},s=yT(),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 Nd(Mt,JSON.stringify(a,null,2)),qe()}function Ld(){let e=$o(),t=e.mcpServers??{};if(!t.recall)return qe();let{recall:s,...n}=t,r={...e,mcpServers:n};return Nd(Mt,JSON.stringify(r,null,2)),qe()}import{existsSync as Cd,mkdirSync as wT,readFileSync as RT,writeFileSync as vd}from"node:fs";import{homedir as kT}from"node:os";import{join as Id}from"node:path";import{z as Xe}from"zod";function jd(){return process.env.RECALL_HOME??Id(kT(),".recall")}function Md(){let e=jd();Cd(e)||wT(e,{recursive:!0})}function Ho(){return Id(jd(),"onboarding.json")}var Dn=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)}),Bo={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:[],threadsIntroSeen:!1};function Fn(){let e=Ho();if(!Cd(e))return{...Bo};try{let t=JSON.parse(RT(e,"utf8")),s=Dn.safeParse(t);return s.success?s.data:{...Bo}}catch(t){return console.error("[onboarding-state] failed to parse onboarding.json, using defaults:",t),{...Bo}}}function Dd(e){Md();let t=Fn(),s=Dn.parse({...t,...e,completedSteps:AT([...t.completedSteps??[],...e.completedSteps??[]]),version:1});return vd(Ho(),JSON.stringify(s,null,2)),s}function Fd(){Md();let e=Fn(),t={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:e.completedSteps,threadsIntroSeen:e.threadsIntroSeen};return vd(Ho(),JSON.stringify(t,null,2)),t}function AT(e){let t=new Set,s=[];for(let n of e)t.has(n)||(t.add(n),s.push(n));return s}Se();Er();hr();U();U();import{createReadStream as xT}from"node:fs";import{createInterface as NT}from"node:readline";function Pn(e){return typeof e=="number"&&Number.isFinite(e)?e:0}function Xo(e){let t=e?.usage;if(!t||typeof t!="object")return null;let s={inputTokens:Pn(t.input_tokens),outputTokens:Pn(t.output_tokens),cacheCreateTokens:Pn(t.cache_creation_input_tokens),cacheReadTokens:Pn(t.cache_read_input_tokens)};return s.inputTokens===0&&s.outputTokens===0&&s.cacheCreateTokens===0&&s.cacheReadTokens===0?null:s}var OT=/\x1B\[[0-9;]*[a-zA-Z]/g;function Wo(e){return e.replace(OT,"")}var qo=12e3;function Pd(e,t){if(e.length<=qo)return e;let s=e.slice(0,qo),n=e.length-qo;return`${s}
|
|
1513
1513
|
|
|
1514
|
-
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function
|
|
1515
|
-
`)}return""}function
|
|
1514
|
+
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function LT(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function CT(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?LT(n.input):"",o=Pd(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${n.name}\`**
|
|
1516
1516
|
|
|
1517
1517
|
\`\`\`json
|
|
1518
1518
|
${o}
|
|
1519
|
-
\`\`\``);continue}if(n.type==="tool_result"){let r=
|
|
1519
|
+
\`\`\``);continue}if(n.type==="tool_result"){let r=Wo(CT(n));if(r){let o=Pd(r,"tool result");t.push(`**Tool result**
|
|
1520
1520
|
|
|
1521
1521
|
\`\`\`
|
|
1522
1522
|
${o}
|
|
1523
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
1524
|
|
|
1525
|
-
`),toolNames:s}}async function*
|
|
1525
|
+
`),toolNames:s}}async function*Ud(e){let t=xT(e,{encoding:"utf8"}),s=NT({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 $d(e,t,s){e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let n=e.prepare(`
|
|
1526
1526
|
INSERT INTO message_usage (
|
|
1527
1527
|
message_uuid, session_id, model,
|
|
1528
1528
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1539,7 +1539,7 @@ ${o}
|
|
|
1539
1539
|
cache_create_tokens = excluded.cache_create_tokens,
|
|
1540
1540
|
cache_read_tokens = excluded.cache_read_tokens,
|
|
1541
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
|
|
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
1543
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
1544
1544
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
1545
1545
|
COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1554,14 +1554,14 @@ ${o}
|
|
|
1554
1554
|
total_cache_create_tokens = @cc,
|
|
1555
1555
|
total_cache_read_tokens = @cr,
|
|
1556
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
|
|
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 IT=500,$n=new Set,Bd=null,Bn=!1;function Hd(){return $n.size}function jT(){return`
|
|
1558
1558
|
SELECT m.uuid, m.session_id, m.timestamp, m.raw_json
|
|
1559
1559
|
FROM messages m
|
|
1560
1560
|
LEFT JOIN message_usage mu ON mu.message_uuid = m.uuid
|
|
1561
1561
|
WHERE m.role = 'assistant' AND mu.message_uuid IS NULL
|
|
1562
1562
|
AND m.uuid NOT IN (SELECT value FROM json_each(?))
|
|
1563
1563
|
LIMIT ?
|
|
1564
|
-
`}function
|
|
1564
|
+
`}function Wd(e,t){let s=t.limit??Number.MAX_SAFE_INTEGER,n=Math.max(1,t.chunkSize??IT),r=e.prepare(jT()),o=e.prepare(`
|
|
1565
1565
|
INSERT INTO message_usage (
|
|
1566
1566
|
message_uuid, session_id, model,
|
|
1567
1567
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1571,7 +1571,7 @@ ${o}
|
|
|
1571
1571
|
@input, @output, @cc, @cr, @ts
|
|
1572
1572
|
)
|
|
1573
1573
|
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([
|
|
1574
|
+
`),a=0,c=0,u=new Set;for(;a<s;){let d=Math.min(n,s-a),m=JSON.stringify([...$n]),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{$n.add(S.uuid);continue}let R=Xo(w.message);if(!R){$n.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)Un(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 qd(e={}){return Wd(f(),e)}function Xd(e={}){return Bn?!1:(Bn=!0,queueMicrotask(()=>{try{let t=Wd(f(),e);Bd={scanned:t.scanned,inserted:t.inserted,sessionsTouched:t.sessionsTouched,finishedAt:new Date().toISOString()}}catch(t){console.error("[stats.backfill] failed:",t)}finally{Bn=!1}}),!0)}function Jd(){return Bn}function Jo(){return Bd}var MT=[[/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}]],Yd={label:"unknown",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30};function Je(e){if(!e)return Yd;for(let[t,s]of MT)if(t.test(e))return s;return Yd}function ke(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 Dt(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 Ft(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 Yo(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=ke({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 Go(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 Gd(e){let t=f(),s=t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1575
1575
|
s.message_count,
|
|
1576
1576
|
s.total_input_tokens, s.total_output_tokens,
|
|
1577
1577
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
@@ -1586,7 +1586,7 @@ ${o}
|
|
|
1586
1586
|
COUNT(*) AS n
|
|
1587
1587
|
FROM message_usage
|
|
1588
1588
|
WHERE session_id = ?
|
|
1589
|
-
GROUP BY model`).all(e),r=
|
|
1589
|
+
GROUP BY model`).all(e),r=Yo(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=ke({inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:u,...Go(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:Dt(d.cents),tokens:Ft(d.totalTokens),model:Je(s.primary_model).label}}}function zd(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
1590
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1591
1591
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1592
1592
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1595,12 +1595,12 @@ ${o}
|
|
|
1595
1595
|
FROM message_usage mu
|
|
1596
1596
|
JOIN sessions s ON s.id = mu.session_id
|
|
1597
1597
|
WHERE s.project_id = ?
|
|
1598
|
-
GROUP BY mu.model`).all(s.id),r=
|
|
1598
|
+
GROUP BY mu.model`).all(s.id),r=Yo(n),o=t.prepare(`SELECT COALESCE(SUM(total_input_tokens), 0) AS input_tokens,
|
|
1599
1599
|
COALESCE(SUM(total_output_tokens), 0) AS output_tokens,
|
|
1600
1600
|
COALESCE(SUM(total_cache_create_tokens), 0) AS cache_create_tokens,
|
|
1601
1601
|
COALESCE(SUM(total_cache_read_tokens), 0) AS cache_read_tokens,
|
|
1602
1602
|
COUNT(*) AS session_count
|
|
1603
|
-
FROM sessions WHERE project_id = ?`).get(s.id),a=
|
|
1603
|
+
FROM sessions WHERE project_id = ?`).get(s.id),a=ke({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...Go(r)},null),u=t.prepare(`SELECT s.id, sa.alias, s.started_at,
|
|
1604
1604
|
s.total_input_tokens, s.total_output_tokens,
|
|
1605
1605
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
1606
1606
|
s.primary_model
|
|
@@ -1611,7 +1611,7 @@ ${o}
|
|
|
1611
1611
|
+ COALESCE(s.total_output_tokens,0)
|
|
1612
1612
|
+ COALESCE(s.total_cache_create_tokens,0)
|
|
1613
1613
|
+ COALESCE(s.total_cache_read_tokens,0)) DESC
|
|
1614
|
-
LIMIT 10`).all(s.id).map(d=>{let m=
|
|
1614
|
+
LIMIT 10`).all(s.id).map(d=>{let m=ke({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:Dt(a.cents),tokens:Ft(a.totalTokens)}}}function Kd(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
1615
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1616
1616
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1617
1617
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1620,7 +1620,7 @@ ${o}
|
|
|
1620
1620
|
FROM message_usage mu
|
|
1621
1621
|
JOIN sessions s ON s.id = mu.session_id
|
|
1622
1622
|
${n}
|
|
1623
|
-
GROUP BY mu.model`),d=
|
|
1623
|
+
GROUP BY mu.model`),d=Yo(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=ke({inputTokens:m,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:T,...Go(d)},null),w=s?a(`SELECT
|
|
1624
1624
|
(SELECT COUNT(DISTINCT m.session_id)
|
|
1625
1625
|
FROM messages m
|
|
1626
1626
|
JOIN sessions s2 ON s2.id = m.session_id
|
|
@@ -1644,7 +1644,7 @@ ${o}
|
|
|
1644
1644
|
JOIN sessions s ON s.id = mu.session_id
|
|
1645
1645
|
${n}
|
|
1646
1646
|
GROUP BY day, mu.model
|
|
1647
|
-
ORDER BY day ASC`),D=new Map;for(let l of R){if(!l.day)continue;let p=
|
|
1647
|
+
ORDER BY day ASC`),D=new Map;for(let l of R){if(!l.day)continue;let p=ke({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 x=[...D.entries()].map(([l,p])=>({day:l,tokens:p.tokens,cents:p.cents})).sort((l,p)=>l.day.localeCompare(p.day)),M=c(`SELECT s.id, p.name AS project, sa.alias, s.started_at,
|
|
1648
1648
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1649
1649
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1650
1650
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1660,7 +1660,7 @@ ${o}
|
|
|
1660
1660
|
+ COALESCE(SUM(mu.output_tokens),0)
|
|
1661
1661
|
+ COALESCE(SUM(mu.cache_create_tokens),0)
|
|
1662
1662
|
+ COALESCE(SUM(mu.cache_read_tokens),0)) DESC
|
|
1663
|
-
LIMIT 10`).map(l=>{let p=
|
|
1663
|
+
LIMIT 10`).map(l=>{let p=ke({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}}),B=c(`SELECT p.id AS project_id,
|
|
1664
1664
|
p.name AS project,
|
|
1665
1665
|
mu.model,
|
|
1666
1666
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
@@ -1672,24 +1672,24 @@ ${o}
|
|
|
1672
1672
|
JOIN sessions s ON s.id = mu.session_id
|
|
1673
1673
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1674
1674
|
${n}
|
|
1675
|
-
GROUP BY p.id, mu.model`),
|
|
1675
|
+
GROUP BY p.id, mu.model`),ie=new Map;for(let l of B){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 $=[...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=ke({inputTokens:p,outputTokens:g,cacheCreateTokens:_,cacheReadTokens:E,byModel:l.byModel},null);return{project:l.project,sessions:l.sessionsApprox,totalTokens:y.totalTokens,cost:y}});$.sort((l,p)=>p.totalTokens-l.totalTokens);let se=$.slice(0,20),i=t.prepare(`SELECT
|
|
1676
1676
|
(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:
|
|
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:x,byModel:d,topSessions:M,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(Hd(),Math.max(0,i.assistant_messages-i.messages_with_usage))},display:{dollars:Dt(S.cents),tokens:Ft(S.totalTokens)}}}U();function hs(e){return Math.max(0,Math.min(1,e))}function zo(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
1678
|
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=
|
|
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=hs(r/10),a=n.latest?(Date.now()-new Date(n.latest).getTime())/(1e3*60*60*24):90,c=hs(1-a/90),d=t.prepare(`SELECT AVG(message_count) AS avg_msgs
|
|
1680
|
+
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,m=hs((d-2)/3),h=t.prepare(`SELECT COUNT(*) AS total,
|
|
1681
1681
|
SUM(CASE WHEN m.content_text IS NOT NULL AND m.content_text != '' THEN 1 ELSE 0 END) AS covered
|
|
1682
1682
|
FROM messages m
|
|
1683
1683
|
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=
|
|
1684
|
+
WHERE s.project_id = ?`).get(e),b=h.total>0?h.covered/h.total:.5,T=hs(b),S=t.prepare(`SELECT COUNT(DISTINCT s.id) AS total,
|
|
1685
1685
|
COUNT(DISTINCT st.session_id) AS tagged
|
|
1686
1686
|
FROM sessions s
|
|
1687
1687
|
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
|
|
1690
|
-
FROM sessions WHERE id = ?`).get(e)??null}function
|
|
1688
|
+
WHERE s.project_id = ?`).get(e),w=S.total>0?S.tagged/S.total:0,R=hs(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 Vd(){let t=f().prepare("SELECT id FROM projects ORDER BY name").all(),s=[];for(let n of t){let r=zo(n.id);r&&s.push(r)}return s}U();import{execFile as DT}from"node:child_process";import{promisify as FT}from"node:util";import{stat as PT}from"node:fs/promises";var Zd=FT(DT),Qd=1e4,UT="%H%x09%aI%x09%s";async function $T(e){try{let{stdout:t}=await Zd("git",["rev-parse","--is-inside-work-tree"],{cwd:e,timeout:Qd});return t.trim()==="true"}catch{return!1}}async function BT(e,t,s){let n=["--no-pager","log","--all","--no-color","--since",t,"--until",s,`--pretty=format:${UT}`],{stdout:r}=await Zd("git",n,{cwd:e,timeout:Qd,maxBuffer:8*1024*1024}),o=[],a=new Set;for(let c of r.split(`
|
|
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 HT(e){return f().prepare(`SELECT id, cwd, started_at, ended_at
|
|
1690
|
+
FROM sessions WHERE id = ?`).get(e)??null}function WT(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
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
|
|
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=HT(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 PT(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 $T(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await BT(t.cwd,s,n),a=WT(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 Hn(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
1693
|
NULLIF(sa.alias, '') AS alias,
|
|
1694
1694
|
p.name AS project,
|
|
1695
1695
|
s.started_at AS startedAt,
|
|
@@ -1703,20 +1703,20 @@ ${o}
|
|
|
1703
1703
|
LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
|
|
1704
1704
|
WHERE lower(sc.commit_sha) = lower(?)
|
|
1705
1705
|
OR lower(sc.commit_sha) LIKE ?
|
|
1706
|
-
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function
|
|
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
1707
|
FROM session_commits
|
|
1708
1708
|
WHERE session_id = ?
|
|
1709
|
-
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var
|
|
1710
|
-
FROM session_commits WHERE session_id = ?`).get(e),n=s?.last_at?Date.parse(s.last_at):0;if(n&&Date.now()-n<
|
|
1709
|
+
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var qT=3e4;function ep(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<qT)return}catch{}Ko(e).catch(t=>{console.error(`[git-correlator] ${e.slice(0,8)} failed:`,t)})}U();import{execFile as XT}from"node:child_process";import{promisify as JT}from"node:util";import{stat as YT}from"node:fs/promises";var GT=JT(XT),zT=60,KT=7,VT=7,ZT=5e3;function QT(){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
1711
|
WHERE (COALESCE(total_input_tokens,0)
|
|
1712
1712
|
+ COALESCE(total_output_tokens,0)
|
|
1713
1713
|
+ COALESCE(total_cache_create_tokens,0)
|
|
1714
1714
|
+ COALESCE(total_cache_read_tokens,0)) > 0
|
|
1715
|
-
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function
|
|
1715
|
+
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function Zo(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 tp(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 ey(){let e=f(),t=e.prepare(`SELECT ss.keywords
|
|
1716
1716
|
FROM session_semantic ss
|
|
1717
1717
|
JOIN sessions s ON s.id = ss.session_id
|
|
1718
1718
|
WHERE s.started_at IS NOT NULL
|
|
1719
|
-
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:
|
|
1719
|
+
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:KT});if(t.length===0)return null;let s=new Set;for(let o of t)for(let a of Zo(o.keywords))s.add(a);if(s.size===0)return null;let n=e.prepare(`SELECT ss.session_id AS session_id,
|
|
1720
1720
|
ss.summary AS summary,
|
|
1721
1721
|
ss.keywords AS keywords,
|
|
1722
1722
|
p.name AS project,
|
|
@@ -1732,7 +1732,7 @@ ${o}
|
|
|
1732
1732
|
WHERE s.started_at IS NOT NULL
|
|
1733
1733
|
AND s.message_count > 2
|
|
1734
1734
|
AND julianday('now') - julianday(s.started_at) >= @ageDays
|
|
1735
|
-
ORDER BY s.started_at ASC`).all({ageDays:
|
|
1735
|
+
ORDER BY s.started_at ASC`).all({ageDays:zT});if(n.length===0)return null;let r=null;for(let o of n){let c=Zo(o.keywords).filter(u=>s.has(u));c.length!==0&&(!r||c.length>r.overlap.length)&&(r={row:o,overlap:c})}return r?{...tp(r.row),summary:r.row.summary,keywords:Zo(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
1736
|
p.name AS project,
|
|
1737
1737
|
NULLIF(sa.alias, '') AS alias,
|
|
1738
1738
|
s.started_at AS started_at,
|
|
@@ -1751,28 +1751,28 @@ ${o}
|
|
|
1751
1751
|
AND (COALESCE(s.total_input_tokens, 0)
|
|
1752
1752
|
+ COALESCE(s.total_output_tokens, 0)
|
|
1753
1753
|
+ COALESCE(s.total_cache_create_tokens, 0)
|
|
1754
|
-
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:
|
|
1754
|
+
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:VT});if(t.length===0)return null;let s=null;for(let n of t){let r=ke({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?{...tp(s.row),totalTokens:s.totalTokens,costCents:s.cents,costDisplay:Dt(s.cents),tokensDisplay:Ft(s.totalTokens),primaryModel:s.row.primary_model,primaryModelLabel:Je(s.row.primary_model).label}:null}async function sy(e){try{if(!(await YT(e)).isDirectory())return null}catch{return null}try{let{stdout:t}=await GT("git",["rev-parse","HEAD"],{cwd:e,timeout:ZT}),s=t.trim();return/^[0-9a-f]{40}$/.test(s)?s:null}catch{return null}}async function ny(){let e=f(),t=e.prepare(`SELECT s.id AS id, s.cwd AS cwd
|
|
1755
1755
|
FROM sessions s
|
|
1756
1756
|
WHERE s.cwd IS NOT NULL AND s.started_at IS NOT NULL
|
|
1757
1757
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1758
|
-
LIMIT 1`).get();if(!t?.cwd)return null;let s=await
|
|
1758
|
+
LIMIT 1`).get();if(!t?.cwd)return null;let s=await sy(t.cwd);if(!s)return null;let n=Hn(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
1759
|
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
|
|
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 sp(){let e=QT(),t=e.semantic?Promise.resolve().then(()=>{try{return ey()}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?ny().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();U();Ve();async function np(e,t=50){let s=await or(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
1761
|
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
|
|
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 rp(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
|
|
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 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 op(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}U();Ve();U();function ip(e){return e.replace(/```json[\s\S]*?```/g,"[tool-call]").replace(/\{[\s\S]{200,}?\}/g,"[json-object]")}function oy(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=ip(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=ip(d.content_text??"");return`[${m}] ${h}`}).join(`
|
|
1764
1764
|
|
|
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
|
|
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 ap(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 oy(s)}var pp=2e3,iy=1e4,at=null,Wn=!1,mp=null,Qo=0;function gp(e){Qo=Math.max(0,Math.floor(e))}var ve=new Set,_p=-1;function fp(e){ve.add(e)}function hp(){ve.add(_p)}function cp(e){if(ve.has(_p))return!0;if(ve.size===0)return!1;let t=f().prepare("SELECT project_id FROM sessions WHERE id = ?").get(e);return t?ve.has(t.project_id):!1}var lp=10,ay=200,cy=2e3;function ly(){let e=Number(process.env.RECALL_VECTOR_HARD_CAP??"");return Number.isFinite(e)&&e>0?Math.min(cy,Math.floor(e)):ay}var uy=3,up=1e3,Es=new Map,ei=new Set;function dp(e,t,s,n){if(e.size>=up&&(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 ${up}-entry cap; evicted oldest entry`))}e instanceof Map?e.set(t,s):e.add(t)}var bs=[],dy=50;function ti(){if(bs.length<8)return null;let e=bs.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 py(){return f().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}async function my(){let e=f(),t=e.prepare("SELECT DISTINCT session_id FROM chunk_queue ORDER BY id LIMIT 1").get();if(!t)return ve.size>0&&ve.clear(),!1;if(cp(t.session_id)||ei.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=ap(s),o=Qo>0?Qo:1/0,a=Math.min(o,ly()),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+=lp){if(cp(s)){h=!0;break}let T=Math.min(c.length,b+lp),S=c.slice(b,T),w=S.map(x=>x.text),R=await ct(w);e.transaction(()=>{for(let x=0;x<S.length;x++){let J=u.run(s,JSON.stringify(S[x].messageUuids),S[x].text),M=Buffer.from(R[x].buffer,R[x].byteOffset,R[x].byteLength);d.run(BigInt(J.lastInsertRowid),M)}})(),m+=S.length,await new Promise(x=>setImmediate(x))}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),mp=new Date().toISOString(),bs.push({ts:Date.now(),chunks:m,durationMs:Date.now()-n}),bs.length>dy&&bs.shift(),Es.has(s)&&Es.delete(s)}catch(r){console.error("[vector-worker] failed for session",s,r);let o=(Es.get(s)??0)+1;dp(Es,s,o,"failureCounts"),o>=uy&&(dp(ei,s,void 0,"blacklist"),Es.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(ve.size>0)try{let r=e.prepare("SELECT project_id FROM sessions WHERE id = ?").get(s);r!==void 0&&ve.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)||ve.delete(r.project_id))}catch(r){console.error("[vector-worker] cancelledProjects cleanup failed:",r)}}return!0}async function Ep(){if(!_e().loaded)return;let e=await my();at!==null&&clearTimeout(at),at=setTimeout(()=>{Ep()},e?pp:iy)}function Ss(){if(!Wn){if(!_e().loaded){console.error("[vector-worker] cannot start: embedder not loaded");return}Wn=!0,at=setTimeout(()=>{Ep()},pp)}}function bp(){Wn=!1,at!==null&&(clearTimeout(at),at=null)}function Ie(){return{running:Wn,queueDepth:py(),lastProcessedAt:mp,blacklistedCount:ei.size}}ee();import{existsSync as yp,mkdirSync as Sp,rmSync as Tp,createWriteStream as gy,statSync as _y}from"node:fs";import{join as qn}from"node:path";import{createHash as fy}from"node:crypto";import{readFile as hy}from"node:fs/promises";var Ey="https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/",wp=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Rp(){return qn(H,"models","BAAI","bge-base-en-v1.5")}function Ye(){let e=Rp();return wp.every(t=>yp(qn(e,t.path)))}async function kp(e){let t=Rp();Sp(t,{recursive:!0}),Sp(qn(t,"onnx"),{recursive:!0});for(let s of wp){let n=qn(t,s.path),r=Ey+s.path,o=0;yp(n)&&(o=_y(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=gy(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 Tp(n),new Error(`Refusing to install: SHA-256 not pinned for ${s.path}. Update model-download.ts.`);let S=await hy(n);if(fy("sha256").update(S).digest("hex")!==s.sha256)throw Tp(n),new Error(`SHA-256 mismatch for ${s.path}`)}}U();var by=[/\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],Sy=1440*60*1e3;function Ty(e){let t=f(),s=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
1766
1766
|
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")
|
|
1767
|
+
ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let u of s)if(u.content_text&&by.some(d=>d.test(u.content_text))){n=!0;break}let r=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
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 yy(e){let t=Ty(e);return f().prepare("UPDATE sessions SET verification_status = ?, verification_computed_at = ? WHERE id = ?").run(t.status,Date.now(),e),t}function Ap(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<Sy){let n={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};return{status:s.verification_status,evidence:n,claimFound:s.verification_status!=="neutral"}}return yy(e)}import{readFileSync as wy,writeFileSync as Ry,mkdirSync as ky,chmodSync as Ay}from"node:fs";import{join as xp}from"node:path";import{homedir as Np}from"node:os";function Op(){return xp(Np(),".recall","config.json")}function Lp(){try{return JSON.parse(wy(Op(),"utf-8"))}catch{return{}}}function xy(e){let t=Op();ky(xp(Np(),".recall"),{recursive:!0}),Ry(t,JSON.stringify(e,null,2)+`
|
|
1769
|
+
`,"utf-8"),Ay(t,384)}function si(){let t=Lp().verification;return typeof t=="object"&&t!==null&&"enabled"in t?!!t.enabled:!1}function Cp(e){let t=Lp();t.verification={...typeof t.verification=="object"&&t.verification!==null?t.verification:{},enabled:e},xy(t)}var Dy=5e3,ui={scanned:0,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0},ni=0,ri=ui,oi=null;async function Fy(){return Date.now()-ni>=Dy&&!oi&&(oi=Sn().then(s=>(ri=s,ni=Date.now(),s)).catch(()=>(ni=Date.now(),ri=ui,ui)).finally(()=>{oi=null})),ri}var Py=2e3,Uy=6,Yn=new Map;function Ts(e){return e.replace(/[\\%_]/g,t=>"\\"+t)}function $y(e,t){let s=Date.now(),n=(Yn.get(e)??[]).filter(a=>s-a.ts<Py);return Yn.set(e,n),n.length<2||n[n.length-1].name===t?!1:n.slice(0,-1).some(a=>a.name===t)}function By(e,t){let s=Yn.get(e)??[];for(s.push({name:t,ts:Date.now()});s.length>Uy;)s.shift();Yn.set(e,s)}function Ip(e,t){let s=t.trim();if(!s)return 0;if(ce(s)){let o=yt(s);if(!o)return 0;s=o}if(de(s))return 0;if($y(e,s))return console.log(`[terminal] dropping rename of pid ${e} \u2192 "${s}", flap signature (competing editor sync sources)`),0;By(e,s);let n=I.sessionsFor(e),r=0;for(let o of n)try{if(Oe(o)===s)continue;ye(o,s),r++}catch{}return r>0&&console.log(`[terminal] rename of pid ${e} \u2192 "${s}" propagated to ${r} session(s)`),r}var jp=(()=>{try{let e=Gn(pi(mi(import.meta.url)),"..","..","package.json");return JSON.parse(li(e,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})(),ii=!1,ai=!1,ci=!1,Wy=pi(mi(import.meta.url)),di=Gn(Wy,"..","web"),Fp=Gn(di,"index.html"),qy=Ly(Fp);function Mp(){return f().prepare(`SELECT
|
|
1770
1770
|
(SELECT COUNT(*) FROM projects) AS projects,
|
|
1771
1771
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
1772
1772
|
(SELECT COUNT(*) FROM messages) AS messages,
|
|
1773
1773
|
(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
|
|
1774
|
+
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var Xy=/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i,Jy=/^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 Xn=new Map,Pp=0,Dp=0,Up=null,Yy=6e4;function Gy(){Pp+=1;let e=Date.now();e-Dp<Yy||(Dp=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 zy(){Up=new Date().toISOString()}function Ky(){let e=ic();return{silentTerminalRejections:Pp,lastTerminalSyncAt:Up,autoExtract:{circuitBroken:e.broken,brokenAt:e.brokenAt,reason:e.reason,consecutiveZeroTokenRuns:e.consecutiveZeroTokenRuns}}}function Jn(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 Vy(e){let t=new Ny;if(t.use("*",Hy({maxSize:1*1024*1024})),t.use("*",async(i,l)=>{let p=i.req.raw.headers.get("host")??"";if(!Xy.test(p))return i.text("Forbidden: invalid Host header",403);let g=i.req.raw.headers.get("origin");if(g&&!Jy.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{_=jy(Buffer.from(g,"utf8"),i)}catch{_=!1}return _?p():(l.req.path.startsWith("/api/terminal/")&&Gy(),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:jp,uptimeSeconds:Math.round(process.uptime()),pipeline:Ky()})),t.get("/api/stats",i=>i.json(Mp())),t.get("/api/stats/session/:id",i=>{let l=Gd(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=zd(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(Kd(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 _=Xd({limit:p});return i.json({mode:"background",started:_,alreadyRunning:!_&&Jd(),limit:p,lastRun:Jo()})}let g=qd({limit:p});return i.json({mode:"sync",started:!1,alreadyRunning:!1,limit:p,result:g,lastRun:Jo()})}),t.get("/api/stats/health",i=>i.json(Vd())),t.get("/api/stats/health/:projectId",i=>{let l=Number(i.req.param("projectId")),p=zo(l);return p?i.json(p):i.json({error:"project not found"},404)}),t.get("/api/config/verification",i=>i.json({enabled:si()})),t.put("/api/config/verification",async i=>{let l=await i.req.json();return typeof l.enabled=="boolean"&&Cp(l.enabled),i.json({enabled:si()})}),t.get("/api/sessions/:id/verification",i=>{let l=i.req.param("id"),p=Ap(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
|
|
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 N of k){let A=N.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=Vo(l);if(p.length>0||i.req.query("refresh")!=="1")return i.json({commits:p});let g=await Ko(l);return i.json({commits:Vo(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:Hn(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=Pt(),y=g&&_.tier==="pro"&&E?E.license_jwt:null,k=(()=>{try{let L=Gn(pi(mi(import.meta.url)),"..","..","package.json");return JSON.parse(li(L,"utf8")).version}catch{return"unknown"}})(),N=l,A={score:N.score,comment:N.comment??null,surface:"web",version:typeof N.version=="string"?N.version:k,os:typeof N.os=="string"?N.os:process.platform,trigger_kind:typeof N.trigger_kind=="string"?N.trigger_kind:"manual",license_jwt:y};try{let L=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(A)}),C=await L.json().catch(()=>({}));return i.json(C,L.status)}catch(L){let C=L instanceof Error?L.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 sp())}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:So(),orphan_projects:Su()})),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=Nu({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=Ou(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=To(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)?(Lu(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let s=j.object({name:j.string().min(1).max(100),description:j.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=Tu({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=j.object({name:j.string().min(1).max(100).optional(),description:j.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 _=yu(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=j.object({project_id:j.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 Ru(l,g.data.project_id),i.json({macro_repo:kt(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):(ku(l,p),i.json({macro_repo:kt(l)}))}),t.get("/api/projects",i=>{let l=f(),p=i.req.query("system")==="1"||i.req.query("system")==="true",g=Jn("s",p),_=l.prepare(`SELECT p.id, p.name, p.decoded_path,
|
|
1776
1776
|
COUNT(CASE WHEN s.id IS NOT NULL${g} THEN 1 END) AS session_count,
|
|
1777
1777
|
COALESCE(SUM(CASE WHEN s.id IS NOT NULL${g} THEN s.message_count ELSE 0 END), 0) AS message_count,
|
|
1778
1778
|
MAX(COALESCE(s.ended_at, s.started_at)) AS latest
|
|
@@ -1780,7 +1780,7 @@ ${o}
|
|
|
1780
1780
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1781
1781
|
GROUP BY p.id
|
|
1782
1782
|
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all();return i.json(_)}),t.get("/api/graph/:project",i=>{let l=f(),p=i.req.param("project"),g=l.prepare(`SELECT id, name, decoded_path FROM projects
|
|
1783
|
-
WHERE name = ? LIMIT 1`).get(p);if(!g)return i.json({error:`project "${p}" not found`},404);let _=l.prepare(`SELECT s.id,
|
|
1783
|
+
WHERE name = ? LIMIT 1`).get(p);if(!g)return i.json({error:`project "${p}" not found`},404);let _=i.req.query("system")==="1"||i.req.query("system")==="true",E=l.prepare(`SELECT s.id,
|
|
1784
1784
|
s.auto_title,
|
|
1785
1785
|
s.auto_title_source,
|
|
1786
1786
|
s.first_user_message,
|
|
@@ -1790,12 +1790,12 @@ ${o}
|
|
|
1790
1790
|
sa.alias AS alias
|
|
1791
1791
|
FROM sessions s
|
|
1792
1792
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1793
|
-
WHERE s.project_id =
|
|
1794
|
-
ORDER BY s.started_at`).all(g.id),E
|
|
1793
|
+
WHERE s.project_id = ?${Jn("s",_)}
|
|
1794
|
+
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
1795
|
FROM thread_edges
|
|
1796
|
-
WHERE session_id IN (${
|
|
1796
|
+
WHERE session_id IN (${A})
|
|
1797
1797
|
AND (parent_session_id IS NULL
|
|
1798
|
-
OR parent_session_id IN (${
|
|
1798
|
+
OR parent_session_id IN (${A}))`).all(...y,...y)}let N=E.map(A=>{let L=A.alias??A.auto_title??A.first_user_message??A.id.slice(0,8),C=null,v=null;if(A.auto_title?.startsWith("/")){let O=A.auto_title.split(" \xB7 ");C=O[0],v=O.length>1?O.slice(1).join(" \xB7 "):null}return{id:A.id.slice(0,8),full_id:A.id,title:L,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:v}});return i.json({project:g,sessions:N,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",N=E?Number(E):void 0;if(N!==void 0&&(!Number.isFinite(N)||N<1))return i.json({error:"invalid limit"},400);try{let A=Os({sourceSessionId:l,targetSessionId:p,linkType:y,approvedOnly:k,limit:N});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 N=E?Number(E):void 0;if(N!==void 0&&(!Number.isFinite(N)||N<1))return i.json({error:"invalid limit"},400);try{let A=lt({status:y,sourceSessionId:p,targetSessionId:g,inferredBy:k,limit:N}),L=new Set;for(let O of A)L.add(O.source_session_id),L.add(O.target_session_id);let C=new Map;if(L.size>0){let O=Array.from(L),F=O.map(()=>"?").join(","),K=f().prepare(`SELECT s.id,
|
|
1799
1799
|
NULLIF(sa.alias, '') AS alias,
|
|
1800
1800
|
s.auto_title,
|
|
1801
1801
|
s.first_user_message,
|
|
@@ -1803,7 +1803,7 @@ ${o}
|
|
|
1803
1803
|
FROM sessions s
|
|
1804
1804
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1805
1805
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1806
|
-
WHERE s.id IN (${F})`).all(...
|
|
1806
|
+
WHERE s.id IN (${F})`).all(...O);for(let Y of K){let P=Y.first_user_message?Y.first_user_message.slice(0,80):null,q=Y.alias??Y.auto_title??P??Y.id.slice(0,8);C.set(Y.id,{title:q,project:Y.project})}}let v=A.map(O=>{let F=C.get(O.source_session_id),W=C.get(O.target_session_id);return{...O,source_title:F?.title??O.source_session_id.slice(0,8),source_project:F?.project??null,target_title:W?.title??O.target_session_id.slice(0,8),target_project:W?.project??null}});return i.json({suggestions:v})}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 N,A=i.req.query("edge_types");if(A){let F=A.split(",").map(W=>W.trim()).filter(Boolean);for(let W of F)if(!o.has(W))return i.json({error:`invalid edge_type: ${W}`},400);N=F}let L=i.req.query("include_wiki_links"),C=L===void 0?!0:!(L==="0"||L==="false"),v=i.req.query("include_suggestions"),O=v==="1"||v==="true";try{let F=mn(l,{budget:g,scoring:E,maxDepth:k,edgeTypes:N,includeWikiLinks:C,includeSuggestions:O});return i.json(F)}catch(F){let W=F instanceof Error?F.message:"unknown error",K=/not found/.test(W)?404:500;return i.json({error:W},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 N=_?Number(_):void 0;if(N!==void 0&&(!Number.isFinite(N)||N<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 L=qi({minOccurrenceCount:y,hasResolved:k,project:g,limit:N,offset:A});return i.json(L)}catch(L){return i.json({error:L.message},400)}}),t.get("/api/bug-patterns/setup-status",i=>{let l=f(),g=l.prepare(`SELECT p.name AS project,
|
|
1807
1807
|
COUNT(s.id) AS total_sessions,
|
|
1808
1808
|
SUM(CASE WHEN oi.session_id IS NOT NULL THEN 1 ELSE 0 END) AS extracted_sessions,
|
|
1809
1809
|
MAX(oi.extracted_at) AS last_extracted_at
|
|
@@ -1811,20 +1811,20 @@ ${o}
|
|
|
1811
1811
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1812
1812
|
LEFT JOIN session_output_index oi ON oi.session_id = s.id
|
|
1813
1813
|
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,
|
|
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=j.object({project:j.string().min(1),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),limit:j.number().int().positive().optional(),force:j.boolean().optional()});t.post("/api/extract-outputs/preflight",async i=>{let l=Ce(i);if(l)return l;let p=$r();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=xo();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=is(E.project);if(!k)return i.json({error:`project "${E.project}" not found`},404);let A=gt({projectId:k.id,limit:E.limit,force:E.force}).eligible.length,L=No(A),C=We(A,E.model),v=At(),O=L.estimated_input_tokens_max+L.estimated_output_tokens_max>v.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,...L,plan_window_estimate:C,budget:v,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:F,expiresAt:W}=wo(E);return i.json({preflight_token:F,expires_at:new Date(W).toISOString(),eligible_session_count:A,...L,plan_window_estimate:C,budget:v,would_exceed_budget:O})});let m=j.object({preflight_token:j.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 _=Ro(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=At(),k=(()=>{let C=is(_.project);return C?gt({projectId:C.id,limit:_.limit,force:_.force}):null})()?.eligible.length??0,N=No(k),A=N.estimated_input_tokens_max+N.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 L=$u({project:_.project,model:_.model,limit:_.limit,force:_.force,origin:i.req.header("origin")??null});return"error"in L?i.json({error:L.error},400):i.json({jobId:L.jobId,reused:L.reused},L.reused?409:200)}),t.get("/api/extract-outputs/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Co(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ge(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Bu(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=Co(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||(Hu(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(At()));let h=j.object({scope:j.enum(["cluster","project"]),target_id:j.string().min(1),mode:j.enum(["synopsis","priorities","root_cause"]),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()});t.post("/api/bug-patterns/synthesize/preflight",async i=>{{let W=$r();if(W>0&&W<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(W/1024**3).toFixed(2)} GB free \u2014 bug synthesis needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:W},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??Wu},E=xn(_);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=Oo({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,N=Uu(k),A=We(N,_.model),L=At(),v=y.estimated_input_tokens_max+y.estimated_output_tokens_max>L.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:F}=wo({project:_.target_id,model:_.model,limit:1,force:!1});return Xn.set(O,_),setTimeout(()=>Xn.delete(O),9e4).unref?.(),i.json({preflight_token:O,expires_at:new Date(F).toISOString(),estimated_input_tokens_max:y.estimated_input_tokens_max,estimated_output_tokens_max:y.estimated_output_tokens_max,plan_window_estimate:A,budget:L,would_exceed_budget:v,context_summary:E.context_summary})});let b=j.object({preflight_token:j.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 _=Ro(g.data.preflight_token),E=Xn.get(g.data.preflight_token)??null;if(Xn.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=At(),k=xn(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 N=Oo({scope:E.scope,mode:E.mode,member_session_count:k.context_summary.session_count,cluster_count:k.context_summary.cluster_count}),A=N.estimated_input_tokens_max+N.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 L=Yu({intent:E,origin:i.req.header("origin")??null});return"error"in L?i.json({error:L.error},400):i.json({jobId:L.jobId,reused:L.reused},L.reused?409:200)}),t.get("/api/bug-patterns/synthesize/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!vo(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ge(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Gu(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=vo(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||(zu(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
1815
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1816
1816
|
FROM session_output_index oi
|
|
1817
1817
|
JOIN sessions s ON s.id = oi.session_id
|
|
1818
1818
|
JOIN projects p ON p.id = s.project_id
|
|
1819
1819
|
WHERE ${_.join(" AND ")}
|
|
1820
1820
|
ORDER BY oi.extracted_at DESC
|
|
1821
|
-
LIMIT ?`).all(...E,p).map(
|
|
1821
|
+
LIMIT ?`).all(...E,p).map(v=>{let O=[];try{let F=JSON.parse(v.bug_signatures);Array.isArray(F)&&(O=F)}catch{O=[]}return{session_id:v.session_id,project:v.project,auto_title:v.auto_title,started_at:v.started_at,extracted_at:v.extracted_at,rawSignatures:O}}),N=k.flatMap(v=>v.rawSignatures.map(O=>O.message_hash).filter(O=>typeof O=="string")),A=Eo(N),L=k.map(v=>({session_id:v.session_id,project:v.project,auto_title:v.auto_title,started_at:v.started_at,extracted_at:v.extracted_at,signatures:v.rawSignatures.map(O=>{let F=O.message_hash?A.get(O.message_hash)??null:null;return{...O,resolved:bo(F),resolution:F}}),signature_count:v.rawSignatures.length})),C=L.reduce((v,O)=>(v.sessions_total+=1,O.signature_count>0?(v.sessions_with_findings+=1,v.total_findings+=O.signature_count):v.sessions_empty+=1,v),{sessions_total:0,sessions_with_findings:0,sessions_empty:0,total_findings:0});return i.json({sessions:L,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=Eu({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):(bu(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
1822
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1823
1823
|
FROM session_output_index oi
|
|
1824
1824
|
JOIN sessions s ON s.id = oi.session_id
|
|
1825
1825
|
JOIN projects p ON p.id = s.project_id
|
|
1826
1826
|
WHERE ${_.join(" AND ")}
|
|
1827
|
-
ORDER BY oi.extracted_at DESC`).all(...E),
|
|
1827
|
+
ORDER BY oi.extracted_at DESC`).all(...E),k=[];for(let P of y){let q=[];try{let G=JSON.parse(P.bug_signatures);Array.isArray(G)&&(q=G)}catch{continue}for(let G of q)k.push({sig:G,session_id:P.session_id,project:P.project,auto_title:P.auto_title})}let N=new Map;for(let P of k){let q=P.sig.message_hash??`nohash:${(P.sig.snippet??"").slice(0,64)}`,G=N.get(q);G?G.push(P):N.set(q,[P])}let A=Array.from(N.keys()).filter(P=>!P.startsWith("nohash:")),L=Eo(A),C=[],v=new Map,O=[];for(let[P,q]of N){let G=q[0],Q=G.sig.message_hash??null,V=Q?L.get(Q)??null:null,X=bo(V);if(!p&&X)continue;let re=Array.from(new Set(q.map(me=>me.project))).sort(),ze=Array.from(new Set(q.map(me=>me.session_id))),Me={id:Q??P,message_hash:Q,error_type:G.sig.error_type??null,snippet:(G.sig.snippet??"").slice(0,200),file:G.sig.file??null,occurrence_count:q.length,projects:re,resolved:X,fix_summary:V?.fix_summary??null,member_session_ids:ze};C.push(Me);for(let me of q)v.has(me.session_id)||v.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 F=[],W=4,K=new Map;function Y(P){let q=K.get(P)??0;return q>=W?!1:(K.set(P,q+1),!0)}for(let P=0;P<C.length;P+=1)for(let q=P+1;q<C.length;q+=1){let G=C[P],Q=C[q],V=null;G.error_type&&G.error_type!=="unknown"&&G.error_type===Q.error_type?V="same_error_type":G.file&&Q.file&&G.file===Q.file&&(V="same_file"),V&&(!Y(G.id)||!Y(Q.id)||F.push({a:G.id,b:Q.id,reason:V}))}return i.json({clusters:C,sessions:Array.from(v.values()),member_edges:O,related_edges:F,totals:{cluster_count:C.length,singleton_count:C.filter(P=>P.occurrence_count===1).length,recurring_count:C.filter(P=>P.occurrence_count>1).length,session_count:v.size,resolved_count:C.filter(P=>P.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=Xi(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=Ji(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=Yi(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 N=Os({sourceSessionId:g,targetSessionId:p,linkType:"wiki_link"});if(N.length>0)return i.json({link:N[0]});try{let A=ma({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=ga(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=Ht(l).filter(v=>v.link_type===E),k=_l(l,y);if(k.length===0)return i.json({links:[]});let N=k.map(v=>v.otherSessionId),A=N.map(()=>"?").join(","),L=p.prepare(`SELECT s.id,
|
|
1828
1828
|
NULLIF(sa.alias, '') AS alias,
|
|
1829
1829
|
s.auto_title,
|
|
1830
1830
|
s.first_user_message,
|
|
@@ -1832,9 +1832,10 @@ ${o}
|
|
|
1832
1832
|
FROM sessions s
|
|
1833
1833
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1834
1834
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1835
|
-
WHERE s.id IN (${
|
|
1835
|
+
WHERE s.id IN (${A})`).all(...N),C=new Map(L.map(v=>[v.id,v]));return i.json({links:k.map(v=>{let O=C.get(v.otherSessionId),F=O?.alias?.trim()||O?.auto_title?.trim()||(O?.first_user_message?O.first_user_message.slice(0,80):"")||v.otherSessionId.slice(0,8);return{linkId:v.linkId,otherSessionId:v.otherSessionId,direction:v.direction,updatedAt:v.updatedAt,title:F,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 _=ur(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 N=Number(k);if(!Number.isInteger(N)||N<=0){y.push({id:Number.isFinite(Number(k))?Number(k):-1,error:"invalid id"});continue}try{ur(N,g),_+=1}catch(A){let L=A.message;/already decided/.test(L)?E+=1:y.push({id:N,error:L})}}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))),N=i.req.query("cursor"),A=null;if(N)try{let K=Buffer.from(N,"base64url").toString("utf8"),Y=JSON.parse(K);typeof Y.ts=="string"&&typeof Y.id=="string"&&(A={ts:Y.ts,id:Y.id})}catch{}let L=i.req.query("system")==="1"||i.req.query("system")==="true",C={limit:k},v="s.message_count > 2"+Jn("s",L);if(p&&(v+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",C.proj=`%${Ts(p)}%`),g&&(v+=" AND s.started_at >= @since",C.since=g),_&&(v+=" 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(Y=>Qe(Y)).filter(Boolean).forEach((Y,P)=>{v+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${P})`,C[`tag_${P}`]=Y}),y){let K=Wr(y);if(K.length===0)return i.json({items:[],nextCursor:null});let Y=K.map((P,q)=>`@col_${q}`).join(",");v+=` AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id IN (${Y}))`,K.forEach((P,q)=>{C[`col_${q}`]=P})}A&&(v+=" 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
1836
|
s.message_count, s.first_user_message, s.git_branch,
|
|
1837
1837
|
s.auto_title, s.auto_title_source, s.verification_status,
|
|
1838
|
+
COALESCE(s.ended_at, s.started_at, '') AS _cursor_ts,
|
|
1838
1839
|
NULLIF(sa.alias, '') AS alias,
|
|
1839
1840
|
CASE
|
|
1840
1841
|
WHEN (sn.content IS NOT NULL AND sn.content != '')
|
|
@@ -1850,17 +1851,17 @@ ${o}
|
|
|
1850
1851
|
JOIN projects p ON p.id = s.project_id
|
|
1851
1852
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1852
1853
|
LEFT JOIN session_notes sn ON sn.session_id = s.id
|
|
1853
|
-
WHERE ${
|
|
1854
|
-
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1855
|
-
LIMIT @limit`).all(
|
|
1854
|
+
WHERE ${v}
|
|
1855
|
+
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC, s.id DESC
|
|
1856
|
+
LIMIT @limit`).all(C),F=O.map(({tags_csv:K,_cursor_ts:Y,...P})=>{let q=P.id,G=I.getOrigin(q),Q=P.alias,V=Q==null?null:I.isSessionAutoLinked(q)?"auto":"manual",X=ts({auto_title:P.auto_title,auto_title_source:P.auto_title_source??null,has_alias:Q!=null&&V==="manual"});return{...P,tags:K?K.split(","):[],origin:G?{editor:G.editor,label:G.label}:null,alias_source:V,title_quality:X}}),W=null;if(O.length===k&&O.length>0){let K=O[O.length-1],Y=JSON.stringify({ts:K._cursor_ts??"",id:K.id});W=Buffer.from(Y,"utf8").toString("base64url")}return i.json({items:F,nextCursor:W})}),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,
|
|
1856
1857
|
NULLIF(sa.alias, '') AS alias
|
|
1857
1858
|
FROM sessions s
|
|
1858
1859
|
JOIN projects p ON p.id = s.project_id
|
|
1859
1860
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1860
|
-
WHERE s.id = ?`).get(p);if(!g)return i.json({error:"not found"},404);let _=
|
|
1861
|
+
WHERE s.id = ?`).get(p);if(!g)return i.json({error:"not found"},404);let _=Xt(p),E=I.getOrigin(p),y=E?{editor:E.editor,label:E.label}:null,k=g.alias==null?null:I.isSessionAutoLinked(p)?"auto":"manual",N=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
1861
1862
|
FROM messages
|
|
1862
1863
|
WHERE session_id = ?
|
|
1863
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(p);return i.json({session:{...g,tags:_,origin:y,alias_source:
|
|
1864
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(p);return i.json({session:{...g,tags:_,origin:y,alias_source:k},messages:N})}),t.get("/api/tags",i=>i.json(dt())),t.get("/api/sessions/:id/tags",i=>i.json({tags:Xt(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(Ra(l,p))}),t.get("/api/config/auto-tag",i=>i.json(_o(Fe()))),t.put("/api/config/auto-tag",async i=>{let l=await i.req.json().catch(()=>({})),p=yn.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 _=cu(g);return _.autopilot&&_.enabled&&_.backend==="api"&&_.apiKey&&Mn(),i.json(_o(_))}),t.get("/api/onboarding",i=>{let p=f().prepare(`SELECT s.id,
|
|
1864
1865
|
p.name AS project,
|
|
1865
1866
|
s.started_at,
|
|
1866
1867
|
s.ended_at,
|
|
@@ -1872,7 +1873,7 @@ ${o}
|
|
|
1872
1873
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1873
1874
|
WHERE s.message_count > 2
|
|
1874
1875
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1875
|
-
LIMIT 1`).get();return i.json({state:An(),mostRecentSession:p??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),p=Rn.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(hd())),t.get("/api/config/mcp-install",i=>i.json({...De(),claudeCliAvailable:le()})),t.post("/api/config/mcp-install",i=>i.json({...ld(),claudeCliAvailable:le()})),t.delete("/api/config/mcp-install",i=>i.json({...ud(),claudeCliAvailable:le()}));let T=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.number().int().min(1).max(500).optional()}).default({}),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:j.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(qo)return i.json({error:"a scan is already running"},409);if(!le())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!De().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=Le(),y=g.data.model??E.model,A=f(),k=()=>A.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,x=k();qo=!0;let N;try{let I=g.data.scanId;N=await rr(_,{model:y,scanId:I});let C=k(),L=Math.max(0,C-x);return I&&Ss(I,{type:"done",result:{success:N.success,exitCode:N.exitCode,tagsAdded:L}}),i.json({success:N.success,exitCode:N.exitCode,tagsAdded:L,model:y,stdout:_e(N.stdout.slice(0,2e3)).redacted,stderrTail:_e(N.stderr.slice(-2e3)).redacted})}finally{qo=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return Ue(i,async p=>{let g=[],_={resolve:()=>{}},E=new Promise(x=>{_.resolve=x}),y=ca(l,x=>{g.push(x);let N=_.resolve;E=new Promise(I=>{_.resolve=I}),N()}),A=!1,k=setInterval(()=>{A||p.writeSSE({event:"heartbeat",data:""}).catch(()=>{A=!0})},15e3);try{for(;!A;){g.length===0&&await E;let x=g.shift();if(x&&(await p.writeSSE({event:x.type,data:JSON.stringify(x)}),x.type==="done"))break}}finally{A=!0,clearInterval(k),y()}})}),t.get("/api/prompts",i=>i.json({prompts:tr.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:le()})),t.post("/api/prompts/run",async i=>{if(!le())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!De().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(()=>({})),_=j.object({name:j.string(),args:j.record(j.string(),j.unknown()).optional(),model:j.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=aa(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let y=E.build(_.data.args??{}),A=Le(),k=_.data.model??A.model,x=await He(y,E.allowedTools,{model:k});return i.json({success:x.success,exitCode:x.exitCode,promptName:E.name,model:k,stdout:x.stdout,stderrTail:x.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(is())),t.get("/api/autopilot/events",i=>Ue(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(is())});let p=[],g=()=>{},_=new Promise(y=>g=y),E=id(y=>{p.push(y);let A=g;_=new Promise(k=>g=k),A()});try{for(;;){if(p.length===0){let A=new Promise(x=>setTimeout(()=>x("tick"),3e4));if(await Promise.race([_.then(()=>"event"),A])==="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=>(wn(),i.json({ok:!0,snapshot:is()})));let S=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=Le();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 _=et(g.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=Qu(_.length);return rd(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=En(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=En(i.req.param("id"));return l?Ue(i,async p=>{await p.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let A of l.results)await p.writeSSE({event:"result",data:JSON.stringify(A)});let g=[],_={resolve:()=>{}},E=new Promise(A=>{_.resolve=A}),y=ed(l,A=>{g.push(A);let k=_.resolve;E=new Promise(x=>{_.resolve=x}),k()});try{for(;l.status==="running"||l.status==="pending";){g.length===0&&await E;let A=g.shift();if(A&&(await p.writeSSE({event:A.type,data:JSON.stringify(A)}),A.type==="done"||A.type==="status"&&(A.status==="cancelled"||A.status==="failed")))break}}finally{y()}}):i.json({error:"scan not found"},404)});let w=j.object({selection:j.array(j.object({sessionId:j.string(),tags:j.array(j.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=En(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 _=od(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return td(l),sd(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=be(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 A=Date.parse(_.started_at),k=_.started_at,x=v.all().filter(N=>N.cwd&&N.cwd.replace(/\/+$/,"")===E&&$t({sessionStartedAt:k,terminalOpenedAt:N.opened_at??null}).allowed);if(Number.isFinite(A)&&x.length>0){let I=x.map(C=>({t:C,gap:A-Date.parse(C.opened_at??"")})).filter(C=>Number.isFinite(C.gap)).sort((C,L)=>C.gap-L.gap)[0];I&&(v.linkSession(l,I.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 Is(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:Ae(l)})}),t.get("/api/config/auto-title",i=>i.json(Ge())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),p=pn.partial().safeParse(l);return p.success?i.json(Gl(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=ke(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(!Ge().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 _l(l);return me(l,E,"agent"),i.json(ke(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=ke(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 me(l,_.title,"agent"),i.json(ke(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??mn;try{let _=await to(l,{model:g,force:p.force===!0,budget:typeof p.budget=="number"?p.budget:void 0,signal:i.req.raw.signal}),E=ke(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 dt)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 L of g){if(typeof L!="string")return i.json({error:`invalid quality_filter entry: ${L}`},400);if(!_.has(L))return i.json({error:`quality_filter must be a subset of ${[..._].join(",")}; got ${L}`},400);E.push(L)}let y=typeof l.model=="string"&&l.model.length>0?l.model:mn,A=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,k=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,N=f().prepare(`SELECT s.id,
|
|
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(Dd(p.data)):i.json({error:"invalid onboarding state",issues:p.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(Fd())),t.get("/api/config/mcp-install",i=>i.json({...qe(),claudeCliAvailable:pe()})),t.post("/api/config/mcp-install",i=>i.json({...Od(),claudeCliAvailable:pe()})),t.delete("/api/config/mcp-install",i=>i.json({...Ld(),claudeCliAvailable:pe()}));let T=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.number().int().min(1).max(500).optional()}).default({}),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:j.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 v=N(),O=Math.max(0,v-A);return C&&vs(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:be(L.stdout.slice(0,2e3)).redacted,stderrTail:be(L.stderr.slice(-2e3)).redacted})}finally{ii=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return Ge(i,async p=>{let g=[],_={resolve:()=>{}},E=new Promise(A=>{_.resolve=A}),y=xa(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(()=>({})),_=j.object({name:j.string(),args:j.record(j.string(),j.unknown()).optional(),model:j.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=Aa(_.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=>Ge(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(fs())});let p=[],g=()=>{},_=new Promise(y=>g=y),E=Ad(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=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.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=bd(_.length);return Rd(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?Ge(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=Sd(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=j.object({selection:j.array(j.object({sessionId:j.string(),tags:j.array(j.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 _=kd(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return Td(l),yd(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=ye(l,p.alias);if(p.pin===!0)I.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=I.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(v=>({t:v,gap:k-Date.parse(v.opened_at??"")})).filter(v=>Number.isFinite(v.gap)).sort((v,O)=>v.gap-O.gap)[0];C&&(I.linkSession(l,C.t.shell_pid),y=!0)}}y||I.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),I.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:Oe(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(gu(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 Ml(l);return he(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 he(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,
|
|
1876
1877
|
s.auto_title,
|
|
1877
1878
|
s.auto_title_source,
|
|
1878
1879
|
NULLIF(sa.alias, '') AS alias
|
|
@@ -1881,7 +1882,29 @@ ${o}
|
|
|
1881
1882
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1882
1883
|
WHERE p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\'
|
|
1883
1884
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1884
|
-
LIMIT @limit`).all({proj:`%${
|
|
1885
|
+
LIMIT @limit`).all({proj:`%${Ts(p)}%`,limit:k}),C=new Set(E),v=L.filter(O=>{let F=O.alias==null?null:I.isSessionAutoLinked(O.id)?"auto":"manual",W=ts({auto_title:O.auto_title,auto_title_source:O.auto_title_source??null,has_alias:O.alias!=null&&F==="manual"});return C.has(W)});return Ge(i,async O=>{let F=v.length,W=[],K=[],Y=[],P=0,q=async(Q,V)=>{P+=1;try{await O.writeSSE({id:String(P),event:Q,data:JSON.stringify(V)})}catch{}};await q("start",{total:F,model:y});let G=0;for(let Q of v){if(i.req.raw.signal.aborted)break;G+=1;try{let V=await ho(Q.id,{model:y,budget:N,signal:i.req.raw.signal});V.written?(W.push(Q.id),await q("progress",{sessionId:Q.id,title:V.title,evidence:V.evidence,confidence:V.confidence,current:G,total:F})):(K.push({sessionId:Q.id,reason:V.skipped??"unknown"}),await q("skipped",{sessionId:Q.id,reason:V.skipped??"unknown",current:G,total:F}))}catch(V){let X=V instanceof Error?V.message:String(V),re=V instanceof Rt?"no-context-available":"failed";Y.push({sessionId:Q.id,error:X}),await q("error",{sessionId:Q.id,error:X,code:re,current:G,total:F})}}await q("done",{generated:W,skipped:K,failed:Y,cancelled:i.req.raw.signal.aborted})})}),t.get("/api/sessions/:id/notes",i=>{let l=i.req.param("id"),p=Js(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=_c(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(!it().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);try{let g=await fc(l),_=hc(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(yr())),t.put("/api/semantic/config",async i=>{let l=await i.req.json().catch(()=>({})),p=js.partial().safeParse(l);return p.success?(Ms(p.data),i.json(yr())):i.json({error:"invalid semantic config",issues:p.error.issues},400)}),t.get("/api/semantic/config",i=>i.json(ae())),t.post("/api/semantic/backfill",je,async i=>{if(ci)return i.json({error:"a scan is already running"},409);if(!ae().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 ci=!0,Ps({limit:g,force:!!p.force}).catch(_=>console.error("[semantic.backfill] error:",_)).finally(()=>{ci=!1}),i.json({ok:!0,limit:g})}),t.post("/api/semantic/sessions/:id",je,async i=>{if(!ae().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 Fs(p);return i.json(g)}),t.get("/api/semantic/vector-status",i=>{let l=_e(),p=Ie(),g=Ye(),_=ti(),E=_?.chunksPerSec??0,y=E>0?Math.ceil(p.queueDepth/E):0;return i.json({embedder:l,worker:p,modelInstalled:g,throughput:{chunksPerSec:E,samples:_?.samples??0,source:_?"local-measured":"no-samples-yet"},etaSeconds:y})}),t.post("/api/semantic/install",je,async i=>{if(Ye())return i.json({ok:!0,status:"already_installed"});if(ai)return i.json({error:"a scan is already running"},409);ai=!0;try{return await kp(),await $e(),Ss(),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{ai=!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
|
+
COUNT(*) AS total,
|
|
1887
|
+
SUM(CASE WHEN s.message_count >= 3 THEN 1 ELSE 0 END) AS eligible,
|
|
1888
|
+
SUM(CASE WHEN s.message_count >= 3 AND EXISTS
|
|
1889
|
+
(SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id) THEN 1 ELSE 0 END)
|
|
1890
|
+
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),N=p.prepare(`SELECT COUNT(*) AS n FROM chunk_queue
|
|
1892
|
+
WHERE session_id NOT IN (SELECT id FROM sessions WHERE project_id = ?)`).get(g.id).n,A=ti(),L=2,C=30,v=L,O=C,F=L*C,W="fallback-baseline",K=0;if(A)v=A.sessionsPerSec,O=A.avgChunksPerSession,F=A.chunksPerSec,W="local-measured",K=A.samples;else if(Ye()){if(!_e().loaded)try{await $e()}catch{}try{let G=["[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(G);let V=Date.now()-Q;V>0&&(F=G.length*1e3/V,v=F/C,W="live-benchmark")}catch{}}let Y=G=>p.prepare(`SELECT
|
|
1893
|
+
COALESCE(SUM(CASE WHEN ec > 5 THEN 5 ELSE ec END), 0) AS total_quick,
|
|
1894
|
+
COALESCE(SUM(CASE WHEN ec > 80 THEN 80 ELSE ec END), 0) AS total_standard,
|
|
1895
|
+
COALESCE(SUM(CASE WHEN ec > 200 THEN 200 ELSE ec END), 0) AS total_full,
|
|
1896
|
+
COALESCE(SUM(ec), 0) AS total_uncapped
|
|
1897
|
+
FROM (
|
|
1898
|
+
SELECT
|
|
1899
|
+
s.id,
|
|
1900
|
+
CASE WHEN s.message_count < 4 THEN 1
|
|
1901
|
+
ELSE (s.message_count + 3) / 4
|
|
1902
|
+
END AS ec
|
|
1903
|
+
FROM sessions s
|
|
1904
|
+
WHERE s.project_id = ? AND s.message_count >= 3 ${G}
|
|
1905
|
+
)`).get(g.id),P=Y("AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)"),q=Y("");return i.json({project:g.name,total:_.total??0,eligible:E,indexed:y,pendingNew:k,pendingForce:E,modelInstalled:Ye(),modelName:"BAAI/bge-base-en-v1.5",embedderLoaded:_e().loaded,workerRunning:Ie().running,queueDepthOther:N,sessionsPerSec:v,avgChunksPerSession:O,chunksPerSec:F,throughputSource:W,throughputSamples:K,estimatedChunksByDepth:{new:{quick:P.total_quick,standard:P.total_standard,full:P.total_full,uncapped:P.total_uncapped},force:{quick:q.total_quick,standard:q.total_standard,full:q.total_full,uncapped:q.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?fp(E):hp(),Ie().queueDepth===0&&bp(),i.json({cleared:_,project:p||null,queueDepth:Ie().queueDepth})}),t.post("/api/semantic/reindex-project",je,async i=>{if(!Ye())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)));gp(_);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
|
+
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)`,N=E.prepare(k).all(y.id);if(N.length===0)return i.json({enqueued:0,queueDepth:Ie().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 N)A.run(C.id)})(),!_e().loaded)try{await $e()}catch(C){let v=C instanceof Error?C.message:"unknown error";return i.json({error:`embedder load failed: ${v}`},500)}return Ie().running||Ss(),i.json({enqueued:N.length,queueDepth:Ie().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 rp(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(X=>X.length>0),E=_.filter(X=>X.startsWith("#")).map(X=>Qe(X)).filter(Boolean),y=_.filter(X=>!X.startsWith("#")),k=y.length>20,A=(k?y.slice(0,20):y).map(X=>`"${X.replace(/"/g,"")}"`),L=A.join(" "),C=Math.max(1,Math.min(200,Number(i.req.query("limit")??30))),v=i.req.query("system")==="1"||i.req.query("system")==="true",O=Jn("s",v);if(A.length===0&&E.length>0){let X=`
|
|
1885
1908
|
SELECT s.id AS session_id,
|
|
1886
1909
|
s.id AS message_uuid,
|
|
1887
1910
|
p.name AS project,
|
|
@@ -1893,8 +1916,8 @@ ${o}
|
|
|
1893
1916
|
FROM sessions s
|
|
1894
1917
|
JOIN projects p ON p.id = s.project_id
|
|
1895
1918
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1896
|
-
WHERE 1=1
|
|
1897
|
-
`,
|
|
1919
|
+
WHERE 1=1${O}
|
|
1920
|
+
`,re={limit:C};g&&(X+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",re.proj=`%${Ts(g)}%`),E.forEach((Me,me)=>{X+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${me})`,re[`tag_${me}`]=Me}),X+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit";let ze=l.prepare(X).all(re);return i.json({query:p,hits:ze,tags:E,truncated:k})}let F=`
|
|
1898
1921
|
SELECT m.session_id AS session_id,
|
|
1899
1922
|
m.uuid AS message_uuid,
|
|
1900
1923
|
p.name AS project,
|
|
@@ -1908,8 +1931,8 @@ ${o}
|
|
|
1908
1931
|
JOIN sessions s ON s.id = m.session_id
|
|
1909
1932
|
JOIN projects p ON p.id = s.project_id
|
|
1910
1933
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1911
|
-
WHERE messages_fts MATCH @fts
|
|
1912
|
-
`,
|
|
1934
|
+
WHERE messages_fts MATCH @fts${O}
|
|
1935
|
+
`,W={fts:L,limit:C};g&&(F+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",W.proj=`%${Ts(g)}%`),E.forEach((X,re)=>{F+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${re})`,W[`tag_${re}`]=X}),F+=" ORDER BY bm25(messages_fts) LIMIT @limit";let Y=l.prepare(F).all(W).map(X=>({...X,matched_via:"fts"}));if(i.req.query("mode")!=="semantic")return i.json({query:p,hits:Y,tags:E,truncated:k});let q=[];try{let X=`
|
|
1913
1936
|
SELECT s.id AS session_id,
|
|
1914
1937
|
s.id AS message_uuid,
|
|
1915
1938
|
p.name AS project,
|
|
@@ -1924,16 +1947,16 @@ ${o}
|
|
|
1924
1947
|
JOIN sessions s ON s.id = ss.session_id
|
|
1925
1948
|
JOIN projects p ON p.id = s.project_id
|
|
1926
1949
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1927
|
-
WHERE sessions_fts MATCH @fts
|
|
1928
|
-
`,
|
|
1950
|
+
WHERE sessions_fts MATCH @fts${O}
|
|
1951
|
+
`,re={fts:L,limit:C};g&&(X+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",re.proj=`%${Ts(g)}%`),E.forEach((ze,Me)=>{X+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${Me})`,re[`tag_${Me}`]=ze}),X+=" ORDER BY rank LIMIT @limit",q=l.prepare(X).all(re)}catch(X){console.error("[search.semantic] failed:",X)}if(_e().loaded)try{let X=await np(p,C),re=Y.map(le=>({id:String(le.session_id),data:le,lane:"bm25"})),ze=q.map(le=>({id:String(le.session_id),data:le,lane:"summary"})),Me=X.map(le=>({id:le.sessionId,data:{session_id:le.sessionId,snippet:le.text,matched_via:"vector"},lane:"vector"})),tm=op([re,ze,Me]).slice(0,C).map(le=>({...le.data,session_id:le.id,rrf_score:le.score,lanes:le.lanes,matched_via:le.lanes.length>1?"fused":le.lanes[0]}));return i.json({query:p,hits:tm,tags:E,mode:"semantic",fusion:"rrf",truncated:k})}catch(X){console.error("[search.vector] failed, falling back:",X)}let G=new Set(Y.map(X=>String(X.session_id))),Q=q.filter(X=>!G.has(String(X.session_id))).map(({rank:X,...re})=>({...re,matched_via:"semantic"})),V=[...Y,...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,
|
|
1929
1952
|
s.started_at, s.ended_at, s.message_count, s.git_branch
|
|
1930
1953
|
FROM sessions s JOIN projects p ON p.id = s.project_id
|
|
1931
|
-
WHERE s.id = ?`).get(p);if(!y)return i.json({error:"not found"},404);let
|
|
1954
|
+
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
|
|
1932
1955
|
FROM messages
|
|
1933
1956
|
WHERE session_id = ?
|
|
1934
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(p),k=qa(y,A,{mode:g,includeSidechain:_,prelude:E});return i.text(k)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:ec(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),p=Ie(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=Bt({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=nc(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=oc(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=Ht(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=ic(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:sc(l)})});let R=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:dc()})),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=jr({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=pc(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=mc(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:Hs({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let p=_c(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 gc(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=Ws();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)),_=Hs({includeDismissed:!1}).find(y=>y.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=lc(_.type,_.pattern,p);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(fc());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:Pr({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),p=re(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=Xs({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&&kc(l,p.name),p.close&&xc(l),p.reopen&&Nc(l),p.archive&&Oc(l),"folder_id"in p&&zc(l,p.folder_id??null);let g=re(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:Hr()})),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=Wc({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=Xc(l,p.name)),"parent_folder_id"in p&&(g=Jc(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 Gc(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 Yc(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=Js({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=Ac(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 _=Wt(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=Lc(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=Cc({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:Rc(l)})});let D=j.object({enabled:j.boolean(),band_lo:j.number().min(0).max(1).optional(),band_hi:j.number().min(0).max(1).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),O=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/preflight",async i=>{let l=xe(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=O.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=Gu({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 Y=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),llm_names:j.boolean().optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/apply",async i=>{let l=xe(i);if(l)return l;let p=await i.req.json().catch(()=>null),g=Y.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=Ku({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 Ue(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let A of Vu(l,g))if(E||(await _.writeSSE({id:String(A.id),event:A.kind,data:JSON.stringify(A.data)}),A.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=xe(i);return l||(Zu(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let M=j.object({project_id:j.number().int().positive(),mode:j.enum(["preflight","apply"]),window_hours:j.number().min(.5).max(168).optional(),score_threshold:j.number().min(0).max(1).optional(),use_live_pids:j.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),p=M.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let g=await $c(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 _=Bc(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=re(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=re(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 _=Le(),E=p.model??_.model,y=wl({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(!zr(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return Ue(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let A of Rl(l,g))if(E||(await _.writeSSE({id:String(A.id),event:A.kind,data:JSON.stringify(A.data)}),A.kind==="done"))break}finally{E=!0,clearInterval(y)}})}),t.get("/api/jobs/:jobId",i=>{let l=zr(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>Al(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 _=ip(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 L of v.all())p.set(L.shell_pid,L.tab_name);let g=l.terminals.filter(L=>!!L&&typeof L.shell_pid=="number"&&typeof L.tab_name=="string").map(L=>({shell_pid:L.shell_pid,tab_name:L.tab_name,cwd:L.cwd??null,opened_at:L.opened_at??new Date().toISOString()})),_=l.extension_instance_id??null,E=[],y=g.filter(L=>{let F=v.claimPidOwnership(L.shell_pid,_);return E.push({shell_pid:L.shell_pid,ownership:F}),F!=="rejected"}),A=v.sync(y),k=0;for(let L of y){let F=p.get(L.shell_pid),B=v.get(L.shell_pid)?.tab_name??L.tab_name,Z=!!B&&!ce(B)&&!ie(B)?B:L.tab_name;F!==void 0&&F!==Z&&(k+=ip(L.shell_pid,Z))}let x=E.filter(L=>L.ownership==="rejected").length;x>0&&console.log(`[terminal/sync] dropped ${x} tab_name update(s), pid(s) owned by a different extension instance`);let N=await VT(),I={resolved:0,expired:0};try{I=Dl()}catch{}let C={rebound:0,ghosts:0,ambiguous:0};try{C=Ml()}catch{}return ly(),i.json({ok:!0,count:v.size(),diff:A,propagated:k,live_sweep:N,deferred_resolved:I,rebound:C})}),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(/\/+$/,""),A=300*1e3;for(let k of v.all()){if(!k.cwd||k.cwd.replace(/\/+$/,"")!==y||ce(k.tab_name))continue;let x=Date.parse(k.opened_at),N=Date.parse(k.last_seen_at);!Number.isFinite(x)||!Number.isFinite(N)||x>E||N+A<E||g.push({shell_pid:k.shell_pid,tab_name:k.tab_name,cwd:k.cwd,opened_at:k.opened_at,last_seen_at:k.last_seen_at,reason:"time-overlap"})}g.sort((k,x)=>Date.parse(x.last_seen_at)-Date.parse(k.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(Ae(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(/\/+$/,"")===_&&!ce(y.tab_name));if(E.length===1){let y=E[0],A=$t({sessionStartedAt:g.started_at??null,terminalOpenedAt:y.opened_at??null});if(!A.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:A.reason});let k=v.getOrigin(l),x=ut({tabName:y.tab_name,origin:k??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return x?(be(l,x),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:x,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 jl(l);if(y){let k=v.get(y.shell_pid),x=$t({sessionStartedAt:g.started_at??null,terminalOpenedAt:k?.opened_at??null});if(!x.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:x.reason});let N=v.getOrigin(l),I=ut({tabName:y.tab_name,origin:N??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(I)return be(l,I),v.linkSession(l,y.shell_pid),i.json({applied:!0,alias:I,linked_pid:y.shell_pid,linked_tab_name:y.tab_name,matched_fingerprints:y.matched_fingerprints,method:"content-match"})}let A=6e4;if(g.started_at){let k=Date.parse(g.started_at);if(Number.isFinite(k)){let x=E.filter(I=>$t({sessionStartedAt:g.started_at,terminalOpenedAt:I.opened_at??null}).allowed).map(I=>({t:I,gap:k-Date.parse(I.opened_at??"")})).filter(I=>Number.isFinite(I.gap)&&I.gap>=0&&I.gap<=A);if(x.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:x.length});let N=x[0];if(N){let I=v.getOrigin(l),C=ut({tabName:N.t.tab_name,origin:I??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(C)return be(l,C),v.linkSession(l,N.t.shell_pid),i.json({applied:!0,alias:C,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),Is(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,A=g.tab_name?.trim()??"";if(A&&!ce(A)&&!ie(A))y=A;else if(A&&ie(A)){let k=lt(A);k&&!ce(k)&&(y=k)}return y?(v.unlinkSession(l),be(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),Is(l),await an(p.file_path);let g=Ae(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 k=_.prepare(`SELECT content_text FROM messages
|
|
1957
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(p),N=lc(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:bc(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),p=Be(l);if(!p)return i.json({error:"not found"},404);let g=Sc(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=yc(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=wc(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=Rc(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=kc(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:Tc(l)})});let R=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:Lc()})),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=Cc(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=jc(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 Ic(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=Nc(_.type,_.pattern,p);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(Mc());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:I.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&&Xc(l,p.name),p.close&&Jc(l),p.reopen&&Yc(l),p.archive&&Gc(l),"folder_id"in p&&gl(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=cl({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=ul(l,p.name)),"parent_folder_id"in p&&(g=dl(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 ml(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 pl(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=qc(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=zc(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=Kc({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:Wc(l)})});let D=j.object({enabled:j.boolean(),band_lo:j.number().min(0).max(1).optional(),band_hi:j.number().min(0).max(1).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),x=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),model:j.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=x.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=gd({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 J=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),llm_names:j.boolean().optional(),model:j.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=J.safeParse(p);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=fd({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 Ge(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of hd(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||(Ed(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let M=j.object({project_id:j.number().int().positive(),mode:j.enum(["preflight","apply"]),window_hours:j.number().min(.5).max(168).optional(),score_threshold:j.number().min(0).max(1).optional(),use_live_pids:j.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),p=M.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let g=await ol(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 _=il(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=Wl({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 Ge(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of ql(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=>Xl(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=I.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:I.size()});let g=I.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:I.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=I.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=I.rename(l.shell_pid,l.tab_name);if(!g)return i.json({error:"unknown shell_pid"},404);let _=Ip(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=I.remove(l.shell_pid);return i.json({ok:!0,removed:p,count:I.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):(I.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:I.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 I.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 I.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=I.claimPidOwnership(O.shell_pid,_);return E.push({shell_pid:O.shell_pid,ownership:F}),F!=="rejected"}),k=I.sync(y),N=0;for(let O of y){let F=p.get(O.shell_pid),W=I.get(O.shell_pid)?.tab_name??O.tab_name,Y=!!W&&!de(W)&&!ce(W)?W:O.tab_name;F!==void 0&&F!==Y&&(N+=Ip(O.shell_pid,Y))}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 Fy(),C={resolved:0,expired:0};try{C=su()}catch{}let v={rebound:0,ghosts:0,ambiguous:0};try{v=tu()}catch{}return zy(),i.json({ok:!0,count:I.size(),diff:k,propagated:N,live_sweep:L,deferred_resolved:C,rebound:v})}),t.get("/api/terminal/registry",i=>i.json({terminals:I.all(),count:I.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=I.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=I.all().find(_=>I.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 I.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(Oe(l))return i.json({applied:!1,reason:"has-alias"});if(I.all().some(y=>I.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=I.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=I.getOrigin(l),A=wt({tabName:y.tab_name,origin:N??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return A?(ye(l,A),I.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 eu(l);if(y){let N=I.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=I.getOrigin(l),C=wt({tabName:y.tab_name,origin:L??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(C)return ye(l,C),I.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=I.getOrigin(l),v=wt({tabName:L.t.tab_name,origin:C??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(v)return ye(l,v),I.linkSession(l,L.t.shell_pid),i.json({applied:!0,alias:v,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 I.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=I.get(p.shell_pid);if(!g)return i.json({error:"terminal not registered"},404);let _=I.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?(I.unlinkSession(l),ye(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);I.unlinkSession(l),Xs(l),await bn(p.file_path);let g=Oe(l);return i.json({ok:!0,alias:g,linked_pid:I.all().find(_=>I.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
|
|
1935
1958
|
WHERE session_id = ? AND rowid > ?
|
|
1936
|
-
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let
|
|
1959
|
+
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let A of N){let L=A.content_text??"";if(L.includes("**Tool result**")&&L.includes(g))return i.json({source:"tool-result",content:L});if(/^\s*1\t/.test(L)&&L.length>200)return i.json({source:"tool-result",content:L})}try{let A=await Iy(g),L=My();if(!A.startsWith(L+"/")&&!A.startsWith(L+"\\"))return i.json({error:"path outside allowed root"},403);let C=[".ssh",".gnupg",".gpg",".aws",".kube",".docker",".password-store"],O=A.slice(L.length+1).split("/")[0].split("\\")[0];if(C.includes(O))return i.json({error:"path inside sensitive directory"},403);let F=await Cy(A),W=2*1024*1024;if(F.size>W)return i.json({error:"file too large",size:F.size,max:W},413);let K=await vy(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
|
|
1937
1960
|
(SELECT COUNT(*) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.message_count > 2) AS sessions,
|
|
1938
1961
|
(SELECT COALESCE(SUM(s.message_count), 0) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=?) AS messages,
|
|
1939
1962
|
(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,
|
|
@@ -1941,7 +1964,7 @@ ${o}
|
|
|
1941
1964
|
JOIN projects p ON p.id = s.project_id
|
|
1942
1965
|
WHERE p.name = ? AND s.git_branch IS NOT NULL
|
|
1943
1966
|
ORDER BY s.git_branch
|
|
1944
|
-
LIMIT 20`).all(p).map(E=>E.git_branch);return i.json({...g,branches:_})});function
|
|
1967
|
+
LIMIT 20`).all(p).map(E=>E.git_branch);return i.json({...g,branches:_})});function B(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="${B(e)}" />`,p=i.indexOf("</head>");return p!==-1?i.slice(0,p)+l+i.slice(p):l+i}function $(i){let l=Mp();return i.html(cc({projects:l.projects,sessions:l.sessions,messages:l.messages,port:Number(i.req.raw.headers.get("host")?.split(":")[1]??0),version:jp}))}function se(){try{return li(Fp,"utf8")}catch{return null}}return qy?(t.use("/assets/*",vp({root:di})),t.get("/favicon.svg",vp({root:di})),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?$(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?$(i):i.html(ie(l))})):t.get("/",i=>$(i)),t}function Zy(){if(Mn(),!!it().heuristicEnabled){try{let{updated:e}=Dl();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}=Fl();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}=Pl();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}=Ul();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 $p(e,t){let s=Vy(t);return new Promise((n,r)=>{try{let o=Oy({fetch:s.fetch,port:e,hostname:"127.0.0.1"},()=>{n(o),setImmediate(()=>{try{Zy()}catch(a){console.error("[daemon] startup maintenance crashed:",a)}})})}catch(o){r(o)}})}ee();U();import{watch as Qy}from"chokidar";import{readdirSync as ew,statSync as _i}from"node:fs";import{basename as tw,join as sw}from"node:path";var nw=1500,gi=new Map;function rw(e){let t=e.split("/"),s=t.findIndex(n=>n==="projects");return s===-1||s+1>=t.length?null:t[s+1]??null}async function Hp(e){let t=0;try{t=_i(e).mtimeMs}catch{return}let s=rw(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 Ud(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=be(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?tw(a)||a:s,u=a??s.replace(/^-/,"/").replace(/-/g,"/"),d=n.prepare(`INSERT INTO projects (encoded_path, decoded_path, name)
|
|
1945
1968
|
VALUES (?, ?, ?)
|
|
1946
1969
|
ON CONFLICT(encoded_path) DO UPDATE SET decoded_path = excluded.decoded_path, name = excluded.name
|
|
1947
1970
|
RETURNING id`),m=n.prepare(`
|
|
@@ -1972,4 +1995,4 @@ ${o}
|
|
|
1972
1995
|
VALUES (@uuid, @session_id, @parent_uuid, @type, @role, @timestamp,
|
|
1973
1996
|
@is_sidechain, @content_text, @tool_names, @raw_json)
|
|
1974
1997
|
ON CONFLICT(uuid) DO NOTHING
|
|
1975
|
-
`),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()){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:R.firstUser,cwd:R.cwd,git_branch:R.branch,version:R.version,indexed_at:T}),b.run(R.sessionId);for(let D of R.entries)h.run(
|
|
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(Nl(R.firstUser)){console.log(`[watcher] skipping daemon-spawn phantom ${R.sessionId} (first message matches autonomous-spawn pattern)`);continue}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:R.firstUser,cwd:R.cwd,git_branch:R.branch,version:R.version,indexed_at:T}),b.run(R.sessionId);for(let D of R.entries)h.run(ow(D));$d(n,R.sessionId,R.entries),Un(n,R.sessionId)}})(),it().heuristicEnabled)for(let w of o.values()){let R=Tt(w.firstUser);R&&he(w.sessionId,R,"heuristic")}}function ow(e){let t=be(e.contentText).redacted,s=be(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=gi.get(e);t?.timer&&clearTimeout(t.timer);let s={timer:null};s.timer=setTimeout(()=>{gi.delete(e),Hp(e).then(async()=>{bn(e);try{let r=f().prepare("SELECT id FROM sessions WHERE file_path = ?").all(e);for(let o of r){Fa(o.id),ep(o.id);try{Oc(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)})},nw),gi.set(e,s)}function Wp(){let e=Qy(Kn,{depth:4,ignoreInitial:!0,persistent:!0,awaitWriteFinish:{stabilityThreshold:500,pollInterval:200},ignored:t=>{if(t.endsWith(".jsonl"))return!1;try{if(_i(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 iw=4;function*qp(e){let t;try{t=ew(e,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(let s of t){let n=sw(e,s.name);s.isDirectory()?yield*qp(n):s.isFile()&&s.name.endsWith(".jsonl")&&(yield n)}}async function Xp(){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 qp(Kn)){if(t.scanned+=1,d.includes("/subagents/")){t.skipped+=1;continue}let m;try{m=_i(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 Hp(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(iw,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 Jp(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 Gp(){let e=new Set([3e3,3001,4200,5e3,5173,8e3,8080,8888,9e3]),t=51370;if(!e.has(t)&&await Jp(t))return t;for(let s=0;s<50;s++){let n=49152+Math.floor(Math.random()*16383);if(!e.has(n)&&await Jp(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 uw,readFileSync as ZC,writeFileSync as Kp,unlinkSync as dw}from"node:fs";import{join as hi}from"node:path";ee();import{randomBytes as aw}from"node:crypto";import{writeFileSync as cw,readFileSync as JC,existsSync as YC}from"node:fs";import{join as lw}from"node:path";var fi=lw(H,"daemon.token");function zp(){z();let e=aw(32).toString("hex");return cw(fi,e,{encoding:"utf8",mode:384}),e}var Vp=hi(H,"daemon.pid"),Zp=hi(H,"daemon.port"),sv=hi(H,"daemon.log");function Qp(e){z(),Kp(Vp,JSON.stringify(e),{encoding:"utf8",mode:384}),Kp(Zp,String(e.port),{encoding:"utf8",mode:384})}function Ei(){for(let e of[Vp,Zp,fi])if(uw(e))try{dw(e)}catch{}}Lr();U();Ve();var mw=Math.max(1,pw().length),em=String(Math.max(2,Math.floor(mw/2)));process.env.OMP_NUM_THREADS||(process.env.OMP_NUM_THREADS=em);process.env.ORT_NUM_THREADS||(process.env.ORT_NUM_THREADS=em);var gw=360*60*1e3,_w=60*1e3,fw=1440*60*1e3,hw=300*1e3,Ew=300*1e3,bw=10*1e3,Sw=1440*60*1e3,Tw=30*1e3,yw=500,ww=1500;async function Rw(){let e=await Gp(),t=zp();(!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 $p(e,t);Qp({pid:process.pid,port:e,startedAt:new Date().toISOString()}),Sr(ae().enabled);try{zt();let x=Ga();x>0&&console.log(`[daemon] archive: migrated ${x} hot row(s) into archive.sqlite`)}catch(x){let J=x instanceof Error?x.message:String(x);console.error(`[daemon] archive migration failed: ${J}`)}let n=Wp(),r=()=>{try{tn()}catch(x){console.error("[daemon] suggestion scan failed:",x)}},o=setTimeout(r,_w),a=setInterval(r,gw),c=()=>{try{let x=I.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,hw),d=setInterval(c,fw),m=()=>{try{let x=I.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,bw),b=setInterval(m,Ew),T=()=>{Li().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,Tw),w=setInterval(T,Sw),R=ac();(async()=>{try{if(!Ye())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 M=await Ke();if(M.tier!=="pro"){console.log(`[daemon] vector-worker auto-resume skipped: ${x.n} chunk(s) pending but license tier is ${M.tier}`);return}_e().loaded||await $e(),Ie().running||(Ss(),console.log(`[daemon] vector-worker auto-resumed: ${x.n} chunk(s) pending`))}catch(x){let J=x instanceof Error?x.message:String(x);console.error(`[daemon] vector-worker auto-resume failed: ${J}`)}})();let D=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(),Ei(),process.exit(0)};process.on("SIGTERM",()=>D("SIGTERM")),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGHUP",()=>D("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)})},yw),setTimeout(()=>{Xp().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)})},ww)}Rw().catch(e=>{console.error("[daemon] fatal:",e),Ei(),process.exit(1)});
|