@clauderecallhq/cli 0.92.3 → 0.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +435 -408
- package/dist/daemon/entrypoint.js +339 -290
- package/dist/mcp-server.js +130 -111
- package/dist/web/assets/{card-a-minimal-ujNERzX7.js → card-a-minimal-Beg8-vFs.js} +1 -1
- package/dist/web/assets/{card-b-terminal-DpJ_tVpg.js → card-b-terminal-BxIeub6M.js} +1 -1
- package/dist/web/assets/{card-c-gradient-CZXVGuNd.js → card-c-gradient-Dp20rmwu.js} +1 -1
- package/dist/web/assets/{card-d-dashboard-CHKD-PnB.js → card-d-dashboard-Dix1TgTv.js} +1 -1
- package/dist/web/assets/{dist-Dr4OUd4W.js → dist-Zvhj1IwK.js} +6 -6
- package/dist/web/assets/index-Bk28z8Va.css +1 -0
- package/dist/web/assets/{index-CSk4z4xG.js → index-Cc61yjtR.js} +53 -56
- package/dist/web/assets/{patterns-BPeZb9N0.js → patterns-Dsv_eCME.js} +1 -1
- package/dist/web/assets/{stats-BSwqSiFU.js → stats-Db9NRYik.js} +1 -1
- package/dist/web/assets/{thread-D2AXyhOx.js → thread-4KmPh02n.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/index-D8_ySbyW.css +0 -1
- /package/dist/web/assets/{_brand-Bw9uSB4O.js → _brand-C2V3ysl0.js} +0 -0
- /package/dist/web/assets/{captureNode-9CVj9vYC.js → captureNode-Da1ofkVZ.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* Claude Recall (proprietary). See LICENSE for terms. */
|
|
3
|
-
var
|
|
3
|
+
var Nf=Object.defineProperty;var se=(e,t)=>()=>(e&&(t=e(e=0)),t);var St=(e,t)=>{for(var n in t)Nf(e,n,{get:t[n],enumerable:!0})};import{homedir as Wa}from"node:os";import{join as Wr,basename as cx}from"node:path";import{existsSync as Of,mkdirSync as Lf,chmodSync as Cf,readdirSync as ux,statSync as dx}from"node:fs";function K(){Of(W)||Lf(W,{recursive:!0,mode:448}),process.platform!=="win32"&&Cf(W,448)}var on,W,ze,Z=se(()=>{"use strict";on=process.env.CLAUDE_PROJECTS_DIR?process.env.CLAUDE_PROJECTS_DIR:Wr(Wa(),".claude","projects"),W=process.env.RECALL_HOME?process.env.RECALL_HOME:Wr(Wa(),".recall"),ze=Wr(W,"db.sqlite")});import{createRequire as t_}from"node:module";var n_,s_,r_,Jr,Gr,Zn,Yr=se(()=>{"use strict";{let e=process.emit.bind(process);process.emit=function(t,...n){let s=n[0];return t==="warning"&&s instanceof Error&&s.name==="ExperimentalWarning"&&/SQLite/i.test(s.message)?!1:e(t,...n)}}n_=t_(import.meta.url),s_=["node","sqlite"].join(":"),r_=n_(s_),Jr=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 n=t.length===0?this.inner.run():this.inner.run(...t);return{changes:n.changes,lastInsertRowid:n.lastInsertRowid}}iterate(...t){return t.length===0?this.inner.iterate():this.inner.iterate(...t)}},Gr=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,n={}){this.inner=new r_.DatabaseSync(t,{readOnly:n.readonly??!1,allowExtension:!0})}prepare(t){return new Jr(this.inner.prepare(t))}exec(t){this.inner.exec(t)}close(){this.inner.close()}pragma(t,n={}){if(t.includes("=")){this.inner.exec(`PRAGMA ${t}`);return}if(n.simple){let s=this.inner.prepare(`PRAGMA ${t}`).get();return s&&typeof s=="object"?Object.values(s)[0]:void 0}return this.inner.prepare(`PRAGMA ${t}`).all()}transaction(t){return((...s)=>{this.txDepth===0?this.inner.exec("BEGIN"):this.inner.exec(`SAVEPOINT sp_${this.txDepth}`),this.txDepth+=1;try{let r=t(...s);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,n){this.extensionLoadingEnabled||(this.inner.enableLoadExtension(!0),this.extensionLoadingEnabled=!0),n===void 0?this.inner.loadExtension(t):this.inner.loadExtension(t,n)}},Zn=Gr});function sc(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),n=new Set(t.map(A=>A.name)),s=[["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"],["skipped_reason","TEXT"]];for(let[A,I]of s)n.has(A)||e.exec(`ALTER TABLE sessions ADD COLUMN ${A} ${I}`);let r=e.prepare("PRAGMA table_info(collection_sessions)").all(),o=new Set(r.map(A=>A.name)),a=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[A,I]of a)o.has(A)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${A} ${I}`);let c=e.prepare("PRAGMA table_info(session_notes)").all(),u=new Set(c.map(A=>A.name)),d=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[A,I]of d)u.has(A)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${A} ${I}`);let p=e.prepare("PRAGMA table_info(threads)").all();new Set(p.map(A=>A.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 f=e.prepare("PRAGMA table_info(thread_folders)").all(),b=new Set(f.map(A=>A.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 Vg=Object.defineProperty;var re=(e,t)=>()=>(e&&(t=e(e=0)),t);var _t=(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
|
-
`);let
|
|
15
|
+
`);let T=e.prepare("PRAGMA table_info(projects)").all(),S=new Set(T.map(A=>A.name)),R=[["repo_root","TEXT"],["main_repo","TEXT"],["is_repo","INTEGER NOT NULL DEFAULT 0"],["is_worktree","INTEGER NOT NULL DEFAULT 0"]];for(let[A,I]of R)S.has(A)||e.exec(`ALTER TABLE projects ADD COLUMN ${A} ${I}`)}var nc,rc=se(()=>{"use strict";nc=`
|
|
16
16
|
PRAGMA journal_mode = WAL;
|
|
17
17
|
PRAGMA synchronous = NORMAL;
|
|
18
18
|
PRAGMA foreign_keys = ON;
|
|
@@ -228,10 +228,11 @@ END;
|
|
|
228
228
|
-- segments of messages, embedded via local bge-base-en-v1.5 (768d). The vector
|
|
229
229
|
-- data is a derived cache; if lost, it is recomputable from messages.
|
|
230
230
|
-- Tables stay empty on Free tier (no model downloaded, no worker started).
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
231
|
+
--
|
|
232
|
+
-- NOTE: the vec_chunks vec0 virtual table is intentionally NOT created here.
|
|
233
|
+
-- It lives in src/db/vecLoader.ts (ensureVecChunksTable), loaded lazily so a
|
|
234
|
+
-- lean MCP child that only does FTS/metadata never instantiates the native
|
|
235
|
+
-- vec0 module. The daemon/worker/CLI call ensureVecChunksTable at open.
|
|
235
236
|
|
|
236
237
|
CREATE TABLE IF NOT EXISTS chunk_meta (
|
|
237
238
|
rowid INTEGER PRIMARY KEY,
|
|
@@ -730,10 +731,31 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_target
|
|
|
730
731
|
ON bug_synthesis_results(scope, target_id, created_at DESC);
|
|
731
732
|
CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
732
733
|
ON bug_synthesis_results(created_at DESC);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
734
|
+
|
|
735
|
+
-- Incremental-indexing parse cursor. One row per JSONL file (a file may
|
|
736
|
+
-- hold multiple sessions, so the cursor is per-FILE, not per-session).
|
|
737
|
+
-- Lets the watcher stream only newly-appended bytes instead of reparsing
|
|
738
|
+
-- from byte 0. Derived/recomputable state \u2014 NOT subject to the three-layer
|
|
739
|
+
-- durability rule (a wrong/missing cursor just forces a full reparse).
|
|
740
|
+
-- size_bytes and line_count are diagnostic only: decideReadStrategy compares
|
|
741
|
+
-- the LIVE stat size against byte_offset, never the stored size_bytes.
|
|
742
|
+
CREATE TABLE IF NOT EXISTS file_cursor (
|
|
743
|
+
file_path TEXT PRIMARY KEY,
|
|
744
|
+
byte_offset INTEGER NOT NULL,
|
|
745
|
+
size_bytes INTEGER NOT NULL,
|
|
746
|
+
line_count INTEGER NOT NULL DEFAULT 0,
|
|
747
|
+
inode INTEGER,
|
|
748
|
+
prefix_hash TEXT,
|
|
749
|
+
mtime REAL,
|
|
750
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
751
|
+
);
|
|
752
|
+
`});function oc(e,t){let n=t.RECALL_DB_PROFILE;if(n&&o_.has(n))return n;let s=(e??"").split(/[\\/]/).pop()??"";return s==="mcp-server.js"||s==="claude-recall-mcp"?"light":s==="query-worker.js"?"worker":"full"}function ic(e){let t=[["busy_timeout",15e3],["journal_size_limit",67108864],["wal_autocheckpoint",1e3]];return e==="full"?[["cache_size",-64e3],["mmap_size",268435456],...t]:e==="worker"?[["cache_size",-16e3],["mmap_size",0],...t]:[["cache_size",-4e3],["mmap_size",0],...t]}var o_,ac=se(()=>{"use strict";o_=new Set(["light","full","worker"])});import*as lc from"sqlite-vec";function at(e){let t=e;cc.has(t)||(lc.load(e),cc.add(t))}function uc(e){e.prepare("SELECT 1 FROM sqlite_master WHERE name = 'vec_chunks'").get()||(at(e),e.exec(i_))}var cc,i_,es=se(()=>{"use strict";cc=new WeakSet;i_=`
|
|
753
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_chunks USING vec0(
|
|
754
|
+
embedding float[768] distance_metric=cosine
|
|
755
|
+
);`});function h(){if(Te)return Te;K();let e=oc(process.argv[1],process.env);a_=e,Te=new Zn(ze);for(let[t,n]of ic(e))Te.pragma(`${t} = ${n}`);Te.pragma("temp_store = MEMORY"),e!=="light"&&at(Te),Te.exec(nc),sc(Te),uc(Te);try{Te.exec("PRAGMA optimize")}catch{}return Te}var Te,a_,H=se(()=>{"use strict";Yr();Z();rc();ac();es();Te=null,a_="full"});import{existsSync as m_}from"node:fs";import{dirname as wc}from"node:path";import{fileURLToPath as g_}from"node:url";function Tt(){if(ts)return ts;let e=wc(g_(import.meta.url));for(;!m_(`${e}/package.json`);){let t=wc(e);if(t===e)throw new Error(`package.json not found walking up from ${import.meta.url}`);e=t}return ts=e,ts}var ts,ns=se(()=>{"use strict";ts=null});function ss(e){let t=null;return()=>t||(t=(async()=>{try{return await e()}finally{t=null}})(),t)}var Kr=se(()=>{"use strict"});function f_(e){return["Semantic search is unavailable on this platform.","",`Reason: ${e.detail}`,"",`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) - or file an issue at","https://gitlab.com/clauderecallhq/clauderecallhq/-/issues with the platform line above."].join(`
|
|
756
|
+
`)}var rs,ye,ln=se(()=>{"use strict";rs="BAAI/bge-base-en-v1.5",ye=class extends Error{kind;path;underlying;cause;constructor(t){super(f_(t)),this.name="EmbedderUnavailableError",this.kind=t.kind,this.path=t.path,this.underlying=t.underlying,this.cause=t}}});var as={};St(as,{embed:()=>R_,embedQuery:()=>k_,getEmbedderStatus:()=>w_,loadEmbedder:()=>y_,unloadEmbedder:()=>A_});import{Worker as __}from"node:worker_threads";import{join as h_}from"node:path";import{existsSync as E_}from"node:fs";function b_(){return h_(Tt(),"dist","daemon","embedder-worker.js")}function Rc(e){for(let t of un.values())t.reject(e);un.clear()}function S_(){if(Ue)return Ue;let e=b_();if(!E_(e))throw new ye({kind:"bundle-missing",detail:"embedder-worker bundle not found - run `npm run build:cli` to emit it",path:e});let t=new __(e);return t.on("message",n=>{let s=un.get(n.id);s&&(un.delete(n.id),s.resolve(n))}),t.on("error",n=>{console.error("[embedder-worker] thread error:",n);let s=n instanceof Error?n:new Error(String(n));Rc(s),Ue=null,Ne=!1}),t.on("exit",n=>{n!==0&&(console.error(`[embedder-worker] exited with code ${n}`),Rc(new Error(`embedder worker exited with code ${n}`))),Ue=null,Ne=!1}),Ue=t,t}function os(e){return new Promise((t,n)=>{let s;try{s=S_()}catch(r){n(r instanceof Error?r:new Error(String(r)));return}un.set(e.id,{resolve:t,reject:n}),s.postMessage(e)})}function is(){return Vr=Vr+1>>>0,String(Vr)}function Zr(e){if(!e.ok)throw e.unavailable?new ye({kind:"platform-unsupported",detail:"embedder worker reports the runtime is unavailable on this platform",underlying:new Error(e.error)}):new Error(e.error);return e}async function y_(){if(!(Ne&&Ue))try{await T_(),Ne=!0}catch(e){throw Ne=!1,e}}function w_(){return{loaded:Ne,modelId:rs,dim:768}}async function R_(e){if(!Ne)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");return Zr(await os({id:is(),type:"embed",texts:e})).embeddings.map(n=>new Float32Array(n))}async function k_(e){if(!Ne)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=Zr(await os({id:is(),type:"embedQuery",text:e}));return new Float32Array(t.embedding)}async function A_(){if(!Ue){Ne=!1;return}try{await os({id:is(),type:"unload"})}catch{}Ne=!1;let e=Ue;Ue=null;try{await e.terminate()}catch{}}var Ue,un,Vr,Ne,T_,kc=se(()=>{"use strict";ns();Kr();ln();Ue=null,un=new Map,Vr=0,Ne=!1;T_=ss(async()=>{Zr(await os({id:is(),type:"load"}))})});var no={};St(no,{LLAMACPP_MODEL_ID:()=>xc,MODEL_ID:()=>rs,embed:()=>D_,embedQuery:()=>j_,getEmbedderStatus:()=>M_,loadEmbedder:()=>I_,unloadEmbedder:()=>P_});import{Worker as x_}from"node:worker_threads";import{join as N_}from"node:path";import{existsSync as O_}from"node:fs";function L_(){return N_(Tt(),"dist","daemon","embedder-worker-llamacpp.js")}function Ac(e){for(let t of dn.values())t.reject(e);dn.clear()}function C_(){if(He)return He;let e=L_();if(!O_(e))throw new ye({kind:"bundle-missing",detail:"embedder-worker-llamacpp bundle not found - run `npm run build:cli` to emit it",path:e});let t=new x_(e);return t.on("message",n=>{let s=dn.get(n.id);s&&(dn.delete(n.id),s.resolve(n))}),t.on("error",n=>{console.error("[embedder-worker-llamacpp] thread error:",n);let s=n instanceof Error?n:new Error(String(n));Ac(s),He=null,Oe=!1}),t.on("exit",n=>{n!==0&&(console.error(`[embedder-worker-llamacpp] exited with code ${n}`),Ac(new Error(`embedder worker exited with code ${n}`))),He=null,Oe=!1}),He=t,t}function cs(e){return new Promise((t,n)=>{let s;try{s=C_()}catch(r){n(r instanceof Error?r:new Error(String(r)));return}dn.set(e.id,{resolve:t,reject:n}),s.postMessage(e)})}function ls(){return eo=eo+1>>>0,String(eo)}function to(e){if(!e.ok)throw e.unavailable?new ye({kind:"platform-unsupported",detail:"embedder worker reports the llama.cpp runtime is unavailable on this platform",underlying:new Error(e.error)}):new Error(e.error);return e}async function I_(){if(!(Oe&&He))try{await v_(),Oe=!0}catch(e){throw Oe=!1,e}}function M_(){return{loaded:Oe,modelId:xc,dim:768}}async function D_(e){if(!Oe)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");return to(await cs({id:ls(),type:"embed",texts:e})).embeddings.map(n=>new Float32Array(n))}async function j_(e){if(!Oe)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=to(await cs({id:ls(),type:"embedQuery",text:e}));return new Float32Array(t.embedding)}async function P_(){if(!He){Oe=!1;return}try{await cs({id:ls(),type:"unload"})}catch{}Oe=!1;let e=He;He=null;try{await e.terminate()}catch{}}var xc,He,dn,eo,Oe,v_,Nc=se(()=>{"use strict";ns();Kr();ln();ln();xc="BAAI/bge-base-en-v1.5-gguf-q8_0";He=null,dn=new Map,eo=0,Oe=!1;v_=ss(async()=>{to(await cs({id:ls(),type:"load"}))})});var Oc={};St(Oc,{EmbedderUnavailableError:()=>ye,embed:()=>Ve,embedQuery:()=>$_,getEmbedderStatus:()=>le,loadEmbedder:()=>Be,unloadEmbedder:()=>U_});function F_(){let e=(process.env.RECALL_EMBEDDER_BACKEND??"").trim().toLowerCase();return e==="llama"||e==="llamacpp"?no:(e===""||e==="onnx"||process.stderr.write(`[embedder] RECALL_EMBEDDER_BACKEND="${e}" is not recognized; falling back to onnx. Valid values: onnx, llama.
|
|
757
|
+
`),as)}var pn,Be,le,Ve,$_,U_,We=se(()=>{"use strict";kc();Nc();ln();pn=F_(),Be=pn.loadEmbedder,le=pn.getEmbedderStatus,Ve=pn.embed,$_=pn.embedQuery,U_=pn.unloadEmbedder});import{writeFileSync as Hh}from"node:fs";import{join as Bh}from"node:path";function ut(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function wt(e,t){let n=ut(t);if(!n)throw new Error("tag must contain at least one alphanumeric character");let s=h(),r=new Date().toISOString();return s.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,n)?{tag:n,added:!1}:(s.transaction(()=>{s.prepare("INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)").run(e,n,r),s.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,n,r)})(),Vc(),{tag:n,added:!0})}function Kc(e,t){let n=ut(t);if(!n)return{tag:"",removed:!1};let s=h(),r=new Date().toISOString();return s.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,n)?(s.transaction(()=>{s.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(e,n),s.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'remove', ?)").run(e,n,r)})(),Vc(),{tag:n,removed:!0}):{tag:n,removed:!1}}function _n(e){return h().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function Rt(){return h().prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
|
|
758
|
+
GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function Vc(){try{K();let e=h(),t=e.prepare("SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag").all(),n=e.prepare("SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC").all(),s={schema:"claude-recall.tags.v1",backed_up_at:new Date().toISOString(),current:t,events:n};Hh(Wh,JSON.stringify(s,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var Wh,kt=se(()=>{"use strict";H();Z();Wh=Bh(W,"tags.json")});function qh(e,t){let n=e.filter(o=>o.content_text&&o.content_text.trim().length>0);if(n.length<=t)return n;let s=new Set;s.add(0),s.add(n.length-1);let r=(n.length-2)/Math.max(1,t-2);for(let o=1;o<t-1;o++)s.add(Math.floor(o*r));return Array.from(s).sort((o,a)=>o-a).slice(0,t).map(o=>n[o])}function At(e){let t=h(),n={limit:e.limit??500},s=e.sessionIds&&e.sessionIds.length>0,r=s?"1=1":"s.message_count > 2";if(s){let a=e.sessionIds.map((c,u)=>`@sid_${u}`).join(", ");r+=` AND s.id IN (${a})`,e.sessionIds.forEach((c,u)=>{n[`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",n.project=e.project),e.collectionId&&(r+=" AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id = @col)",n.col=e.collectionId),t.prepare(`SELECT s.id, p.name AS project, s.git_branch,
|
|
737
759
|
NULLIF(sa.alias, '') AS alias,
|
|
738
760
|
COALESCE(s.first_user_message, '') AS first_user_message
|
|
739
761
|
FROM sessions s
|
|
@@ -743,15 +765,15 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
743
765
|
ORDER BY COALESCE(s.started_at, '') DESC
|
|
744
766
|
LIMIT @limit`).all(n).map(a=>{let c=t.prepare(`SELECT role, COALESCE(content_text, '') AS content_text
|
|
745
767
|
FROM messages WHERE session_id = ?
|
|
746
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(a.id),d=
|
|
768
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(a.id),d=qh(c,5).map(p=>`${p.role}: ${p.content_text.slice(0,400)}`).join(`
|
|
747
769
|
---
|
|
748
|
-
`);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:
|
|
749
|
-
`)}function
|
|
750
|
-
`)}function
|
|
751
|
-
`)}function
|
|
752
|
-
`)}function
|
|
770
|
+
`);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:_n(a.id)}})}var gs=se(()=>{"use strict";H();kt()});import{z as Ee}from"zod";function mo(e){let t=e.minTags??2,n=e.maxTags??4,s=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.`)):(s&&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}-${n} 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(`
|
|
771
|
+
`)}function Gh(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(`
|
|
772
|
+
`)}function zh(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(`
|
|
773
|
+
`)}function Vh(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(`
|
|
774
|
+
`)}function Qc(e){return go.find(t=>t.name===e)}var Xh,Jh,Yh,Kh,Qh,Zh,eE,tE,go,fo=se(()=>{"use strict";Xh={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).")};Jh={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.")};Yh={sessionId:Ee.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};Kh={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).")};Qh={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:Xh,build:mo,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},Zh={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:Jh,build:Gh,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},eE={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:Yh,build:zh,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},tE={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:Kh,build:Vh,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},go=[Qh,Zh,eE,tE]});function fs(e,t){let n=hn.get(e);if(!(!n||n.size===0))for(let s of n)try{s(t)}catch{}}function Zc(e,t){let n=hn.get(e);return n||(n=new Set,hn.set(e,n)),n.add(t),()=>{let s=hn.get(e);s&&(s.delete(t),s.size===0&&hn.delete(e))}}var hn,_o=se(()=>{"use strict";hn=new Map});import{existsSync as nE,statSync as sE}from"node:fs";import{delimiter as rE,join as oE}from"node:path";function xt(e){if(e.includes("/")||e.includes("\\")||e.includes(".."))return null;let t=(process.env.PATH??"").split(rE).filter(Boolean),n=process.platform==="win32"?[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e];for(let s of t)for(let r of n){let o=oE(s,r);try{if(nE(o)&&sE(o).isFile())return o}catch{}}return null}function _s(e){if(process.platform!=="win32"||!e)return!1;let t=e.toLowerCase();return t.endsWith(".cmd")||t.endsWith(".bat")}var hs=se(()=>{"use strict"});var pt={};St(pt,{_resetClaudePathCacheForTests:()=>lE,buildScanPrompt:()=>tl,isClaudeCliAvailable:()=>me,runClaudeCliScan:()=>ho,spawnClaudePrompt:()=>dt});import{spawn as iE}from"node:child_process";function el(){if(Nt!==void 0&&En!==void 0)return{path:Nt,available:En};let e=xt("claude");return Nt=e??"claude",En=e!==null,{path:Nt,available:En}}function cE(){return el().path}function me(){return el().available}function lE(){Nt=void 0,En=void 0}function tl(e){return mo({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 uE(e,t){let n=t.get(e);return n||e.slice(0,8)}function dE(e){try{return At(e).map(n=>({id:n.id,label:n.alias&&n.alias.trim().length>0?n.alias:n.first_user_message&&n.first_user_message.trim().length>0?n.first_user_message.slice(0,60):n.id.slice(0,8)}))}catch{return[]}}function pE(e){let{scanId:t,total:n,labelTable:s}=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 p=d.input,f=typeof p?.sessionId=="string"?p.sessionId:null;!f||r.has(f)||(r.add(f),fs(t,{type:"progress",current:r.size,total:n,sessionId:f,sessionLabel:uE(f,s)}))}}}function mE(e){let t="";return n=>{t+=n.toString("utf8");let s=t.indexOf(`
|
|
753
775
|
`);for(;s!==-1;){let r=t.slice(0,s);t=t.slice(s+1),r.length>0&&e(r),s=t.indexOf(`
|
|
754
|
-
`)}}}async function
|
|
776
|
+
`)}}}async function ho(e,t={},n){let s=!!t.scanId,r=s?dE(e):[],o=new Map(r.map(u=>[u.id,u.label])),a=r.length,c;return s&&t.scanId&&(c=pE({scanId:t.scanId,total:a,labelTable:o})),nl({prompt:tl(e),allowedTools:aE.split(","),opts:t,onProgress:n,onStdoutLine:c,outputFormat:s?"stream-json":"json"})}async function dt(e,t,n={},s){return nl({prompt:e,allowedTools:t,opts:n,onProgress:s,outputFormat:"json"})}function nl(e){let{prompt:t,allowedTools:n,opts:s,onProgress:r,onStdoutLine:o,outputFormat:a}=e,c=["-p",t,"--output-format",a,"--allowedTools",n.join(","),"--permission-mode","bypassPermissions","--no-session-persistence"];return a==="stream-json"&&c.push("--verbose"),s.model&&c.push("--model",s.model),new Promise(u=>{let d=cE(),p=iE(d,c,{stdio:["ignore","pipe","pipe"],shell:_s(d)||process.platform==="win32"&&Nt==="claude"}),f=[],b=[],T=o?mE(o):void 0;p.stdout.on("data",R=>{f.push(R),T&&T(R)}),p.stderr.on("data",R=>{if(b.push(R),r){let A=R.toString("utf8").trim();A&&r(A)}});let S=setTimeout(()=>{p.kill("SIGKILL")},1800*1e3);p.on("close",R=>{clearTimeout(S),u({success:R===0,stdout:Buffer.concat(f).toString("utf8"),stderr:Buffer.concat(b).toString("utf8"),exitCode:R})}),p.on("error",R=>{clearTimeout(S),u({success:!1,stdout:"",stderr:String(R),exitCode:null})})})}var aE,Nt,En,we=se(()=>{"use strict";gs();fo();_o();hs();aE=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});var bl={};St(bl,{RetentionConfigSchema:()=>ko,readRetentionConfig:()=>Ao,writeRetentionConfig:()=>As});import{existsSync as gl,mkdirSync as eb,readFileSync as tb,writeFileSync as nb}from"node:fs";import{homedir as sb}from"node:os";import{join as fl}from"node:path";import{z as Rs}from"zod";function _l(){return process.env.RECALL_HOME??fl(sb(),".recall")}function rb(){let e=_l();gl(e)||eb(e,{recursive:!0})}function hl(){return fl(_l(),"config.json")}function El(){let e=hl();if(!gl(e))return{};try{return JSON.parse(tb(e,"utf8"))}catch(t){let n=t instanceof Error?t.message:String(t);return console.error(`[retention-config] failed to parse config.json: ${n}`),{}}}function Ao(){let e=El().retention;if(!e)return{...ks};let t=ko.safeParse({...ks,...e});return t.success?t.data:{...ks}}function As(e){rb();let t=El(),n=ko.parse({...ks,...t.retention??{},...e}),s={...t,retention:n};return nb(hl(),JSON.stringify(s,null,2)),n}var ko,ks,xo=se(()=>{"use strict";ko=Rs.object({autoArchiveEnabled:Rs.boolean().default(!1),autoArchiveAfterDays:Rs.number().int().min(7).max(3650).default(90),lastRunAt:Rs.string().nullable().default(null)}),ks={autoArchiveEnabled:!1,autoArchiveAfterDays:90,lastRunAt:null}});import Ce from"chalk";import{formatDistanceToNowStrict as KO,parseISO as VO}from"date-fns";var Re,xs=se(()=>{"use strict";Re={dim:Ce.gray,bold:Ce.bold,project:Ce.cyan,user:Ce.blue,assistant:Ce.green,tool:Ce.magenta,warn:Ce.yellow,err:Ce.red,ok:Ce.green,accent:Ce.hex("#f97316")}});import{existsSync as ob}from"node:fs";import{join as ib}from"node:path";function Sn(){if(Sl&&ob(bn))return;K();let e=h(),t=bn.replace(/'/g,"''");e.exec(`ATTACH DATABASE '${t}' AS archive`);try{e.exec(`
|
|
755
777
|
CREATE TABLE IF NOT EXISTS archive.messages_archive (
|
|
756
778
|
uuid TEXT PRIMARY KEY,
|
|
757
779
|
session_id TEXT NOT NULL,
|
|
@@ -766,12 +788,12 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
766
788
|
archived_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
767
789
|
);
|
|
768
790
|
CREATE INDEX IF NOT EXISTS archive.idx_messages_archive_session ON messages_archive(session_id);
|
|
769
|
-
`)}finally{e.exec("DETACH DATABASE archive")}
|
|
791
|
+
`)}finally{e.exec("DETACH DATABASE archive")}Sl=!0}function Tl(){let e=h();if(e.prepare("SELECT COUNT(*) AS n FROM messages_archive").get().n===0)return 0;Sn();let n=bn.replace(/'/g,"''"),s=0;e.exec(`ATTACH DATABASE '${n}' AS archive`);try{e.transaction(()=>{s=e.prepare(`INSERT OR IGNORE INTO archive.messages_archive
|
|
770
792
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
771
793
|
is_sidechain, content_text, tool_names, raw_json, archived_at)
|
|
772
794
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
773
795
|
is_sidechain, content_text, tool_names, raw_json, archived_at
|
|
774
|
-
FROM main.messages_archive`).run().changes,e.prepare("DELETE FROM main.messages_archive").run()})()}finally{e.exec("DETACH DATABASE archive")}return s}var
|
|
796
|
+
FROM main.messages_archive`).run().changes,e.prepare("DELETE FROM main.messages_archive").run()})()}finally{e.exec("DETACH DATABASE archive")}return s}var bn,Sl,No=se(()=>{"use strict";Z();H();bn=ib(W,"archive.sqlite"),Sl=!1});var wl={};St(wl,{runArchive:()=>gb});function Oo(e){Sn();let t=h(),n=bn.replace(/'/g,"''");t.exec(`ATTACH DATABASE '${n}' AS archive`);try{return e(t)}finally{t.exec("DETACH DATABASE archive")}}function ab(){return Oo(e=>{let t=e.prepare(`SELECT
|
|
775
797
|
SUM(CASE WHEN archive_status = 'archived' THEN 1 ELSE 0 END) AS archived,
|
|
776
798
|
SUM(CASE WHEN archive_status != 'archived' THEN 1 ELSE 0 END) AS live
|
|
777
799
|
FROM sessions`).get(),n=e.prepare("SELECT COUNT(*) AS n FROM messages").get().n,s=e.prepare(`SELECT
|
|
@@ -780,15 +802,15 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
780
802
|
SELECT MAX(timestamp) AS t FROM main.messages_archive WHERE timestamp IS NOT NULL
|
|
781
803
|
UNION ALL
|
|
782
804
|
SELECT MAX(timestamp) AS t FROM archive.messages_archive WHERE timestamp IS NOT NULL
|
|
783
|
-
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:n,archivedMessages:s,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function
|
|
805
|
+
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:n,archivedMessages:s,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function yl(e){return e?e.slice(0,10):"\u2014"}function cb(){let e=ab();return console.log(Re.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 ${yl(e.oldestLiveTimestamp)}`),console.log(` Newest archived ${yl(e.newestArchivedTimestamp)}`),console.log(""),console.log(Re.dim(" recall archive run --before YYYY-MM-DD")),console.log(Re.dim(" recall archive restore <session-id>")),0}function lb(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e.before))return console.error("--before must be YYYY-MM-DD"),1;let n=h().prepare(`SELECT s.id, s.ended_at, s.message_count
|
|
784
806
|
FROM sessions s
|
|
785
807
|
WHERE s.archive_status != 'archived'
|
|
786
|
-
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(n.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let s=n.reduce((c,u)=>c+(u.message_count??0),0);if(console.log(`${n.length.toLocaleString()} session(s), ${s.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(
|
|
808
|
+
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(n.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let s=n.reduce((c,u)=>c+(u.message_count??0),0);if(console.log(`${n.length.toLocaleString()} session(s), ${s.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(Re.dim("Dry run \u2014 no rows moved. Re-run without --dry-run to apply.")),0;let r=n.map(c=>c.id),o=Date.now(),a=Oo(c=>c.transaction(d=>{let p=0,f=c.prepare(`INSERT OR IGNORE INTO archive.messages_archive
|
|
787
809
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
788
810
|
is_sidechain, content_text, tool_names, raw_json, archived_at)
|
|
789
811
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
790
812
|
is_sidechain, content_text, tool_names, raw_json, datetime('now')
|
|
791
|
-
FROM messages WHERE session_id = ?`),
|
|
813
|
+
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){f.run(S);let R=b.run(S);p+=Number(R.changes??0),T.run(S)}return p})(r));return console.log(`Archived ${n.length.toLocaleString()} session(s), moved ${a.toLocaleString()} message(s) in ${Date.now()-o}ms.`),console.log(Re.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function ub(e){if(!e)return console.error("Usage: recall archive restore <session-id>"),1;let n=h().prepare("SELECT id, archive_status FROM sessions WHERE id = ?").get(e);if(!n)return console.error(`Session ${e} not found.`),1;if(n.archive_status!=="archived")return console.error(`Session ${e} is not archived (status=${n.archive_status}).`),1;let s=Oo(r=>r.transaction(()=>{let a=r.prepare(`INSERT OR IGNORE INTO messages
|
|
792
814
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
793
815
|
is_sidechain, content_text, tool_names, raw_json)
|
|
794
816
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
@@ -798,32 +820,32 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
|
798
820
|
is_sidechain, content_text, tool_names, raw_json)
|
|
799
821
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
800
822
|
is_sidechain, content_text, tool_names, raw_json
|
|
801
|
-
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 = ?"),p=Number(a.run(e).changes??0),f=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),p+f})());return console.log(`Restored ${s.toLocaleString()} message(s) for session ${e}.`),0}function
|
|
823
|
+
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 = ?"),p=Number(a.run(e).changes??0),f=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),p+f})());return console.log(`Restored ${s.toLocaleString()} message(s) for session ${e}.`),0}function db(){let e=Ao();return console.log(Re.dim("\u2014 Auto-archive \u2014")),console.log(` Enabled ${e.autoArchiveEnabled?Re.ok("YES"):"no"}`),console.log(` After ${e.autoArchiveAfterDays} days`),console.log(` Last run ${e.lastRunAt??"\u2014"}`),0}function pb(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 n=As({autoArchiveEnabled:!0,autoArchiveAfterDays:t});return console.log(`Auto-archive: ENABLED (after ${n.autoArchiveAfterDays} days).`),console.log(Re.dim(" The daemon will run a daily archive pass on the next tick.")),0}function mb(){return As({autoArchiveEnabled:!1}),console.log("Auto-archive: DISABLED. Existing archived sessions stay archived."),0}async function gb(e){let t=e._action??"list";if(t==="list"||t==="stats")return cb();if(t==="run")return e.before?lb({before:e.before,dryRun:e.dryRun===!0}):(console.error("Usage: recall archive run --before YYYY-MM-DD [--dry-run]"),1);if(t==="restore")return ub(e._sessionId??"");if(t==="auto"){let n=e._subAction??"status";return n==="status"?db():n==="on"||n==="enable"?pb(e.after):n==="off"||n==="disable"?mb():(console.error("Usage: recall archive auto <status|on|off> [--after <days>]"),1)}return console.error(`Usage: recall archive <list|run|restore|auto> [args]
|
|
802
824
|
list \u2014 show archive counts
|
|
803
825
|
run --before YYYY-MM-DD [--dry-run] \u2014 move sessions older than DATE
|
|
804
826
|
restore <session-id> \u2014 pull a session back from archive
|
|
805
|
-
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}var
|
|
827
|
+
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}var Rl=se(()=>{"use strict";xs();H();No();xo()});var Ba=se(()=>{"use strict";H()});import{cpus as XA}from"node:os";import{Hono as cA}from"hono";import{serve as lA}from"@hono/node-server";Z();import{existsSync as vf,readFileSync as If,writeFileSync as gx,unlinkSync as fx}from"node:fs";import{join as Mf}from"node:path";var qa=Mf(W,"license.json");function an(){if(!vf(qa))return null;try{let e=If(qa,"utf8"),t=JSON.parse(e);return typeof t.license_jwt!="string"||t.license_jwt.length===0?null:t}catch{return null}}import{jwtVerify as Df,importSPKI as jf}from"jose";var Xa=`-----BEGIN PUBLIC KEY-----
|
|
806
828
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZysO2FffTLdyxQnTmnt78/ayvqz9
|
|
807
829
|
kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
808
830
|
-----END PUBLIC KEY-----
|
|
809
|
-
`,
|
|
810
|
-
`,{mode:384})}async function
|
|
831
|
+
`,qr="ES256",Ja="clauderecall.com",Ga="clauderecall-cli";var Qn=null;async function Pf(){return Qn||(Qn=await jf(Xa,qr),Qn)}async function Ya(e){try{let t=await Pf(),{payload:n}=await Df(e,t,{issuer:Ja,audience:Ga,algorithms:[qr]});return{valid:!0,claims:n}}catch(t){return{valid:!1,reason:t instanceof Error?t.message:"verification failed"}}}import{createHash as Ff}from"node:crypto";import{hostname as $f,userInfo as Uf,platform as Hf,arch as Bf}from"node:os";function za(){let e="unknown";try{e=Uf().username}catch{}let t=[$f(),e,Hf(),Bf()];return Ff("sha256").update(t.join("\0")).digest("hex")}Z();import{existsSync as Wf,readFileSync as qf,writeFileSync as Xf}from"node:fs";import{join as Jf}from"node:path";function Ka(){let e=process.env.RECALL_API_BASE;if(e&&e.length>0){let t=e.replace(/\/$/,""),n;try{n=new URL(t)}catch{throw new Error(`RECALL_API_BASE is not a valid URL: ${t}`)}let s=n.hostname==="127.0.0.1"||n.hostname==="localhost"||n.hostname==="::1";if(n.protocol==="https:"||n.protocol==="http:"&&s)return t;throw new Error(`RECALL_API_BASE must be HTTPS, or HTTP with loopback hostname. Got: ${t}`)}return"https://clauderecall.com"}var Xr=Jf(W,"license-check.json"),Gf=1440*60*1e3,Yf=720*60*60*1e3,zf=1e4;function Va(){if(!Wf(Xr))return null;try{let e=JSON.parse(qf(Xr,"utf8"));return typeof e.license_key!="string"||typeof e.last_checked_at!="string"||typeof e.revoked!="boolean"?null:e}catch{return null}}function Kf(e){K(),Xf(Xr,JSON.stringify(e,null,2)+`
|
|
832
|
+
`,{mode:384})}async function Vf(e,t){let n=null,s=null;try{n=new AbortController,s=setTimeout(()=>n?.abort(),zf);let r=await fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({license_key:e}),signal:n.signal});if(!r.ok)return null;let o=await r.json();return typeof o?.revoked!="boolean"?null:o}catch{return null}finally{s&&clearTimeout(s)}}async function Qa(e,t={}){let n=Va(),s=t.apiUrl??`${Ka()}/api/license/check`,r=n?.license_key===e,o=!n||!r||Date.now()-new Date(n.last_checked_at).getTime()>=Gf;if(!t.force&&!o)return n;let a=await Vf(e,s);if(!a)return r?n:null;let c={license_key:e,last_checked_at:new Date().toISOString(),revoked:a.revoked,reason:a.reason??null};return Kf(c),c}function Za(e){let t=Va();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()>Yf?{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 Qf=Date.UTC(2026,5,1,7,0,0);var Zf=1440*60*1e3,Mx=60*Zf;async function it(){let e=an();if(!e)return{tier:"free"};let t=await Ya(e.license_jwt);if(!t.valid||!t.claims)return{tier:"free",invalid_reason:t.reason};if(t.claims.machine_fp&&t.claims.machine_fp!==za())return{tier:"free",invalid_reason:"machine fingerprint mismatch \u2014 re-activate on this device"};let n=Za(e.license_key);return n?.revoked?{tier:"free",invalid_reason:n.reason}:e_(e,t.claims)}async function ec(e){let t=an();if(!t)return{ran:!1,revoked:!1,reason:null,last_checked_at:null};let n=await Qa(t.license_key,{force:e?.force??!1});return n?{ran:!0,revoked:n.revoked,reason:n.reason,last_checked_at:n.last_checked_at}:{ran:!0,revoked:!1,reason:null,last_checked_at:null}}function e_(e,t){let n=t.test_mode===!0&&process.env.NODE_ENV==="production";return{tier:n?"free":"pro",key_short:e.key_short,customer_email:e.customer_email,activated_at:e.activated_at,test_mode:e.test_mode,...n?{test_mode_blocked:!0}:{},expires_at:typeof t.exp=="number"?new Date(t.exp*1e3).toISOString():null}}async function tc(){return(await it()).tier==="pro"}H();H();import{createHash as H_}from"node:crypto";H();Z();import{writeFileSync as c_,readFileSync as sN,existsSync as l_,mkdirSync as u_,readdirSync as rN,unlinkSync as oN}from"node:fs";import{join as dc}from"node:path";import{randomUUID as pc}from"node:crypto";var zr=dc(W,"bug-patterns");function d_(){K(),l_(zr)||u_(zr,{recursive:!0})}function Ke(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 mc(e){return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at}}function gc(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=h(),n=new Date().toISOString(),s=e.id??pc(),r=e.first_seen_at??n,o=e.last_seen_at??n,a=Array.from(new Set(e.member_session_ids));t.transaction(()=>{t.prepare(`INSERT INTO bug_pattern_clusters
|
|
811
833
|
(id, signature_hash, example_message, occurrence_count,
|
|
812
834
|
first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
|
|
813
835
|
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(s,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)
|
|
814
836
|
VALUES (?, ?, ?)
|
|
815
|
-
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of a)u.run(s,d,n)})();let c=
|
|
837
|
+
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of a)u.run(s,d,n)})();let c=fc(s);if(!c)throw new Error("createCluster succeeded but read-back failed");return cn(s),c}function fc(e){let t=h(),n=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!n)return null;let s=t.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ? ORDER BY matched_at ASC, session_id ASC").all(e);return{cluster:Ke(n),members:s.map(mc)}}function _c(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 n=h();if(!n.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;n.transaction(()=>{let c=n.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
816
838
|
VALUES (?, ?, ?)
|
|
817
839
|
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=n.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;n.prepare(`UPDATE bug_pattern_clusters
|
|
818
840
|
SET occurrence_count = ?, last_seen_at = ?
|
|
819
|
-
WHERE id = ?`).run(u,r,e)}})(),o>0&&
|
|
841
|
+
WHERE id = ?`).run(u,r,e)}})(),o>0&&cn(e);let a=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Ke(a),added:o}}function hc(e={}){let t=h(),n=[],s=[];typeof e.minOccurrenceCount=="number"&&(n.push("c.occurrence_count >= ?"),s.push(Math.max(1,Math.floor(e.minOccurrenceCount)))),typeof e.hasResolved=="boolean"&&n.push(e.hasResolved?"c.resolved_in_session_id IS NOT NULL":"c.resolved_in_session_id IS NULL"),e.project&&(n.push(`EXISTS (
|
|
820
842
|
SELECT 1 FROM bug_pattern_members m
|
|
821
843
|
JOIN sessions s ON s.id = m.session_id
|
|
822
844
|
JOIN projects p ON p.id = s.project_id
|
|
823
845
|
WHERE m.cluster_id = c.id AND p.name = ?
|
|
824
846
|
)`),s.push(e.project));let r=n.length>0?`WHERE ${n.join(" AND ")}`:"",o=t.prepare(`SELECT COUNT(*) AS n FROM bug_pattern_clusters c ${r}`).get(...s),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}
|
|
825
847
|
ORDER BY c.occurrence_count DESC, c.last_seen_at DESC
|
|
826
|
-
LIMIT ? OFFSET ?`).all(...s,a,c).map(
|
|
848
|
+
LIMIT ? OFFSET ?`).all(...s,a,c).map(Ke),total:o.n}}function p_(e){let t=e.first_user_message?e.first_user_message.slice(0,80):null,n=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:n,alias:e.alias,auto_title:e.auto_title,project:e.project,started_at:e.started_at}}function Ec(e){let t=h(),n=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!n)return null;let s=t.prepare(`SELECT m.cluster_id, m.session_id, m.matched_at,
|
|
827
849
|
NULLIF(sa.alias, '') AS alias,
|
|
828
850
|
s.auto_title,
|
|
829
851
|
s.first_user_message,
|
|
@@ -834,31 +856,31 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
834
856
|
LEFT JOIN session_aliases sa ON sa.session_id = m.session_id
|
|
835
857
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
836
858
|
WHERE m.cluster_id = ?
|
|
837
|
-
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:
|
|
859
|
+
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:Ke(n),members:s.map(p_)}}function bc(e,t,n){if(!e)throw new Error("clusterId is required");if(!t)throw new Error("sessionId is required");if(typeof n!="string"||!n.trim())throw new Error("fixSummary is required");let s=h();if(!s.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);if(!s.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}`);s.prepare(`UPDATE bug_pattern_clusters
|
|
838
860
|
SET resolved_in_session_id = ?, fix_summary = ?
|
|
839
|
-
WHERE id = ?`).run(t,n.trim(),e),
|
|
840
|
-
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=n.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=
|
|
861
|
+
WHERE id = ?`).run(t,n.trim(),e),cn(e);let a=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Ke(a)}}function Sc(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 n=h(),s=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)throw new Error(`cluster ${e} not found`);let r=Array.from(new Set(t)),o=r.map(()=>"?").join(","),a=n.prepare(`SELECT session_id, matched_at FROM bug_pattern_members
|
|
862
|
+
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=n.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=pc(),d=new Date().toISOString(),p=[];n.transaction(()=>{n.prepare(`INSERT INTO bug_pattern_clusters
|
|
841
863
|
(id, signature_hash, example_message, occurrence_count,
|
|
842
864
|
first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
|
|
843
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(u,s.signature_hash,s.example_message,a.length,s.first_seen_at,d);let
|
|
844
|
-
VALUES (?, ?, ?)`),
|
|
865
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(u,s.signature_hash,s.example_message,a.length,s.first_seen_at,d);let T=n.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
866
|
+
VALUES (?, ?, ?)`),S=n.prepare("DELETE FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?");for(let A of a)T.run(u,A.session_id,A.matched_at),S.run(e,A.session_id);let R=c-a.length;n.prepare("UPDATE bug_pattern_clusters SET occurrence_count = ? WHERE id = ?").run(R,e),p=n.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ?").all(u)})(),cn(e),cn(u);let f=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e),b=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(u);return{original:Ke(f),split:Ke(b),movedMembers:p.map(mc)}}function cn(e){try{d_();let t=fc(e);if(!t)return;let n=dc(zr,`${e}.json`),s={schema:"claude-recall.bug-pattern-cluster.v1",cluster:t.cluster,members:t.members,backed_up_at:new Date().toISOString()};c_(n,JSON.stringify(s,null,2))}catch(t){console.error("[bug-patterns] backup failed:",t)}}function Tc(e){return e?h().prepare(`SELECT * FROM bug_pattern_clusters
|
|
845
867
|
WHERE signature_hash = ?
|
|
846
|
-
ORDER BY last_seen_at DESC, id ASC`).all(e).map(
|
|
868
|
+
ORDER BY last_seen_at DESC, id ASC`).all(e).map(Ke):[]}function yc(e){if(!e)return new Set;let n=h().prepare(`SELECT DISTINCT m.session_id
|
|
847
869
|
FROM bug_pattern_members m
|
|
848
870
|
JOIN bug_pattern_clusters c ON c.id = m.cluster_id
|
|
849
|
-
WHERE c.signature_hash = ?`).all(e);return new Set(n.map(s=>s.session_id))}var
|
|
871
|
+
WHERE c.signature_hash = ?`).all(e);return new Set(n.map(s=>s.session_id))}var B_=/\b0x[0-9a-fA-F]+\b/g,W_=/\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,q_=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g,X_=/:\d+:\d+/g,J_=/\bline\s+\d+\b/gi,G_=/\bcolumn\s+\d+\b/gi,Y_=/\b(?:pid|PID|process(?:\s+id)?)\s*[:=]?\s*\d+\b/gi,z_=/\b(?:port|:)\s*[:=]?\s*\d{2,5}\b/gi,K_=/\b\d{4,}\b/g,V_=/(['"`])[^'"`\n]{1,128}\1/g;function Q_(e){if(!e)return"";let t=String(e);return t=t.replace(B_,"<hex>"),t=t.replace(W_,"<uuid>"),t=t.replace(q_,"<ts>"),t=t.replace(X_,":<line>:<col>"),t=t.replace(J_,"line <n>"),t=t.replace(G_,"column <n>"),t=t.replace(Y_,"pid <n>"),t=t.replace(z_,"port <n>"),t=t.replace(K_,"<num>"),t=t.replace(V_,"<arg>"),t=t.replace(/\s+/g," ").trim(),t.toLowerCase()}function Z_(e){let t=(e.error_type??"unknown").toLowerCase().trim(),n=Q_(e.snippet??e.message_hash??""),s=`${t}|${n}`;return H_("sha256").update(s).digest("hex").slice(0,16)}function eh(e){let t=h(),n=["oi.bug_signatures IS NOT NULL"],s=[];e&&(n.push("p.name = ?"),s.push(e));let r=`WHERE ${n.join(" AND ")}`,o=t.prepare(`SELECT oi.session_id AS session_id,
|
|
850
872
|
p.name AS project,
|
|
851
873
|
s.started_at AS started_at,
|
|
852
874
|
oi.bug_signatures AS bug_signatures
|
|
853
875
|
FROM session_output_index oi
|
|
854
876
|
LEFT JOIN sessions s ON s.id = oi.session_id
|
|
855
877
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
856
|
-
${r}`).all(...s),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 f=
|
|
857
|
-
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${
|
|
858
|
-
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),d=!0}if(
|
|
859
|
-
WHERE id IN (${
|
|
878
|
+
${r}`).all(...s),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 f=Z_(d);a.push({session_id:c.session_id,project:c.project,started_at:c.started_at,signature:d,fingerprint:f})}}return a}function th(e){let t=new Map;for(let s of e){let r=t.get(s.fingerprint);r||(r=[],t.set(s.fingerprint,r)),r.push(s)}let n=[];for(let[s,r]of t){let o=new Set,a=[];for(let p of r)o.has(p.session_id)||(o.add(p.session_id),a.push(p));let c=[...a].sort((p,f)=>{let b=p.started_at??"",T=f.started_at??"";return b&&T?b<T?-1:b>T?1:0:b?-1:T?1:0}),u=c.find(p=>p.started_at),d=[...c].reverse().find(p=>p.started_at);n.push({fingerprint:s,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 n}function nh(e,t){if(e.length!==t.length)return 0;let n=0,s=0,r=0;for(let a=0;a<e.length;a++)n+=e[a]*t[a],s+=e[a]*e[a],r+=t[a]*t[a];let o=Math.sqrt(s)*Math.sqrt(r);return o>0?n/o:0}function sh(e){let{records:t,vectors:n,epsilon:s,minPts:r}=e,o=t.length;if(o===0)return[];let a=[];for(let p=0;p<o;p++){let f=[];for(let b=0;b<o;b++){if(p===b)continue;1-nh(n[p],n[b])<=s&&f.push(b)}a.push(f)}let c=new Array(o).fill(!1),u=new Array(o).fill(-1),d=[];for(let p=0;p<o;p++){if(c[p])continue;c[p]=!0;let f=a[p];if(f.length<r)continue;let b=d.length;d.push({members:[t[p]]}),u[p]=b;let T=[...f];for(;T.length>0;){let S=T.shift();if(!c[S]&&(c[S]=!0,a[S].length>=r))for(let R of a[S])(!c[R]||u[R]===-1)&&T.push(R);u[S]===-1&&(u[S]=b,d[b].members.push(t[S]))}}return d}async function rh(e,t,n,s){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=sh({records:e,vectors:o,epsilon:n,minPts:s}),c=[];for(let u of a){if(u.members.length===0)continue;let d=new Set,p=[];for(let A of u.members)d.has(A.session_id)||(d.add(A.session_id),p.push(A));if(p.length===0)continue;let b=`sem:${[...p.map(A=>A.fingerprint)].sort()[0]}`,T=[...p].sort((A,I)=>{let j=A.started_at??"",J=I.started_at??"";return j<J?-1:j>J?1:0}),S=T.find(A=>A.started_at),R=[...T].reverse().find(A=>A.started_at);c.push({fingerprint:b,example_message:p[0].signature.snippet??p[0].signature.message_hash??"",members:p,first_seen_at:S?.started_at??new Date().toISOString(),last_seen_at:R?.started_at??new Date().toISOString()})}return c}function Lc(e,t){let n={clusters_created:0,clusters_merged:0,members_added:0,cluster_ids:[]};for(let s of e){if(s.members.length<t)continue;let r=Tc(s.fingerprint),o=yc(s.fingerprint),a=s.members.map(d=>d.session_id).filter(d=>!o.has(d));if(r.length===0){let d=gc({signature_hash:s.fingerprint,example_message:s.example_message.slice(0,256),member_session_ids:s.members.map(p=>p.session_id),first_seen_at:s.first_seen_at,last_seen_at:s.last_seen_at});n.clusters_created+=1,n.members_added+=d.members.length,n.cluster_ids.push(d.cluster.id);continue}if(a.length===0){n.cluster_ids.push(r[0].id);continue}let c=r[0],u=_c(c.id,a);u.added>0&&(n.clusters_merged+=1,n.members_added+=u.added),n.cluster_ids.push(c.id)}return n}async function Cc(e={}){let t=Math.max(2,Math.floor(e.minClusterSize??3)),n=Math.max(1,Math.min(5e3,Math.floor(e.limit??1e3))),s=e.semanticEpsilon??.15,r=Math.max(1,Math.floor(e.semanticMinPts??1)),o=eh(e.project),a=new Set(o.map(j=>j.session_id)),c=th(o),u=[],d=!1;if(e.semantic){let j=e.embedder??null;if(!j)try{j=await ah()}catch(J){let F=(J instanceof Error?J.message:String(J)).split(`
|
|
879
|
+
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${F}
|
|
880
|
+
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),d=!0}if(j){let J=[];for(let M of c)M.members.length===1&&J.push(M.members[0]);J.length>=2&&(u=await rh(J,j,s,r))}}let p=c.filter(j=>j.members.length>=t),f=u.filter(j=>j.members.length>=t),b=Lc(p,t),T=Lc(f,t),S=[...b.cluster_ids,...T.cluster_ids],R=Array.from(new Set(S)),A=[];if(R.length>0){let j=h(),J=R.map(()=>"?").join(","),M=j.prepare(`SELECT * FROM bug_pattern_clusters
|
|
881
|
+
WHERE id IN (${J})
|
|
860
882
|
ORDER BY occurrence_count DESC, last_seen_at DESC
|
|
861
|
-
LIMIT ?`).all(...R,n);for(let
|
|
883
|
+
LIMIT ?`).all(...R,n);for(let F of M)A.push({id:F.id,signature_hash:F.signature_hash,example_message:F.example_message,occurrence_count:F.occurrence_count,first_seen_at:F.first_seen_at,last_seen_at:F.last_seen_at,resolved_in_session_id:F.resolved_in_session_id,fix_summary:F.fix_summary})}return{progress:{total_sessions:a.size,total_signatures:o.length,exact_match_groups:p.length,semantic_groups:f.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:A}}var oh=async()=>{let{embed:e,loadEmbedder:t,getEmbedderStatus:n}=await Promise.resolve().then(()=>(We(),Oc));return n().loaded||await t(),e},ih=oh;async function ah(){return ih()}H();H();Z();import{writeFileSync as vc,readFileSync as jN,existsSync as Ic,mkdirSync as Mc,readdirSync as PN}from"node:fs";import{join as us}from"node:path";var ch=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),Dc=new Set(["regex","llm","embedding","manual","auto","citation","git","terminal-registry"]),lh=new Set(["pending","approved","rejected"]),uh=new Set(["L1","L2","L3","L4","user"]),so=us(W,"links"),ro=us(W,"suggestions"),dh=us(ro,"index.json");function ph(){K(),Ic(so)||Mc(so,{recursive:!0})}function mh(){K(),Ic(ro)||Mc(ro,{recursive:!0})}function jc(e){try{return JSON.parse(e)}catch{return e}}function ds(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:jc(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function oo(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:jc(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function Pc(e){if(!Number.isFinite(e)||e<0||e>1)throw new Error("confidence must be a number in [0, 1]")}function io(e){if(!ch.has(e))throw new Error(`invalid link_type: ${e}`)}function gh(e){if(!Dc.has(e))throw new Error(`invalid source: ${e}`)}function Fc(e){if(!uh.has(e))throw new Error(`invalid inferred_by: ${e}`)}function $c(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 Uc(e){$c(e.source_session_id,e.target_session_id),io(e.link_type),gh(e.source),Pc(e.confidence);let t=h(),n=new Date().toISOString(),s=JSON.stringify(e.evidence??null),r=e.approved?1:0;t.prepare(`INSERT INTO session_links
|
|
862
884
|
(source_session_id, target_session_id, link_type,
|
|
863
885
|
confidence, source, evidence, approved,
|
|
864
886
|
created_at, updated_at)
|
|
@@ -871,11 +893,11 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
871
893
|
updated_at = excluded.updated_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,e.source,s,r,n,n);let o=t.prepare(`SELECT * FROM session_links
|
|
872
894
|
WHERE source_session_id = ?
|
|
873
895
|
AND target_session_id = ?
|
|
874
|
-
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
|
|
896
|
+
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 co(e.source_session_id),ds(o)}function ps(e={}){let t=h(),n=[],s=[];e.sourceSessionId&&(n.push("source_session_id = ?"),s.push(e.sourceSessionId)),e.targetSessionId&&(n.push("target_session_id = ?"),s.push(e.targetSessionId)),e.linkType&&(io(e.linkType),n.push("link_type = ?"),s.push(e.linkType)),e.approvedOnly&&n.push("approved = 1");let r=n.length?`WHERE ${n.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_links ${r}
|
|
875
897
|
ORDER BY confidence DESC, updated_at DESC
|
|
876
|
-
LIMIT ?`).all(...s,o).map(
|
|
898
|
+
LIMIT ?`).all(...s,o).map(ds)}function mn(e){return h().prepare(`SELECT * FROM session_links
|
|
877
899
|
WHERE source_session_id = ? OR target_session_id = ?
|
|
878
|
-
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(
|
|
900
|
+
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(ds)}function Hc(e){let t=h(),n=t.prepare("SELECT source_session_id FROM session_links WHERE id = ?").get(e);if(!n)return{removed:0,sourceSessionId:null};let s=t.prepare("DELETE FROM session_links WHERE id = ?").run(e);return s.changes>0&&co(n.source_session_id),{removed:s.changes,sourceSessionId:n.source_session_id}}function gn(e,t={}){$c(e.source_session_id,e.target_session_id),io(e.link_type),Pc(e.confidence),Fc(e.inferred_by);let n=h(),s=new Date().toISOString(),r=JSON.stringify(e.evidence??null);n.prepare(`INSERT INTO session_link_suggestions
|
|
879
901
|
(source_session_id, target_session_id, link_type,
|
|
880
902
|
confidence, evidence, status, inferred_by,
|
|
881
903
|
created_at, decided_at)
|
|
@@ -891,13 +913,13 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
891
913
|
THEN excluded.evidence
|
|
892
914
|
ELSE session_link_suggestions.evidence
|
|
893
915
|
END,
|
|
894
|
-
created_at = session_link_suggestions.created_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,
|
|
916
|
+
created_at = session_link_suggestions.created_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,r,e.inferred_by,s);let o=n.prepare(`SELECT * FROM session_link_suggestions
|
|
895
917
|
WHERE source_session_id = ?
|
|
896
918
|
AND target_session_id = ?
|
|
897
919
|
AND link_type = ?
|
|
898
|
-
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!
|
|
920
|
+
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!o)throw new Error("createSuggestion succeeded but read-back failed");return t.deferMirror||ct(),oo(o)}function yt(e={}){let t=h(),n=[],s=[];if(e.status){if(!lh.has(e.status))throw new Error(`invalid status: ${e.status}`);n.push("status = ?"),s.push(e.status)}e.sourceSessionId&&(n.push("source_session_id = ?"),s.push(e.sourceSessionId)),e.targetSessionId&&(n.push("target_session_id = ?"),s.push(e.targetSessionId)),e.inferredBy&&(Fc(e.inferredBy),n.push("inferred_by = ?"),s.push(e.inferredBy));let r=n.length?`WHERE ${n.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${r}
|
|
899
921
|
ORDER BY confidence DESC, created_at DESC
|
|
900
|
-
LIMIT ?`).all(...s,o).map(
|
|
922
|
+
LIMIT ?`).all(...s,o).map(oo)}function ao(e,t,n={}){if(t!=="approved"&&t!=="rejected")throw new Error(`invalid decision: ${t}`);let s=n.source??"manual";if(!Dc.has(s))throw new Error(`invalid source: ${s}`);let r=h(),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
|
|
901
923
|
SET status = ?, decided_at = ?
|
|
902
924
|
WHERE id = ?`).run(t,a,e),t==="approved"&&(r.prepare(`INSERT INTO session_links
|
|
903
925
|
(source_session_id, target_session_id, link_type,
|
|
@@ -912,7 +934,7 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
912
934
|
updated_at = excluded.updated_at`).run(o.source_session_id,o.target_session_id,o.link_type,o.confidence,s,o.evidence,a,a),c=r.prepare(`SELECT * FROM session_links
|
|
913
935
|
WHERE source_session_id = ?
|
|
914
936
|
AND target_session_id = ?
|
|
915
|
-
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),
|
|
937
|
+
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),ct(),t==="approved"&&co(o.source_session_id);let u=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);return{suggestion:oo(u),link:c?ds(c):null}}function co(e){try{ph();let t=ps({sourceSessionId:e}),n=us(so,`${e}.json`);if(t.length===0)return;let s={schema:"claude-recall.session-links.v1",source_session_id:e,backed_up_at:new Date().toISOString(),links:t};vc(n,JSON.stringify(s,null,2))}catch(t){console.error("[session-links] backup failed:",t)}}function ct(){try{mh();let e=yt({limit:5e3}),t={schema:"claude-recall.session-link-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:e};vc(dh,JSON.stringify(t,null,2))}catch(e){console.error("[session-links] suggestions backup failed:",e)}}H();Z();import{writeFileSync as fh,readFileSync as WN,existsSync as _h,mkdirSync as hh,readdirSync as qN}from"node:fs";import{join as Bc}from"node:path";var lo=Bc(W,"output-index");function Eh(){K(),_h(lo)||hh(lo,{recursive:!0})}function fn(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function bh(e){if(!e)return null;try{return JSON.parse(e)}catch{return e}}function Wc(e){return{session_id:e.session_id,files_written:fn(e.files_written),brands_mentioned:fn(e.brands_mentioned),terms_introduced:fn(e.terms_introduced),plan_ids_referenced:fn(e.plan_ids_referenced),bug_signatures:fn(e.bug_signatures),raw_extraction:bh(e.raw_extraction),extracted_at:e.extracted_at,extractor_version:e.extractor_version}}function qc(e){if(!e.session_id)throw new Error("session_id is required");let t=h(),n=new Date().toISOString(),s=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
|
|
916
938
|
(session_id, files_written, brands_mentioned, terms_introduced,
|
|
917
939
|
plan_ids_referenced, bug_signatures, raw_extraction,
|
|
918
940
|
extracted_at, extractor_version)
|
|
@@ -925,34 +947,39 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
925
947
|
bug_signatures = excluded.bug_signatures,
|
|
926
948
|
raw_extraction = excluded.raw_extraction,
|
|
927
949
|
extracted_at = excluded.extracted_at,
|
|
928
|
-
extractor_version = excluded.extractor_version`).run(e.session_id,s,r,o,a,c,u,n,d);let p=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!p)throw new Error("setOutputIndex succeeded but read-back failed");let f=
|
|
950
|
+
extractor_version = excluded.extractor_version`).run(e.session_id,s,r,o,a,c,u,n,d);let p=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!p)throw new Error("setOutputIndex succeeded but read-back failed");let f=Wc(p);return Sh(e.session_id),f}function lt(e){let n=h().prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e);return n?Wc(n):null}function Sh(e){try{Eh();let t=lt(e);if(!t)return;let n=Bc(lo,`${e}.json`),s={schema:"claude-recall.session-output-index.v1",backed_up_at:new Date().toISOString(),...t};fh(n,JSON.stringify(s,null,2))}catch(t){console.error("[output-index] backup failed:",t)}}var uo={citation:"same-project",similar:"same-project",skill_track:"same-project",bug_pattern:"cross-project",wiki_link:"cross-project",temporal_proximity:"same-project"},Th=2,yh=.25,wh=5,Rh=60,kh=25;function ms(e){return e.trim().toLowerCase()}function Ah(e){let t=new Set;for(let n of e.files_written)t.add(`file:${ms(n)}`);for(let n of e.brands_mentioned)t.add(`brand:${ms(n)}`);for(let n of e.terms_introduced)t.add(`term:${ms(n)}`);for(let n of e.plan_ids_referenced)t.add(`plan:${ms(n)}`);return t}function xh(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Rh);return Math.max(.2,t)}function Nh(e,t){if(!e||!t)return 0;let n=Date.parse(e),s=Date.parse(t);return!Number.isFinite(n)||!Number.isFinite(s)?0:Math.abs(s-n)/(1e3*60*60*24)}function Oh(e){let t=lt(e);return t?{session_id:t.session_id,files_written:t.files_written,brands_mentioned:t.brands_mentioned,terms_introduced:t.terms_introduced.map(n=>n.term),plan_ids_referenced:t.plan_ids_referenced}:null}function Lh(e){return h().prepare(`SELECT s.id AS id, s.project_id AS project_id, s.started_at AS started_at,
|
|
951
|
+
s.rowid AS rowid,
|
|
952
|
+
(SELECT COALESCE(MAX(m.rowid), 0) FROM messages m WHERE m.session_id = s.id)
|
|
953
|
+
AS maxMessageRowid
|
|
929
954
|
FROM sessions s
|
|
930
955
|
JOIN session_output_index oi ON oi.session_id = s.id
|
|
931
956
|
WHERE s.project_id = ?
|
|
932
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function
|
|
933
|
-
s.project_id
|
|
934
|
-
FROM messages m
|
|
935
|
-
JOIN sessions s ON s.id = m.session_id
|
|
936
|
-
WHERE s.project_id = ?
|
|
937
|
-
AND m.is_sidechain = 0
|
|
938
|
-
AND m.content_text IS NOT NULL
|
|
939
|
-
AND length(m.content_text) > 0`).all(e):t.prepare(`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
|
|
957
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function Ch(e,t){let n=new Map,s=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=Ah(a);if(c.size!==0){s.set(o.id,c);for(let u of c){let d=n.get(u);d?d.push(o.id):n.set(u,[o.id])}}}return{posting:n,vocab:s,startedAt:r}}function vh(e,t){let n=t.vocab.get(e),s=t.startedAt.get(e)??null;if(!n||!s)return[];let r=new Map;for(let a of n){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>=s)continue;let p=r.get(u);p?p.push(a):r.set(u,[a])}}let o=[];for(let[a,c]of r){let u=c.length;if(u<Th)continue;let d=t.startedAt.get(a)??null,p=Nh(s,d),f=xh(p),b=Math.min(1,u/wh*f);if(b<yh)continue;let T=c.slice(0,12);o.push({target_session_id:a,matched_terms:T,overlap:u,days_apart:Math.round(p*10)/10,recency:Math.round(f*1e3)/1e3,confidence:Math.round(b*1e3)/1e3})}return o.sort((a,c)=>c.confidence-a.confidence),o.slice(0,kh)}async function Xc(e){if(uo.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project \u2014 refusing to run inference");let t=Lh(e.projectId),n=new Map;for(let c of t){let u=Oh(c.id);u&&n.set(c.id,u)}let s=Ch(t,n),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 c of t){if(e.signal?.aborted)break;if(typeof e.sinceRowid=="number"&&c.maxMessageRowid<=e.sinceRowid)continue;r.current_session_id=c.id,e.onProgress?.({...r});let u=vh(c.id,s);for(let d of u)o.push({source_session_id:c.id,target_session_id:d.target_session_id,confidence:d.confidence,matched_terms:d.matched_terms,overlap:d.overlap,recency:d.recency,days_apart:d.days_apart});r.processed_sessions+=1,e.onProgress?.({...r})}let a=[];return o.length>0&&(h().transaction(()=>{for(let d of o)try{let p=gn({source_session_id:d.source_session_id,target_session_id:d.target_session_id,link_type:"citation",confidence:d.confidence,evidence:{matched_terms:d.matched_terms,overlap_count:d.overlap,recency:d.recency,days_apart:d.days_apart},inferred_by:"L2"},{deferMirror:!0});a.push(p.id),r.suggestions_created+=1}catch(p){console.error("[citation-inference] createSuggestion failed:",p)}})(),ct()),r.current_session_id=null,e.onProgress?.({...r}),{progress:r,suggestion_ids:a}}H();var Ih=/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,Mh=[/\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],Dh=.95,jh=.85;var po=50;function Ph(e){if(!e)return[];let t=new Set,n=[],s=e.match(Ih);if(!s)return n;for(let r of s){let o=r.toLowerCase();if(!t.has(o)&&(t.add(o),n.push(o),n.length>=po))break}return n}function Fh(e){if(!e)return[];let t=new Set,n=[];for(let s of Mh){s.lastIndex=0;let r=e.match(s);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),n.push(a),n.length>=po))break}if(n.length>=po)break}}return n}function Jc(e){let t=h();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 Gc(e,t){let n=h(),s=typeof t=="number";if(typeof e=="number"){let o=`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
|
|
940
958
|
s.project_id
|
|
941
959
|
FROM messages m
|
|
942
960
|
JOIN sessions s ON s.id = m.session_id
|
|
943
|
-
WHERE
|
|
961
|
+
WHERE s.project_id = ?
|
|
962
|
+
AND m.is_sidechain = 0
|
|
944
963
|
AND m.content_text IS NOT NULL
|
|
945
|
-
AND length(m.content_text) > 0
|
|
964
|
+
AND length(m.content_text) > 0`+(s?`
|
|
965
|
+
AND m.rowid > ?`:"");return s?n.prepare(o).all(e,t):n.prepare(o).all(e)}let r=`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
|
|
966
|
+
s.project_id
|
|
967
|
+
FROM messages m
|
|
968
|
+
JOIN sessions s ON s.id = m.session_id
|
|
969
|
+
WHERE m.is_sidechain = 0
|
|
970
|
+
AND m.content_text IS NOT NULL
|
|
971
|
+
AND length(m.content_text) > 0`+(s?`
|
|
972
|
+
AND m.rowid > ?`:"");return s?n.prepare(r).all(t):n.prepare(r).all()}function $h(e){let t=h(),n=typeof e=="number"?"WHERE s.project_id = ?":"",s=typeof e=="number"?[e]:[];return t.prepare(`SELECT oi.session_id AS session_id,
|
|
946
973
|
s.project_id AS project_id,
|
|
947
974
|
oi.plan_ids_referenced AS plan_ids_json
|
|
948
975
|
FROM session_output_index oi
|
|
949
976
|
JOIN sessions s ON s.id = oi.session_id
|
|
950
|
-
${n}`).all(...s)}function
|
|
951
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(n),console.log(`[semantic-config] gate flip: source=${t} value=${n} path=pooled at=${new Date().toISOString()}`);return}catch(r){s=r;let o=r instanceof Error?r.message:String(r);console.error(`[semantic-config] pooled getDb() failed while syncing semantic_enabled=${n}: ${o} \u2014 falling back to raw connection`)}try{let r=new
|
|
977
|
+
${n}`).all(...s)}function Uh(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(n=>typeof n=="string")}catch{}return[]}function Yc(e={}){if(uo.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project");let t=Jc(e.projectId),n=new Set(t.map(c=>c.id)),s=new Map;for(let c of t)s.set(c.id,c.project_id);let r=h(),o=0;return r.transaction(()=>{for(let c of Gc(e.projectId,e.sinceRowid)){if(e.signal?.aborted)break;let u=Ph(c.content_text);if(u.length===0)continue;let d=s.get(c.session_id);if(d!==void 0)for(let p of u){if(p===c.session_id)continue;let f=s.get(p);if(!(f===void 0&&!n.has(p))&&!(f!==void 0&&d!==void 0&&f!==d))try{gn({source_session_id:c.session_id,target_session_id:p,link_type:"citation",confidence:Dh,evidence:{matched_uuid:p,source_message_uuid:c.message_uuid,scanner:"uuid-ref"},inferred_by:"L1"},{deferMirror:!0}),o+=1}catch{}}}})(),o>0&&ct(),{created:o}}function zc(e={}){let t=$h(e.projectId);if(t.length===0)return{created:0};let n=new Map;for(let d of t){let p=Uh(d.plan_ids_json);for(let f of p){let b=f.trim().toLowerCase();if(!b)continue;let T=n.get(b);T?T.push({id:d.session_id,project_id:d.project_id}):n.set(b,[{id:d.session_id,project_id:d.project_id}])}}if(n.size===0)return{created:0};let s=Jc(e.projectId),r=new Map;for(let d of s)r.set(d.id,d.project_id);let o=0,a=new Map;for(let d of Gc(e.projectId,e.sinceRowid)){if(e.signal?.aborted)break;let p=Fh(d.content_text);if(p.length===0)continue;let f=r.get(d.session_id);if(f!==void 0)for(let b of p){let T=n.get(b);if(T)for(let S of T){if(S.id===d.session_id||S.project_id!==f)continue;let R=a.get(d.session_id);R||(R=new Map,a.set(d.session_id,R));let A=R.get(S.id);A||(A=new Set,R.set(S.id,A)),A.add(b)}}}return h().transaction(()=>{for(let[d,p]of a)for(let[f,b]of p)try{gn({source_session_id:d,target_session_id:f,link_type:"citation",confidence:jh,evidence:{matched_plan_ids:Array.from(b).slice(0,12),scanner:"plan-ref"},inferred_by:"L1"},{deferMirror:!0}),o+=1}catch{}})(),o>0&&ct(),{created:o}}H();we();import{existsSync as bE,mkdirSync as SE,writeFileSync as TE}from"node:fs";import{homedir as yE}from"node:os";import{join as bo}from"node:path";Yr();H();Z();import{existsSync as sl,mkdirSync as gE,readFileSync as fE,writeFileSync as _E}from"node:fs";import{homedir as hE}from"node:os";import{join as rl}from"node:path";import{z as Le}from"zod";function ol(){return process.env.RECALL_HOME??rl(hE(),".recall")}function EE(){let e=ol();sl(e)||gE(e,{recursive:!0})}function il(){return rl(ol(),"config.json")}var bs=Le.object({enabled:Le.boolean().default(!1),model:Le.string().optional(),ratePerMinute:Le.number().int().min(1).max(600).default(30),lastProcessedSessionId:Le.string().nullable().default(null),backfillPaused:Le.boolean().default(!1),autoExtractEnabled:Le.boolean().default(!1),autoExtractIntervalMinutes:Le.number().int().min(5).max(720).default(60),autoExtractBatchSize:Le.number().int().min(1).max(20).default(1),autoResumeWorker:Le.boolean().default(!1)}),Es={enabled:!1,ratePerMinute:30,lastProcessedSessionId:null,backfillPaused:!1,autoExtractEnabled:!1,autoExtractIntervalMinutes:60,autoExtractBatchSize:1,autoResumeWorker:!1};function al(){let e=il();if(!sl(e))return{};try{return JSON.parse(fE(e,"utf8"))}catch(t){return console.error("[semantic-config] failed to parse config.json, using defaults:",t),{}}}function ae(){let e=al().semantic;if(!e)return{...Es};let t=bs.safeParse({...Es,...e});return t.success?t.data:{...Es}}function Ss(e,t="unknown"){EE();let n=al(),s=bs.parse({...Es,...n.semantic??{},...e}),r={...n,semantic:s};return _E(il(),JSON.stringify(r,null,2)),Eo(s.enabled,t),s}function Eo(e,t="unknown"){let n=e?"1":"0",s=null;try{h().prepare(`INSERT INTO app_settings(key, value) VALUES ('semantic_enabled', ?)
|
|
978
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(n),console.log(`[semantic-config] gate flip: source=${t} value=${n} path=pooled at=${new Date().toISOString()}`);return}catch(r){s=r;let o=r instanceof Error?r.message:String(r);console.error(`[semantic-config] pooled getDb() failed while syncing semantic_enabled=${n}: ${o} \u2014 falling back to raw connection`)}try{let r=new Zn(ze);try{r.exec(`CREATE TABLE IF NOT EXISTS app_settings (
|
|
952
979
|
key TEXT PRIMARY KEY,
|
|
953
980
|
value TEXT NOT NULL
|
|
954
981
|
);`),r.prepare(`INSERT INTO app_settings(key, value) VALUES ('semantic_enabled', ?)
|
|
955
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(n),console.log(`[semantic-config] gate flip: source=${t} value=${n} path=fallback at=${new Date().toISOString()}`)}finally{r.close()}}catch(r){let o=r instanceof Error?r.message:String(r);console.error(`[semantic-config] raw-connection fallback ALSO failed for semantic_enabled=${n}: ${o} (original: ${s instanceof Error?s.message:String(s)})`)}}var
|
|
982
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(n),console.log(`[semantic-config] gate flip: source=${t} value=${n} path=fallback at=${new Date().toISOString()}`)}finally{r.close()}}catch(r){let o=r instanceof Error?r.message:String(r);console.error(`[semantic-config] raw-connection fallback ALSO failed for semantic_enabled=${n}: ${o} (original: ${s instanceof Error?s.message:String(s)})`)}}var wE=1,RE=12e3,cl=3,kE=[];function AE(){return process.env.RECALL_HOME??bo(yE(),".recall")}function ll(){return bo(AE(),"semantic")}function xE(){let e=ll();bE(e)||SE(e,{recursive:!0})}function NE(e){let t=h(),n=t.prepare(`SELECT s.id, s.message_count, s.first_user_message,
|
|
956
983
|
p.name AS project,
|
|
957
984
|
NULLIF(sa.alias, '') AS alias
|
|
958
985
|
FROM sessions s
|
|
@@ -961,10 +988,10 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
961
988
|
WHERE s.id = ?`).get(e);if(!n)return null;let s=t.prepare(`SELECT role, content_text
|
|
962
989
|
FROM messages
|
|
963
990
|
WHERE session_id = ? AND is_sidechain = 0
|
|
964
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of s){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,p=`${c}: ${d}`;if(o+p.length>
|
|
991
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of s){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,p=`${c}: ${d}`;if(o+p.length>RE)break;r.push(p),o+=p.length}return{id:n.id,alias:n.alias,project:n.project,firstUserMessage:n.first_user_message,excerpt:r.join(`
|
|
965
992
|
|
|
966
|
-
`),messageCount:n.message_count}}function
|
|
967
|
-
`)}function
|
|
993
|
+
`),messageCount:n.message_count}}function OE(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(`
|
|
994
|
+
`)}function LE(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 n=t.indexOf("{"),s=t.lastIndexOf("}");if(n===-1||s===-1||s<=n)return null;let r=t.slice(n,s+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 CE(e){let t=h(),n=e.keywords.join(",");t.prepare(`INSERT INTO session_semantic
|
|
968
995
|
(session_id, summary, keywords, model, source_message_count, generated_at)
|
|
969
996
|
VALUES (@session_id, @summary, @keywords, @model, @source_message_count, @generated_at)
|
|
970
997
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
@@ -972,16 +999,16 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
972
999
|
keywords = excluded.keywords,
|
|
973
1000
|
model = excluded.model,
|
|
974
1001
|
source_message_count = excluded.source_message_count,
|
|
975
|
-
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:n,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),
|
|
1002
|
+
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:n,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),xE();let s=bo(ll(),`${e.sessionId}.json`);TE(s,JSON.stringify({version:wE,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 Ts=null;function vE(){let t=ae().ratePerMinute,n=t/6e4;return(!Ts||Ts.capacity!==t)&&(Ts={tokens:t,capacity:t,refillPerMs:n,lastRefill:Date.now()}),Ts}function IE(e){let t=Date.now(),n=t-e.lastRefill;n>0&&(e.tokens=Math.min(e.capacity,e.tokens+n*e.refillPerMs),e.lastRefill=t)}async function ME(e){for(;;){if(e?.aborted)throw new Error("aborted");let t=vE();if(IE(t),t.tokens>=1){t.tokens-=1;return}let n=1-t.tokens,s=Math.max(50,Math.ceil(n/t.refillPerMs));await new Promise(r=>setTimeout(r,Math.min(s,5e3)))}}async function ys(e,t={}){let n=ae();if(!n.enabled)return{sessionId:e,ok:!1,reason:"disabled"};if(!me())return{sessionId:e,ok:!1,reason:"claude-cli-missing"};let s=NE(e);if(!s)return{sessionId:e,ok:!1,reason:"session-not-found"};if(s.messageCount<cl)return{sessionId:e,ok:!1,reason:"too-short"};if(!s.excerpt.trim())return{sessionId:e,ok:!1,reason:"empty-excerpt"};await ME(t.signal);let r=OE(s),o=await dt(r,kE,{model:n.model});if(!o.success)return{sessionId:e,ok:!1,reason:`claude-cli-exit-${o.exitCode??"null"}`,model:n.model??null};let a=LE(o.stdout);return a?(CE({sessionId:s.id,summary:a.summary,keywords:a.keywords,model:n.model??null,sourceMessageCount:s.messageCount,generatedAt:new Date().toISOString()}),{sessionId:e,ok:!0,model:n.model??null}):{sessionId:e,ok:!1,reason:"parse-failed",model:n.model??null}}async function ws(e={}){let t=ae();if(!t.enabled)return{total:0,processed:0,ok:0,failed:0,currentSessionId:null};let n=h(),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=n.prepare(`SELECT s.id
|
|
976
1003
|
FROM sessions s
|
|
977
1004
|
LEFT JOIN session_semantic ss ON ss.session_id = s.id
|
|
978
1005
|
WHERE ${o}
|
|
979
1006
|
ORDER BY COALESCE(s.started_at, '') ASC, s.id ASC
|
|
980
|
-
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
|
|
1007
|
+
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 ys(u,{signal:e.signal})).ok?c.ok+=1:c.failed+=1}catch(p){c.failed+=1,console.error("[semantic.backfill] failed for",u,p)}c.processed+=1,Ss({lastProcessedSessionId:u},"pipeline"),e.onProgress?.({...c})}return c.currentSessionId=null,e.onProgress?.({...c}),c}async function ul(e){if(!ae().enabled)return;let s=h().prepare(`SELECT s.message_count, s.ended_at,
|
|
981
1008
|
ss.generated_at, ss.source_message_count
|
|
982
1009
|
FROM sessions s
|
|
983
1010
|
LEFT JOIN session_semantic ss ON ss.session_id = s.id
|
|
984
|
-
WHERE s.id = ?`).get(e);if(s&&!(s.message_count<
|
|
1011
|
+
WHERE s.id = ?`).get(e);if(s&&!(s.message_count<cl)&&!(s.generated_at&&s.source_message_count!=null&&s.source_message_count>=s.message_count))try{await ys(e)}catch(r){console.error("[semantic] processSession error for",e,r)}}function So(){let e=ae(),t=h(),n=t.prepare("SELECT COUNT(*) AS n FROM sessions WHERE message_count >= 3").get().n,s=t.prepare("SELECT COUNT(*) AS n FROM session_semantic").get().n;return{enabled:e.enabled,claudeCliAvailable:me(),ratePerMinute:e.ratePerMinute,model:e.model??null,totalSessions:n,processedSessions:s,pendingSessions:Math.max(0,n-s),lastProcessedSessionId:e.lastProcessedSessionId,backfillPaused:e.backfillPaused}}H();import{createHash as FE}from"node:crypto";var DE=[{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 jE(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))),n=e.slice(-Math.min(4,Math.floor(e.length/4)));return`${t}${"\u2022".repeat(Math.max(3,e.length-t.length-n.length))}${n}`}function PE(e){let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+e.charCodeAt(n)|0;return(t>>>0).toString(36)}function be(e){if(!e)return{redacted:e,count:0};let t=e,n=0,s=new Set;for(let r of DE)r.regex.lastIndex=0,t=t.replace(r.regex,o=>{let a=`${r.name}::${PE(o)}`;return s.has(a)||(s.add(a),n+=1),`[REDACTED ${r.name}: ${jE(o)}]`});return{redacted:t,count:n}}we();var wo=1,mt="claude-haiku-4-5-20251001",$E=3,UE=32e3,dl=2e3,HE=30,BE=30,WE=30,qE=30;function XE(e){let n=h().prepare(`SELECT s.id,
|
|
985
1012
|
NULLIF(sa.alias, '') AS alias,
|
|
986
1013
|
s.auto_title,
|
|
987
1014
|
s.auto_title_source,
|
|
@@ -992,15 +1019,15 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
992
1019
|
FROM sessions s
|
|
993
1020
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
994
1021
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
995
|
-
WHERE s.id = ?`).get(e);return n?{...n,alias_source:n.alias?"manual":null}:null}function
|
|
1022
|
+
WHERE s.id = ?`).get(e);return n?{...n,alias_source:n.alias?"manual":null}:null}function pl(e,t={}){if(e.message_count<$E)return{eligible:!1,reason:"too-short"};let n=e.title_quality;if(n==="programmatic"||n==="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 s=lt(e.id);if(s&&s.extractor_version>=wo)return{eligible:!1,reason:"already-extracted"}}return{eligible:!0}}function JE(e){let t=XE(e);if(!t)return null;let s=h().prepare(`SELECT role, content_text
|
|
996
1023
|
FROM messages
|
|
997
1024
|
WHERE session_id = ?
|
|
998
1025
|
AND is_sidechain = 0
|
|
999
1026
|
AND content_text IS NOT NULL
|
|
1000
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of s){let c=a.role??"system",u=a.content_text.trim();if(!u)continue;let d=u.length>
|
|
1027
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of s){let c=a.role??"system",u=a.content_text.trim();if(!u)continue;let d=u.length>dl?u.slice(0,dl)+"\u2026":u,p=`${c}: ${d}`;if(o+p.length>UE)break;r.push(p),o+=p.length}return{meta:t,excerpt:r.join(`
|
|
1001
1028
|
|
|
1002
|
-
`)}}function
|
|
1003
|
-
`)}function
|
|
1029
|
+
`)}}function GE(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(`
|
|
1030
|
+
`)}function YE(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function To(e,t,n=!1){if(!Array.isArray(e))return[];let s=new Set,r=[];for(let o of e){if(typeof o!="string")continue;let a=n?o.trim().toLowerCase():o.trim();if(!(!a||a.length>256)&&!s.has(a)&&(s.add(a),r.push(a),r.length>=t))break}return r}function zE(e,t){if(!Array.isArray(e))return[];let n=new Set,s=[];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||n.has(c))continue;n.add(c);let u=typeof a=="number"&&Number.isFinite(a)&&a>0?Math.floor(a):1;if(s.push({term:c,frequency:u}),s.length>=t)break}return s}function KE(e,t){if(!Array.isArray(e))return[];let n=[];for(let s of e){if(!s||typeof s!="object")continue;let r=s.error_type,o=s.snippet,a=s.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,p=FE("sha256").update(`${c}::${u}`).digest("hex").slice(0,12);if(n.push({error_type:c,message_hash:p,snippet:u,file:d}),n.length>=t)break}return n}function VE(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 n=t.indexOf("{"),s=t.lastIndexOf("}");if(n===-1||s===-1||s<=n)return null;let r=t.slice(n,s+1),o;try{o=JSON.parse(r)}catch{return null}let a=To(o.files_written,200),c=To(o.brands_mentioned,BE),u=zE(o.terms_introduced,HE),d=To(o.plan_ids_referenced,WE),p=KE(o.bug_signatures,qE);return a.length===0&&c.length===0&&u.length===0&&d.length===0&&p.length===0&&!YE(o.files_written)?null:{files_written:a,brands_mentioned:c,terms_introduced:u,plan_ids_referenced:d,bug_signatures:p}}var yo=null;async function QE(e,t){return yo?yo(e,t):dt(e,[],{model:t})}async function Ro(e,t={}){if(t.signal?.aborted)return{session_id:e,ok:!1,failed:"aborted"};let n=JE(e);if(!n)return{session_id:e,ok:!1,skipped:"session-not-found"};let s=pl(n.meta,{force:t.force});if(!s.eligible)return{session_id:e,ok:!1,skipped:s.reason};if(!yo&&!me())return{session_id:e,ok:!1,failed:"claude-cli-missing"};let r=GE(n),o=t.model??mt,a=await QE(r,o);if(!a.success){let p=(a.stderr||a.stdout||"").slice(0,400),f=p?be(p).redacted:void 0;return{session_id:e,ok:!1,failed:"claude-cli-error",exit_code:a.exitCode,stderr_excerpt:f}}let c=VE(a.stdout);if(!c){let p=a.stdout.slice(0,400),f=p?be(p).redacted:void 0;return{session_id:e,ok:!1,failed:"parse-failed",exit_code:a.exitCode,stderr_excerpt:f}}let u=ZE(a.stdout),d=qc({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:wo});return{session_id:e,ok:!0,index:d,usage:u}}function ZE(e){try{let t=JSON.parse(e.trim());if(t&&typeof t=="object"&&t.usage){let n=t.usage,s={};return typeof n.input_tokens=="number"&&(s.input_tokens=n.input_tokens),typeof n.output_tokens=="number"&&(s.output_tokens=n.output_tokens),s}}catch{}return null}function Ot(e={}){let t=h(),n=[],s=[];typeof e.projectId=="number"&&(n.push("s.project_id = ?"),s.push(e.projectId));let r=Math.max(1,Math.min(1e4,e.limit??1e3)),o=n.length?`WHERE ${n.join(" AND ")}`:"",a=t.prepare(`SELECT s.id,
|
|
1004
1031
|
NULLIF(sa.alias, '') AS alias,
|
|
1005
1032
|
s.auto_title,
|
|
1006
1033
|
s.auto_title_source,
|
|
@@ -1012,33 +1039,45 @@ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
|
1012
1039
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1013
1040
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1014
1041
|
${o}
|
|
1015
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...s),c=[],u=new Map;for(let d of a){let p={...d,alias_source:d.alias?"manual":null},f=
|
|
1042
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...s),c=[],u=new Map;for(let d of a){let p={...d,alias_source:d.alias?"manual":null},f=pl(p,{force:e.force});if(!f.eligible){let b=f.reason??"session-not-found";u.set(b,(u.get(b)??0)+1);continue}if(c.push(p),c.length>=r)break}return{eligible:c,skipped:u}}async function ml(e={}){let t=Ot({projectId:e.projectId,limit:e.limit,force:e.force}),n={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())n.skipped+=r;e.onProgress?.({...n});let s=[];for(let r of t.eligible){if(e.signal?.aborted)break;n.current_session_id=r.id,e.onProgress?.({...n});let o=await Ro(r.id,{model:e.model,force:e.force,signal:e.signal});s.push(o),e.onResult?.(o),o.ok?n.ok+=1:o.skipped?n.skipped+=1:n.failed+=1,o.usage?.input_tokens&&(n.total_input_tokens+=o.usage.input_tokens),o.usage?.output_tokens&&(n.total_output_tokens+=o.usage.output_tokens),n.processed+=1,e.onProgress?.({...n})}return n.current_session_id=null,e.onProgress?.({...n}),{progress:n,results:s}}we();var re="[daemon:inference]",Lo=!1,Co=!1,vo=!1,Io=!1,Mo=!1,kl=0,Do=!1,jo=!1,Ns=3,gt=0,Lt=!1,Os=null,Ct=null,Al=!1;function fb(){return h().prepare("SELECT id, name FROM projects").all()}async function xl(){if(Lo)return;Lo=!0;let e=Date.now();try{let n=(await Cc({minClusterSize:2})).progress;(n.clusters_created||n.clusters_merged||n.members_added)&&console.log(`${re} bug-patterns: created=${n.clusters_created} merged=${n.clusters_merged} members_added=${n.members_added} (${Date.now()-e}ms)`)}catch(t){console.error(`${re} bug-patterns failed:`,t)}finally{Lo=!1}}async function Nl(){if(Co)return;Co=!0;let e=Date.now();try{let t=h(),n=Dl(t,Ll),s=t.prepare("SELECT COALESCE(MAX(rowid), 0) AS m FROM messages").get(),r=0,o=0,a=!1;for(let c of fb())try{let u=await Xc({projectId:c.id,sinceRowid:n});r+=u.progress.suggestions_created,o+=1}catch(u){a=!0,console.error(`${re} citations failed for project "${c.name}":`,u)}a||jl(t,Ll,s.m),r>0&&console.log(`${re} citations: ${r} suggestion(s) across ${o} project(s) since_rowid=${n} (${Date.now()-e}ms)`)}catch(t){console.error(`${re} citations failed:`,t)}finally{Co=!1}}var Ol="l1_inference_watermark_rowid",Ll="citations_inference_watermark_rowid";function Dl(e,t){let n=e.prepare("SELECT value FROM app_settings WHERE key = ?").get(t);if(!n)return 0;let s=Number(n.value);return Number.isFinite(s)&&s>=0?s:0}function jl(e,t,n){e.prepare("INSERT INTO app_settings(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(t,String(n))}function Cl(){if(vo)return;vo=!0;let e=Date.now();try{let t=h(),n=Dl(t,Ol),s=t.prepare("SELECT COALESCE(MAX(rowid), 0) AS m FROM messages").get(),r=Yc({sinceRowid:n}),o=zc({sinceRowid:n});jl(t,Ol,s.m),r.created+o.created>0&&console.log(`${re} l1: uuid=${r.created} plan=${o.created} since_rowid=${n} (${Date.now()-e}ms)`)}catch(t){console.error(`${re} l1 failed:`,t)}finally{vo=!1}}async function vl(){if(Io)return;let e=ae();if(!e.enabled||e.backfillPaused)return;Io=!0;let t=Date.now();try{let n=await ws({limit:25});n.processed>0&&console.log(`${re} backfill: processed=${n.processed} ok=${n.ok} failed=${n.failed} (${Date.now()-t}ms)`)}catch(n){console.error(`${re} backfill failed:`,n)}finally{Io=!1}}async function Il(){if(Mo)return;let e=ae();if(e.autoExtractEnabled&&!Al&&Lt&&(_b(),console.log(`${re} auto-extract: circuit breaker reset (config toggled on).`)),Al=e.autoExtractEnabled,!e.autoExtractEnabled||Lt)return;let t=e.autoExtractIntervalMinutes*60*1e3;if(Date.now()-kl<t)return;if(!me()){Do||(console.log(`${re} auto-extract: claude CLI not on PATH \u2014 pausing nibbler (will retry; install Claude Code or run \`recall semantic auto-extract off\` to silence)`),Do=!0);return}if(Do=!1,!await tc().catch(()=>!1)){jo||(console.log(`${re} auto-extract: Pro license required \u2014 pausing nibbler (run \`recall semantic auto-extract off\` to silence; upgrade unlocks)`),jo=!0);return}jo=!1,Mo=!0,kl=Date.now();let s=Date.now();try{let r=e.autoExtractBatchSize,{eligible:o}=Ot({limit:r});if(o.length===0)return;let a=0,c=0,u=0,d=0,p=new Map,f=null;for(let S of o){let R=await Ro(S.id,{model:mt});if(R.ok)a+=1;else if(!R.skipped){c+=1;let A=R.failed??"unknown";p.set(A,(p.get(A)??0)+1),!f&&R.stderr_excerpt&&(f=R.stderr_excerpt)}R.usage?.input_tokens&&(u+=R.usage.input_tokens),R.usage?.output_tokens&&(d+=R.usage.output_tokens)}let b=p.size>0?" reasons="+Array.from(p.entries()).map(([S,R])=>`${S}:${R}`).join(","):"";if(console.log(`${re} auto-extract: processed=${o.length} ok=${a} failed=${c} tokens=${u}+${d} (${Date.now()-s}ms)${b}`),f){let R=be(f).redacted.replace(/\s+/g," ").trim().slice(0,300);console.log(`${re} auto-extract: first failure excerpt: ${R}`)}o.length>0&&u===0&&d===0?(gt+=1,gt>=Ns&&(Lt=!0,Os=Date.now(),Ct=`${Ns} consecutive zero-token runs (claude CLI returning no usage)`,console.log(`${re} auto-extract: CIRCUIT BREAKER tripped \u2014 ${Ct}. Pausing nibbler. Run \`recall semantic auto-extract off\` then \`... on\` to reset, or restart the daemon.`))):(a>0||u>0||d>0)&&(gt=0)}catch(r){console.error(`${re} auto-extract failed:`,r),gt+=1,gt>=Ns&&(Lt=!0,Os=Date.now(),Ct=`${Ns} consecutive thrown errors`,console.log(`${re} auto-extract: CIRCUIT BREAKER tripped \u2014 ${Ct}. Pausing nibbler.`))}finally{Mo=!1}}function Pl(){return{broken:Lt,brokenAt:Os,reason:Ct,consecutiveZeroTokenRuns:gt}}function _b(){Lt=!1,Os=null,Ct=null,gt=0}var Po=!1;async function Ml(){if(Po)return;let{readRetentionConfig:e,writeRetentionConfig:t}=await Promise.resolve().then(()=>(xo(),bl)),n=e();if(!n.autoArchiveEnabled)return;let s=1380*60*1e3;if(n.lastRunAt&&Date.now()-Date.parse(n.lastRunAt)<s)return;Po=!0;let r=Date.now();try{let a=new Date(Date.now()-n.autoArchiveAfterDays*24*60*60*1e3).toISOString().slice(0,10),{runArchive:c}=await Promise.resolve().then(()=>(Rl(),wl)),u=await c({_action:"run",before:a,dryRun:!1});t({lastRunAt:new Date().toISOString()}),console.log(`${re} auto-archive: cutoff=${a} exit=${u} (${Date.now()-r}ms)`)}catch(o){let a=o instanceof Error?o.message:String(o);console.error(`${re} auto-archive failed: ${a}`)}finally{Po=!1}}function Fl(){let p=[],f=[];p.push(setTimeout(()=>{xl()},9e4)),f.push(setInterval(()=>{xl()},18e5)),p.push(setTimeout(()=>{Nl()},18e4)),f.push(setInterval(()=>{Nl()},36e5)),p.push(setTimeout(()=>Cl(),12e4)),f.push(setInterval(()=>Cl(),18e5)),p.push(setTimeout(()=>{vl()},24e4)),f.push(setInterval(()=>{vl()},9e5)),p.push(setTimeout(()=>{Il()},3e5)),f.push(setInterval(()=>{Il()},9e5));let b=3600*1e3,T=300*1e3;return p.push(setTimeout(()=>{Ml()},T)),f.push(setInterval(()=>{Ml()},b)),console.log(`${re} scheduled: bug-patterns (30m), citations (60m), l1 (30m), backfill (15m, when enabled), auto-extract (60m, when enabled), auto-archive (24h, when enabled)`),{startupTimers:p,intervalTimers:f,stop:()=>{for(let S of p)clearTimeout(S);for(let S of f)clearInterval(S)}}}H();import{serveStatic as lf}from"@hono/node-server/serve-static";import{existsSync as uA,readFileSync as ja}from"node:fs";import{stat as dA,readFile as pA,realpath as mA}from"node:fs/promises";import{timingSafeEqual as gA}from"node:crypto";import{homedir as fA}from"node:os";import{dirname as $a,join as Br}from"node:path";import{fileURLToPath as Ua}from"node:url";import{existsSync as Di,readdirSync as dw,readFileSync as Yd,statSync as pw,statfsSync as zd}from"node:fs";import{homedir as mw}from"node:os";import{join as Kd}from"node:path";xs();H();es();Z();Z();H();import{watch as zT}from"chokidar";import{readdirSync as KT,statSync as $t}from"node:fs";import{createReadStream as hb}from"node:fs";function Ls(e){return typeof e=="number"&&Number.isFinite(e)?e:0}function Uo(e){let t=e?.usage;if(!t||typeof t!="object")return null;let n={inputTokens:Ls(t.input_tokens),outputTokens:Ls(t.output_tokens),cacheCreateTokens:Ls(t.cache_creation_input_tokens),cacheReadTokens:Ls(t.cache_read_input_tokens)};return n.inputTokens===0&&n.outputTokens===0&&n.cacheCreateTokens===0&&n.cacheReadTokens===0?null:n}var Eb=/\x1B\[[0-9;]*[a-zA-Z]/g;function Fo(e){return e.replace(Eb,"")}var $o=12e3;function $l(e,t){if(e.length<=$o)return e;let n=e.slice(0,$o),s=e.length-$o;return`${n}
|
|
1016
1043
|
|
|
1017
|
-
\u27E8\u2026 ${s.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function
|
|
1018
|
-
`)}return""}function
|
|
1044
|
+
\u27E8\u2026 ${s.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function bb(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function Sb(e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content)){let t=[];for(let n of e.content)if(n&&typeof n=="object"){let s=n;s.type==="text"&&typeof s.text=="string"?t.push(s.text):s.type==="image"&&t.push("[image]")}return t.join(`
|
|
1045
|
+
`)}return""}function Tb(e){if(!e)return{text:"",toolNames:[]};if(typeof e.content=="string")return{text:Fo(e.content),toolNames:[]};if(!Array.isArray(e.content))return{text:"",toolNames:[]};let t=[],n=[];for(let s of e.content)if(!(!s||typeof s!="object")){if(s.type==="text"&&typeof s.text=="string"){t.push(Fo(s.text));continue}if(s.type==="tool_use"&&typeof s.name=="string"){n.push(s.name);let r=s.input!=null?bb(s.input):"",o=$l(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${s.name}\`**
|
|
1019
1046
|
|
|
1020
1047
|
\`\`\`json
|
|
1021
1048
|
${o}
|
|
1022
|
-
\`\`\``);continue}if(s.type==="tool_result"){let r=
|
|
1049
|
+
\`\`\``);continue}if(s.type==="tool_result"){let r=Fo(Sb(s));if(r){let o=$l(r,"tool result");t.push(`**Tool result**
|
|
1023
1050
|
|
|
1024
1051
|
\`\`\`
|
|
1025
1052
|
${o}
|
|
1026
1053
|
\`\`\``)}else t.push("_(tool result was empty or image-only)_");continue}if(s.type==="image"){t.push("_(image)_");continue}t.push(`_(unknown block: ${s.type})_`)}return{text:t.join(`
|
|
1027
1054
|
|
|
1028
|
-
`),toolNames:n}}
|
|
1055
|
+
`),toolNames:n}}function yb(e){if(e.endsWith("\r")&&(e=e.slice(0,-1)),!e.trim())return null;let t;try{t=JSON.parse(e)}catch{return null}if(!t.uuid||!t.sessionId)return null;let{text:n,toolNames:s}=Tb(t.message);return{uuid:t.uuid,parentUuid:t.parentUuid??null,sessionId:t.sessionId,type:t.type??"unknown",role:t.message?.role??null,timestamp:t.timestamp??null,isSidechain:t.isSidechain===!0,cwd:t.cwd??null,gitBranch:t.gitBranch??null,version:t.version??null,contentText:n,toolNames:s,raw:e,usage:Uo(t.message),model:t.message?.model??null}}async function Ul(e,t){let n=[];await new Promise((u,d)=>{let p=hb(e,{start:t});p.on("data",f=>{n.push(f)}),p.on("end",u),p.on("error",d)});let s=Buffer.concat(n),r=s.lastIndexOf(10);if(r===-1)return{entries:[],endOffset:t};let o=r+1,a=s.subarray(0,o).toString("utf8"),c=[];for(let u of a.split(`
|
|
1056
|
+
`)){let d=yb(u);d!==null&&c.push(d)}return{entries:c,endOffset:t+o}}function Hl(e,t,n){return e?t.inode!==e.inode?{mode:"full"}:t.size<e.byteOffset?{mode:"full"}:n?{mode:"incremental",start:e.byteOffset}:{mode:"full"}:{mode:"full"}}import{createHash as Bl}from"node:crypto";import{openSync as wb,readSync as Rb,closeSync as kb}from"node:fs";var Wl=4096;function Ho(e,t=Wl,n=Wl){let s=Math.max(0,Math.min(n,t));if(s===0)return Bl("sha256").update(Buffer.alloc(0)).digest("hex");let r=wb(e,"r");try{let o=Buffer.allocUnsafe(s),a=Rb(r,o,0,s,0);return Bl("sha256").update(o.subarray(0,a)).digest("hex")}finally{kb(r)}}function ql(e,t){let n=e.prepare("SELECT byte_offset, size_bytes, inode, prefix_hash FROM file_cursor WHERE file_path = ? LIMIT 1").get(t);return!n||n.inode==null||n.prefix_hash==null?null:{byteOffset:n.byte_offset,sizeBytes:n.size_bytes,inode:n.inode,prefixHash:n.prefix_hash}}function Xl(e,t,n){e.prepare(`
|
|
1057
|
+
INSERT INTO file_cursor (file_path, byte_offset, size_bytes, line_count, inode, prefix_hash, mtime, updated_at)
|
|
1058
|
+
VALUES (@file_path, @byte_offset, @size_bytes, @line_count, @inode, @prefix_hash, @mtime, datetime('now'))
|
|
1059
|
+
ON CONFLICT(file_path) DO UPDATE SET
|
|
1060
|
+
byte_offset = excluded.byte_offset,
|
|
1061
|
+
size_bytes = excluded.size_bytes,
|
|
1062
|
+
line_count = excluded.line_count,
|
|
1063
|
+
inode = excluded.inode,
|
|
1064
|
+
prefix_hash = excluded.prefix_hash,
|
|
1065
|
+
mtime = excluded.mtime,
|
|
1066
|
+
updated_at = datetime('now')
|
|
1067
|
+
`).run({file_path:t,byte_offset:n.byteOffset,size_bytes:n.sizeBytes,line_count:n.lineCount,inode:n.inode,prefix_hash:n.prefixHash,mtime:n.mtime})}import{basename as nr,join as VT}from"node:path";import{execFile as Yb}from"node:child_process";import{promisify as zb}from"node:util";import{existsSync as Kb}from"node:fs";import{basename as Vb}from"node:path";Z();import{existsSync as Ab,readFileSync as xb,writeFileSync as Nb}from"node:fs";import{join as Ob}from"node:path";import{z as fe}from"zod";var Bo=Ob(W,"terminals.json"),Jl=1440*60*1e3,Lb=3e4,Cb=6e4,vb=fe.object({shell_pid:fe.number(),tab_name:fe.string(),cwd:fe.string().nullable().optional(),opened_at:fe.string(),last_seen_at:fe.string()}),Ib=fe.object({schema:fe.string().optional(),saved_at:fe.string().optional(),terminals:fe.array(vb).max(500).default([]),sessions_by_pid:fe.record(fe.string(),fe.array(fe.string()).max(50)).optional().default({})}),Gl=/^[⠀-⣿✳\s]+/,Yl=/^\d+(\.\d+){1,3}$/;function ue(e){let t=e.trim();return!!(!t||Gl.test(t)||Yl.test(t))}function vt(e){let t=e.trim();if(!t||Yl.test(t))return null;let n=t.replace(Gl,"").trim();return n.length>0?n:null}function Wo(e,t){if(!ue(e))return e;let n=vt(e);return n||(t&&!ue(t)?t:e)}function Mb(e){let t=e.now-e.withinMs,n=e.pending.filter(s=>{let r=Date.parse(s.started_at);return Number.isFinite(r)&&r>=t});if(n.length===0)return{kind:"none"};if(e.shellPid!=null){let s=n.find(r=>r.shell_pid===e.shellPid);if(s)return{kind:"pid-match",entry:s}}if(e.cwd){let s=e.cwd.replace(/\/+$/,""),r=n.filter(o=>o.cwd&&o.cwd.replace(/\/+$/,"")===s);if(r.length===1)return{kind:"singleton-cwd",entry:r[0]};if(r.length>=2)return{kind:"ambiguous",candidates:r}}return{kind:"none"}}var qo=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,!!Ab(Bo)))try{let t=xb(Bo,"utf8"),n=JSON.parse(t),s=Ib.safeParse(n);if(!s.success){console.warn("[terminal-registry] terminals.json failed validation, starting with empty registry:",s.error.issues);return}let r=s.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{K();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(([n,s])=>[String(n),Array.from(s)]))};Nb(Bo,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let n=new Date().toISOString(),s=this.entries.get(t.shell_pid),r=Wo(t.tab_name,s?.tab_name),o=s?.opened_at??t.opened_at,a={...t,tab_name:r,opened_at:o,last_seen_at:n};return this.entries.set(t.shell_pid,a),this.gc(),this.save(),a}rename(t,n){this.ensureLoaded();let s=this.entries.get(t);if(!s)return null;let r=Wo(n,s.tab_name),o={...s,tab_name:r,last_seen_at:new Date().toISOString()};return this.entries.set(t,o),this.save(),o}remove(t){this.ensureLoaded();let n=this.entries.delete(t),s=this.sessionsByPid.delete(t);return this.outputTails.delete(t),this.pidOwnership.delete(t),(n||s)&&this.save(),n}claimPidOwnership(t,n,s=Date.now()){if(this.ensureLoaded(),!n)return"anonymous";let r=this.pidOwnership.get(t);return r?r.instance_id===n?(r.last_claim_at=s,"refreshed"):s-r.last_claim_at>Cb?(this.pidOwnership.set(t,{instance_id:n,last_claim_at:s}),"claimed"):"rejected":(this.pidOwnership.set(t,{instance_id:n,last_claim_at:s}),"claimed")}pidOwnershipSnapshot(){return this.ensureLoaded(),Array.from(this.pidOwnership.entries()).map(([t,n])=>({shell_pid:t,instance_id:n.instance_id,last_claim_at:n.last_claim_at}))}sync(t){this.ensureLoaded();let n=new Date().toISOString(),s=0,r=0;for(let o of t){let a=this.entries.get(o.shell_pid),c=Wo(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:n}),a?(a.tab_name!==c||a.cwd!==o.cwd)&&r++:s++}return this.gc(),this.save(),{added:s,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,n){this.ensureLoaded();let s=this.sessionsByPid.get(n);s||(s=new Set,this.sessionsByPid.set(n,s)),s.has(t)||(s.add(t),this.save())}sessionsFor(t){return this.ensureLoaded(),Array.from(this.sessionsByPid.get(t)??[])}isSessionAutoLinked(t){this.ensureLoaded();for(let n of this.sessionsByPid.values())if(n.has(t))return!0;return!1}unlinkSession(t){this.ensureLoaded();let n=!1;for(let[s,r]of this.sessionsByPid)r.delete(t)&&(n=!0,r.size===0&&this.sessionsByPid.delete(s));return n&&this.save(),n}pushPending(t){this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.push({...t})}takePendingMatched(t){this.ensureLoaded(),this.gcPending();let n=Mb({pending:this.pendingClaudeStarts,shellPid:t.shellPid,cwd:t.cwd,withinMs:t.withinMs,now:Date.now()});if(n.kind==="pid-match"||n.kind==="singleton-cwd"){let s=this.pendingClaudeStarts.indexOf(n.entry);s>=0&&this.pendingClaudeStarts.splice(s,1)}return n}pendingSize(){return this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.length}deferSessionLink(t,n,s,r){this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.set(t,{parent_shell_pid:n,queued_at:Date.now(),cwd:s,git_branch:r})}allDeferredLinks(){return this.ensureLoaded(),this.gcDeferredLinks(),Array.from(this.deferredLinks.entries()).map(([t,n])=>({session_id:t,...n}))}resolveDeferredLink(t){return this.deferredLinks.delete(t)}deferredLinkSize(){return this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.size}gcDeferredLinks(){let t=Date.now()-9e4;for(let[n,s]of this.deferredLinks)s.queued_at<t&&this.deferredLinks.delete(n)}gcPending(){let t=Date.now()-Lb;this.pendingClaudeStarts.length!==0&&(this.pendingClaudeStarts=this.pendingClaudeStarts.filter(n=>{let s=Date.parse(n.started_at);return Number.isFinite(s)&&s>=t}))}outputTails=new Map;setOutputTail(t,n,s){this.ensureLoaded(),this.outputTails.set(t,{text:n,captured_at:s})}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,n){this.ensureLoaded(),this.origins.set(t,n),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[n,s]of this.entries){let r=Date.parse(s.last_seen_at);!Number.isNaN(r)&&r<t&&this.entries.delete(n)}}gcOrigins(){let t=Date.now()-Jl;for(let[n,s]of this.origins)s.detectedAt<t&&this.origins.delete(n)}reapStaleLinks(t=10080*60*1e3){this.ensureLoaded();let s=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>=s)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,n=0;for(let s of[...this.entries.keys()]){let r=!0;try{process.kill(s,0)}catch(a){a.code==="ESRCH"&&(r=!1)}if(r)continue;this.entries.delete(s),t++;let o=this.sessionsByPid.get(s);o&&(n+=o.size,this.sessionsByPid.delete(s)),this.outputTails.delete(s),this.pidOwnership.delete(s)}return(t||n)&&this.save(),{pruned_pids:t,pruned_sessions:n}}},D=new qo;H();Z();import{writeFileSync as Db}from"node:fs";import{join as jb}from"node:path";var Pb=jb(W,"aliases.json");function Xo(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Se(e){return h().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e)?.alias??null}function Fb(){return h().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:Xo(t.previous_aliases)}))}function _e(e,t){let n=t.trim();if(!n)throw new Error("alias must be non-empty");let s=h(),r=new Date().toISOString(),o=s.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),a=[];return o&&(a=Xo(o.previous_aliases),o.alias!==n&&a.push({alias:o.alias,replaced_at:r})),s.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
|
|
1029
1068
|
VALUES (?, ?, ?, ?)
|
|
1030
1069
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
1031
1070
|
alias = excluded.alias,
|
|
1032
1071
|
updated_at = excluded.updated_at,
|
|
1033
|
-
previous_aliases = excluded.previous_aliases`).run(e,n,r,JSON.stringify(a)),
|
|
1034
|
-
WHERE session_id = ?`).run(n,JSON.stringify(r),e),
|
|
1035
|
-
`),r=null,o=null,a=null;for(let c of s)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
|
|
1036
|
-
`)){let s=n.trim();if(!s)continue;let r=s.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
|
|
1072
|
+
previous_aliases = excluded.previous_aliases`).run(e,n,r,JSON.stringify(a)),zl(),{session_id:e,alias:n,updated_at:r,previous_aliases:a}}function Cs(e){let t=h(),n=new Date().toISOString(),s=t.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e);if(!s)return;let r=Xo(s.previous_aliases);r.push({alias:s.alias,replaced_at:n}),t.prepare(`UPDATE session_aliases SET alias = '', updated_at = ?, previous_aliases = ?
|
|
1073
|
+
WHERE session_id = ?`).run(n,JSON.stringify(r),e),zl()}function zl(){try{K();let e=Fb(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};Db(Pb,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}H();import{execFile as $b}from"node:child_process";import{readFile as Ub}from"node:fs/promises";import{promisify as Hb}from"node:util";var Bb=Hb($b),Kl=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function Wb(e){let t={};for(let n of Kl){let s=e[n];typeof s=="string"&&s.length>0&&(t[n]=s)}return t}function qb(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 n=e.TERM_PROGRAM;return n==="WarpTerminal"?{editor:"warp",label:"Warp",detectedAt:t}:n==="iTerm.app"?{editor:"iterm",label:"iTerm",detectedAt:t}:n==="Apple_Terminal"?{editor:"apple-terminal",label:"Terminal",detectedAt:t}:n==="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 Xb(e){if(process.platform==="linux")try{let t=await Ub(`/proc/${e}/environ`,"utf8");return Jb(t)}catch{return{}}try{let{stdout:t}=await Bb("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return Gb(t)}catch{return{}}}function Jb(e){let t={};for(let n of e.split("\0")){if(!n)continue;let s=n.indexOf("=");if(s<=0)continue;let r=n.slice(0,s),o=n.slice(s+1);t[r]=o}return t}function Gb(e){let t={},n=new Set(Kl),s=e.replace(/\s+/g," ").trim().split(" ");for(let r of s){let o=r.indexOf("=");if(o<=0)continue;let a=r.slice(0,o);n.has(a)&&(t[a]=r.slice(o+1))}return t}async function Vl(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await Xb(e),n=Wb(t);return qb(n)}catch{return null}}var Is=zb(Yb),vs;function Qb(){if(vs!==void 0)return vs;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(Kb(t))return vs=t,t;return vs=null,null}var Zb=3,eS=3600*1e3,Tn=new Map;function tu(){let e=D.all(),t=e.map(n=>n.shell_pid).sort((n,s)=>n-s).join(",");return`${e.length}:${t}`}function tS(e){let t=Tn.get(e);return t?Date.now()-t.lastAt>eS?(Tn.delete(e),!1):t.refusals<Zb?!1:t.fingerprint===tu():!1}function Ql(e){let t=tu(),n=Tn.get(e);n&&n.fingerprint===t?(n.refusals+=1,n.lastAt=Date.now()):Tn.set(e,{refusals:1,fingerprint:t,lastAt:Date.now()})}function nS(e){Tn.delete(e)}async function sS(e){let t=Qb();if(!t)return null;try{let{stdout:n}=await Is(t,["-Fpc",e],{timeout:2e3}),s=n.split(`
|
|
1074
|
+
`),r=null,o=null,a=null;for(let c of s)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 Is("/bin/ps",["-o","ppid=","-p",String(e)],{timeout:2e3}),n=Number(t.trim());return Number.isFinite(n)&&n>0?n:null}catch{return null}}async function rS(e){try{let{stdout:t}=await Is("/bin/ps",["-o","lstart=","-p",String(e)],{timeout:2e3}),n=Date.parse(t.trim());return Number.isFinite(n)?n:null}catch{return null}}var eu=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 pe(e){let t=e.trim().toLowerCase();if(!t)return!0;let n=t.replace(/^[-/]+/,"").replace(/\s*\(\d+\)\s*$/,"").trim();if(eu.has(n))return!0;if(/^[-/][\w./-]*$/.test(t)){let s=n.replace(/^.*\//,"");if(eu.has(s))return!0}return!1}function It(e){let t=e.tabName?.trim();return t&&!pe(t)&&!ue(t)?t:null}function oS(e){try{return h().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(e)??null}catch{return null}}async function Ms(e){let t=Vb(e,".jsonl");if(Se(t)||tS(t))return;let n=oS(t),s=await sS(e),r=s?await Zl(s):null,o=s?await Vl(s):null;o&&D.setOrigin(t,o);let a=null,c=null,u=null,d=D.takePendingMatched({shellPid:r,cwd:n?.cwd??null,withinMs:3e4});if(d.kind==="ambiguous"){console.log(`[correlator] ambiguous pending for ${t.slice(0,8)} (${d.candidates.length} candidates in ${n?.cwd??"?"}) \u2014 refusing to guess; heuristic title will display`),Ql(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=D.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 p=null;if(!a&&s){let S=s;for(let R=0;R<4&&S!=null;R++){let A=await Zl(S);if(!A)break;let I=D.get(A);if(I){a=I,c=A,u="ppid";break}p==null&&(p=A),S=A}}if(!a&&n?.cwd){let S=n.cwd.replace(/\/+$/,""),R=D.all().filter(A=>A.cwd&&A.cwd.replace(/\/+$/,"")===S);R.length===1?(a=R[0],c=a.shell_pid,u="cwd"):R.length>=2&&(console.log(`[correlator] ${R.length} registered terminals in ${S} for ${t.slice(0,8)} \u2014 refusing to guess; heuristic title will display`),Ql(t))}let f=null;if(a?.tab_name&&!pe(a.tab_name)&&!ue(a.tab_name))f=a.tab_name;else if(a?.tab_name&&ue(a.tab_name)){let S=vt(a.tab_name);S&&!pe(S)&&(f=S)}if(!f&&!(u==="pending-pid"||u==="pending-cwd")&&a&&n?.cwd){let S=n.cwd.replace(/\/+$/,""),R=D.all().filter(A=>A.shell_pid!==c&&A.cwd&&A.cwd.replace(/\/+$/,"")===S&&!pe(A.tab_name)&&!ue(A.tab_name)).sort((A,I)=>Date.parse(I.last_seen_at)-Date.parse(A.last_seen_at))[0];R&&(f=R.tab_name)}let T=It({tabName:f,origin:o,cwd:n?.cwd??null,gitBranch:n?.git_branch??null});if(p!=null&&!c&&D.deferSessionLink(t,p,n?.cwd??null,n?.git_branch??null),!!T)try{_e(t,T),nS(t);let S=!!f&&!pe(f)&&!ue(f)&&T===f.trim();c!=null&&D.linkSession(t,c);let R=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 ${R}, 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 iS(){try{let{stdout:e}=await Is("/bin/ps",["-eo","pid=,ppid=,comm="],{timeout:2e3}),t=new Map;for(let n of e.split(`
|
|
1075
|
+
`)){let s=n.trim();if(!s)continue;let r=s.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 aS=9e4;function cS(e){let t=(Date.now()-aS)/1e3,n=e.replace(/\/+$/,"");return h().prepare(`SELECT s.id, NULLIF(sa.alias, '') AS alias, s.started_at AS started_at
|
|
1037
1076
|
FROM sessions s
|
|
1038
1077
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1039
|
-
WHERE s.cwd = ? AND s.file_mtime > ?`).all(n,t).map(r=>({id:r.id,alias:r.alias,started_at_ms:r.started_at?Date.parse(r.started_at):null}))}async function
|
|
1078
|
+
WHERE s.cwd = ? AND s.file_mtime > ?`).all(n,t).map(r=>({id:r.id,alias:r.alias,started_at_ms:r.started_at?Date.parse(r.started_at):null}))}async function lS(e){let t=await import("node:fs/promises"),n="";try{let r=await t.open(e,"r");try{let o=Buffer.alloc(32768),{bytesRead:a}=await r.read(o,0,o.length,0);n=o.toString("utf8",0,a)}finally{await r.close()}}catch{return[]}let s=[];for(let r of n.split(`
|
|
1040
1079
|
`)){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+`
|
|
1041
|
-
`);if(u){for(let d of u.split(/\r?\n/)){let p=d.trim();p.length>=60&&p.length<=400&&s.push(p)}if(s.length>=8)break}}return s}async function
|
|
1080
|
+
`);if(u){for(let d of u.split(/\r?\n/)){let p=d.trim();p.length>=60&&p.length<=400&&s.push(p)}if(s.length>=8)break}}return s}async function nu(e){if(Se(e))return null;let t=h().prepare("SELECT cwd, file_path FROM sessions WHERE id = ?").get(e);if(!t?.cwd||!t.file_path)return null;let n=await lS(t.file_path);if(n.length===0)return null;let s=t.cwd.replace(/\/+$/,""),r=D.allOutputTails(),o=[];for(let[a,c]of r){let u=D.get(a);if(!u||!u.cwd||u.cwd.replace(/\/+$/,"")!==s||pe(u.tab_name)||ue(u.tab_name))continue;let d=0;for(let p of n)c.text.includes(p)&&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 uS=3e4;function su(){let e=Date.now(),t=D.all(),n=o=>{let a=Date.parse(o.last_seen_at);return!Number.isFinite(a)||e-a>uS},s=new Map;for(let o of t){if(n(o)||!o.cwd||pe(o.tab_name)||ue(o.tab_name))continue;let a=`${o.cwd.replace(/\/+$/,"")}::${o.tab_name}`,c=s.get(a);c||(c=[],s.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(!n(o))continue;let a=D.sessionsFor(o.shell_pid);if(a.length!==0){r.ghosts++;for(let c of a){let u=Se(c);if(!u)continue;let d=h().prepare("SELECT cwd FROM sessions WHERE id = ?").get(c);if(!d?.cwd)continue;let p=`${d.cwd.replace(/\/+$/,"")}::${u}`,f=s.get(p)??[];if(f.length===0)continue;if(f.length>1){r.ambiguous++;continue}let b=f[0];b.shell_pid!==o.shell_pid&&(D.unlinkSession(c),D.linkSession(c,b.shell_pid),r.rebound++)}}}return r}function ru(){let e={resolved:0,expired:0},t=D.allDeferredLinks();for(let n of t){let s=D.get(n.parent_shell_pid);if(!s||pe(s.tab_name)||ue(s.tab_name))continue;let r=Se(n.session_id);if(r&&!D.isSessionAutoLinked(n.session_id)){D.resolveDeferredLink(n.session_id);continue}let o=D.getOrigin(n.session_id),a=It({tabName:s.tab_name,origin:o??null,cwd:n.cwd,gitBranch:n.git_branch});if(!a){D.resolveDeferredLink(n.session_id);continue}r!==a&&_e(n.session_id,a),D.linkSession(n.session_id,n.parent_shell_pid),D.resolveDeferredLink(n.session_id),e.resolved++}return e}var dS=6e4;async function Ds(){let e=await iS(),t={scanned:e.size,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0};if(e.size===0)return t;let n=[];for(let[a,c]of e){let u=D.get(c);if(!u||!u.cwd||pe(u.tab_name)||ue(u.tab_name))continue;let d=u.tab_name.trim();if(!d)continue;let p=await rS(a);n.push({claudePid:a,shellPid:c,cwd:u.cwd.replace(/\/+$/,""),target:d,startTimeMs:p})}let s=new Map,r=a=>{let c=s.get(a);if(c)return c;let u=cS(a);return s.set(a,u),u},o=new Set;for(let a of n){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=dS;for(let p of c){if(p.started_at_ms==null)continue;let f=Math.abs(p.started_at_ms-a.startTimeMs);f<d&&(d=f,u=p)}}if(!u&&c.length===1&&(u=c[0]),!u){t.ambiguous_cwd++;continue}if(o.add(u.id),u.alias&&!D.isSessionAutoLinked(u.id)){t.skipped_manual++;continue}if(u.alias===a.target){D.linkSession(u.id,a.shellPid);continue}try{_e(u.id,a.target),D.linkSession(u.id,a.shellPid),u.alias?t.renamed++:t.linked++,console.log(`[correlator] linked ${u.id.slice(0,8)} \u2192 "${a.target}" (live sweep, claude pid ${a.claudePid}, shell pid ${a.shellPid})`)}catch{}}return t}function Jo(e,t,n,s={}){s.insertOnly||e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let r=e.prepare(`
|
|
1042
1081
|
INSERT INTO message_usage (
|
|
1043
1082
|
message_uuid, session_id, model,
|
|
1044
1083
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1055,7 +1094,7 @@ ${o}
|
|
|
1055
1094
|
cache_create_tokens = excluded.cache_create_tokens,
|
|
1056
1095
|
cache_read_tokens = excluded.cache_read_tokens,
|
|
1057
1096
|
timestamp = excluded.timestamp
|
|
1058
|
-
`);for(let
|
|
1097
|
+
`);for(let o of n)o.usage&&o.role==="assistant"&&r.run({uuid:o.uuid,session_id:t,model:o.model,input:o.usage.inputTokens,output:o.usage.outputTokens,cc:o.usage.cacheCreateTokens,cr:o.usage.cacheReadTokens,ts:o.timestamp})}function yn(e,t){let n=e.prepare(`SELECT
|
|
1059
1098
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
1060
1099
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
1061
1100
|
COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1070,11 +1109,11 @@ ${o}
|
|
|
1070
1109
|
total_cache_create_tokens = @cc,
|
|
1071
1110
|
total_cache_read_tokens = @cr,
|
|
1072
1111
|
primary_model = @model
|
|
1073
|
-
WHERE id = @id`).run({id:t,input:n.input_tokens,output:n.output_tokens,cc:n.cache_create_tokens,cr:n.cache_read_tokens,model:s?.model??null})}
|
|
1074
|
-
`)){if(!c)continue;let[u,d,...p]=c.split(" ");!u||a.has(u)||(a.add(u),o.push({commit_sha:u,committed_at:d??null,subject:p.join(" ")||null}))}return o}function
|
|
1075
|
-
FROM sessions WHERE id = ?`).get(e)??null}function
|
|
1112
|
+
WHERE id = @id`).run({id:t,input:n.input_tokens,output:n.output_tokens,cc:n.cache_create_tokens,cr:n.cache_read_tokens,model:s?.model??null})}H();import{execFile as pS}from"node:child_process";import{promisify as mS}from"node:util";import{stat as gS}from"node:fs/promises";var ou=mS(pS),iu=1e4,fS="%H%x09%aI%x09%s";async function _S(e){try{let{stdout:t}=await ou("git",["rev-parse","--is-inside-work-tree"],{cwd:e,timeout:iu});return t.trim()==="true"}catch{return!1}}async function hS(e,t,n){let s=["--no-pager","log","--all","--no-color","--since",t,"--until",n,`--pretty=format:${fS}`],{stdout:r}=await ou("git",s,{cwd:e,timeout:iu,maxBuffer:8*1024*1024}),o=[],a=new Set;for(let c of r.split(`
|
|
1113
|
+
`)){if(!c)continue;let[u,d,...p]=c.split(" ");!u||a.has(u)||(a.add(u),o.push({commit_sha:u,committed_at:d??null,subject:p.join(" ")||null}))}return o}function ES(e){return h().prepare(`SELECT id, cwd, started_at, ended_at
|
|
1114
|
+
FROM sessions WHERE id = ?`).get(e)??null}function bS(e,t,n){if(n.length===0)return 0;let s=h(),r=new Date().toISOString(),o=s.prepare(`INSERT OR IGNORE INTO session_commits
|
|
1076
1115
|
(session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
|
|
1077
|
-
VALUES (?, ?, ?, ?, ?, ?)`),a=0;return s.transaction(u=>{for(let d of u)o.run(e,d.commit_sha,d.committed_at,d.subject,t,r).changes>0&&(a+=1)})(n),a}async function
|
|
1116
|
+
VALUES (?, ?, ?, ?, ?, ?)`),a=0;return s.transaction(u=>{for(let d of u)o.run(e,d.commit_sha,d.committed_at,d.subject,t,r).changes>0&&(a+=1)})(n),a}async function Go(e){let t=ES(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 n=t.started_at,s=t.ended_at===t.started_at?new Date(Date.parse(t.ended_at)+1e3).toISOString():t.ended_at;try{if(!(await gS(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 _S(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await hS(t.cwd,n,s),a=bS(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 js(e){let t=h(),n=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(n))return[];let s=`${n.toLowerCase()}%`;return t.prepare(`SELECT sc.session_id AS sessionId,
|
|
1078
1117
|
NULLIF(sa.alias, '') AS alias,
|
|
1079
1118
|
p.name AS project,
|
|
1080
1119
|
s.started_at AS startedAt,
|
|
@@ -1088,18 +1127,18 @@ ${o}
|
|
|
1088
1127
|
LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
|
|
1089
1128
|
WHERE lower(sc.commit_sha) = lower(?)
|
|
1090
1129
|
OR lower(sc.commit_sha) LIKE ?
|
|
1091
|
-
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(n,s)}function
|
|
1130
|
+
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(n,s)}function Yo(e){return h().prepare(`SELECT commit_sha, committed_at, subject, correlated_at
|
|
1092
1131
|
FROM session_commits
|
|
1093
1132
|
WHERE session_id = ?
|
|
1094
|
-
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var
|
|
1095
|
-
FROM session_commits WHERE session_id = ?`).get(e),s=n?.last_at?Date.parse(n.last_at):0;if(s&&Date.now()-s<
|
|
1096
|
-
`)[0].trim();return n.length>
|
|
1133
|
+
ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var SS=3e4;function au(e){try{let n=h().prepare(`SELECT MAX(correlated_at) AS last_at
|
|
1134
|
+
FROM session_commits WHERE session_id = ?`).get(e),s=n?.last_at?Date.parse(n.last_at):0;if(s&&Date.now()-s<SS)return}catch{}Go(e).catch(t=>{console.error(`[git-correlator] ${e.slice(0,8)} failed:`,t)})}H();Z();import{writeFileSync as xS,mkdirSync as NS,existsSync as OS}from"node:fs";import{join as hu}from"node:path";H();var cu=80;function lu(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 n=t.split(`
|
|
1135
|
+
`)[0].trim();return n.length>cu?n.slice(0,cu)+"\u2026":n}function TS(e){return h().prepare(`SELECT s.id AS id,
|
|
1097
1136
|
sa.alias AS alias,
|
|
1098
1137
|
s.auto_title AS auto_title,
|
|
1099
1138
|
s.first_user_message AS first_user_message
|
|
1100
1139
|
FROM sessions s
|
|
1101
1140
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1102
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
1141
|
+
WHERE s.id = ?`).get(e)??null}function yS(e){let t=TS(e);return t?lu(t):e.slice(0,8)}function uu(e){if(!e)return null;let t=h(),n=t.prepare(`SELECT e.thread_id AS thread_id,
|
|
1103
1142
|
t.name AS thread_name,
|
|
1104
1143
|
e.parent_session_id AS parent_session_id,
|
|
1105
1144
|
e.added_at AS added_at
|
|
@@ -1108,7 +1147,7 @@ ${o}
|
|
|
1108
1147
|
WHERE e.session_id = ?
|
|
1109
1148
|
AND t.archived = 0
|
|
1110
1149
|
ORDER BY e.added_at DESC
|
|
1111
|
-
LIMIT 1`).get(e);if(!n)return null;let s=n.parent_session_id?{id:n.parent_session_id,title:
|
|
1150
|
+
LIMIT 1`).get(e);if(!n)return null;let s=n.parent_session_id?{id:n.parent_session_id,title:yS(n.parent_session_id)}:null,r=n.parent_session_id?[e,n.parent_session_id]:[e],o=r.map(()=>"?").join(", "),c=t.prepare(`SELECT e.session_id AS session_id,
|
|
1112
1151
|
s.id AS id,
|
|
1113
1152
|
sa.alias AS alias,
|
|
1114
1153
|
s.auto_title AS auto_title,
|
|
@@ -1118,33 +1157,33 @@ ${o}
|
|
|
1118
1157
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1119
1158
|
WHERE e.thread_id = ?
|
|
1120
1159
|
AND e.session_id NOT IN (${o})
|
|
1121
|
-
ORDER BY e.added_at ASC`).all(n.thread_id,...r).map(u=>({id:u.session_id,title:u.id?
|
|
1160
|
+
ORDER BY e.added_at ASC`).all(n.thread_id,...r).map(u=>({id:u.session_id,title:u.id?lu(u):u.session_id.slice(0,8)}));return{thread_id:n.thread_id,thread_name:n.thread_name,parent_session:s,siblings:c}}var du=[/^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 zo(e){return du.some(t=>t.test(e))}var wS=[/^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 pu(e){return e?wS.some(t=>t.test(e)):!1}var RS=[/^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],kS=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],AS=20;function wn(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<AS)return"low_signal";for(let t of du)if(t.test(e.auto_title))return"recursive_meta";for(let t of RS)if(t.test(e.auto_title))return"programmatic";for(let t of kS)if(t.test(e.auto_title))return"template_pending";return"clean"}function Ps(e){if(!e)return"";let t=e.split("|")[0];return t=t.replace(/\s*\([^)]*\)\s*$/,""),t=t.replace(/\s+/g," ").trim(),t}function mu(e){if(!e)return e;let t=e.lastIndexOf(" \xB7 ");if(t===-1)return e;let n=e.slice(0,t),s=e.slice(t+3),r=Ps(s);return!r||r===s?e:`${n} \xB7 ${r}`}var Qo=hu(W,"titles"),LS=80,CS=60,vS=100,IS=50,Rn=5,Fs=15,MS=500;function Eu(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(n=>!!n&&typeof n=="object"&&typeof n.title=="string"&&typeof n.replaced_at=="string")}catch{}return[]}function Dt(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 n=DS(t,e);if(n)return n;let s=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(s&&s.length<=LS?s:t.slice(0,CS)).trim()||null}function DS(e,t){let n=e.match(/^\/([A-Za-z0-9][A-Za-z0-9_-]*)\s+([\s\S]*)$/);if(n){let s=`/${n[1]}`,r=t.replace(/^\s*\/[A-Za-z0-9][A-Za-z0-9_-]*\s+/,""),o=Zo(r);return o?Mt(`${s} \xB7 ${o}`):s}for(let s of jS){if(!e.match(s.match))continue;let o=s.prefix,a=s.extract?s.extract(e,t):Zo(t);return a?s.completeFromExtract?Mt(a):Mt(`${o} \xB7 ${a}`):o}for(let s of $S){if(!e.match(s.match))continue;let o=s.extract?s.extract(e,t):$s(t);return o?s.completeFromExtract?Mt(o):Mt(`${s.prefix} \xB7 ${o}`):s.prefix}return null}var jS=[{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 s=e.match(/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i)?.[1]?.trim(),r=Zo(t);return s&&r?`${s} \xB7 ${r}`:s||r}},{match:/^Read the file at /i,prefix:"Read",extract:e=>{let n=e.match(/^Read the file at\s+([^\s]+)/i)?.[1];return n?n.split("/").filter(Boolean).slice(-2).join("/")||n:null}},{match:/^(?:read|inspect) this and (?:then )?begin\b/i,prefix:"Begin from preamble",completeFromExtract:!0,extract:(e,t)=>FS(t)},{match:/^Base directory for this skill:/i,prefix:"[skill]",completeFromExtract:!0,extract:(e,t)=>{let n=t.match(/\.claude\/skills\/([^/\s]+)/);return n?.[1]?`[skill] ${n[1]}`:null}},{match:/^You are extracting a structured Output Index/i,prefix:"[output-index]",completeFromExtract:!0,extract:(e,t)=>PS(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>gu(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)=>gu(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 n=t[1],s=t[2]?.trim();return s?`Implementing ${n} \xB7 ${s}`:`Implementing ${n}`}}];function PS(e){if(!e)return"[output-index] extractor";let t=e.match(/^Session:\s*([0-9a-f]{8})\b/im),n=e.match(/^Opening prompt:\s*([^\n]{1,140})/im),s=t?.[1]?.trim(),r=n?.[1]?.trim().replace(/\s+/g," "),o=r&&r.length>70?r.slice(0,67)+"\u2026":r;return s&&o?`[output-index] \xB7 ${s} \xB7 ${o}`:s?`[output-index] \xB7 ${s}`:o?`[output-index] \xB7 ${o}`:"[output-index] extractor"}function gu(e,t){if(!e)return t;let n=e.indexOf("Messages:");if(n===-1)return t;let s=e.slice(n+9),r=/^\s*\d+\.\s*([^\n]+)/gm;for(let o of s.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 FS(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 n=t[1].split("/").filter(Boolean);return(n[n.length-1]??"").replace(/\.[^.]+$/,"")||null}var $S=[{match:/^Score this person'?s relevance/i,prefix:"Score relevance",extract:(e,t)=>$s(t)},{match:/^You are a [^\n.]{1,60}co-pilot/i,prefix:"co-pilot",completeFromExtract:!0,extract:(e,t)=>{let s=e.match(/^You are a ([^\n.]{1,60}?)co-pilot/i)?.[1]?.trim(),r=s?`${s} co-pilot`:"co-pilot",o=$s(t);return o?`${r} \xB7 ${o}`:r}},{match:/^Return ONLY valid JSON/i,prefix:"JSON-only",extract:(e,t)=>$s(t)}];function $s(e){let t=/^\s*(Name|Company|Prospect|Author|Brand|Client)\s*:\s*([^\n]+)/gim,n;for(;(n=t.exec(e))!==null;){let s=n[2].trim();if(!s||/^(null|none|n\/a|—|-)$/i.test(s))continue;let r=s.replace(/\s+/g," ");return Ps(r)||r}return null}function Mt(e){return e.slice(0,vS).trim()}var US=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],HS=[/^(?: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 Ko(e){let t=e.trim();return t.length<3?!0:US.some(n=>n.test(t))}function BS(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return HS.some(n=>n.test(t))}function Zo(e){let t=WS(e);return t===null?null:Ps(t)||t}function WS(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,n,s=[];for(;(n=t.exec(e))!==null;){let u=n[2].trim(),d=n[3].trim().replace(/\s+/g," ");if(!Ko(d)){if(BS(u))return d;s.push(d)}}if(s.length>0)return s[0];let r=e.match(/\b(?:Topic|Subject|Brand|About|Client|For|Re)\s*:\s*([^\n]{2,80})/i);if(r?.[1]&&!Ko(r[1]))return r[1].trim().replace(/\s+/g," ");let o=/===\s*([^=\n]{2,120}?)\s*===/g;for(;(n=o.exec(e))!==null;){let u=n[1].trim().replace(/\.(md|txt|json)$/i,""),d=u.replace(/\s*\([^)]*\)\s*$/,"").trim();if(d&&!Ko(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(`
|
|
1122
1161
|
`).map(d=>d.trim()).find(d=>d.length>=4);if(u)return u.slice(0,60)}let c=e.split(`
|
|
1123
|
-
`).map(u=>u.trim()).find(u=>u.length>=4);return c?c.slice(0,60):null}function
|
|
1162
|
+
`).map(u=>u.trim()).find(u=>u.length>=4);return c?c.slice(0,60):null}function ei(e){let t=h(),n=t.prepare(`SELECT rowid AS rid, content_text
|
|
1124
1163
|
FROM messages
|
|
1125
1164
|
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1126
1165
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1127
1166
|
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1128
|
-
LIMIT ?`).all(e,
|
|
1167
|
+
LIMIT ?`).all(e,Rn),s=t.prepare(`SELECT rowid AS rid, content_text
|
|
1129
1168
|
FROM messages
|
|
1130
1169
|
WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
|
|
1131
1170
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1132
1171
|
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1133
|
-
LIMIT ?`).all(e,
|
|
1134
|
-
${f+1}. ${
|
|
1135
|
-
`),u=null;try{u
|
|
1136
|
-
`)}var
|
|
1137
|
-
`)}async function
|
|
1138
|
-
FROM sessions WHERE id = ?`).get(e);if(!o||n==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===s&&o.auto_title_source===n)return;let a=
|
|
1172
|
+
LIMIT ?`).all(e,Fs),r=new Map;for(let p of n)r.set(p.rid,p.content_text);for(let p of s)r.set(p.rid,p.content_text);if(r.size===0)throw new Error("no user messages available to summarise");let o=Array.from(r.entries()).sort((p,f)=>p[0]-f[0]).map(([,p])=>({content_text:p})),a=n.length===Rn&&s.length===Fs&&r.size===Rn+Fs,c=o.map((p,f)=>{let b=(p.content_text??"").slice(0,MS);return a&&f===Rn?`--- (middle of session omitted) ---
|
|
1173
|
+
${f+1}. ${b}`:`${f+1}. ${b}`}).join(`
|
|
1174
|
+
`),u=null;try{u=uu(e)}catch(p){console.error("[autoTitle] thread context resolution failed:",p),u=null}let d=[];return u&&(d.push(qS(u)),d.push("")),d.push(`You will receive a sample of user messages from a Claude Code session: the first ${Rn}`,`messages (initial intent) and the last ${Fs} 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(`
|
|
1175
|
+
`)}var Vo=5;function qS(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 n=e.siblings.length;if(n>0){let r=e.siblings.slice(0,Vo).map(a=>`"${a.title}"`).join(", "),o=n>Vo?`, and ${n-Vo} more`:"";t.push(`- Sibling sessions (${n}): ${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(`
|
|
1176
|
+
`)}async function bu(e){let t=ei(e),{spawnClaudePrompt:n,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(we(),pt));if(!s())throw new Error("claude CLI not found on PATH");let r=await n(t,[],{});if(!r.success)throw new Error(`claude CLI exited ${r.exitCode}: ${r.stderr.slice(-500)}`);let o=XS(r.stdout);if(!o)throw new Error("claude CLI returned an empty title");return o.slice(0,IS)}function XS(e){let t=e.trim();if(!t)return"";try{let n=JSON.parse(t);if(n&&typeof n=="object"){let s=n.result;if(typeof s=="string")return fu(s)}}catch{}return fu(t)}function fu(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}function he(e,t,n){let s=t.trim();if(!s)return;let r=h(),o=r.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1177
|
+
FROM sessions WHERE id = ?`).get(e);if(!o||n==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===s&&o.auto_title_source===n)return;let a=Eu(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
|
|
1139
1178
|
SET auto_title = ?,
|
|
1140
1179
|
auto_title_source = ?,
|
|
1141
1180
|
auto_title_generated_at = ?,
|
|
1142
1181
|
auto_title_history = ?
|
|
1143
|
-
WHERE id = ?`).run(s,n,Date.now(),JSON.stringify(a),e),
|
|
1144
|
-
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:
|
|
1182
|
+
WHERE id = ?`).run(s,n,Date.now(),JSON.stringify(a),e),zS(e,s,n,c)}function ve(e){let t=h().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
1183
|
+
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:Eu(t.auto_title_history)}:null}function Su(){let t=h().prepare(`SELECT id, first_user_message
|
|
1145
1184
|
FROM sessions
|
|
1146
1185
|
WHERE auto_title IS NULL
|
|
1147
|
-
AND first_user_message IS NOT NULL`).all(),n=0;for(let s of t){let r=
|
|
1186
|
+
AND first_user_message IS NOT NULL`).all(),n=0;for(let s of t){let r=Dt(s.first_user_message);r&&(he(s.id,r,"heuristic"),n+=1)}return{updated:n}}function Tu(e){let t=h(),n=e?.projectId?" AND s.project_id = ?":"",s=e?.projectId?[e.projectId,e.projectId]:[],r=t.prepare(`WITH dups AS (
|
|
1148
1187
|
SELECT auto_title, project_id
|
|
1149
1188
|
FROM sessions
|
|
1150
1189
|
WHERE auto_title IS NOT NULL
|
|
@@ -1197,7 +1236,7 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1197
1236
|
AND content_text IS NOT NULL
|
|
1198
1237
|
AND content_text != ''
|
|
1199
1238
|
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1200
|
-
LIMIT 8`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),p=null;for(let f of d){let
|
|
1239
|
+
LIMIT 8`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),p=null;for(let f of d){let b=f.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();if(!b||/^<local-command-caveat>/.test(b))continue;let T=Dt(b);if(T&&!_u(T)){p=T;break}}if(!p){let f=d.map(T=>T.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim()).find(T=>T.length>0&&!/^<local-command-caveat>/.test(T)),b=f?Dt(f):null;b&&_u(b)&&(p=`[vacuous] ${b}`)}p&&(he(u.id,p,"heuristic"),c+=1)}return{scanned:a,updated:c}}function _u(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(s=>s.test(t))}function yu(e){let t=h(),n=e?.projectId?" AND s.project_id = ?":"",s=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1201
1240
|
FROM sessions s
|
|
1202
1241
|
WHERE s.auto_title_source = 'heuristic'${n}
|
|
1203
1242
|
AND (
|
|
@@ -1212,47 +1251,47 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1212
1251
|
AND content_text IS NOT NULL
|
|
1213
1252
|
AND content_text != ''
|
|
1214
1253
|
ORDER BY COALESCE(timestamp, ''), rowid ASC
|
|
1215
|
-
LIMIT 10`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),p=
|
|
1254
|
+
LIMIT 10`),a=0,c=0;for(let u of r){a+=1;let d=o.all(u.id),p=JS(d,u.auto_title);p&&(he(u.id,p,"heuristic"),c+=1)}return{scanned:a,updated:c}}function JS(e,t){for(let n of e){let s=GS(n.content_text);if(!s||zo(s))continue;let r=Dt(s);if(r){if(r===t)return null;if(!zo(r))return r}}return t.startsWith("[meta]")?null:Mt(`[meta] ${t}`)}function wu(e){let t=h(),n=e?.projectId?" AND s.project_id = ?":"",s=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
|
|
1216
1255
|
FROM sessions s
|
|
1217
1256
|
WHERE s.auto_title_source = 'heuristic'${n}
|
|
1218
1257
|
AND s.auto_title IS NOT NULL
|
|
1219
1258
|
AND s.auto_title LIKE '% \xB7 %'
|
|
1220
|
-
AND (s.auto_title LIKE '%|%' OR s.auto_title LIKE '%(%')`).all(...s),o=0,a=0;for(let c of r){o+=1;let u=
|
|
1221
|
-
`;
|
|
1222
|
-
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}import{existsSync as
|
|
1223
|
-
VALUES (?, ?, ?, ?, ?)`).run(e,s,t,n?JSON.stringify(n):null,r)}function
|
|
1259
|
+
AND (s.auto_title LIKE '%|%' OR s.auto_title LIKE '%(%')`).all(...s),o=0,a=0;for(let c of r){o+=1;let u=mu(c.auto_title);u!==c.auto_title&&(he(c.id,u,"heuristic"),a+=1)}return{scanned:o,updated:a}}function GS(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 YS(){K(),OS(Qo)||NS(Qo,{recursive:!0})}function zS(e,t,n,s){try{YS();let r=hu(Qo,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${n} \xB7 updated ${s}
|
|
1260
|
+
`;xS(r,o+t+`
|
|
1261
|
+
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}import{existsSync as Ru,mkdirSync as KS,readFileSync as VS,writeFileSync as QS}from"node:fs";import{homedir as ZS}from"node:os";import{join as ku}from"node:path";import{z as ti}from"zod";function Au(){return process.env.RECALL_HOME??ku(ZS(),".recall")}function eT(){let e=Au();Ru(e)||KS(e,{recursive:!0})}function xu(){return ku(Au(),"config.json")}var Hs=ti.object({heuristicEnabled:ti.boolean().default(!0),agentEnabled:ti.boolean().default(!1)}),Us={heuristicEnabled:!0,agentEnabled:!1};function Nu(){let e=xu();if(!Ru(e))return{};try{return JSON.parse(VS(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function ft(){let e=Nu().autoTitle;if(!e)return{...Us};let t=Hs.safeParse({...Us,...e});return t.success?t.data:{...Us}}function Ou(e){eT();let t=Nu(),n=Hs.parse({...Us,...t.autoTitle??{},...e}),s={...t,autoTitle:n};return QS(xu(),JSON.stringify(s,null,2)),n}H();Z();import{randomUUID as oi}from"node:crypto";import{existsSync as aT,mkdirSync as cT,writeFileSync as Fu}from"node:fs";import{homedir as lT}from"node:os";import{basename as uT,join as ii}from"node:path";H();Z();import{randomUUID as tT}from"node:crypto";import{writeFileSync as nT,readFileSync as bC,existsSync as SC}from"node:fs";import{join as sT}from"node:path";var rT=sT(W,"collections.json"),Bs=8;function Ws(e){return{...e}}function ke(e,t,n,s=null,r=new Date().toISOString()){h().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
|
|
1262
|
+
VALUES (?, ?, ?, ?, ?)`).run(e,s,t,n?JSON.stringify(n):null,r)}function qs(e){let t=h().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function Lu(e){if(!e)return 0;let t=0,n=e,s=new Set,r=h();for(;n;){if(s.has(n))throw new Error("collection cycle detected");s.add(n);let o=r.prepare("SELECT parent_id FROM collections WHERE id = ?").get(n);if(!o)break;t+=1,n=o.parent_id}return t}function oT(e,t){let n=h(),s=e,r=new Set;for(;s;){if(r.has(s))return!1;if(r.add(s),s===t)return!0;let o=n.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!o)return!1;s=o.parent_id}return!1}function Cu(e=!1){return h().prepare(`SELECT c.*,
|
|
1224
1263
|
(SELECT COUNT(*) FROM collection_sessions cs WHERE cs.collection_id = c.id) AS session_count
|
|
1225
1264
|
FROM collections c
|
|
1226
1265
|
${e?"":"WHERE c.archived_at IS NULL"}
|
|
1227
|
-
ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(s=>({...
|
|
1266
|
+
ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(s=>({...Ws(s),session_count:s.session_count}))}function Qe(e){let t=h().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?Ws(t):null}function vu(e,t=!0){let n=h(),s=t?ni(e):[e];if(s.length===0)return[];let r=s.map(()=>"?").join(",");return n.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1228
1267
|
FROM collection_sessions
|
|
1229
1268
|
WHERE collection_id IN (${r})
|
|
1230
|
-
ORDER BY added_at DESC`).all(...s)}function
|
|
1269
|
+
ORDER BY added_at DESC`).all(...s)}function ni(e){let t=h(),n=[e],s=[e],r=new Set([e]);for(;s.length>0;){let o=s.map(()=>"?").join(","),a=t.prepare(`SELECT id FROM collections WHERE parent_id IN (${o})`).all(...s),c=[];for(let u of a)r.has(u.id)||(r.add(u.id),n.push(u.id),c.push(u.id));s=c}return n}function Iu(e){return h().prepare(`SELECT c.* FROM collections c
|
|
1231
1270
|
JOIN collection_sessions cs ON cs.collection_id = c.id
|
|
1232
1271
|
WHERE cs.session_id = ? AND c.archived_at IS NULL
|
|
1233
|
-
ORDER BY LOWER(c.name)`).all(e)}function
|
|
1272
|
+
ORDER BY LOWER(c.name)`).all(e)}function kn(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 n=h(),s=new Date().toISOString(),r=tT();if(e.parent_id){if(!Qe(e.parent_id))throw new Error("parent collection not found");if(Lu(e.parent_id)>=Bs-1)throw new Error(`max collection depth is ${Bs}`)}return n.transaction(()=>{n.prepare(`INSERT INTO collections
|
|
1234
1273
|
(id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
|
|
1235
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",s,s),
|
|
1274
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",s,s),ke(r,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,s)})(),_t(),Qe(r)}function Mu(e,t){let n=h(),s=qs(e),r=new Date().toISOString(),o={name:t.name!==void 0?t.name.trim():s.name,description:t.description!==void 0?t.description:s.description,icon:t.icon!==void 0?t.icon:s.icon,color:t.color!==void 0?t.color:s.color,parent_id:t.parent_id!==void 0?t.parent_id:s.parent_id,sort_key:t.sort_key!==void 0?t.sort_key:s.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!==s.parent_id&&t.parent_id){if(t.parent_id===e)throw new Error("cannot set parent to self");if(!Qe(t.parent_id))throw new Error("parent collection not found");if(oT(t.parent_id,e))throw new Error("cannot move collection into one of its descendants");if(Lu(t.parent_id)>=Bs-1)throw new Error(`max collection depth is ${Bs}`)}return n.transaction(()=>{n.prepare(`UPDATE collections
|
|
1236
1275
|
SET name = ?, description = ?, icon = ?, color = ?,
|
|
1237
1276
|
parent_id = ?, sort_key = ?, updated_at = ?
|
|
1238
|
-
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!==s.name&&
|
|
1239
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,d,n,c,u),
|
|
1240
|
-
WHERE rule_id = ?`).all(e);if(n.length===0)return{removed:0};let s=new Date().toISOString();return t.transaction(()=>{t.prepare("DELETE FROM collection_sessions WHERE rule_id = ?").run(e);for(let o of n)
|
|
1277
|
+
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!==s.name&&ke(e,"rename",{from:s.name,to:o.name},null,r),t.description!==void 0&&t.description!==s.description&&ke(e,"describe",{description:o.description},null,r),(t.icon!==void 0&&t.icon!==s.icon||t.color!==void 0&&t.color!==s.color)&&ke(e,"recolor",{icon:o.icon,color:o.color},null,r),t.parent_id!==void 0&&t.parent_id!==s.parent_id&&ke(e,"move",{from:s.parent_id,to:o.parent_id},null,r),t.sort_key!==void 0&&t.sort_key!==s.sort_key&&ke(e,"reorder",{from:s.sort_key,to:o.sort_key},null,r)})(),_t(),Qe(e)}function Du(e){let t=h(),n=qs(e);if(n.archived_at)return Ws(n);let s=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = ?, updated_at = ? WHERE id = ?").run(s,s,e),ke(e,"archive",{name:n.name},null,s)})(),_t(),Qe(e)}function ju(e){let t=h(),n=qs(e);if(!n.archived_at)return Ws(n);let s=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = NULL, updated_at = ? WHERE id = ?").run(s,e),ke(e,"restore",{name:n.name},null,s)})(),_t(),Qe(e)}function An(e,t,n=null,s={}){let r=h();if(qs(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=s.source??"manual",u=s.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)
|
|
1278
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,d,n,c,u),ke(e,"add",{note:n,source:c,rule_id:u},t,d)})(),_t(),{added:!0}}function Pu(e,t){let n=h();if(!n.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let r=new Date().toISOString();return n.transaction(()=>{n.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),ke(e,"remove",null,t,r)})(),_t(),{removed:!0}}function Xs(e){let t=h(),n=t.prepare(`SELECT collection_id, session_id FROM collection_sessions
|
|
1279
|
+
WHERE rule_id = ?`).all(e);if(n.length===0)return{removed:0};let s=new Date().toISOString();return t.transaction(()=>{t.prepare("DELETE FROM collection_sessions WHERE rule_id = ?").run(e);for(let o of n)ke(o.collection_id,"remove",{rule_id:e},o.session_id,s)})(),_t(),{removed:n.length}}function iT(){return h().prepare(`SELECT id, collection_id, session_id, action, payload, at
|
|
1241
1280
|
FROM collection_events
|
|
1242
|
-
ORDER BY at ASC, id ASC`).all()}function
|
|
1281
|
+
ORDER BY at ASC, id ASC`).all()}function _t(){try{K();let e=h(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
|
|
1243
1282
|
created_at, updated_at, archived_at
|
|
1244
1283
|
FROM collections
|
|
1245
1284
|
ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),n=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
|
|
1246
1285
|
FROM collection_sessions
|
|
1247
|
-
ORDER BY collection_id, added_at`).all(),s=
|
|
1286
|
+
ORDER BY collection_id, added_at`).all(),s=iT(),r={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:n,events:s};nT(rT,JSON.stringify(r,null,2))}catch(e){console.error("[collections] backup failed:",e)}}var Js=ii(W,"auto-rules"),dT=ii(Js,"rules.json"),pT=ii(Js,"suggestions.json"),si="Repositories",mT="Topics",$u=3;var gT=5,fT=2,_T=[/\bROADMAP\.md\b/g,/\bPROJECT\.md\b/g,/\bdocs\/[A-Za-z0-9._-]+\.md\b/g,/\.planning\/[A-Za-z0-9._\-/]+/g];function ai(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 hT(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 ET(e){switch(e){case"cwd-prefix":case"project-id":case"git-branch-prefix":return si;case"tag":return mT;case"plan-file":return null}}function bT(e){let n=h().prepare("SELECT id FROM collections WHERE name = ? AND parent_id IS NULL AND archived_at IS NULL").get(e);if(n)return n.id;let o=kn({name:e,icon:e===si?"\u{1F4E6}":"\u{1F3F7}",sort_key:e===si?"0000-repos":"0001-topics"});return h().prepare(`INSERT INTO auto_collection_rules
|
|
1248
1287
|
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1249
|
-
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(
|
|
1250
|
-
WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(s=>s.id)}}function
|
|
1288
|
+
VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(oi(),`seed:${e}`,o.id,new Date().toISOString()),o.id}function ST(e,t,n){let s;if(n!==void 0)s=n;else{let o=ET(t);s=o?bT(o):null}return kn({name:e,parent_id:s}).id}function Gs(e){let t=h().prepare("SELECT * FROM auto_collection_rules WHERE id = ?").get(e);return t?ai(t):null}function TT(e){let t=h();switch(e.type){case"cwd-prefix":return t.prepare("SELECT id FROM sessions WHERE cwd IS NOT NULL AND cwd LIKE ? ESCAPE '\\'").all(Ys(e.pattern)+"%").map(s=>s.id);case"git-branch-prefix":return t.prepare("SELECT id FROM sessions WHERE git_branch IS NOT NULL AND git_branch LIKE ? ESCAPE '\\'").all(Ys(e.pattern)+"%").map(s=>s.id);case"project-id":{let n=Number(e.pattern);return Number.isFinite(n)?t.prepare("SELECT id FROM sessions WHERE project_id = ?").all(n).map(r=>r.id):[]}case"tag":return t.prepare("SELECT session_id FROM session_tags WHERE tag = ?").all(e.pattern).map(s=>s.session_id);case"plan-file":return t.prepare(`SELECT id, first_user_message FROM sessions
|
|
1289
|
+
WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(s=>s.id)}}function Uu(e,t,n=3){let s=h(),r=`s.id AS id, s.cwd AS cwd, s.started_at AS started_at,
|
|
1251
1290
|
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=s.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1252
1291
|
WHERE s.cwd IS NOT NULL AND s.cwd LIKE ? ESCAPE '\\'
|
|
1253
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(
|
|
1292
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(Ys(t)+"%",n);break;case"git-branch-prefix":a=s.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1254
1293
|
WHERE s.git_branch IS NOT NULL AND s.git_branch LIKE ? ESCAPE '\\'
|
|
1255
|
-
ORDER BY s.started_at DESC LIMIT ?`).all(
|
|
1294
|
+
ORDER BY s.started_at DESC LIMIT ?`).all(Ys(t)+"%",n);break;case"project-id":{let c=Number(t);Number.isFinite(c)&&(a=s.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1256
1295
|
WHERE s.project_id = ?
|
|
1257
1296
|
ORDER BY s.started_at DESC LIMIT ?`).all(c,n));break}case"tag":a=s.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1258
1297
|
JOIN session_tags st ON st.session_id = s.id
|
|
@@ -1260,21 +1299,21 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1260
1299
|
ORDER BY s.started_at DESC LIMIT ?`).all(t,n);break;case"plan-file":a=s.prepare(`SELECT ${r} FROM sessions s ${o}
|
|
1261
1300
|
WHERE s.first_user_message IS NOT NULL
|
|
1262
1301
|
AND s.first_user_message LIKE ?
|
|
1263
|
-
ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",n);break}return a.map(c=>({id:c.id,title:
|
|
1264
|
-
`)[0].trim();return n.length>80?n.slice(0,80)+"\u2026":n}function
|
|
1302
|
+
ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",n);break}return a.map(c=>({id:c.id,title:yT(c),cwd:c.cwd,started_at:c.started_at}))}function yT(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 n=t.split(`
|
|
1303
|
+
`)[0].trim();return n.length>80?n.slice(0,80)+"\u2026":n}function wT(e,t,n=h()){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 s=Number(e.pattern);return Number.isFinite(s)&&t.project_id===s}case"tag":return!!n.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 Hu(e){let t=h(),n=t.prepare("SELECT id, project_id, cwd, git_branch, first_user_message FROM sessions WHERE id = ?").get(e);if(!n)return{added:0};let s=t.prepare(`SELECT * FROM auto_collection_rules
|
|
1265
1304
|
WHERE enabled = 1 AND created_by != 'seed'
|
|
1266
|
-
ORDER BY priority, created_at`).all(),r=0;for(let o of s){let a=
|
|
1305
|
+
ORDER BY priority, created_at`).all(),r=0;for(let o of s){let a=ai(o);if(wT(a,n,t))try{An(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 ri(e){if(!e.enabled)return{added:0};let t=0;for(let n of TT(e))try{An(e.collection_id,n,null,{source:"auto",rule_id:e.id}).added&&(t+=1)}catch(s){console.error(`[auto-collections] backfill failed for rule ${e.id} / session ${n}:`,s)}return{added:t}}function ci(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");let n=(e.pattern??"").trim();if(!n)throw new Error("pattern required");let s=h(),r=new Date().toISOString(),o=oi(),a=e.collection_id;a||(a=ST(t,e.type,e.parent_collection_id)),s.prepare(`INSERT INTO auto_collection_rules
|
|
1267
1306
|
(id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
|
|
1268
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(o,t,e.type,n,a,e.priority??100,e.enabled===!1?0:1,r,e.created_by??"user"),s.prepare("DELETE FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").run(e.type,n);let c=
|
|
1307
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(o,t,e.type,n,a,e.priority??100,e.enabled===!1?0:1,r,e.created_by??"user"),s.prepare("DELETE FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").run(e.type,n);let c=Gs(o);return ri(c),jt(),c}function Bu(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 h().prepare(t).all().map(ai)}function Wu(e,t){let n=h(),s=Gs(e);if(!s)throw new Error(`rule not found: ${e}`);let r={name:t.name!==void 0?t.name.trim():s.name,pattern:t.pattern!==void 0?t.pattern.trim():s.pattern,enabled:t.enabled!==void 0?t.enabled:s.enabled,priority:t.priority!==void 0?t.priority:s.priority};if(!r.name)throw new Error("name required");if(!r.pattern)throw new Error("pattern required");n.prepare(`UPDATE auto_collection_rules
|
|
1269
1308
|
SET name = ?, pattern = ?, enabled = ?, priority = ?
|
|
1270
|
-
WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o=
|
|
1309
|
+
WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o=Gs(e);return t.pattern!==void 0&&t.pattern!==s.pattern?(Xs(e),o.enabled&&ri(o)):t.enabled!==void 0&&t.enabled!==s.enabled&&(o.enabled?ri(o):Xs(e)),jt(),o}function qu(e){let t=h();if(!Gs(e))return{removed:0};let s=Xs(e);return t.prepare("DELETE FROM auto_collection_rules WHERE id = ?").run(e),jt(),s}function zs(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 h().prepare(t).all().map(hT)}function Xu(e){h().prepare("UPDATE auto_collection_suggestions SET dismissed = 1 WHERE id = ?").run(e),jt()}function Ju(e){let t=h(),n=t.prepare("SELECT * FROM auto_collection_suggestions WHERE id = ?").get(e);if(!n)throw new Error(`suggestion not found: ${e}`);if(n.dismissed)throw new Error(`suggestion already dismissed: ${e}`);let s=ci({name:n.suggested_name,type:n.type,pattern:n.pattern,parent_collection_id:n.suggested_parent_collection_id===null?void 0:n.suggested_parent_collection_id,created_by:"suggestion-accepted"});return t.prepare("DELETE FROM auto_collection_suggestions WHERE id = ?").run(e),jt(),s}function Ks(){let e=h(),t=new Date().toISOString(),n=lT(),s=new Set(e.prepare("SELECT decoded_path FROM projects").all().map(c=>c.decoded_path)),r=[...RT(n,t).filter(c=>!s.has(c.pattern)),...kT(t),...AT(t)];if(e.prepare("DELETE FROM auto_collection_suggestions WHERE type = 'project-id'").run(),s.size>0){let c=Array.from(s).map(()=>"?").join(",");e.prepare(`DELETE FROM auto_collection_suggestions WHERE type = 'cwd-prefix' AND pattern IN (${c})`).run(...Array.from(s))}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
|
|
1271
1310
|
SET session_count = ?, detected_at = ?, suggested_name = ?, suggested_parent_collection_id = ?
|
|
1272
1311
|
WHERE id = ?`).run(c.session_count,t,c.suggested_name,c.suggested_parent_collection_id,d.id):e.prepare(`INSERT INTO auto_collection_suggestions
|
|
1273
1312
|
(id, type, pattern, suggested_name, suggested_parent_collection_id, session_count, detected_at, dismissed)
|
|
1274
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(
|
|
1275
|
-
WHERE first_user_message IS NOT NULL AND first_user_message != ''`).all(),n=new Map;for(let r of t)for(let o of
|
|
1313
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(oi(),c.type,c.pattern,c.suggested_name,c.suggested_parent_collection_id,c.session_count,t)}return jt(),zs()}function RT(e,t){let s=h().prepare("SELECT id, cwd FROM sessions WHERE cwd IS NOT NULL AND cwd != ''").all(),r=new Map;for(let a of s){let c=a.cwd.split("/").filter(Boolean),u="";for(let d of c){if(u=`${u}/${d}`,u===e||u==="/")continue;let p=r.get(u);p||(p=new Set,r.set(u,p)),p.add(a.id)}}let o=[];for(let[a,c]of r.entries()){if(c.size<$u)continue;let u=!1;for(let[d,p]of r.entries())if(d!==a&&d.startsWith(a+"/")&&p.size>=$u){u=!0;break}u||o.push({type:"cwd-prefix",pattern:a,suggested_name:uT(a)||a,suggested_parent_collection_id:null,session_count:c.size,detected_at:t,dismissed:!1})}return o}function kT(e){return h().prepare("SELECT tag, COUNT(*) AS n FROM session_tags GROUP BY tag HAVING n >= ?").all(gT).map(n=>({type:"tag",pattern:n.tag,suggested_name:n.tag,suggested_parent_collection_id:null,session_count:n.n,detected_at:e,dismissed:!1}))}function AT(e){let t=h().prepare(`SELECT id, first_user_message FROM sessions
|
|
1314
|
+
WHERE first_user_message IS NOT NULL AND first_user_message != ''`).all(),n=new Map;for(let r of t)for(let o of _T){o.lastIndex=0;let a=r.first_user_message.match(o);if(a)for(let c of a){let u=n.get(c);u||(u=new Set,n.set(c,u)),u.add(r.id)}}let s=[];for(let[r,o]of n.entries())o.size<fT||s.push({type:"plan-file",pattern:r,suggested_name:r,suggested_parent_collection_id:null,session_count:o.size,detected_at:e,dismissed:!1});return s}function Ys(e){return e.replace(/[\\%_]/g,"\\$&")}function jt(){try{K(),aT(Js)||cT(Js,{recursive:!0});let e=h(),t=e.prepare("SELECT * FROM auto_collection_rules ORDER BY created_at, id").all(),n=e.prepare("SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC, id").all();Fu(dT,JSON.stringify({schema:"claude-recall.auto-rules.v1",backed_up_at:new Date().toISOString(),rules:t},null,2)),Fu(pT,JSON.stringify({schema:"claude-recall.auto-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:n},null,2))}catch(e){console.error("[auto-collections] backup failed:",e)}}function Gu(){let e=h().prepare("SELECT DISTINCT collection_id FROM auto_collection_rules").all();return new Set(e.map(t=>t.collection_id))}var xT=/^[ \t]*<!--\s*claude-recall-alias\s*:\s*([^\n]+?)\s*-->[ \t]*\n?/;function li(e){if(e==null)return{alias:null,stripped:""};let t=e.match(xT);if(!t)return{alias:null,stripped:e};let n=t[1].trim();return n?{alias:n.length>200?n.slice(0,200):n,stripped:e.slice(t[0].length)}:{alias:null,stripped:e.slice(t[0].length)}}var Vs=new Map;function Yu(e,t){let s=(Vs.get(e)??Promise.resolve()).catch(()=>{}).then(t);return Vs.set(e,s),s.catch(()=>{}).finally(()=>{Vs.get(e)===s&&Vs.delete(e)}),s}import{realpathSync as NT,statSync as OT,readFileSync as LT}from"node:fs";import{dirname as zu,join as CT,parse as vT}from"node:path";var Pt="(temporary sessions)";function Nn(e){return e?e.startsWith("/private/var/folders/")||e.startsWith("/var/folders/")||e.startsWith("/tmp/")||e.startsWith("/private/tmp/")||e==="/tmp"||e==="/private/tmp":!1}function Ku(e,t){let n=e;for(;n!=="/"&&n!=="."&&n!=="";){if(t.has(n))return n;let s=zu(n);if(s===n)break;n=s}return null}var xn=new Map;function Qs(e,t={}){let n=t.foldWorktrees===!0,s=`${n?"1":"0"}:${e}`,r=xn.get(s);if(r)return r;let o={root:e,isRepo:!1,isWorktree:!1,mainRepo:null},a;try{a=NT(e)}catch{return xn.set(s,o),o}let{root:c}=vT(a),u=a;for(;;){let d=CT(u,".git"),p=null;try{p=OT(d)}catch{p=null}if(p&&p.isDirectory()){let f={root:u,isRepo:!0,isWorktree:!1,mainRepo:null};return xn.set(s,f),f}if(p&&p.isFile()){let f=IT(d),S={root:n&&f?f:u,isRepo:!0,isWorktree:f!==null,mainRepo:f};return xn.set(s,S),S}if(u===c)break;u=zu(u)}return xn.set(s,o),o}function IT(e){let t;try{t=LT(e,"utf8")}catch{return null}let n=t.match(/^gitdir:\s*(.+?)\s*$/m);if(!n)return null;let s=n[1].trim(),o=s.indexOf("/.git/worktrees/");return o<0?null:s.slice(0,o)}H();We();H();function Vu(e){return e.replace(/```json[\s\S]*?```/g,"[tool-call]").replace(/\{[\s\S]{200,}?\}/g,"[json-object]")}function MT(e){let t=[],o=0;for(;o<e.length;){let a=[],c=0;for(;a.length<5&&o<e.length;){let d=e[o],p=Vu(d.content_text??"");if(c+p.length>2e3&&a.length>=3)break;a.push(d),c+=p.length,o++}if(a.length===0)break;let u=a.map(d=>{let p=d.role??"system",f=Vu(d.content_text??"");return`[${p}] ${f}`}).join(`
|
|
1276
1315
|
|
|
1277
|
-
`);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
|
|
1316
|
+
`);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 Qu(e){let n=h().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 MT(n)}H();function ui(e){let t=h();t.transaction(()=>{t.prepare("DELETE FROM vec_chunks WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(e),t.prepare("SELECT name FROM sqlite_master WHERE name='vec_chunks_v1_backup'").get()&&t.prepare("DELETE FROM vec_chunks_v1_backup WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(e),t.prepare("DELETE FROM chunk_meta WHERE session_id = ?").run(e)})()}function mi(){try{return!!h().prepare("SELECT 1 AS held FROM migration_state WHERE status IN ('in_progress', 'paused') LIMIT 1").get()}catch(e){if((e instanceof Error?e.message:String(e)).includes("no such table: migration_state"))return!1;throw e}}function od(){return h().prepare("UPDATE chunk_queue SET action = 'embed' WHERE action = 'pending_post_migration'").run().changes}function DT(){try{return!!h().prepare("SELECT 1 AS present FROM chunk_queue WHERE action = 'pending_post_migration' LIMIT 1").get()}catch(e){if((e instanceof Error?e.message:String(e)).includes("no such table: chunk_queue"))return!1;throw e}}var id=2e3,jT=1e4,ht=null,Zs=!1,gi=null,di=0;function ad(e){di=Math.max(0,Math.floor(e))}var Ie=new Set,cd=-1;function ld(e){Ie.add(e)}function ud(){Ie.add(cd)}function Zu(e){if(Ie.has(cd))return!0;if(Ie.size===0)return!1;let t=h().prepare("SELECT project_id FROM sessions WHERE id = ?").get(e);return t?Ie.has(t.project_id):!1}var ed=10,PT=200,FT=2e3;function $T(){let e=Number(process.env.RECALL_VECTOR_HARD_CAP??"");return Number.isFinite(e)&&e>0?Math.min(FT,Math.floor(e)):PT}var UT=3,td=1e3,On=new Map,pi=new Set;function nd(e,t,n,s){if(e.size>=td&&(e instanceof Map,!e.has(t))){let r=e.keys().next();r.done||(e.delete(r.value),console.warn(`[vector-worker] ${s??"tracking-map"} hit ${td}-entry cap; evicted oldest entry`))}e instanceof Map?e.set(t,n):e.add(t)}var Ln=[],HT=50,sd=.3,Ft=[],BT=50,rd=!1,Cn=null,WT=0;function qT(e){if(!rd){rd=!0;return}e<=0||(Ln.push({ts:Date.now(),durationMs:e}),Ln.length>HT&&Ln.shift(),Cn=Cn===null?e:sd*e+(1-sd)*Cn,WT+=1)}function XT(e){e<=0||(Ft.push({ts:Date.now(),chunks:e}),Ft.length>BT&&Ft.shift())}function fi(){if(Ln.length<5||Cn===null)return null;let e=1e3/Cn,t=Ft.length>0?Ft.reduce((n,s)=>n+s.chunks,0)/Ft.length:30;return{samples:Ln.length,chunksPerSec:e,sessionsPerSec:e/Math.max(1,t),avgChunksPerSession:t,lastProcessedAt:gi}}function JT(){return h().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}async function GT(){if(mi())return!1;let e=h(),t=e.prepare("SELECT DISTINCT session_id FROM chunk_queue ORDER BY id LIMIT 1").get();if(!t)return Ie.size>0&&Ie.clear(),!1;if(Zu(t.session_id)||pi.has(t.session_id))return e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(t.session_id),!0;let n=t.session_id;try{ui(n);let s=Qu(n),r=di>0?di:1/0,o=Math.min(r,$T()),a=o<s.length?s.slice(0,o):s;if(a.length===0)return e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(n),!0;let c=le().modelId,u=e.prepare("INSERT INTO chunk_meta(session_id, message_uuids, text, embedding_model_id, embedding_dim, stale, generated_at) VALUES (?, ?, ?, ?, 768, 0, datetime('now'))"),d=e.prepare("INSERT INTO vec_chunks(rowid, embedding) VALUES (?, ?)"),p=0,f=!1;for(let b=0;b<a.length;b+=ed){if(Zu(n)){f=!0;break}let T=Math.min(a.length,b+ed),S=a.slice(b,T),R=S.map(F=>F.text),A=Date.now(),I=await Ve(R),j=Date.now()-A;e.transaction(()=>{for(let F=0;F<S.length;F++){let ne=u.run(n,JSON.stringify(S[F].messageUuids),S[F].text,c),U=Buffer.from(I[F].buffer,I[F].byteOffset,I[F].byteLength);d.run(BigInt(ne.lastInsertRowid),U)}})();let M=S.length>0?j/S.length:0;for(let F=0;F<S.length;F++)qT(M);p+=S.length,await new Promise(F=>setImmediate(F))}f&&ui(n),e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(n),gi=new Date().toISOString(),XT(p),On.has(n)&&On.delete(n)}catch(s){console.error("[vector-worker] failed for session",n,s);let r=(On.get(n)??0)+1;nd(On,n,r,"failureCounts"),r>=UT&&(nd(pi,n,void 0,"blacklist"),On.delete(n),console.error(`[vector-worker] blacklisted session ${n} after ${r} failures \u2014 will not retry until daemon restart`)),e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(n)}finally{if(Ie.size>0)try{let s=e.prepare("SELECT project_id FROM sessions WHERE id = ?").get(n);s!==void 0&&Ie.has(s.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(s.project_id)||Ie.delete(s.project_id))}catch(s){console.error("[vector-worker] cancelledProjects cleanup failed:",s)}}return!0}async function dd(){if(!le().loaded)return;DT()&&od();let e=await GT();ht!==null&&clearTimeout(ht),ht=setTimeout(()=>{dd()},e?id:jT)}function vn(){if(!Zs){if(!le().loaded){console.error("[vector-worker] cannot start: embedder not loaded");return}Zs=!0,ht=setTimeout(()=>{dd()},id)}}function pd(){Zs=!1,ht!==null&&(clearTimeout(ht),ht=null)}function Me(){return{running:Zs,queueDepth:JT(),lastProcessedAt:gi,blacklistedCount:pi.size,pausedForMigration:mi()}}var De=new Map;function md(e,t,n=Date.now()){let s=De.get(e);if(s?(s.count+=1,t>s.lastSize?s.noProgressCount=0:s.noProgressCount+=1,s.lastSize=t):De.set(e,{count:1,noProgressCount:0,firstSeenAt:n,lastSize:t}),De.size>1e4&&(gd(n),De.size>1e4)){let r=[...De.entries()].sort((a,c)=>a[1].firstSeenAt-c[1].firstSeenAt),o=De.size-1e4;for(let a=0;a<o;a+=1){let c=r[a];c&&De.delete(c[0])}}}function gd(e){let t=e-36e5;for(let[n,s]of De)s.firstSeenAt<t&&De.delete(n)}function er(e=10,t=Date.now()){gd(t);let n=[];for(let[s,r]of De)n.push({path:s,count:r.count,noProgressCount:r.noProgressCount,firstSeenAt:r.firstSeenAt});return n.sort((s,r)=>r.noProgressCount-s.noProgressCount||r.count-s.count||s.path.localeCompare(r.path)),n.slice(0,e)}function fd(e,t,n=1500,s=8e3){return t===void 0?n:Math.max(n,t+s-e)}function _i(e){return e.replace(/\\/g,"/").includes("/subagents/")}function hi(e){let t=e.split(/[/\\]/),n=t.findIndex(s=>s==="projects");return n===-1||n+1>=t.length?null:t[n+1]??null}function Ei(e){let{filePath:t,existing:n,currentMtime:s}=e;return _i(t)?{kind:"skip-durable",reason:"subagent_path"}:hi(t)?s===null?{kind:"skip-transient",reason:"file_vanished"}:n?.skipped_reason?{kind:"skip-already-marked"}:n&&n.file_mtime>=s?{kind:"skip-transient",reason:"unchanged_mtime"}:{kind:"process"}:{kind:"skip-durable",reason:"no_encoded_project"}}function tr(e,t,n,s={}){switch(n.kind){case"process":case"skip-transient":case"skip-already-marked":return;case"skip-durable":{YT(e,t,n.reason,n.detail,s);return}default:{let r=n;return}}}function YT(e,t,n,s,r){switch(n){case"subagent_path":return;case"no_encoded_project":{console.warn(`[watcher] cannot persist marker for ${t} \u2014 no encoded-project segment`);return}case"daemon_spawn_phantom":return;case"parse_failed":return;default:{let o=n;return}}}var bi=new Map,Ed=new Map;var Si=hi,sr=_i;function QT(e){let s=h().prepare("SELECT COUNT(*) AS n FROM sessions WHERE file_path = ?").get(e).n;console.log(`[watcher] indexed ${nr(e)} (${s} session${s===1?"":"s"})`)}function ZT(e,t){let n=nr(e).replace(/\.jsonl$/,"");if(!n)return;let s=Si(e);if(!s)return;let o=`parse_failed:${t instanceof Error&&t.name||"Error"}`,a=h(),c=a.prepare("SELECT id, skipped_reason FROM sessions WHERE id = ? LIMIT 1").get(n);if(c?.skipped_reason)return;let u=0;try{u=$t(e).mtimeMs}catch{}if(c){a.prepare("UPDATE sessions SET skipped_reason = ? WHERE id = ?").run(o,n);return}if(u===0)return;let d=s.replace(/^-/,"/").replace(/-/g,"/"),p=nr(d)||s,{id:f}=a.prepare(`INSERT INTO projects (encoded_path, decoded_path, name, repo_root, main_repo, is_repo, is_worktree)
|
|
1278
1317
|
VALUES (?, ?, ?, NULL, NULL, 0, 0)
|
|
1279
1318
|
ON CONFLICT(encoded_path) DO UPDATE SET decoded_path = excluded.decoded_path
|
|
1280
1319
|
RETURNING id`).get(s,d,p);a.prepare(`INSERT INTO sessions (
|
|
@@ -1283,7 +1322,7 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1283
1322
|
user_message_count, assistant_message_count,
|
|
1284
1323
|
first_user_message, cwd, git_branch, version, indexed_at,
|
|
1285
1324
|
skipped_reason
|
|
1286
|
-
) VALUES (?, ?, ?, ?, NULL, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, ?, ?)`).run(n,f,e,u,new Date().toISOString(),o)}async function
|
|
1325
|
+
) VALUES (?, ?, ?, ?, NULL, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, ?, ?)`).run(n,f,e,u,new Date().toISOString(),o)}async function ey(e){let t=null;try{t=$t(e).mtimeMs}catch{}let n=h(),s=n.prepare("SELECT id, file_mtime, skipped_reason FROM sessions WHERE file_path = ? LIMIT 1").get(e),r=Ei({filePath:e,existing:s?{file_mtime:s.file_mtime,skipped_reason:s.skipped_reason}:null,currentMtime:t});if(r.kind!=="process"){tr(n,e,r);return}let o=Si(e);if(!o)return;let a=t,c=$t(e,{bigint:!0}),u=ql(n,e),d=u?Ho(e,u.byteOffset)===u.prefixHash:!1,p=Hl(u,{size:Number(c.size),inode:Number(c.ino)},d),f=p.mode==="incremental"?p.start:0,{entries:b,endOffset:T}=await Ul(e,f),S=new Map,R=null;for(let x of b){let w=S.get(x.sessionId);if(w||(w={sessionId:x.sessionId,entries:[],earliest:null,latest:null,firstUser:null,users:0,assistants:0,cwd:null,branch:null,version:null},S.set(x.sessionId,w)),w.entries.push(x),x.timestamp&&((!w.earliest||x.timestamp<w.earliest)&&(w.earliest=x.timestamp),(!w.latest||x.timestamp>w.latest)&&(w.latest=x.timestamp)),x.role==="user"&&!x.isSidechain){if(w.users+=1,!w.firstUser&&x.contentText){let L=x.contentText.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();L&&!/^<local-command-caveat>/.test(L)&&(w.firstUser=be(L).redacted.slice(0,2e3))}}else x.role==="assistant"&&!x.isSidechain&&(w.assistants+=1);!w.cwd&&x.cwd&&(w.cwd=x.cwd),!w.branch&&x.gitBranch&&(w.branch=x.gitBranch),!w.version&&x.version&&(w.version=x.version),!R&&x.cwd&&(R=x.cwd)}let A=R?nr(R)||R:o,I=R??o.replace(/^-/,"/").replace(/-/g,"/"),j=null,J=null,M=0,F=0;if(Nn(I))j=Pt;else{let x=Qs(I);x.isRepo&&(j=x.root,J=x.mainRepo,M=1,F=x.isWorktree?1:0)}let ne=n.prepare(`INSERT INTO projects (encoded_path, decoded_path, name, repo_root, main_repo, is_repo, is_worktree)
|
|
1287
1326
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1288
1327
|
ON CONFLICT(encoded_path) DO UPDATE SET
|
|
1289
1328
|
decoded_path = excluded.decoded_path,
|
|
@@ -1292,7 +1331,7 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1292
1331
|
main_repo = excluded.main_repo,
|
|
1293
1332
|
is_repo = excluded.is_repo,
|
|
1294
1333
|
is_worktree = excluded.is_worktree
|
|
1295
|
-
RETURNING id`),
|
|
1334
|
+
RETURNING id`),U=n.prepare(`
|
|
1296
1335
|
INSERT INTO sessions (
|
|
1297
1336
|
id, project_id, file_path, file_mtime,
|
|
1298
1337
|
started_at, ended_at, message_count,
|
|
@@ -1314,13 +1353,13 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1314
1353
|
git_branch = excluded.git_branch,
|
|
1315
1354
|
version = excluded.version,
|
|
1316
1355
|
indexed_at = excluded.indexed_at
|
|
1317
|
-
`),
|
|
1356
|
+
`),v=n.prepare(`
|
|
1318
1357
|
INSERT INTO messages (uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1319
1358
|
is_sidechain, content_text, tool_names, raw_json)
|
|
1320
1359
|
VALUES (@uuid, @session_id, @parent_uuid, @type, @role, @timestamp,
|
|
1321
1360
|
@is_sidechain, @content_text, @tool_names, @raw_json)
|
|
1322
1361
|
ON CONFLICT(uuid) DO NOTHING
|
|
1323
|
-
`),
|
|
1362
|
+
`),i=n.prepare("DELETE FROM messages WHERE session_id = ?"),l=n.prepare(`
|
|
1324
1363
|
INSERT INTO sessions (
|
|
1325
1364
|
id, project_id, file_path, file_mtime,
|
|
1326
1365
|
started_at, ended_at, message_count,
|
|
@@ -1335,16 +1374,25 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1335
1374
|
file_mtime = excluded.file_mtime,
|
|
1336
1375
|
indexed_at = excluded.indexed_at,
|
|
1337
1376
|
skipped_reason = 'daemon_spawn_phantom'
|
|
1338
|
-
`),
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
`)
|
|
1377
|
+
`),m=n.prepare("SELECT 1 FROM sessions WHERE id = ? LIMIT 1"),g=n.prepare(`
|
|
1378
|
+
UPDATE sessions SET
|
|
1379
|
+
message_count = (SELECT COUNT(*) FROM messages WHERE session_id = @id),
|
|
1380
|
+
user_message_count = (SELECT COUNT(*) FROM messages WHERE session_id = @id AND role='user' AND is_sidechain=0),
|
|
1381
|
+
assistant_message_count = (SELECT COUNT(*) FROM messages WHERE session_id = @id AND role='assistant' AND is_sidechain=0),
|
|
1382
|
+
ended_at = COALESCE((SELECT MAX(timestamp) FROM messages WHERE session_id = @id AND timestamp IS NOT NULL), ended_at),
|
|
1383
|
+
file_mtime = @file_mtime,
|
|
1384
|
+
indexed_at = @indexed_at
|
|
1385
|
+
WHERE id = @id
|
|
1386
|
+
`),_=new Date().toISOString(),E=new Set,y=new Set;n.transaction(()=>{let{id:x}=ne.get(o,I,A,j,J,M,F);for(let w of S.values()){if(pu(w.firstUser)){console.log(`[watcher] skipping daemon-spawn phantom ${w.sessionId} (first message matches autonomous-spawn pattern)`),i.run(w.sessionId),l.run({id:w.sessionId,project_id:x,file_path:e,file_mtime:a,started_at:w.earliest,ended_at:w.latest,cwd:w.cwd,indexed_at:_}),tr(n,e,{kind:"skip-durable",reason:"daemon_spawn_phantom"}),E.add(w.sessionId);continue}if(p.mode==="incremental"&&m.get(w.sessionId)){y.add(w.sessionId);for(let $ of w.entries)v.run(_d($));g.run({id:w.sessionId,file_mtime:a,indexed_at:_}),Jo(n,w.sessionId,w.entries,{insertOnly:!0}),yn(n,w.sessionId);continue}let L=li(w.firstUser),C=L.alias?L.stripped:w.firstUser;if(U.run({id:w.sessionId,project_id:x,file_path:e,file_mtime:a,started_at:w.earliest,ended_at:w.latest,message_count:w.entries.length,user_message_count:w.users,assistant_message_count:w.assistants,first_user_message:C,cwd:w.cwd,git_branch:w.branch,version:w.version,indexed_at:_}),L.alias&&!Se(w.sessionId))try{_e(w.sessionId,L.alias)}catch($){console.error(`[watcher] header-alias setAlias failed for ${w.sessionId}:`,$)}i.run(w.sessionId);for(let $ of w.entries)v.run(_d($));Jo(n,w.sessionId,w.entries),yn(n,w.sessionId)}})();let O=Ho(e,T),N=n.prepare("SELECT COUNT(*) AS n FROM messages m JOIN sessions s ON s.id = m.session_id WHERE s.file_path = ?").get(e);if(Xl(n,e,{byteOffset:T,sizeBytes:Number(c.size),inode:Number(c.ino),prefixHash:O,mtime:a,lineCount:N.n}),ft().heuristicEnabled)for(let x of S.values()){if(E.has(x.sessionId)||y.has(x.sessionId))continue;let w=li(x.firstUser).stripped,L=Dt(w);L&&he(x.sessionId,L,"heuristic")}}function bd(e){return Ed.set(e,Date.now()),Yu(e,()=>ey(e)).finally(()=>{let t=0;try{t=$t(e).size}catch{}md(e,t)})}function _d(e){let t=be(e.contentText).redacted,n=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:n}}function hd(e){let t=bi.get(e);t?.timer&&clearTimeout(t.timer);let n=fd(Date.now(),Ed.get(e)),s={timer:null};s.timer=setTimeout(()=>{bi.delete(e),bd(e).then(async()=>{QT(e),Ms(e);try{let o=h().prepare("SELECT id FROM sessions WHERE file_path = ? AND skipped_reason IS NULL").all(e);for(let a of o){ul(a.id),au(a.id);try{Hu(a.id)}catch(c){console.error("[watcher] auto-collections apply failed:",c)}}}catch(r){console.error("[watcher] semantic dispatch failed:",r)}}).catch(r=>{console.error(`[watcher] reindex failed for ${e}:`,r)})},n),bi.set(e,s)}function ty(e){if(e.endsWith(".jsonl"))return sr(e);try{if($t(e).isDirectory())return!1}catch{}return!0}function Sd(){let e=zT(on,{depth:4,ignoreInitial:!0,persistent:!0,awaitWriteFinish:{stabilityThreshold:500,pollInterval:200},ignored:ty});return e.on("add",t=>t.endsWith(".jsonl")&&!sr(t)&&hd(t)),e.on("change",t=>t.endsWith(".jsonl")&&!sr(t)&&hd(t)),e.on("ready",()=>{console.log(`[watcher] ready, watching ${on}`)}),e.on("error",t=>{console.error("[watcher] chokidar error:",t)}),process.env.RECALL_WATCHER_DEBUG==="1"&&(e.on("add",t=>console.log(`[watcher.debug] add: ${t}`)),e.on("change",t=>console.log(`[watcher.debug] change: ${t}`)),e.on("unlink",t=>console.log(`[watcher.debug] unlink: ${t}`))),e}var ny=4;function*Ti(e){let t;try{t=KT(e,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(let n of t){if(n.isSymbolicLink())continue;let s=VT(e,n.name);n.isDirectory()?yield*Ti(s):n.isFile()&&n.name.endsWith(".jsonl")&&(yield s)}}async function yi(){let e=Date.now(),t={scanned:0,reindexed:0,upToDate:0,skipped:0,errors:0,durationMs:0},n=h(),s=[],r=n.prepare("SELECT file_mtime, skipped_reason FROM sessions WHERE file_path = ? LIMIT 1");for(let d of Ti(on)){t.scanned+=1;let p;try{p=$t(d).mtimeMs}catch{p=null}let f=r.get(d),b=Ei({filePath:d,existing:f??null,currentMtime:p});switch(b.kind){case"skip-durable":tr(n,d,b),t.skipped+=1;continue;case"skip-already-marked":t.skipped+=1;continue;case"skip-transient":b.reason==="unchanged_mtime"?t.upToDate+=1:t.errors+=1;continue;case"process":s.push(d);continue}}if(s.length===0)return t.durationMs=Date.now()-e,t;let o=s.slice(),a=async()=>{for(;o.length>0;){let d=o.shift();if(!d)break;try{await bd(d),t.reindexed+=1}catch(p){t.errors+=1;let f=p instanceof Error?p.message:String(p);try{ZT(d,p)}catch(b){console.error(`[ingestion-sweep] marker write failed for ${d}:`,b)}console.error(`[ingestion-sweep] failed for ${d}: ${f}`)}}},c=Math.min(ny,s.length),u=[];for(let d=0;d<c;d+=1)u.push(a());return await Promise.all(u),t.durationMs=Date.now()-e,t}async function Td(){try{let e=await yi();e.reindexed>0&&console.log(`[safety-sweep] reindexed ${e.reindexed} file(s) the watcher missed (scanned=${e.scanned}, upToDate=${e.upToDate})`)}catch(e){console.error("[safety-sweep] failed:",e)}}import{execFileSync as kd}from"node:child_process";var sy=["dist/mcp-server.js","dist/mcp/server.js"];function ry(e){for(let t of sy)if(e.includes(t))return!0;return!1}function Ut(e={}){let t=e.psOutput??oy(),n=e.isProcessAlive??iy,s=e.getParentCommand??ay,r=[];for(let o of t.split(`
|
|
1387
|
+
`)){let a=o.trim();if(!a||!ry(a)||cy(a))continue;let c=a.split(/\s+/);if(c.length<5)continue;let u=Number(c[0]),d=Number(c[1]),p=c[2],f=c[3],b=c[4],T=0,S=0;if(/^\d+$/.test(f)&&(b.includes(".")||/^\d+$/.test(b))?(T=Number(f),S=Number(b)):S=Number(f),!Number.isFinite(u)||!Number.isFinite(d))continue;let A=d>1&&n(d);r.push({pid:u,ppid:d,parentAlive:A,etimeSeconds:ly(p),pcpu:Number.isFinite(S)?S:0,rssKb:Number.isFinite(T)?T:0,orphan:!A,parentCommand:A?s(d):null})}return r}function oy(){try{return kd("ps",["-axo","pid,ppid,etime,rss,pcpu,command"],{encoding:"utf8",timeout:2e3,maxBuffer:5*1024*1024})}catch(e){let t=e instanceof Error?e.message:String(e);return process.stderr.write(`[mcp-processes] ps -axo failed: ${t}
|
|
1388
|
+
`),""}}function iy(e){if(!Number.isFinite(e)||e<=1)return!1;try{return process.kill(e,0),!0}catch{return!1}}function ay(e){if(!Number.isFinite(e)||e<=1)return null;try{let n=kd("ps",["-p",String(e),"-o","command="],{encoding:"utf8",timeout:1e3,maxBuffer:1048576}).trim();return n.length?n.slice(0,200):null}catch{return null}}function cy(e){let t=e.split(/\s+/);if(t.length<5)return!1;let n=[t[5]??"",t[4]??""];for(let s of n)if(s&&(s.endsWith("/grep")||s==="grep"||s.endsWith("/awk")||s==="awk"||s.endsWith("/rg")||s==="rg"))return!0;return!1}function ly(e){if(!e)return 0;let t=0,n=e,s=e.indexOf("-");s>=0&&(t=yd(e.slice(0,s)),n=e.slice(s+1));let r=n.split(":").map(yd),o=0,a=0,c=0;return r.length===3?[o,a,c]=r:r.length===2?[a,c]=r:r.length===1&&(c=r[0]),t*86400+o*3600+a*60+c}function yd(e){let t=Number(e);return Number.isFinite(t)?t:0}var uy=50,dy=60;function wi(e){return e.pcpu>=uy&&e.etimeSeconds>=dy}var wd=4,py=1024*1024;function Rd(e){return e.reduce((t,n)=>t+(Number.isFinite(n.rssKb)&&n.rssKb>0?n.rssKb:0),0)}function Ri(e){let t=e??Ut(),n=t.length,s=Rd(t),r=t.filter(b=>b.orphan),o=r.length,a=Rd(r),c=o>wd,u=a>py;if(!(c||u))return{flagged:!1,severity:"ok",count:n,aggregateRssKb:s,orphanCount:o,orphanRssKb:a,message:null};let p=[];c&&p.push(`${o} orphaned MCP children (threshold ${wd})`),u&&p.push(`${my(a)} aggregate RSS across orphaned children (threshold 1 GiB)`);let f=`Zombie MCP threshold breached: ${p.join(" + ")}. Each orphaned MCP child (its parent claude/VS Code tab has exited) still holds a SQLite read connection and can pin WAL checkpoints. Run \`recall mcp-prune --all\` to reap (note: pre-2026-05-23 builds may miss processes from stale install paths -- \`pkill -f mcp-server.js\` is the nuclear option).`;return{flagged:!0,severity:"high",count:n,aggregateRssKb:s,orphanCount:o,orphanRssKb:a,message:f}}function my(e){return e<1024?`${e} KB`:e<1024*1024?`${(e/1024).toFixed(1)} MB`:`${(e/(1024*1024)).toFixed(2)} GB`}import{execFileSync as gy}from"node:child_process";var fy=["dist/daemon/entrypoint.js"];function _y(e){for(let t of fy)if(e.includes(t))return!0;return!1}function hy(e){if(e.length<5)return!1;let t=[e[5]??"",e[4]??""];for(let n of t)if(n&&(n.endsWith("/grep")||n==="grep"||n.endsWith("/awk")||n==="awk"||n.endsWith("/rg")||n==="rg"))return!0;return!1}function ki(e={}){let t=e.psOutput??Ey(),n=new Set(e.excludePids??[]),s=[];for(let r of t.split(`
|
|
1389
|
+
`)){let o=r.trim();if(!o||!_y(o))continue;let a=o.split(/\s+/);if(hy(a)||a.length<5)continue;let c=Number(a[0]),u=Number(a[1]),d=a[2];!Number.isFinite(c)||!Number.isFinite(u)||n.has(c)||s.push({pid:c,ppid:u,etimeSeconds:by(d),etime:d,command:o})}return s.sort((r,o)=>r.etimeSeconds-o.etimeSeconds),s}function Ey(){try{return gy("ps",["-axo","pid,ppid,etime,rss,pcpu,command"],{encoding:"utf8",timeout:2e3,maxBuffer:5*1024*1024})}catch(e){let t=e instanceof Error?e.message:String(e);return process.stderr.write(`[daemon-processes] ps -axo failed: ${t}
|
|
1390
|
+
`),""}}function by(e){if(!e)return 0;let t=0,n=e,s=e.indexOf("-");s>=0&&(t=Ad(e.slice(0,s)),n=e.slice(s+1));let r=n.split(":").map(Ad),o=0,a=0,c=0;return r.length===3?[o,a,c]=r:r.length===2?[a,c]=r:r.length===1&&(c=r[0]),t*86400+o*3600+a*60+c}function Ad(e){let t=Number(e);return Number.isFinite(t)?t:0}Z();import{existsSync as rr,readFileSync as $v,writeFileSync as Bt,unlinkSync as Ay}from"node:fs";import{basename as or,join as Ai}from"node:path";import{randomBytes as Ry}from"node:crypto";Z();import{join as ky}from"node:path";Z();import{appendFileSync as Sy}from"node:fs";import{join as Ty}from"node:path";var yy=Ty(W,"daemon.log"),wy="[state-files-audit]";function Ht(e,t){let s=(new Error().stack??"").split(`
|
|
1391
|
+
`).slice(2,4).map(a=>a.trim().replace(/file:\/\/.*?(\bdist\b|\bsrc\b)/g,"$1")).join(" << "),o=`${new Date().toISOString()} ${wy} ${e}: ${t} | caller: ${s}
|
|
1392
|
+
`;try{Sy(yy,o)}catch{}}var xd=ky(W,"daemon.token");function Nd(){let e=Ry(32).toString("hex");return Ht("mint-token",`length=${e.length}`),e}var xy=Ai(W,"daemon.pid"),Ny=Ai(W,"daemon.port"),Xv=Ai(W,"daemon.log");function In(){return{pid:xy,port:Ny,token:xd}}function Od(e,t={}){let n=t.paths??In();K(),Bt(n.pid,JSON.stringify({pid:e.pid,port:e.port,startedAt:e.startedAt}),{encoding:"utf8",mode:384}),Bt(n.port,String(e.port),{encoding:"utf8",mode:384}),Bt(n.token,e.token,{encoding:"utf8",mode:384}),Ht("write-info-atomic",`pid=${e.pid} port=${e.port}`)}function xi(e={}){let t=e.paths??In(),n=[];for(let s of[t.pid,t.port,t.token])if(rr(s))try{Ay(s),n.push(or(s))}catch{}return Ht("clear-force",`cleared: ${n.join(",")||"(none -- files already absent)"}`),{deleted:!0,reason:`cleared ${n.length} file(s)`,cleared:n}}function Ld(e){return Oy(e)}function Oy(e){try{return process.kill(e,0),!0}catch{return!1}}function Cd(e,t={}){let n=t.paths??In(),s=[];return K(),rr(n.pid)||(Bt(n.pid,JSON.stringify({pid:e.pid,port:e.port,startedAt:e.startedAt}),{encoding:"utf8",mode:384}),s.push(or(n.pid))),rr(n.port)||(Bt(n.port,String(e.port),{encoding:"utf8",mode:384}),s.push(or(n.port))),rr(n.token)||(Bt(n.token,e.token,{encoding:"utf8",mode:384}),s.push(or(n.token))),s.length>0&&Ht("heal",`restored: ${s.join(",")}`),{restored:s}}import{statSync as Ly}from"node:fs";function Ni(e){if(e instanceof Error)return e.stack??e.message;try{return JSON.stringify(e)}catch{return String(e)}}var Cy=6e4,vy=5*6e4,vd=1073741824,Iy=5368709120,My=6e4,Dy=100;function Oi(e,t="PASSIVE",n){let s=Date.now(),r=e.pragma(`wal_checkpoint(${t})`),o=Py(r),a={busy:o.busy,log:o.log,moved:o.checkpointed,mode:t,durationMs:Date.now()-s};return n&&(t==="PASSIVE"&&a.log>=Dy&&a.moved===0?n(`[wal-maintenance] PASSIVE checkpoint blocked: log=${a.log} frames pending, moved=0 (readers holding snapshots)`):a.moved>0&&n(`[wal-maintenance] ${t} checkpoint: log=${a.log} moved=${a.moved} busy=${a.busy} (${a.durationMs}ms)`)),a}function Id(e){let t=e.db,n=e.walPath,s=e.checkpointEveryMs??Cy,r=e.sizeCheckEveryMs??vy,o=e.warnBytes??vd,a=e.errorBytes??Iy,c=e.forceRestartCooldownMs??My,u=e.logger??(I=>{process.stderr.write(I+`
|
|
1393
|
+
`)}),d=e.statFn??(I=>Ly(I)),p=e.describeSuspectsFn??jy,f=0,b=null,T=()=>{try{Oi(t,"PASSIVE",u).moved>0&&(b=Date.now())}catch(I){u(`[wal-maintenance] PASSIVE checkpoint threw: ${Ni(I)}`)}},S=()=>{let I;try{I=d(n).size}catch(j){let J=j.code;if(J!=="ENOENT"){let M=j instanceof Error?j.message:String(j);u(`[wal-maintenance] WAL stat failed (${J??"unknown"}): ${M}`)}return}if(I>=a){u(`[wal-maintenance] ERROR: WAL ${ir(I)} exceeds error threshold ${ir(a)}`);let j=p();j&&u(`[wal-maintenance] ${j}`);let J=Date.now();if(J-f>=c){f=J;try{let M=Oi(t,"RESTART",u);u(`[wal-maintenance] forced RESTART: moved=${M.moved} busy=${M.busy} log=${M.log} (${M.durationMs}ms)`),M.moved>0&&(b=Date.now())}catch(M){u(`[wal-maintenance] forced RESTART threw: ${Ni(M)}`)}}}else I>=o&&u(`[wal-maintenance] WARN: WAL ${ir(I)} exceeds warn threshold ${ir(o)}`)},R=setInterval(T,s),A=setInterval(S,r);return typeof R.unref=="function"&&R.unref(),typeof A.unref=="function"&&A.unref(),{stop:()=>{clearInterval(R),clearInterval(A)},checkpointNow:(I="PASSIVE")=>{let j=Oi(t,I,u);return j.moved>0&&(b=Date.now()),j},walSizeBytes:()=>{try{return d(n).size}catch{return 0}},lastCheckpointAt:()=>b}}function jy(e={}){let n=(e.list??Ut)();if(n.length===0)return null;let s=n.find(wi);if(s)return`pin suspect: pid ${s.pid} at ${s.pcpu.toFixed(0)}%cpu, elapsed ${s.etimeSeconds}s (parent ${s.parentCommand??s.ppid})`;let r=n.slice().sort((o,a)=>a.etimeSeconds-o.etimeSeconds)[0];return`pin suspect: pid ${r.pid}, elapsed ${r.etimeSeconds}s (parent ${r.parentCommand??r.ppid})`}function Py(e){let t=Array.isArray(e)?e[0]??{}:e??{};return{busy:Li(t.busy),log:Li(t.log),checkpointed:Li(t.checkpointed)}}function Li(e){return typeof e=="number"&&Number.isFinite(e)?e:typeof e=="bigint"?Number(e):0}function ir(e){return e>=1e9?`${(e/1e9).toFixed(2)} GB`:e>=1e6?`${(e/1e6).toFixed(0)} MB`:e>=1e3?`${(e/1e3).toFixed(0)} KB`:`${e} B`}Z();H();import{existsSync as Fy,readFileSync as $y,renameSync as Dd,writeFileSync as Uy}from"node:fs";import{join as Hy}from"node:path";var By=24,Wy=3600*1e3,qy=1e3,Xy=1e3,Jy=1e4,Gy=5e3,Yy=1440*60*1e3,Md=1e4;function jd(){return Hy(W,"doctor-state.json")}function Pd(){let e=jd();if(!Fy(e))return{chunkQueue:{samples:[]}};let t;try{t=$y(e,"utf8")}catch{return{chunkQueue:{samples:[]}}}try{let n=JSON.parse(t),s=n.chunkQueue?.samples,r=Array.isArray(s)?s.filter(u=>typeof u=="object"&&u!==null&&typeof u.ts=="string"&&typeof u.size=="number"&&typeof u.semanticEnabled=="boolean"):[],o=n.autoPruneCounters?.events,a=Array.isArray(o)?o.filter(u=>{if(!u||typeof u!="object")return!1;let d=u;return!(typeof d.ts!="string"||typeof d.pid!="number"||!Number.isFinite(d.pid)||d.action!=="would_kill"&&d.action!=="killed"&&d.action!=="failed"||d.reason!=="orphan_10min"&&d.reason!=="runaway_cpu_5min")}):[],c={chunkQueue:{samples:r}};return(a.length>0||o!==void 0)&&(c.autoPruneCounters={events:a}),c}catch{try{Dd(e,`${e}.corrupt.${Date.now()}`)}catch{}return{chunkQueue:{samples:[]}}}}function Fd(e){let t=jd(),n=`${t}.tmp`;try{Uy(n,JSON.stringify(e,null,2),{mode:384}),Dd(n,t)}catch{}}function Ci(){let e=h(),t=0;try{t=e.prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}catch{}let n=!1;try{n=e.prepare("SELECT value FROM app_settings WHERE key = 'semantic_enabled'").get()?.value==="1"}catch{}let s=Pd(),r=Date.now(),o=s.chunkQueue.samples,a=o.map(S=>({s:S,ms:Date.parse(S.ts)})).filter(S=>Number.isFinite(S.ms)&&r-S.ms<=Wy).sort((S,R)=>S.ms-R.ms),c=null;a.length>0&&(c=t-a[0].s.size);let u=a.length,p={chunkQueue:{samples:[...o,{ts:new Date(r).toISOString(),size:t,semanticEnabled:n}].slice(-By)}};s.autoPruneCounters&&(p.autoPruneCounters=s.autoPruneCounters),Fd(p);let f="ok",b=`chunk_queue growth: ok (current ${t.toLocaleString()} row${t===1?"":"s"}, semantic ${n?"on":"off"}, ${u} prior sample${u===1?"":"s"} in last hour).`,T=null;return t>qy&&!n&&c!==null&&c>Xy?(f="critical",b=`chunk_queue growth: CRITICAL \u2014 ${t.toLocaleString()} rows with semantic disabled, grew by ${c.toLocaleString()} in the last hour. Schema-sync race shape.`,T="Schema-sync race likely \u2014 Phase 1.1 fix shipped on `feat/daemon-state-integrity`; if rows persist after a clean restart, re-investigate. Inspect with `sqlite3 ~/.recall/db.sqlite 'SELECT action, COUNT(*) FROM chunk_queue GROUP BY action;'`."):t>Jy?(f="high",b=`chunk_queue growth: HIGH \u2014 ${t.toLocaleString()} rows pending (semantic ${n?"on":"off"}`+(c!==null?`, last-hour \u0394 ${c>=0?"+":""}${c.toLocaleString()}`:"")+").",T="Queue is large but stable \u2014 operator-authorized `recall semantic backfill` (or the daemon's embed worker, if intentionally on) would drain."):c!==null&&c>Gy&&(f="medium",b=`chunk_queue growth: MEDIUM \u2014 grew by ${c.toLocaleString()} in the last hour (current ${t.toLocaleString()}, semantic ${n?"on":"off"}).`,T="Growth is fast even though the absolute size is small. Re-run `recall doctor` in 10\u201330 min; if growth continues without semantic on, investigate the schema-sync gate."),{status:f,currentSize:t,semanticEnabled:n,lastHourGrowth:c,priorSampleCount:u,message:b,remediation:T}}function $d(e,t=Date.now()){if(e.length===0)return;let n=Pd(),r=[...n.autoPruneCounters?.events??[],...e],o=zy(r,t),a={chunkQueue:n.chunkQueue,autoPruneCounters:{events:o}};Fd(a)}function zy(e,t){let n=e.filter(s=>{let r=Date.parse(s.ts);return Number.isFinite(r)&&t-r<=Yy});return n.length>Md?n.slice(-Md):n}var Ud="dry-run",Ky=600,Vy=95,Qy=300,Zy=5e3;function ar(e=process.env){let t=e.RECALL_AUTO_PRUNE;if(typeof t!="string")return Ud;let n=t.trim().toLowerCase();return n==="off"||n==="dry-run"||n==="enabled"?n:Ud}async function Wd(e={}){let t=e.mode??ar(),n=e.log??sw,s=e.now??Date.now,r={mode:t,candidates:[],wouldHaveKilled:[],killed:[],failed:[]};if(t==="off")return r;let o=e.list??Ut,a;try{a=o()}catch(p){return n(`[auto-prune] inventory scan failed: ${Ze(p)}`),r}for(let p of a){let f=ew(p);f!==null&&r.candidates.push({pid:p.pid,reason:f,proc:p})}if(r.candidates.length===0)return r;if(t==="dry-run"){for(let p of r.candidates)n(vi("WOULD kill",p.pid,p.reason,p.proc)),r.wouldHaveKilled.push({pid:p.pid,reason:p.reason});return Bd(r,s()),r}let c=e.kill??tw,u=e.sleep??nw,d=[];for(let p of r.candidates)try{c(p.pid,"SIGTERM"),d.push({pid:p.pid,reason:p.reason})}catch(f){f?.code==="ESRCH"?r.killed.push({pid:p.pid,reason:p.reason}):(n(`[auto-prune] kill failed pid=${p.pid} reason=${p.reason} error=${Ze(f)}`),r.failed.push({pid:p.pid,reason:p.reason,error:Ze(f)}))}await u(Zy);for(let p of d){let f=!1;try{c(p.pid,0),f=!0}catch(b){b?.code==="ESRCH"?r.killed.push({pid:p.pid,reason:p.reason}):(n(`[auto-prune] liveness probe failed pid=${p.pid} reason=${p.reason} error=${Ze(b)}`),r.failed.push({pid:p.pid,reason:p.reason,error:Ze(b)}))}if(f)try{c(p.pid,"SIGKILL"),r.killed.push({pid:p.pid,reason:p.reason}),n(vi("killed",p.pid,p.reason,Hd(r.candidates,p.pid)))}catch(b){n(`[auto-prune] SIGKILL failed pid=${p.pid} reason=${p.reason} error=${Ze(b)}`),r.failed.push({pid:p.pid,reason:p.reason,error:Ze(b)})}else n(vi("killed",p.pid,p.reason,Hd(r.candidates,p.pid)))}return Bd(r,s()),r}function ew(e){return e.orphan&&e.etimeSeconds>Ky?"orphan_10min":e.pcpu>Vy&&e.etimeSeconds>Qy?"runaway_cpu_5min":null}function Hd(e,t){return e.find(s=>s.pid===t).proc}function vi(e,t,n,s){return`[auto-prune] ${e} pid=${t} reason=${n} etime=${s.etimeSeconds}s pcpu=${s.pcpu}% rssKb=${s.rssKb} ppid=${s.ppid} parentAlive=${s.parentAlive}`}function Bd(e,t){let n=new Date(t).toISOString(),s=[];for(let r of e.wouldHaveKilled)s.push({ts:n,pid:r.pid,action:"would_kill",reason:r.reason});for(let r of e.killed)s.push({ts:n,pid:r.pid,action:"killed",reason:r.reason});for(let r of e.failed)s.push({ts:n,pid:r.pid,action:"failed",reason:r.reason});if(s.length!==0)try{$d(s,t)}catch(r){process.stderr.write(`[auto-prune] counter persist failed: ${Ze(r)}
|
|
1394
|
+
`)}}function tw(e,t){process.kill(e,t)}function nw(e){return new Promise(t=>{setTimeout(t,e)})}function sw(e){process.stderr.write(e+`
|
|
1395
|
+
`)}function Ze(e){if(e instanceof Error){let t=e.code;return t?`${t} ${e.message}`:e.message}return String(e)}Z();import{existsSync as rw,readFileSync as ow,renameSync as Xd,writeFileSync as iw}from"node:fs";import{createHash as aw}from"node:crypto";import{join as cw}from"node:path";function Jd(){return cw(W,"doctor-alerts.json")}function lw(e,t){let n=Object.keys(t).sort(),s={};for(let o of n)s[o]=t[o];let r=aw("sha256");return r.update(e),r.update("\0"),r.update(JSON.stringify(s)),r.digest("hex")}function Ii(){let e=Jd();if(!rw(e))return{version:1,lastTickAt:new Date(0).toISOString(),alerts:[]};let t;try{t=ow(e,"utf8")}catch{return{version:1,lastTickAt:new Date(0).toISOString(),alerts:[]}}let n;try{n=JSON.parse(t)}catch{return qd(e),{version:1,lastTickAt:new Date(0).toISOString(),alerts:[]}}if(!n||typeof n!="object"||Array.isArray(n))return qd(e),{version:1,lastTickAt:new Date(0).toISOString(),alerts:[]};let s=n,r=Array.isArray(s.alerts)?s.alerts.filter(uw):[];return{version:1,lastTickAt:typeof s.lastTickAt=="string"?s.lastTickAt:new Date(0).toISOString(),alerts:r}}function qd(e){try{Xd(e,`${e}.corrupt.${Date.now()}`)}catch{}}function uw(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.id=="string"&&typeof t.check=="string"&&(t.severity==="critical"||t.severity==="high"||t.severity==="medium")&&typeof t.message=="string"&&typeof t.remediation=="string"&&typeof t.firstSeenAt=="string"&&typeof t.lastSeenAt=="string"&&typeof t.seenCount=="number"&&typeof t.acknowledged=="boolean"}function Mi(e){let t=Jd(),n=`${t}.tmp`;try{iw(n,JSON.stringify(e,null,2),{mode:384}),Xd(n,t)}catch{}}function Gd(e,t,n=new Date){let s=n.toISOString(),r=new Map;for(let o of e.alerts)r.set(o.id,o);for(let o of t){let a=lw(o.check,o.keyFacts),c=r.get(a);c?r.set(a,{...c,severity:o.severity,message:o.message,remediation:o.remediation,lastSeenAt:s,seenCount:c.seenCount+1}):r.set(a,{id:a,check:o.check,severity:o.severity,message:o.message,remediation:o.remediation,firstSeenAt:s,lastSeenAt:s,seenCount:1,acknowledged:!1})}return{version:1,lastTickAt:s,alerts:[...r.values()]}}var gw=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],MI=new RegExp(`^(${gw.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);var Vd=50;var DI=5*6e4;function fw(e){return e==="db.sqlite"||e==="db.sqlite-wal"||e==="db.sqlite-shm"?!1:!!(e.startsWith("db.sqlite.")||e.endsWith(".bak")||e.includes(".bak."))}function Qd(e=W){let t=0,n=0;try{let T=zd(e);t=Number(T.bavail)*Number(T.bsize),n=Number(T.blocks)*Number(T.bsize)}catch{}let s=n>0?t/n*100:100,r=[];try{r=dw(e)}catch{r=[]}let o=0,a=0,c=[],u=Date.now(),d=720*60*60*1e3;for(let T of r){if(!fw(T))continue;let S;try{S=pw(Kd(e,T))}catch{continue}if(!S.isFile())continue;a+=1,o+=S.size;let R=Math.max(0,u-S.mtimeMs),A=Math.floor(R/(1440*60*1e3));R>=d&&c.push({name:T,sizeBytes:S.size,ageDays:A})}c.sort((T,S)=>S.sizeBytes!==T.sizeBytes?S.sizeBytes-T.sizeBytes:S.ageDays-T.ageDays);let p=2*1024**3,f=5*1024**3,b="ok";return n>0&&s<10||o>f?b="high":n>0&&s<20||o>p?b="medium":a>0&&(b="low"),{freeBytes:t,totalBytes:n,freePercent:s,backupTotalBytes:o,backupFileCount:a,oldFiles:c,severity:b}}function Zd(e={}){if((e.liveDaemons??ki({excludePids:[process.pid]})).length===0)return{flagged:!1,severity:"ok",daemonAlive:!1,missing:[],message:null,remediation:null};let n=e.paths??In(),s=e.existsSync??Di,r=e.isProcessAlive??Ld,o=!1;if(s(n.pid))try{let u=JSON.parse(Yd(n.pid,"utf8"));typeof u.pid=="number"&&r(u.pid)&&(o=!0)}catch{}if(!o)return{flagged:!1,severity:"ok",daemonAlive:!0,missing:[],message:null,remediation:null};let a=[];return s(n.port)||a.push("daemon.port"),s(n.token)||a.push("daemon.token"),a.length===0?{flagged:!1,severity:"ok",daemonAlive:!0,missing:[],message:null,remediation:null}:{flagged:!0,severity:"critical",daemonAlive:!0,missing:a,message:`Daemon process is alive (per pidfile) but companion state file${a.length===1?"":"s"} ${a.join(", ")} missing. Web UI and authenticated CLI calls will return 401 until the 30s heal tick restores them.`,remediation:"The 30-second heal tick re-creates these files automatically. To force immediate recovery: recall stop && recall start. Inspect ~/.recall/daemon.log for [state-files-audit] entries to identify the deletion source."}}function ep(e=Kd(mw(),".claude.json")){let t={status:"ok",configPath:e,configExists:Di(e),findings:[]};if(!t.configExists)return t;let n;try{n=Yd(e,"utf8")}catch{return t}let s;try{s=JSON.parse(n)}catch{return t}if(!s||typeof s!="object"||Array.isArray(s))return t;let r=s.mcpServers;if(!r||typeof r!="object"||Array.isArray(r))return t;for(let[o,a]of Object.entries(r)){if(!a||typeof a!="object")continue;let c=a.args;if(!Array.isArray(c)||c.length===0)continue;let u=c[0];if(typeof u!="string"||u.length===0||!u.startsWith("/")&&!u.startsWith("~")||Di(u))continue;let d=o==="recall";t.findings.push({name:o,stalePath:u,severity:d?"HIGH":"MEDIUM",remediation:d?"restart daemon (`recall stop && recall start`) \u2014 auto-repoint runs on boot now":"this is a third-party MCP \u2014 the vendor's package may have moved; reinstall their package"})}return t.findings.some(o=>o.severity==="HIGH")?t.status="fail":t.findings.length>0&&(t.status="warn"),t}function ji(){try{let e=zd(W);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}function tp(e){let{projects:t,sessions:n,messages:s,port:r,version:o}=e;return`<!DOCTYPE html>
|
|
1348
1396
|
<html lang="en">
|
|
1349
1397
|
<head>
|
|
1350
1398
|
<meta charset="utf-8" />
|
|
@@ -1399,32 +1447,32 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1399
1447
|
</footer>
|
|
1400
1448
|
</main>
|
|
1401
1449
|
</body>
|
|
1402
|
-
</html>`}var
|
|
1450
|
+
</html>`}var _w=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,hw=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,Ew=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function bw(e){return e.replace(_w,"").trim()}function Sw(e){let t=e.replace(hw,"[tool call]");return t=t.replace(Ew,"[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,`
|
|
1403
1451
|
|
|
1404
|
-
`),t.trim()}function
|
|
1405
|
-
`)}function
|
|
1452
|
+
`),t.trim()}function Tw(e){return e.role??e.type??"message"}function np(e,t,n={}){let s=n.mode??"condensed",r=n.includeSidechain===!0,o=n.since?Date.parse(n.since):0,a=t.filter(p=>!(!r&&p.is_sidechain===1||o&&p.timestamp&&Date.parse(p.timestamp)<o)),c=[];n.prelude&&(c.push(n.prelude.trim()),c.push("")),c.push(`# Claude Recall, past session context (${s})`),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 p of a){let f=p.content_text??"",b=bw(f);s==="condensed"&&(b=Sw(b));let T=b.length>0,S=!!p.tool_names&&p.tool_names.length>0;if(!T&&!S){d+=1;continue}let R=Tw(p),A=p.timestamp?` \`${p.timestamp}\``:"";c.push(`## ${R}${A}`),c.push(""),S&&s==="condensed"&&(c.push(`_tools used: ${p.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(`
|
|
1453
|
+
`)}function Mn(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),n=Date.parse(e.terminalOpenedAt);return Number.isFinite(t)?Number.isFinite(n)?n-t>6e4?{allowed:!1,reason:"terminal-postdates-session"}:{allowed:!0}:{allowed:!1,reason:"missing-terminal-opened-at"}:{allowed:!1,reason:"missing-session-started-at"}}H();Z();import{writeFileSync as yw,mkdirSync as ww,existsSync as Rw}from"node:fs";import{join as rp}from"node:path";var Pi=rp(W,"notes"),sp=200,kw=12e3,Aw=800,xw=8e3;function op(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ip(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t.filter(n=>!!n&&typeof n=="object"&&typeof n.synopsis=="string"&&typeof n.replaced_at=="string"):[]}catch{return[]}}function Nw(){K(),Rw(Pi)||ww(Pi,{recursive:!0})}function Ow(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:op(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ip(e.auto_synopsis_history)}}var Lw="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function cr(e){let t=h().prepare(`SELECT ${Lw} FROM session_notes WHERE session_id = ?`).get(e);return t?Ow(t):null}function ap(e,t){let n=h(),s=new Date().toISOString(),r=n.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),o=[];return r&&(o=op(r.previous_versions),r.content!==t&&r.content.length>0&&o.push({content:r.content,replaced_at:s})),n.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
|
|
1406
1454
|
VALUES (?, ?, ?, ?)
|
|
1407
1455
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
1408
1456
|
content = excluded.content,
|
|
1409
1457
|
updated_at = excluded.updated_at,
|
|
1410
|
-
previous_versions = excluded.previous_versions`).run(e,t,s,JSON.stringify(o)),
|
|
1458
|
+
previous_versions = excluded.previous_versions`).run(e,t,s,JSON.stringify(o)),vw(e,t,s),cr(e)??{session_id:e,content:t,updated_at:s,previous_versions:o,auto_synopsis:null,auto_synopsis_generated_at:null,auto_synopsis_history:[]}}async function cp(e){let n=h().prepare(`SELECT rowid AS rid, role, content_text
|
|
1411
1459
|
FROM messages
|
|
1412
1460
|
WHERE session_id = ? AND is_sidechain = 0
|
|
1413
1461
|
AND content_text IS NOT NULL AND content_text != ''
|
|
1414
1462
|
AND role IN ('user', 'assistant')
|
|
1415
1463
|
ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
|
|
1416
|
-
LIMIT ?`).all(e,
|
|
1464
|
+
LIMIT ?`).all(e,sp);if(n.length===0)throw new Error("no messages available to summarise");let s=kw,r=[];for(let b of n){if(s<=0)break;let T=(b.content_text??"").slice(0,Aw);r.push({rid:b.rid,role:b.role,content:T}),s-=T.length}r.reverse();let o=n.length===sp||s<=0,a=r.map(b=>`**${b.role}**: ${b.content}`).join(`
|
|
1417
1465
|
|
|
1418
1466
|
`),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(`
|
|
1419
|
-
`),{spawnClaudePrompt:u,isClaudeCliAvailable:d}=await Promise.resolve().then(()=>(
|
|
1467
|
+
`),{spawnClaudePrompt:u,isClaudeCliAvailable:d}=await Promise.resolve().then(()=>(we(),pt));if(!d())throw new Error("claude CLI not found on PATH");let p=await u(c,[],{});if(!p.success)throw new Error(`claude CLI exited ${p.exitCode}: ${p.stderr.slice(-500)}`);let f=Cw(p.stdout);if(!f)throw new Error("claude CLI returned an empty synopsis");return f.slice(0,xw)}function Cw(e){let t=e.trim();if(!t)return"";try{let n=JSON.parse(t);if(n&&typeof n=="object"){let s=n.result;if(typeof s=="string")return s.trim()}}catch{}return t}function lp(e,t){let n=h(),s=new Date().toISOString(),r=Date.now(),o=n.prepare("SELECT auto_synopsis, auto_synopsis_history, content, updated_at FROM session_notes WHERE session_id = ?").get(e),a=ip(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:s}),o?n.prepare(`UPDATE session_notes
|
|
1420
1468
|
SET auto_synopsis = ?,
|
|
1421
1469
|
auto_synopsis_generated_at = ?,
|
|
1422
1470
|
auto_synopsis_history = ?
|
|
1423
1471
|
WHERE session_id = ?`).run(t,r,JSON.stringify(a),e):n.prepare(`INSERT INTO session_notes
|
|
1424
1472
|
(session_id, content, updated_at, previous_versions, auto_synopsis,
|
|
1425
1473
|
auto_synopsis_generated_at, auto_synopsis_history)
|
|
1426
|
-
VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,s,t,r,JSON.stringify(a)),
|
|
1427
|
-
|
|
1474
|
+
VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,s,t,r,JSON.stringify(a)),cr(e)}function vw(e,t,n){try{Nw();let s=rp(Pi,`${e}.md`),r=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${n} -->
|
|
1475
|
+
`;yw(s,r+t)}catch(s){console.error("[notes] mirror write failed:",s)}}kt();H();Z();import{randomUUID as up}from"node:crypto";import{writeFileSync as dp,readFileSync as GI,existsSync as Iw,mkdirSync as Mw}from"node:fs";import{join as Fi}from"node:path";var lr=Fi(W,"threads"),Dw=Fi(lr,"index.json");function pp(){K(),Iw(lr)||Mw(lr,{recursive:!0})}function $i(e,t,n){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:n?.project??null,project_count:n?.project_count??0,folder_id:e.folder_id??null}}function mp(e){let t=new Map;if(e.length===0)return t;let n=h(),s=e.map(()=>"?").join(","),r=n.prepare(`SELECT te.thread_id AS thread_id,
|
|
1428
1476
|
p.name AS project,
|
|
1429
1477
|
COUNT(*) AS n,
|
|
1430
1478
|
SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
|
|
@@ -1432,7 +1480,7 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1432
1480
|
LEFT JOIN sessions s ON s.id = te.session_id
|
|
1433
1481
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1434
1482
|
WHERE te.thread_id IN (${s})
|
|
1435
|
-
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(f=>f.project!==null),d=u.length,p=null;u.length>0&&(p=[...u].sort((
|
|
1483
|
+
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(f=>f.project!==null),d=u.length,p=null;u.length>0&&(p=[...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:p,project_count:d})}return t}function gp(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 fp(e){let n=h().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
|
|
1436
1484
|
s.auto_title AS auto_title,
|
|
1437
1485
|
s.auto_title_source AS auto_title_source,
|
|
1438
1486
|
s.first_user_message AS first_user_message,
|
|
@@ -1440,11 +1488,11 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1440
1488
|
FROM (SELECT ? AS sid) q
|
|
1441
1489
|
LEFT JOIN sessions s ON s.id = q.sid
|
|
1442
1490
|
LEFT JOIN session_aliases sa ON sa.session_id = q.sid
|
|
1443
|
-
LEFT JOIN projects p ON p.id = s.project_id`).get(e),s=n?.auto_title_source??null;return{alias:n?.alias??null,auto_title:n?.auto_title??null,auto_title_source:s==="agent"||s==="heuristic"?s:null,first_user_message:n?.first_user_message??null,project:n?.project??null}}function
|
|
1491
|
+
LEFT JOIN projects p ON p.id = s.project_id`).get(e),s=n?.auto_title_source??null;return{alias:n?.alias??null,auto_title:n?.auto_title??null,auto_title_source:s==="agent"||s==="heuristic"?s:null,first_user_message:n?.first_user_message??null,project:n?.project??null}}function Ui(e){let n=h().prepare(`SELECT
|
|
1444
1492
|
COUNT(*) AS session_count,
|
|
1445
1493
|
SUM(CASE WHEN role = 'origin' THEN 1 ELSE 0 END) AS origin_count
|
|
1446
|
-
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:n?.session_count??0,origin_count:n?.origin_count??0}}function
|
|
1447
|
-
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(s,e.originSessionId,r),
|
|
1494
|
+
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:n?.session_count??0,origin_count:n?.origin_count??0}}function Ae(e){let t=ce(e);t&&(pp(),dp(Fi(lr,`${e}.json`),JSON.stringify(t,null,2)),_p())}function _p(){pp();let e=Hi({includeArchived:!0});dp(Dw,JSON.stringify({threads:e},null,2))}function ur(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let n=h(),s=up(),r=new Date().toISOString();n.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(s,t,e.summary?.trim()||null,r),e.originSessionId&&n.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1495
|
+
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(s,e.originSessionId,r),Ae(s);let o=ce(s);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function Hi(e={}){let t=h(),n=e.includeArchived?"":"WHERE archived = 0",s=t.prepare(`SELECT * FROM threads ${n} ORDER BY created_at DESC`).all(),r=mp(s.map(o=>o.id));return s.map(o=>$i(o,Ui(o.id),r.get(o.id)))}function ce(e){let t=h(),n=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!n)return null;let s=t.prepare(`SELECT e.*,
|
|
1448
1496
|
NULLIF(sa.alias, '') AS alias,
|
|
1449
1497
|
s.auto_title AS auto_title,
|
|
1450
1498
|
s.auto_title_source AS auto_title_source,
|
|
@@ -1455,10 +1503,10 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1455
1503
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
1456
1504
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1457
1505
|
WHERE e.thread_id = ?
|
|
1458
|
-
ORDER BY e.added_at ASC`).all(e).map(
|
|
1506
|
+
ORDER BY e.added_at ASC`).all(e).map(gp),r=mp([e]).get(e);return{...$i(n,Ui(n.id),r),edges:s}}function hp(e){return h().prepare(`SELECT t.* FROM threads t
|
|
1459
1507
|
JOIN thread_edges e ON e.thread_id = t.id
|
|
1460
1508
|
WHERE e.session_id = ? AND t.archived = 0
|
|
1461
|
-
ORDER BY t.created_at DESC`).all(e).map(s
|
|
1509
|
+
ORDER BY t.created_at DESC`).all(e).map(s=>$i(s,Ui(s.id)))}function dr(e){let t=h();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let s=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
|
|
1462
1510
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1463
1511
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1464
1512
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
@@ -1466,22 +1514,22 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1466
1514
|
role = excluded.role,
|
|
1467
1515
|
confidence = excluded.confidence,
|
|
1468
1516
|
source = excluded.source,
|
|
1469
|
-
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,s),
|
|
1517
|
+
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,s),Ae(e.threadId);let u=fp(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:r,role:o,confidence:a,source:c,added_at:s,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 Ep(e,t){let s=h().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return s.changes>0&&Ae(e),{removed:s.changes}}function Dn(e,t,n){let s=h(),r=s.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(n!==null){if(n===t)throw new Error("cycle detected: session cannot be its own parent");let c=s.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),u=n,d=new Set;for(;u!==null;){if(u===t)throw new Error(`cycle detected: setting parent of ${t} to ${n} would create a loop`);if(d.has(u))break;d.add(u),u=c.get(e,u)?.parent_session_id??null}}let o=n?"child":"origin";s.prepare(`UPDATE thread_edges
|
|
1470
1518
|
SET parent_session_id = ?, role = ?, added_at = ?
|
|
1471
|
-
WHERE thread_id = ? AND session_id = ?`).run(n,o,new Date().toISOString(),e,t),
|
|
1519
|
+
WHERE thread_id = ? AND session_id = ?`).run(n,o,new Date().toISOString(),e,t),Ae(e);let a=fp(t);return gp({...r,parent_session_id:n,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 bp(e,t){let n=t.trim();if(!n)throw new Error("name cannot be empty");h().prepare("UPDATE threads SET name = ? WHERE id = ?").run(n,e),Ae(e);let r=ce(e);if(!r)throw new Error(`thread ${e} not found`);return r}function Sp(e){h().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),Ae(e);let n=ce(e);if(!n)throw new Error(`thread ${e} not found`);return n}function Tp(e){h().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),Ae(e);let n=ce(e);if(!n)throw new Error(`thread ${e} not found`);return n}function yp(e){h().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),Ae(e);let n=ce(e);if(!n)throw new Error(`thread ${e} not found`);return n}function wp(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let n=h(),s=new Date().toISOString();n.transaction(()=>{let o=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let a of o)n.prepare(`INSERT INTO thread_edges
|
|
1472
1520
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1473
1521
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1474
1522
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
1475
1523
|
parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
|
|
1476
1524
|
role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
|
|
1477
1525
|
confidence = MAX(thread_edges.confidence, excluded.confidence),
|
|
1478
|
-
source = thread_edges.source`).run(t,a.session_id,a.parent_session_id,a.role,a.confidence,a.source,s);n.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),
|
|
1526
|
+
source = thread_edges.source`).run(t,a.session_id,a.parent_session_id,a.role,a.confidence,a.source,s);n.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),Ae(t),_p();let r=ce(t);if(!r)throw new Error("merge destination disappeared");return r}function Rp(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=h(),n=new Date().toISOString(),s=up();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(s,e.newThreadName.trim(),n);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
|
|
1479
1527
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1480
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(s,o,a.parent_session_id,a.role,a.confidence,a.source,n),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),
|
|
1481
|
-
`).toLowerCase();for(let a of e.authored_paths){let c=a.toLowerCase();if(t.touched_files.has(a)||o.includes(c)){n+=.5,s=a;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=
|
|
1482
|
-
`,d),f=p===-1?u.length:p,
|
|
1483
|
-
`)){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)&&s.push(a)}return s}catch{continue}return[]}async function
|
|
1484
|
-
`))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function
|
|
1528
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(s,o,a.parent_session_id,a.role,a.confidence,a.source,n),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),Ae(e.threadId),Ae(s);let r=ce(s);if(!r)throw new Error("split destination disappeared");return r}H();import{execFile as Kw}from"node:child_process";import{promisify as Vw}from"node:util";import{readlink as Qw,readFile as Op}from"node:fs/promises";import{platform as _r}from"node:os";import{readFileSync as jw,statSync as Pw}from"node:fs";var Fw=200*1024*1024,mr=.7,gr=.5,xp=gr,$w=[{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"}],Uw=["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 kp(e){if(!e)return null;if(e.startsWith("/")){let n=e.split(" \xB7 ");if(n.length>1)return n[1].trim().toLowerCase()}let t=e.split(" \xB7 ");return t.length>1?t[t.length-1].trim().toLowerCase():null}function Hw(e,t){let n=t-e;if(n<0)return{weight:0,label:null};for(let s of $w)if(n<=s.maxGapMs)return{weight:s.weight,label:s.label};return{weight:0,label:null}}function Bw(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let t=[.4,.35,.3,.25,.2,.15,.1],n=Math.min(e.length,t.length);for(let s=0;s<n;s++){let r=e[s];if(!r)continue;let o=r.toLowerCase();for(let a of Uw)if(o.includes(a))return{weight:t[s],matched:a,matchedIndex:s}}return{weight:0,matched:null,matchedIndex:-1}}function Bi(e,t){if(!e||!t||e.length!==t.length)return 0;let n=0;for(let s=0;s<e.length;s++)n+=e[s]*t[s];return n<-1?-1:n>1?1:n}function Ww(e,t){if(e.length===0||t.length===0)return 0;let n=0;for(let s of e)for(let r of t){let o=Bi(s,r);o>n&&(n=o)}return n}function qw(e,t){let n=Bi(e.mean_embedding,t.mean_embedding),s=Bi(e.tail_pool,t.head_pool),r=Ww(e.sample_chunks,t.sample_chunks),o=0,a=null;if(n>o&&(o=n,a="mean"),s>o&&(o=s,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 Xw(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 Jw(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let n=0;for(let r of t)e.has(r)&&n++;return n===0?{weight:0,count:0}:{weight:Math.min(.4,n*.1),count:n}}function Gw(e,t){let n=kp(e),s=kp(t);return n&&s&&n===s?{weight:.1,brand:n}:{weight:0,brand:null}}function Ap(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function Yw(e,t){let n=0,s=null,r=!1;if(e.authored_paths.size>0){let o=t.recent_user_messages.slice(0,3).join(`
|
|
1529
|
+
`).toLowerCase();for(let a of e.authored_paths){let c=a.toLowerCase();if(t.touched_files.has(a)||o.includes(c)){n+=.5,s=a;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=Ap(t.recent_user_messages[0]);if(o.length>=200)for(let a of e.authored_content){let c=Ap(a);if(c.length<200)continue;let u=Math.min(c.length,240),d=c.slice(0,u);if(o.includes(d)){n+=.4,r=!0;break}}}return{weight:Math.min(.6,n),pathMatch:s,contentMatch:r}}function zw(e,t,n=xp){if(t.started_at_ms<=e.started_at_ms)return null;let s=e.ended_at_ms??e.started_at_ms;if(t.started_at_ms<s)return null;let r=Hw(s,t.started_at_ms),o=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],a=Bw(o),c=Jw(e.touched_files,t.touched_files),u=Gw(e.auto_title,t.auto_title),d=qw(e,t),p=Xw(e,t),f=Yw(e,t),b=r.weight+a.weight+c.weight+u.weight+d.weight+p.weight+f.weight;if(b<n)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)})`),p.same&&T.push(`same cluster (+${p.weight})`),f.weight>0){let S=[];f.pathMatch&&S.push(`opened authored path "${f.pathMatch.split("/").pop()}"`),f.contentMatch&&S.push("verbatim-paste of authored content"),T.push(`doc-authorship: ${S.join(" + ")} (+${f.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:p.weight,doc_authorship:f.weight},reasons:T}}function Wt(e,t=xp){let n=[];for(let s=0;s<e.length;s++){let r=e[s],o=null;for(let a=0;a<s;a++){let c=e[a],u=zw(c,r,t);u&&(!o||u.confidence>o.confidence)&&(o=u)}o&&n.push(o)}return n}function Np(e,t){let n=new Map,s=c=>{let u=c;for(;n.get(u)!==u;)u=n.get(u);let d=c;for(;n.get(d)!==u;){let p=n.get(d);n.set(d,u),d=p}return u},r=(c,u)=>{let d=s(c),p=s(u);d!==p&&n.set(d,p)};for(let c of e)n.has(c.parent_id)||n.set(c.parent_id,c.parent_id),n.has(c.child_id)||n.set(c.child_id,c.child_id),r(c.parent_id,c.child_id);let o=new Map;for(let c of n.keys()){let u=s(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 fr(e,t={}){let n=t.maxUserMessages??5,s=t.userMessageMaxLen??2e3,r=new Set,o=[],a=new Set,c=[],u;try{if(Pw(e).size>Fw)return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c};u=jw(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 p=u.indexOf(`
|
|
1530
|
+
`,d),f=p===-1?u.length:p,b=u.slice(d,f);if(d=p===-1?u.length:p+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<n){let A=S.message.content.trim();A&&o.push(A.length>s?A.slice(0,s):A)}let R=S.message?.content;if(Array.isArray(R))for(let A of R){if(!A||typeof A!="object")continue;let I=A;if(I.type!=="tool_use")continue;let j=I.input??{},J=typeof j.file_path=="string"?j.file_path:null;if(J){let M=pr(J);M&&r.add(M)}if((I.name==="Write"||I.name==="Edit"||I.name==="MultiEdit")&&J){let M=pr(J);M&&a.add(M);let F=typeof j.content=="string"?j.content:typeof j.new_string=="string"?j.new_string:null;F&&F.length>=200&&c.push(F.length>4096?F.slice(0,4096):F)}if(I.name==="Bash"&&typeof j.command=="string")for(let M of j.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let F=pr(M[1]);F&&r.add(F)}if((I.name==="Glob"||I.name==="Grep")&&typeof j.pattern=="string"){let M=pr(j.pattern);M&&!M.includes("*")&&r.add(M)}}}return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}function pr(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var qi=Vw(Kw),Zw=6,Lp="Active ",Cp=" sessions \u2014 ",eR=6e4;async function tR(){if(_r()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:n}=await qi(t,["-eo","pid=,comm="],{timeout:2e3}),s=[];for(let r of n.split(`
|
|
1531
|
+
`)){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)&&s.push(a)}return s}catch{continue}return[]}async function nR(e){let t=_r();if(t==="linux")try{return(await Qw(`/proc/${e}/cwd`)).replace(/\/+$/,"")}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let n of["/usr/sbin/lsof","/usr/bin/lsof"])try{let{stdout:s}=await qi(n,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let r of s.split(`
|
|
1532
|
+
`))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function sR(e){let t=_r();if(t==="linux")try{let n=await Op(`/proc/${e}/stat`,"utf8"),s=n.lastIndexOf(")");if(s===-1)return null;let r=n.slice(s+1).trim().split(/\s+/),o=Number(r[19]);if(!Number.isFinite(o))return null;let a=await Op("/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 n of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await qi(n,["-o","lstart=","-p",String(e)],{timeout:2e3}),r=Date.parse(s.trim());return Number.isFinite(r)?r:null}catch{continue}return null}async function rR(e,t){let n=await tR();if(n.length===0)return null;let s=e.replace(/\/+$/,""),r=[];for(let a of n){let c=await nR(a);if(c&&(c===s||c.startsWith(s+"/"))){let u=await sR(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=eR;for(let d of t){if(o.has(d.session_id)||!d.started_at)continue;let p=Date.parse(d.started_at);if(!Number.isFinite(p))continue;let f=Math.abs(p-a);f<u&&(u=f,c=d)}c&&o.add(c.session_id)}return o}function oR(e){let n=h().prepare("SELECT id, name, decoded_path FROM projects WHERE id = ? LIMIT 1").get(e);if(!n)throw new Error(`project ${e} not found`);return n}function iR(e,t){let n=h(),s=`${Lp}${t}${Cp}%`,r=n.prepare(`SELECT t.id
|
|
1485
1533
|
FROM threads t
|
|
1486
1534
|
WHERE t.archived = 0
|
|
1487
1535
|
AND t.name LIKE ?
|
|
@@ -1492,7 +1540,7 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1492
1540
|
WHERE s.project_id = ?
|
|
1493
1541
|
AND t.archived = 0
|
|
1494
1542
|
AND t.name LIKE ?
|
|
1495
|
-
LIMIT 1`).get(e,s);return o?ce(o.id):null}function
|
|
1543
|
+
LIMIT 1`).get(e,s);return o?ce(o.id):null}function vp(e){let t=e?new Date(e):new Date,n=t.getFullYear(),s=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${n}-${s}-${r}`}function Wi(e,t){let n=h(),s=t>0,r=s?Date.now()-t*60*60*1e3:0;return s?n.prepare(`SELECT s.id AS session_id,
|
|
1496
1544
|
sa.alias AS alias,
|
|
1497
1545
|
s.auto_title AS auto_title,
|
|
1498
1546
|
s.first_user_message AS first_user_message,
|
|
@@ -1512,19 +1560,19 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1512
1560
|
FROM sessions s
|
|
1513
1561
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1514
1562
|
WHERE s.project_id = ?
|
|
1515
|
-
ORDER BY s.started_at ASC`).all(e)}function
|
|
1563
|
+
ORDER BY s.started_at ASC`).all(e)}function aR(e){let t=h(),n=[];for(let s of e){if(!s.started_at)continue;let r=Date.parse(s.started_at);if(!Number.isFinite(r))continue;let o=t.prepare("SELECT file_path, ended_at FROM sessions WHERE id = ?").get(s.session_id);if(!o?.file_path)continue;let a=o.ended_at?Date.parse(o.ended_at):null,c=fr(o.file_path,{maxUserMessages:7});n.push({id:s.session_id,started_at_ms:r,ended_at_ms:Number.isFinite(a)?a:null,first_user_message:s.first_user_message,recent_user_messages:c.recent_user_messages,auto_title:s.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 n}function cR(e){let n=h().prepare(`SELECT session_id, parent_session_id, source
|
|
1516
1564
|
FROM thread_edges
|
|
1517
|
-
WHERE thread_id = ?`).all(e),s=new Map;for(let r of n)s.set(r.session_id,{parent_session_id:r.parent_session_id,source:r.source});return s}async function
|
|
1565
|
+
WHERE thread_id = ?`).all(e),s=new Map;for(let r of n)s.set(r.session_id,{parent_session_id:r.parent_session_id,source:r.source});return s}async function Ip(e,t={}){let n=oR(e),s=t.windowHours??Zw,r=t.scoreThreshold??gr,o=t.useLivePids??!0,a=[],c=[];if(o&&n.decoded_path){let R=Wi(e,0),A=await rR(n.decoded_path,R);if(A===null){let j=_r()==="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(j),c=Wi(e,s)}else A.size===0?(a.push(`No active terminals open in ${n.name} (cwd=${n.decoded_path}). Open a Claude terminal in this repo and re-run.`),c=[]):c=R.filter(I=>A.has(I.session_id))}else c=Wi(e,s);c.length===0&&!a.length&&a.push(`No active sessions in ${n.name} within the last ${s}h.`);let u=iR(e,n.name),d=new Set(u?u.edges.map(R=>R.session_id):[]),p=c.filter(R=>!d.has(R.session_id)),f=aR(c);f.sort((R,A)=>R.started_at_ms-A.started_at_ms);let b=Wt(f,r),T=u?u.edges.filter(R=>R.source!=="auto-active"&&(R.parent_session_id||R.role==="origin")).map(R=>({session_id:R.session_id,parent_session_id:R.parent_session_id})):[],S=u?u.name:`${Lp}${n.name}${Cp}${vp(t.todayIso)}`;return{project:n,thread:{id:u?.id??null,name:S,exists:!!u,existing_session_count:u?.edges.length??0},candidates:c,proposed_additions:p,proposed_edges:b,preserved_manual_edges:T,warnings:a}}function Mp(e){let t={thread_id:"",added:0,edges_set:0,preserved_manual:e.preserved_manual_edges.length},n;e.thread.exists&&e.thread.id?n=e.thread.id:n=ur({name:e.thread.name,summary:`Auto-captured by sync-active on ${vp()}. 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=n;let s=cR(n);for(let a of e.candidates)s.has(a.session_id)||(dr({threadId:n,sessionId:a.session_id,source:"auto-active",confidence:.5}),t.added++,s.set(a.session_id,{parent_session_id:null,source:"auto-active"}));for(let a of e.proposed_edges){let c=s.get(a.child_id);if(c&&c.source==="auto-active"&&c.parent_session_id!==a.parent_id&&s.has(a.parent_id))try{Dn(n,a.child_id,a.parent_id),t.edges_set++,s.set(a.child_id,{parent_session_id:a.parent_id,source:c.source})}catch{}}let o=h().prepare(`SELECT session_id FROM thread_edges
|
|
1518
1566
|
WHERE thread_id = ?
|
|
1519
1567
|
AND source = 'auto-active'
|
|
1520
1568
|
AND parent_session_id IS NULL
|
|
1521
|
-
AND role = 'child'`).all(n);for(let a of o)try{
|
|
1522
|
-
VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,n,s,o,a),
|
|
1569
|
+
AND role = 'child'`).all(n);for(let a of o)try{Dn(n,a.session_id,null)}catch{}return t}H();Z();import{randomUUID as lR}from"node:crypto";import{writeFileSync as uR}from"node:fs";import{join as dR}from"node:path";var pR=dR(W,"thread-folders.json");function Dp(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 jn(){try{K();let e=Xi({includeArchived:!0});uR(pR,JSON.stringify({folders:e},null,2))}catch{}}function Xi(e={}){let t=e.includeArchived?"":"WHERE archived = 0";return h().prepare(`SELECT * FROM thread_folders ${t} ORDER BY sort_order, name`).all().map(Dp)}function Et(e){let t=h().prepare("SELECT * FROM thread_folders WHERE id = ?").get(e);return t?Dp(t):null}function jp(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 n=e.parentFolderId??null,s=e.projectScope??null;if(n){let c=Et(n);if(!c)throw new Error(`parent folder ${n} not found`);s=c.project_scope}let r=lR(),o=new Date().toISOString(),a=Pp(n,s);return h().prepare(`INSERT INTO thread_folders (id, name, parent_folder_id, project_scope, created_at, archived, sort_order)
|
|
1570
|
+
VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,n,s,o,a),jn(),{id:r,name:t,parent_folder_id:n,project_scope:s,created_at:o,archived:!1,sort_order:a}}function Pp(e,t){return h().prepare(`SELECT COALESCE(MAX(sort_order), -100) + 100 AS next
|
|
1523
1571
|
FROM thread_folders
|
|
1524
1572
|
WHERE parent_folder_id IS ?
|
|
1525
|
-
AND project_scope IS ?`).get(e,t)?.next??0}function
|
|
1573
|
+
AND project_scope IS ?`).get(e,t)?.next??0}function Fp(e,t){let n=t.trim();if(!n)throw new Error("folder name cannot be empty");if(n.length>200)throw new Error("folder name too long (200 char max)");let s=Et(e);if(!s)throw new Error(`folder ${e} not found`);return h().prepare("UPDATE thread_folders SET name = ? WHERE id = ?").run(n,e),jn(),{...s,name:n}}function $p(e,t){let n=Et(e);if(!n)throw new Error(`folder ${e} not found`);if(t===e)throw new Error("cannot move a folder under itself");let s=n.project_scope;if(t!==null){let o=Et(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=Et(a);if(!u)break;a=u.parent_folder_id,c++}s=o.project_scope}let r=Pp(t,s);return h().prepare("UPDATE thread_folders SET parent_folder_id = ?, project_scope = ?, sort_order = ? WHERE id = ?").run(t,s,r,e),jn(),{...n,parent_folder_id:t,project_scope:s,sort_order:r}}function Up(e,t,n){if(!Array.isArray(n)||n.length===0)throw new Error("ordered_ids must be a non-empty array");let s=new Set;for(let d of n){if(typeof d!="string"||!d)throw new Error("ordered_ids must contain non-empty strings");if(s.has(d))throw new Error(`duplicate id in ordered_ids: ${d}`);s.add(d)}let r=h(),o=r.prepare(`SELECT id FROM thread_folders
|
|
1526
1574
|
WHERE parent_folder_id IS ?
|
|
1527
|
-
AND project_scope IS ?`).all(e,t),a=new Set(o.map(d=>d.id));if(a.size!==n.length)throw new Error(`ordered_ids length ${n.length} does not match sibling count ${a.size}`);for(let d of n)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((p,f)=>c.run(f*100,p))})(n),
|
|
1575
|
+
AND project_scope IS ?`).all(e,t),a=new Set(o.map(d=>d.id));if(a.size!==n.length)throw new Error(`ordered_ids length ${n.length} does not match sibling count ${a.size}`);for(let d of n)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((p,f)=>c.run(f*100,p))})(n),jn()}function Hp(e){if(!Et(e))throw new Error(`folder ${e} not found`);h().prepare("DELETE FROM thread_folders WHERE id = ?").run(e),jn()}function Bp(e,t){if(t!==null&&!Et(t))throw new Error(`folder ${t} not found`);if(h().prepare("UPDATE threads SET folder_id = ? WHERE id = ?").run(t,e).changes===0)throw new Error(`thread ${e} not found`)}function Wp(e,t){let n=new Set,s=[];for(let r of t){if(n.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;n.add(r.id),s.push({linkId:r.id,otherSessionId:a,direction:o,updatedAt:r.updated_at,link:r})}return s.sort((r,o)=>r.updatedAt<o.updatedAt?1:r.updatedAt>o.updatedAt?-1:0),s}H();var mR=4e3,gR=2,fR=30,_R=.2,hR={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function hr(e){return e?Math.ceil(e.length/4):0}function qp(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/fR);return Math.max(_R,t)}function Xp(e,t){if(!e||!t)return 0;let n=Date.parse(e),s=Date.parse(t);return!Number.isFinite(n)||!Number.isFinite(s)?0:Math.abs(s-n)/(1e3*60*60*24)}function Jp(e){return h().prepare(`SELECT s.id,
|
|
1528
1576
|
NULLIF(sa.alias, '') AS alias,
|
|
1529
1577
|
s.auto_title,
|
|
1530
1578
|
s.auto_title_source,
|
|
@@ -1535,72 +1583,72 @@ ${f+1}. ${S}`:`${f+1}. ${S}`}).join(`
|
|
|
1535
1583
|
FROM sessions s
|
|
1536
1584
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1537
1585
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1538
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
1586
|
+
WHERE s.id = ?`).get(e)??null}function Gp(e){let n=h().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!n||!n.summary)return null;let s=n.summary.trim();return s.length>0?s:null}function Yp(e){let t=e.alias?.trim(),n=e.auto_title?.trim(),s=e.first_user_message?.trim();return n&&e.auto_title_source==="agent"?n:t||n||(s?s.slice(0,80):e.id.slice(0,8))}function ER(e){let n=h().prepare(`SELECT id, auto_title, started_at
|
|
1539
1587
|
FROM sessions
|
|
1540
1588
|
WHERE project_id = ?
|
|
1541
|
-
ORDER BY COALESCE(started_at, ''), id`).all(e),s=new Set,r=new Set,o=[];for(let
|
|
1589
|
+
ORDER BY COALESCE(started_at, ''), id`).all(e),s=new Set,r=new Set,o=[];for(let b of n){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(),R=T.length>1?T.slice(1).join(" \xB7 ").trim():null;o.push({id:b.id,brand:R||null,skill:S||null}),R&&s.add(R),S&&r.add(S)}let a=[...s].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 p=new Map,f=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 R=`${T}.${S}`,A=(p.get(R)??0)+1;p.set(R,A),f.set(b.id,`${T}.${S}.${A}`)}return{byId:f}}function bR(e){return{table:e!==null?ER(e):null,originProjectId:e,cache:new Map}}function Er(e,t){let n=e.cache.get(t);if(n)return n;let s=Jp(t);if(!s)return null;let r=e.table&&s.project_id===e.originProjectId?e.table.byId.get(t)??null:null,o={session_id:s.id,title:Yp(s),decimal:r,summary:Gp(s.id),project:s.project,started_at:s.started_at};return e.cache.set(t,o),o}function SR(e,t){let s=h().prepare(`SELECT DISTINCT te.parent_session_id AS pid
|
|
1542
1590
|
FROM thread_edges te
|
|
1543
1591
|
WHERE te.session_id = ?
|
|
1544
|
-
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of s){if(!o.pid)continue;let a=
|
|
1592
|
+
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of s){if(!o.pid)continue;let a=Er(e,o.pid);a&&r.push(a)}return r}function TR(e,t){let s=h().prepare(`SELECT DISTINCT te.session_id AS sid
|
|
1545
1593
|
FROM thread_edges te
|
|
1546
|
-
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of s){if(!o.sid)continue;let a=
|
|
1547
|
-
${o}`}return r}function
|
|
1594
|
+
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of s){if(!o.sid)continue;let a=Er(e,o.sid);a&&r.push(a)}return r}function zp(e){let t=hR[e.linkType]??.5,n=qt(e.confidence),s=t*n,r=qp(e.daysApart),o=e.embeddingCosine??.5,a=qt(e.pagerank);if(e.scoring==="pagerank")return qt(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?qt(s):qt(o);let c=.35*s+.2*r+.2*o+.25*a;return qt(c)}function qt(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function yR(e,t,n,s,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 n)a(e,c.session_id);for(let c of n)a(c.session_id,e);for(let c of s)a(e,c.session_id);for(let c of s)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 p=new Set;for(let f of c){let b=o.get(f);if(b)for(let T of b){if(u.has(T))continue;let S=mn(T).filter(R=>R.approved);for(let R of S)a(R.source_session_id,R.target_session_id),a(R.target_session_id,R.source_session_id);u.add(T),p.add(T)}}if(p.size===0)break;for(let f of p)c.add(f)}}return{edges:o}}function wR(e,t={}){let n=t.iterations??12,s=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<n;d++){let p=new Map(r.map(f=>[f,(1-s)/r.length]));for(let f of r){let b=e.edges.get(f);if(!b||b.size===0)continue;let T=(a.get(f)??0)/b.size;for(let S of b)p.set(S,(p.get(S)??0)+s*T)}a=p}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,p]of a)u.set(d,p/c);return u}var Kp=240;function Vp(e,t){let n=e.replace(/\s+/g," ").trim();return n.length<=t?n:`${n.slice(0,t-1).trimEnd()}\u2026`}function RR(e){let t=e.decimal?`${e.decimal} `:"",n=e.session_id.slice(0,8),s="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",r=`- ${t}${e.title} (${n})${s}`;if(e.summary){let o=Vp(e.summary,Kp);return`${r}
|
|
1595
|
+
${o}`}return r}function kR(e,t,n){let s=[],r=[],o=0,a=e.decimal?`${e.decimal}: `:"",c=`# Neighborhood for ${e.session_id} (${a}${e.title})`;if(s.push(c),o+=hr(c),e.summary){let u=Vp(e.summary,Kp*4);s.push(u),o+=hr(u)}s.push("");for(let u of t){if(u.refs.length===0)continue;let d=`## ${u.heading}`,p=hr(d),f=[],b=0;for(let T of u.refs){let S=RR(T),R=hr(S);if(o+p+b+R>n){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}f.push(S),b+=R}if(f.length>0){s.push(d);for(let T of f)s.push(T);s.push(""),o+=p+b}}for(;s.length>0&&s[s.length-1]==="";)s.pop();return{bundle:s.join(`
|
|
1548
1596
|
`)+`
|
|
1549
|
-
`,budgetUsed:o,truncated:r}}function
|
|
1597
|
+
`,budgetUsed:o,truncated:r}}function AR(e,t,n,s,r,o){let a=[];for(let c of n){if(s&&!s.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=Er(e,u);if(!d)continue;let p=Xp(t.started_at,d.started_at),f=zp({confidence:c.confidence,linkType:c.link_type,daysApart:p,embeddingCosine:null,pagerank:o.get(u)??0,scoring:r});a.push({...d,score:f,evidence:`(suggestion, ${c.inferred_by}) confidence=${c.confidence.toFixed(2)} ${Math.round(p)}d apart`,link_type:c.link_type})}return a}function br(e,t={}){let n=Math.max(100,Math.floor(t.budget??mR)),s=t.scoring??"hybrid",r=Math.max(1,Math.min(5,t.maxDepth??gR)),o=t.includeWikiLinks??!0,a=t.includeSuggestions??!1,c=t.edgeTypes?new Set(t.edgeTypes):null,u=Jp(e);if(!u)throw new Error(`session not found: ${e}`);let d=bR(u.project_id),p={session_id:u.id,title:Yp(u),decimal:d.table?.byId.get(u.id)??null,summary:Gp(u.id),project:u.project,started_at:u.started_at};d.cache.set(u.id,p);let f=SR(d,e),b=TR(d,e),T=mn(e).filter(U=>U.approved).filter(U=>!c||c.has(U.link_type)).filter(U=>o||U.link_type!=="wiki_link"),S=yR(e,T,f,b,r),R=wR(S),A=[],I=[],j=[],J=[];for(let U of T){let v=U.source_session_id===e?U.target_session_id:U.source_session_id,i=Er(d,v);if(!i)continue;let l=Xp(p.started_at,i.started_at),m=zp({confidence:U.confidence,linkType:U.link_type,daysApart:l,embeddingCosine:null,pagerank:R.get(v)??0,scoring:s}),g=qp(l),_=`${U.link_type} confidence=${U.confidence.toFixed(2)} recency=${g.toFixed(2)} (${Math.round(l)}d apart)`,E={...i,score:m,evidence:_,link_type:U.link_type};U.link_type==="citation"?A.push(E):U.link_type==="similar"?I.push(E):U.link_type==="wiki_link"?J.push(E):j.push(E)}if(a){let U=yt({sourceSessionId:e,status:"pending",limit:100}),v=yt({targetSessionId:e,status:"pending",limit:100}),i=[...U,...v],l=new Set,m=i.filter(_=>l.has(_.id)?!1:(l.add(_.id),!0)),g=AR(d,p,m,c,s,R);for(let _ of g)_.link_type==="citation"?A.push(_):_.link_type==="similar"?I.push(_):_.link_type==="wiki_link"?J.push(_):j.push(_)}let M=(U,v)=>v.score-U.score;A.sort(M),I.sort(M),j.sort(M),J.sort(M);let ne=kR(p,[{heading:"Parents",refs:f},{heading:"Children",refs:b},{heading:"Citations (approved)",refs:A},{heading:"Similar sessions",refs:I},{heading:"Cousins (skill track + temporal)",refs:j},{heading:"Wiki links (manual)",refs:J}],n);return{origin:p,parents:f,children:b,citations:A,similar:I,cousins:j,wikiLinks:J,bundle:ne.bundle,budgetUsed:ne.budgetUsed,budgetRemaining:Math.max(0,n-ne.budgetUsed),truncated:ne.truncated}}import{randomUUID as IR}from"node:crypto";var xR=50;function NR(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),n=new Map,s=new Set,r=[];for(let u of t)if(u.parent_session_id){let d=n.get(u.parent_session_id)??[];d.push(u),n.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(s.has(u.session_id))continue;s.add(u.session_id),r.push(u.session_id);let d=n.get(u.session_id)??[];for(let p of d)s.has(p.session_id)||c.push(p)}for(let u of t)s.has(u.session_id)||(s.add(u.session_id),r.push(u.session_id));return r}function OR(e){let t=ei(e.sessionId),n=["",`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(`
|
|
1550
1598
|
`);return`${t}
|
|
1551
|
-
${n}`}function
|
|
1599
|
+
${n}`}function LR(e){return ve(e)?.auto_title_source??null}async function CR(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(we(),pt));if(!n())throw new Error("claude CLI not found on PATH");let s=await t(e.prompt,[],{model:e.model});if(!s.success)throw new Error(`claude CLI exited ${s.exitCode}: ${s.stderr.slice(-500)}`);let r=vR(s.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,xR)}function vR(e){let t=e.trim();if(!t)return"";try{let n=JSON.parse(t);if(n&&typeof n=="object"){let s=n.result;if(typeof s=="string")return Qp(s)}}catch{}return Qp(t)}function Qp(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function em(e,t={}){let n=ce(e);if(!n)throw new Error(`thread not found: ${e}`);let s=NR(n.edges),r=s.length,o=t.force??!1,a=t.signal,c=[],u=[],d=[];for(let p=0;p<s.length;p++){let f=s[p],b=p+1;if(a?.aborted){let S={sessionId:f,reason:"cancelled"};u.push(S),t.onSkipped?.(S);continue}if(!o&&LR(f)==="agent"){let S={sessionId:f,reason:"already-titled"};u.push(S),t.onSkipped?.(S);continue}let T;try{if(Zp)T=await Zp({sessionId:f,current:b,total:r});else{let S=OR({sessionId:f,current:b,total:r});T=await CR({prompt:S,model:t.model})}}catch(S){let R=S instanceof Error?S.message:String(S??"unknown error"),A={sessionId:f,error:R};d.push(A),t.onFailed?.(A);continue}try{he(f,T,"agent")}catch(S){let R=S instanceof Error?S.message:String(S??"unknown error"),A={sessionId:f,error:`setAutoTitle failed: ${R}`};d.push(A),t.onFailed?.(A);continue}c.push(f),t.onProgress?.({current:b,total:r,sessionId:f,title:T})}return{generated:c,skipped:u,failed:d}}var Zp=null;var Fn=new Map,MR=300*1e3;function Pn(e,t,n){let s=e.events.length+1;e.events.push({id:s,kind:t,data:n});for(let r of e.waiters)r.resolve();e.waiters.clear()}function DR(e){e.cleanupTimer&&clearTimeout(e.cleanupTimer),e.cleanupTimer=setTimeout(()=>{Fn.delete(e.jobId)},MR)}function jR(e){let t=0,n=0,s=0;for(let r of e.events)r.kind==="progress"&&(t+=1),r.kind==="skipped"&&(n+=1),r.kind==="error"&&(s+=1);return{jobId:e.jobId,threadId:e.threadId,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.total,done:t,skipped:n,failed:s,result:e.result}}function tm(e){let t=IR(),n=new AbortController,s={jobId:t,threadId:e.threadId,status:"running",startedAt:new Date().toISOString(),endedAt:null,total:0,events:[],waiters:new Set,controller:n,result:null,cleanupTimer:null};return Fn.set(t,s),(async()=>{await Promise.resolve();try{let r=await em(e.threadId,{force:e.force??!1,signal:n.signal,model:e.model,onProgress:o=>{s.total=o.total,Pn(s,"progress",o)},onSkipped:o=>{Pn(s,"skipped",o)},onFailed:o=>{Pn(s,"error",o)}});s.result=r,s.status=n.signal.aborted?"cancelled":"done",s.endedAt=new Date().toISOString(),Pn(s,"done",r)}catch(r){let o=r instanceof Error?r.message:String(r??"unknown error");s.result={generated:[],skipped:[],failed:[{sessionId:e.threadId,error:o}]},s.status="failed",s.endedAt=new Date().toISOString(),Pn(s,"done",s.result)}finally{DR(s)}})(),t}async function*nm(e,t=0){let n=Fn.get(e);if(!n)return;let s=t;for(;;){for(;s<n.events.length;){let r=n.events[s];if(s+=1,yield r,r.kind==="done")return}if(n.endedAt)return;await new Promise(r=>{n.waiters.add({resolve:r})})}}function sm(e){let t=Fn.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Ji(e){let t=Fn.get(e);return t?jR(t):null}import{existsSync as rm,mkdirSync as PR,readFileSync as FR,writeFileSync as $R,chmodSync as UR}from"node:fs";import{homedir as HR}from"node:os";import{join as om}from"node:path";import{z as et}from"zod";function im(){return process.env.RECALL_HOME??om(HR(),".recall")}function BR(){let e=im();rm(e)||PR(e,{recursive:!0})}function am(){return om(im(),"config.json")}var Tr=et.object({enabled:et.boolean().default(!1),backend:et.enum(["api","mcp"]).default("api"),apiKey:et.string().optional(),model:et.string().default("claude-opus-4-7"),maxTagsPerSession:et.number().int().min(1).max(10).default(4),minTagsPerSession:et.number().int().min(1).max(10).default(2),autopilot:et.boolean().default(!1)}),Sr={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function cm(){let e=am();if(!rm(e))return{};try{return JSON.parse(FR(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function qe(){let e=cm().autoTag;if(!e)return{...Sr};let t=Tr.safeParse({...Sr,...e});return t.success?t.data:{...Sr}}function lm(e){BR();let t=cm(),n=Tr.parse({...Sr,...t.autoTag??{},...e}),s={...t,autoTag:n},r=am();$R(r,JSON.stringify(s,null,2));try{UR(r,384)}catch(o){console.error("[auto-tag-config] chmod 0600 failed (continuing):",o)}return n}function Gi(e){let{apiKey:t,...n}=e;return{...n,apiKey:t?"sk-ant-\u2026":null,hasApiKey:!!t}}H();var yr="claude-haiku-4-5-20251001",um=80,WR=2e3,Xt=class extends Error{sessionId;constructor(t){super(`no neighborhood context available for session ${t}`),this.name="NoContextAvailableError",this.sessionId=t}},dm=null;async function qR(e,t){if(dm)return dm(e,t);let{spawnClaudePrompt:n,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(we(),pt));return s()?n(e,[],{model:t}):{success:!1,stdout:"",stderr:"claude CLI not found on PATH",exitCode:null}}function XR(e){let n=h().prepare(`SELECT s.id,
|
|
1552
1600
|
s.auto_title,
|
|
1553
1601
|
s.auto_title_source,
|
|
1554
1602
|
CASE WHEN sa.alias IS NOT NULL AND sa.alias != ''
|
|
1555
1603
|
THEN 1 ELSE 0 END AS has_alias_int
|
|
1556
1604
|
FROM sessions s
|
|
1557
1605
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1558
|
-
WHERE s.id = ?`).get(e);return n?{id:n.id,auto_title:n.auto_title,auto_title_source:n.auto_title_source??null,has_alias:n.has_alias_int===1}:null}function
|
|
1559
|
-
`)}function
|
|
1606
|
+
WHERE s.id = ?`).get(e);return n?{id:n.id,auto_title:n.auto_title,auto_title_source:n.auto_title_source??null,has_alias:n.has_alias_int===1}:null}function JR(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 GR(e,t){if(e.has_alias)return{eligible:!1,reason:"manual_alias"};let n=wn({auto_title:e.auto_title,auto_title_source:e.auto_title_source,has_alias:e.has_alias});if(t)return{eligible:!0};switch(n){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 YR(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(`
|
|
1607
|
+
`)}function zR(e){let t=e.trim();if(!t)return null;let n=t;try{let o=JSON.parse(t);if(o&&typeof o=="object"){let a=o;if(typeof a.result=="string")n=a.result.trim();else if(typeof a.title=="string")return pm(a)}}catch{}let s=n.match(/```(?:json)?\s*\n([\s\S]*?)\n?```/i);s&&(n=s[1].trim());let r=null;try{r=JSON.parse(n)}catch{let o=n.indexOf("{"),a=n.lastIndexOf("}");if(o>=0&&a>o)try{r=JSON.parse(n.slice(o,a+1))}catch{return null}else return null}return!r||typeof r!="object"?null:pm(r)}function pm(e){let t=e.title;if(typeof t!="string")return null;let n=KR(t).trim();if(!n)return null;let s=e.evidence,r=typeof s=="string"?s.trim():"";return{title:n,evidence:r}}function KR(e){return e.replace(/^["'`]+|["'`]+$/g,"")}function VR(e){let t=e.replace(/\s+/g," ").trim().replace(/[.!?]+$/g,"").trim();return t.length<=um?t:t.slice(0,um)}function QR(e,t){let n=e.auto_title??"";return!n&&e.has_alias&&(n=h().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e.id)?.alias??""),{session_id:e.id,title:n,source:e.auto_title_source,confidence:0,evidence:`skipped: ${t}`,written:!1,skipped:t}}async function Yi(e,t={}){if(t.signal?.aborted)return{session_id:e,title:"",source:null,confidence:0,evidence:"aborted before start",written:!1,skipped:"aborted"};let n=XR(e);if(!n)throw new Error(`session not found: ${e}`);let s=GR(n,t.force===!0);if(!s.eligible)return QR(n,s.reason);let r=t.budget??WR,o=br(e,{budget:r});if(JR(o))throw new Xt(e);if(t.signal?.aborted)return{session_id:e,title:n.auto_title??"",source:n.auto_title_source,confidence:0,evidence:"aborted before CLI call",written:!1,skipped:"aborted"};let a=YR(o.bundle),c=t.model??yr,u=await qR(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=zR(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 p=VR(d.title);if(!p)throw new Error("regeneration produced empty title after clamp");he(e,p,"agent");let b=ve(e)?.auto_title??p;return{session_id:e,title:b,source:"agent",confidence:ek(o),evidence:d.evidence||`regenerated from neighborhood (${ZR(o)})`,written:!0}}function ZR(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 ek(e){let t=e.parents.length,n=e.children.length,s=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,n)+.2*Math.min(1,s/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 rt}from"hono/streaming";import{bodyLimit as yA}from"hono/body-limit";import{z as P}from"zod";H();H();We();function $n(e){return h().prepare("SELECT id, name FROM projects WHERE name = ? LIMIT 1").get(e)??null}H();function mm(e){let t=h(),n=new Date().toISOString();return t.prepare(`INSERT INTO bug_signature_resolutions
|
|
1560
1608
|
(message_hash, resolved_in_session_id, fix_summary, resolved_at, unresolved_at)
|
|
1561
1609
|
VALUES (?, ?, ?, ?, NULL)
|
|
1562
1610
|
ON CONFLICT(message_hash) DO UPDATE SET
|
|
1563
1611
|
resolved_in_session_id = excluded.resolved_in_session_id,
|
|
1564
1612
|
fix_summary = excluded.fix_summary,
|
|
1565
1613
|
resolved_at = excluded.resolved_at,
|
|
1566
|
-
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,n),
|
|
1614
|
+
unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,n),tk(e.messageHash)}function gm(e){h().prepare(`UPDATE bug_signature_resolutions
|
|
1567
1615
|
SET unresolved_at = ?
|
|
1568
|
-
WHERE message_hash = ?`).run(new Date().toISOString(),e)}function
|
|
1569
|
-
WHERE message_hash IN (${n})`).all(...e),r=new Map;for(let o of s)r.set(o.message_hash,o);return r}function
|
|
1616
|
+
WHERE message_hash = ?`).run(new Date().toISOString(),e)}function tk(e){return h().prepare("SELECT * FROM bug_signature_resolutions WHERE message_hash = ?").get(e)??null}function zi(e){if(e.length===0)return new Map;let t=h(),n=e.map(()=>"?").join(","),s=t.prepare(`SELECT * FROM bug_signature_resolutions
|
|
1617
|
+
WHERE message_hash IN (${n})`).all(...e),r=new Map;for(let o of s)r.set(o.message_hash,o);return r}function Ki(e){return!!e&&e.unresolved_at===null}H();function Un(){return new Date().toISOString()}function Vi(){let e=h(),t=e.prepare(`SELECT id, name, description, created_at, updated_at
|
|
1570
1618
|
FROM macro_repos
|
|
1571
1619
|
ORDER BY name COLLATE NOCASE`).all();if(t.length===0)return[];let n=t.map(a=>a.id),s=n.map(()=>"?").join(","),r=e.prepare(`SELECT m.macro_repo_id, m.project_id, p.name AS project_name
|
|
1572
1620
|
FROM macro_repo_members m
|
|
1573
1621
|
JOIN projects p ON p.id = m.project_id
|
|
1574
1622
|
WHERE m.macro_repo_id IN (${s})
|
|
1575
|
-
ORDER BY p.name COLLATE NOCASE`).all(...n),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
|
|
1623
|
+
ORDER BY p.name COLLATE NOCASE`).all(...n),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 Jt(e){return Vi().find(n=>n.id===e)??null}function fm(){return h().prepare(`SELECT p.id, p.name
|
|
1576
1624
|
FROM projects p
|
|
1577
1625
|
LEFT JOIN macro_repo_members m ON m.project_id = p.id
|
|
1578
1626
|
WHERE m.project_id IS NULL
|
|
1579
|
-
ORDER BY p.name COLLATE NOCASE`).all()}function
|
|
1580
|
-
VALUES (?, ?, ?, ?)`).run(t,e.description??null,s,s),o=Number(r.lastInsertRowid);return
|
|
1627
|
+
ORDER BY p.name COLLATE NOCASE`).all()}function _m(e){let t=e.name.trim();if(!t)throw new Error("macro repo name is required");let n=h(),s=Un(),r=n.prepare(`INSERT INTO macro_repos (name, description, created_at, updated_at)
|
|
1628
|
+
VALUES (?, ?, ?, ?)`).run(t,e.description??null,s,s),o=Number(r.lastInsertRowid);return Jt(o)}function hm(e,t){let n=Jt(e);if(!n)throw new Error(`macro repo ${e} not found`);let s=t.name!==void 0?t.name.trim():n.name;if(!s)throw new Error("macro repo name cannot be empty");let r=t.description!==void 0?t.description:n.description;return h().prepare(`UPDATE macro_repos
|
|
1581
1629
|
SET name = ?, description = ?, updated_at = ?
|
|
1582
|
-
WHERE id = ?`).run(s,r,
|
|
1583
|
-
VALUES (?, ?, ?)`).run(e,t,
|
|
1584
|
-
WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),n.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(
|
|
1630
|
+
WHERE id = ?`).run(s,r,Un(),e),Jt(e)}function Em(e){h().prepare("DELETE FROM macro_repos WHERE id = ?").run(e)}function bm(e,t){let n=h();if(!n.prepare("SELECT 1 FROM macro_repos WHERE id = ?").get(e))throw new Error(`macro repo ${e} not found`);if(!n.prepare("SELECT 1 FROM projects WHERE id = ?").get(t))throw new Error(`project ${t} not found`);n.prepare(`INSERT OR IGNORE INTO macro_repo_members (macro_repo_id, project_id, added_at)
|
|
1631
|
+
VALUES (?, ?, ?)`).run(e,t,Un()),n.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(Un(),e)}function Sm(e,t){let n=h();n.prepare(`DELETE FROM macro_repo_members
|
|
1632
|
+
WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),n.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(Un(),e)}H();function Tm(e){let t=h(),n=new Date().toISOString(),s=t.prepare(`INSERT INTO bug_synthesis_results
|
|
1585
1633
|
(scope, target_id, mode, model, output_markdown, input_tokens, output_tokens, context_summary, created_at, job_id)
|
|
1586
|
-
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??{}),n,e.job_id??null),r=Number(s.lastInsertRowid);return
|
|
1634
|
+
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??{}),n,e.job_id??null),r=Number(s.lastInsertRowid);return Qi(r)}function ym(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 Qi(e){let n=h().prepare("SELECT * FROM bug_synthesis_results WHERE id = ?").get(e);return n?ym(n):null}function wm(e={}){let t=h(),n=[],s=[];e.scope&&(n.push("scope = ?"),s.push(e.scope)),e.target_id&&(n.push("target_id = ?"),s.push(e.target_id));let r=n.length?`WHERE ${n.join(" AND ")}`:"",o=Math.min(Math.max(1,e.limit??50),500);return t.prepare(`SELECT * FROM bug_synthesis_results
|
|
1587
1635
|
${r}
|
|
1588
1636
|
ORDER BY created_at DESC
|
|
1589
|
-
LIMIT ?`).all(...s,o).map(
|
|
1637
|
+
LIMIT ?`).all(...s,o).map(ym)}function Rm(e){let n=h().prepare(`SELECT target_id, COUNT(*) AS n
|
|
1590
1638
|
FROM bug_synthesis_results
|
|
1591
1639
|
WHERE scope = ?
|
|
1592
|
-
GROUP BY target_id`).all(e),s=new Map;for(let r of n)s.set(r.target_id,r.n);return s}function
|
|
1593
|
-
`,"utf8")}catch(n){console.error("[launcher-audit] failed to append:",n)}}function
|
|
1594
|
-
`)){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<n||(s+=Number(u.input_tokens??0),r+=Number(u.output_tokens??0),o+=1)}return{input_tokens:s,output_tokens:r,records_counted:o}}var
|
|
1640
|
+
GROUP BY target_id`).all(e),s=new Map;for(let r of n)s.set(r.target_id,r.n);return s}function km(e){h().prepare("DELETE FROM bug_synthesis_results WHERE id = ?").run(e)}import{randomBytes as nk,timingSafeEqual as sk}from"node:crypto";var rk=6e4,ok=new Set(["127.0.0.1","localhost"]),Hn=new Map;function Zi(){return Date.now()}function Am(){let e=Zi();for(let[t,n]of Hn)(n.expiresAt<=e||n.used)&&Hn.delete(t)}function je(e){let t=e.req.header("origin")??"";if(t)try{let r=new URL(t);if(!ok.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 n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="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 ea(e){Am();let t=nk(32).toString("hex"),n=Zi()+rk;return Hn.set(t,{token:t,intent:e,expiresAt:n,used:!1}),{token:t,expiresAt:n}}function ta(e){if(Am(),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 n of Hn.values()){if(n.used||n.expiresAt<=Zi())continue;let s;try{s=Buffer.from(n.token,"hex")}catch{continue}if(s.length===t.length&&sk(s,t))return n.used=!0,Hn.delete(n.token),n.intent}return null}import{existsSync as dk,mkdirSync as eD,readFileSync as pk,writeFileSync as tD}from"node:fs";import{homedir as mk}from"node:os";import{join as Im}from"node:path";import{z as na}from"zod";import{appendFileSync as ik,existsSync as xm,mkdirSync as ak,readFileSync as ck}from"node:fs";import{homedir as lk}from"node:os";import{join as Nm}from"node:path";function Om(){return process.env.RECALL_HOME??Nm(lk(),".recall")}function uk(){let e=Om();xm(e)||ak(e,{recursive:!0})}function Lm(){return Nm(Om(),"launcher-audit.log")}function oe(e){uk();let t={ts:new Date().toISOString(),...e};try{ik(Lm(),JSON.stringify(t)+`
|
|
1641
|
+
`,"utf8")}catch(n){console.error("[launcher-audit] failed to append:",n)}}function Cm(e){let t=Lm();if(!xm(t))return{input_tokens:0,output_tokens:0,records_counted:0};let n=Date.now()-e,s=0,r=0,o=0,a;try{a=ck(t,"utf8")}catch{return{input_tokens:0,output_tokens:0,records_counted:0}}for(let c of a.split(`
|
|
1642
|
+
`)){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<n||(s+=Number(u.input_tokens??0),r+=Number(u.output_tokens??0),o+=1)}return{input_tokens:s,output_tokens:r,records_counted:o}}var gk=1440*60*1e3,fk=na.object({dailyTokenBudget:na.number().int().nonnegative().default(1e6),sessionCeiling:na.number().int().positive().max(1e4).default(500)}),sa={dailyTokenBudget:1e6,sessionCeiling:500};function _k(){return process.env.RECALL_HOME??Im(mk(),".recall")}function hk(){return Im(_k(),"config.json")}function Ek(){let e=hk();if(!dk(e))return{};try{return JSON.parse(pk(e,"utf8"))}catch(t){return console.error("[launcher-budget] failed to parse config.json, using defaults:",t),{}}}function ra(){let e=Ek().launcher;if(!e)return{...sa};let t=fk.safeParse({...sa,...e});return t.success?t.data:{...sa}}function Gt(){let e=ra(),t=Cm(gk),n=t.input_tokens+t.output_tokens,s=Math.max(0,e.dailyTokenBudget-n);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:n,remaining_tokens_24h:s}}function oa(e){return{estimated_input_tokens_max:e*2e4,estimated_output_tokens_max:e*1e3}}function ia(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 n=Math.max(1,e.cluster_count??1);return{estimated_input_tokens_max:Math.min(5e4,1e3*n),estimated_output_tokens_max:t}}var vm={pro:45,"max-5x":225,"max-20x":900};function bk(e){let t=e.toLowerCase();return t.includes("haiku")?1:t.includes("sonnet")?5:t.includes("opus")?10:5}var Sk=4e3;function Mm(e){return Math.max(1,Math.ceil(e/Sk))}function tt(e,t){let n=bk(t),s=e*n,r=Object.keys(vm).map(o=>{let a=s/vm[o];return{plan:o,fraction:a,pct:Math.round(a*1e3)/10,would_exhaust_window:a>1}});return{model:t,model_multiplier:n,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 Tk}from"node:crypto";var Yt=new Map,Bn=new Map,yk=300*1e3;function wr(e,t,n){e.events.push({id:e.events.length+1,kind:t,data:n});for(let s of e.waiters)s();e.waiters.clear()}function wk(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{Yt.delete(e.jobId),Bn.get(e.project)===e.jobId&&Bn.delete(e.project)},yk),e.cleanupTimer.unref?.())}function aa(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 Dm(e){let t=$n(e.project);if(!t)return{error:`project "${e.project}" not found`};let n=Bn.get(t.name);if(n){let p=Yt.get(n);if(p&&p.status==="running")return{jobId:n,reused:!0};Bn.delete(t.name)}let s=Tk(),r=e.model??mt,o=Math.max(1,e.limit??200),a=e.force??!1,c=new AbortController,u=new Date().toISOString(),d={jobId:s,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 Yt.set(s,d),Bn.set(t.name,s),oe({kind:"run-launched",job_id:s,project:t.name,model:r,limit:o,origin:e.origin??null}),(async()=>{await Promise.resolve();try{await ml({projectId:t.id,limit:o,force:a,model:r,signal:c.signal,onProgress:p=>{d.progress=p,wr(d,"progress",p)},onResult:p=>{!p.ok&&!p.skipped&&wr(d,"error",{session_id:p.session_id,reason:p.failed??"unknown"})}}),d.status=c.signal.aborted?"cancelled":"done",d.endedAt=new Date().toISOString(),wr(d,"done",aa(d)),oe({kind:d.status==="cancelled"?"run-cancelled":"run-completed",job_id:s,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(p){let f=p instanceof Error?p.message:String(p??"unknown error");d.status="failed",d.endedAt=new Date().toISOString(),d.error=f,wr(d,"done",aa(d)),oe({kind:"run-failed",job_id:s,project:t.name,model:r,limit:o,origin:e.origin??null,reason:f,input_tokens:d.progress.total_input_tokens,output_tokens:d.progress.total_output_tokens})}finally{wk(d)}})(),{jobId:s,reused:!1}}async function*jm(e,t=0){let n=Yt.get(e);if(!n)return;let s=t;for(;;){for(;s<n.events.length;){let r=n.events[s];if(!r)break;if(s+=1,yield r,r.kind==="done")return}if(n.status!=="running")return;await new Promise(r=>n.waiters.add(r))}}function Pm(e){let t=Yt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function ca(e){let t=Yt.get(e);return t?aa(t):null}H();import{randomUUID as Rk}from"node:crypto";import{spawn as kk}from"node:child_process";hs();var Fm="claude-haiku-4-5-20251001",Kt=new Map,Xn=new Map,Ak=300*1e3;function $m(e){return`${e.scope}:${e.target_id}:${e.mode}`}function Wn(e,t,n){e.events.push({id:e.events.length+1,kind:t,data:n});for(let s of e.waiters)s();e.waiters.clear()}function xk(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{Kt.delete(e.jobId);let t=$m(e.intent);Xn.get(t)===e.jobId&&Xn.delete(t)},Ak),e.cleanupTimer.unref?.())}function zt(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 Um(){return h().prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
|
|
1595
1643
|
oi.bug_signatures
|
|
1596
1644
|
FROM session_output_index oi
|
|
1597
1645
|
JOIN sessions s ON s.id = oi.session_id
|
|
1598
1646
|
JOIN projects p ON p.id = s.project_id
|
|
1599
1647
|
WHERE oi.bug_signatures IS NOT NULL
|
|
1600
|
-
AND oi.bug_signatures != '[]'`).all()}function
|
|
1648
|
+
AND oi.bug_signatures != '[]'`).all()}function Hm(e){try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function Nk(e){let t=Um(),n=[];for(let a of t)for(let c of Hm(a.bug_signatures)){let u=c.message_hash??`nohash:${(c.snippet??"").slice(0,64)}`;(c.message_hash===e||u===e)&&n.push({sig:c,session_id:a.session_id,project:a.project,auto_title:a.auto_title})}if(n.length===0)return null;let s=n[0],r=Array.from(new Set(n.map(a=>a.session_id))),o=Array.from(new Set(n.map(a=>a.project))).sort();return{message_hash:s.sig.message_hash??null,error_type:s.sig.error_type??null,snippet:(s.sig.snippet??"").slice(0,400),file:s.sig.file??null,occurrence_count:n.length,projects:o,member_session_ids:r}}function Ok(e){let t=Um().filter(o=>o.project===e),n=new Map;for(let o of t)for(let a of Hm(o.bug_signatures)){let c=a.message_hash??`nohash:${(a.snippet??"").slice(0,64)}`,u=n.get(c);u?(u.sigs.push(a),u.sessions.add(o.session_id)):n.set(c,{sigs:[a],first:a,sessions:new Set([o.session_id])})}let s=new Map;try{let o=Array.from(n.keys()).filter(a=>!a.startsWith("nohash:"));if(o.length>0){let a=h(),c=o.map(()=>"?").join(","),u=a.prepare(`SELECT message_hash, fix_summary
|
|
1601
1649
|
FROM bug_signature_resolutions
|
|
1602
1650
|
WHERE message_hash IN (${c})
|
|
1603
|
-
AND unresolved_at IS NULL`).all(...o);for(let d of u)s.set(d.message_hash,{fix_summary:d.fix_summary})}}catch{}let r=[];for(let[o,a]of n){let c=a.first.message_hash??null,u=c?s.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
|
|
1651
|
+
AND unresolved_at IS NULL`).all(...o);for(let d of u)s.set(d.message_hash,{fix_summary:d.fix_summary})}}catch{}let r=[];for(let[o,a]of n){let c=a.first.message_hash??null,u=c?s.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 Lk(e){let t=e.replace(/\s+/g," ").trim();return t.length===0?"":t.slice(0,Math.min(30,t.length))}function Ck(e,t){let n=h(),s=Lk(t),r=[];for(let o of e.slice(0,8)){let a=n.prepare(`SELECT s.id, p.name AS project, s.auto_title
|
|
1604
1652
|
FROM sessions s
|
|
1605
1653
|
JOIN projects p ON p.id = s.project_id
|
|
1606
1654
|
WHERE s.id = ?`).get(o);if(!a)continue;let c=[];if(s.length>0){let u=n.prepare(`SELECT rowid FROM messages
|
|
@@ -1608,13 +1656,13 @@ ${n}`}function Gw(e){return ve(e)?.auto_title_source??null}async function Yw(e){
|
|
|
1608
1656
|
AND is_sidechain = 0
|
|
1609
1657
|
AND content_text LIKE ?
|
|
1610
1658
|
ORDER BY rowid
|
|
1611
|
-
LIMIT 5`).all(o,`%${s}%`),d=new Set,p=0;for(let f of u){let
|
|
1659
|
+
LIMIT 5`).all(o,`%${s}%`),d=new Set,p=0;for(let f of u){let b=n.prepare(`SELECT rowid, role, content_text FROM messages
|
|
1612
1660
|
WHERE session_id = ?
|
|
1613
1661
|
AND is_sidechain = 0
|
|
1614
1662
|
AND rowid BETWEEN ? AND ?
|
|
1615
|
-
ORDER BY rowid`).all(o,f.rowid-2,f.rowid+2);for(let
|
|
1616
|
-
`)}function
|
|
1617
|
-
`)}var
|
|
1663
|
+
ORDER BY rowid`).all(o,f.rowid-2,f.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(p+S.length>4e3)break;p+=S.length,c.push({role:T.role??"unknown",content:S})}if(p>=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 vk="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 Ik(e,t){let n=[];n.push("[CONTEXT]"),n.push("Bug fingerprint:"),n.push(`- error_type: ${e.error_type??"unknown"}`),n.push(`- description: ${e.snippet||"(no snippet)"}`),n.push(`- file: ${e.file??"(no file recorded)"}`),n.push(`- occurrences: ${e.occurrence_count} sessions`),n.push(`- projects: ${e.projects.join(", ")}`),n.push(`- finding_id: ${e.message_hash??"(no hash)"}`),n.push(""),n.push("Member session excerpts:");for(let s of t){let r=s.auto_title??"(untitled)";if(n.push(`=== Session ${s.short_id} | ${s.project} | "${r}" ===`),s.excerpts.length===0)n.push("(no surrounding messages found for this snippet)");else for(let o of s.excerpts)n.push(`${o.role}: ${o.content}`);n.push("")}return n.join(`
|
|
1664
|
+
`)}function Mk(e,t,n){let s=[];s.push("[CONTEXT]"),s.push(`Project: ${e}`),s.push(`Total clusters: ${n}`),s.push(""),s.push("Clusters (sorted by occurrence_count desc):");for(let r of t)s.push(`- cluster_id: ${r.cluster_id}`),s.push(` error_type: ${r.error_type??"unknown"}`),s.push(` snippet: ${r.snippet||"(none)"}`),r.file&&s.push(` file: ${r.file}`),s.push(` occurrence_count: ${r.occurrence_count}`),s.push(` resolved: ${r.resolved?"true":"false"}`),r.fix_summary&&s.push(` fix_summary: ${r.fix_summary}`),s.push("");return t.length<n&&s.push(`(Showing top ${t.length} of ${n} clusters by occurrence.)`),s.join(`
|
|
1665
|
+
`)}var Dk=`[TASK]
|
|
1618
1666
|
Output a Markdown synopsis with these sections:
|
|
1619
1667
|
|
|
1620
1668
|
## What this bug is
|
|
@@ -1636,7 +1684,7 @@ prefer "consider <pattern>" over "do <action>" unless the evidence
|
|
|
1636
1684
|
is overwhelming.
|
|
1637
1685
|
|
|
1638
1686
|
## Confidence
|
|
1639
|
-
One of: high / medium / low. With one sentence justifying the level.`,
|
|
1687
|
+
One of: high / medium / low. With one sentence justifying the level.`,jk=`[TASK]
|
|
1640
1688
|
Output a Markdown response with these sections:
|
|
1641
1689
|
|
|
1642
1690
|
## Most likely root cause
|
|
@@ -1653,7 +1701,7 @@ list is acceptable but explicitly say "(none found)".
|
|
|
1653
1701
|
At most 2 alternatives, each with one sentence why it is less likely.
|
|
1654
1702
|
|
|
1655
1703
|
## Confidence
|
|
1656
|
-
high / medium / low + one-sentence justification.`,
|
|
1704
|
+
high / medium / low + one-sentence justification.`,Pk=`[TASK]
|
|
1657
1705
|
Output a Markdown response with these sections:
|
|
1658
1706
|
|
|
1659
1707
|
## Top 5 recurring concerns
|
|
@@ -1671,20 +1719,20 @@ Clusters that look small + high-confidence-fix. Empty list is fine.
|
|
|
1671
1719
|
Clusters that suggest deeper issues (multiple files, repeated patterns).
|
|
1672
1720
|
|
|
1673
1721
|
## Confidence
|
|
1674
|
-
high / medium / low.`;function
|
|
1675
|
-
`)}var
|
|
1722
|
+
high / medium / low.`;function Fk(e,t){let n;return e.scope==="cluster"?n=e.mode==="root_cause"?jk:Dk:n=Pk,[vk,"",t,"",n].join(`
|
|
1723
|
+
`)}var $k=null;var qn;function Uk(){return qn!==void 0||(qn=xt("claude")??"claude"),qn}function Hk(e){let t="";return n=>{t+=n.toString("utf8");let s=t.indexOf(`
|
|
1676
1724
|
`);for(;s!==-1;){let r=t.slice(0,s);t=t.slice(s+1),r.length>0&&e(r),s=t.indexOf(`
|
|
1677
|
-
`)}}}function
|
|
1678
|
-
`);if(t.length<4)return!1;let n=0,s=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?n++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&s++;return n/t.length>.7||s/t.length>.5}function
|
|
1725
|
+
`)}}}function Bk(e){return new Promise(t=>{let n=["-p",e.prompt,"--output-format","stream-json","--verbose","--allowedTools","","--permission-mode","bypassPermissions","--model",e.model],s="",r=0,o=0,a=null,c=Uk(),u=kk(c,n,{stdio:["ignore","pipe","pipe"],shell:_s(c)||process.platform==="win32"&&qn==="claude"}),d=()=>{try{u.kill("SIGTERM")}catch{}};e.signal.addEventListener("abort",d,{once:!0});let f=Hk(T=>{let S=T.trim();if(!S.startsWith("{"))return;let R;try{R=JSON.parse(S)}catch{return}if(!R||typeof R!="object")return;let A=R;if(A.type==="assistant"&&A.message?.content){for(let j of A.message.content)j?.type==="text"&&typeof j.text=="string"&&(s+=j.text,e.onPartial(j.text,s));let I=A.message?.usage;I&&(typeof I.input_tokens=="number"&&(r=Math.max(r,I.input_tokens)),typeof I.output_tokens=="number"&&(o=Math.max(o,I.output_tokens)))}});u.stdout.on("data",T=>f(T)),u.stderr.on("data",T=>{let S=T.toString("utf8");S.trim()&&(a=(a??"")+S)});let b=setTimeout(()=>{try{u.kill("SIGKILL")}catch{}},1800*1e3);u.on("close",T=>{clearTimeout(b),e.signal.removeEventListener("abort",d);let S=T===0?null:a&&a.trim()||(e.signal.aborted?null:`claude CLI exited with code ${T}`);t({output_markdown:s,input_tokens:r,output_tokens:o,error:S})}),u.on("error",T=>{clearTimeout(b),e.signal.removeEventListener("abort",d),t({output_markdown:s,input_tokens:r,output_tokens:o,error:T instanceof Error?T.message:String(T)})})})}function Rr(e){if(e.scope==="cluster"){let r=Nk(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=Ok(e.target_id);if(t.length===0)return null;let n=t.slice(0,30),s=t.reduce((r,o)=>r+o.occurrence_count,0);return{project_clusters:n,total_project_clusters:t.length,context_summary:{cluster_count:t.length,session_count:0,findings_count:s}}}function Bm(e){let t=Rr(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 n=$m(e.intent),s=Xn.get(n);if(s){let u=Kt.get(s);if(u&&u.status==="running")return{jobId:s,reused:!0};Xn.delete(n)}let r=Rk(),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 Kt.set(r,c),Xn.set(n,r),oe({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=Ck(t.cluster.member_session_ids,t.cluster.snippet);u=Ik(t.cluster,b)}else if(e.intent.scope==="project"&&t.project_clusters)u=Mk(e.intent.target_id,t.project_clusters,t.total_project_clusters??t.project_clusters.length);else throw new Error("inconsistent prepared context");let d=Fk(e.intent,u),f=await($k??Bk)({prompt:d,model:e.intent.model,signal:o.signal,onPartial:(b,T)=>{c.output_markdown=T,Wn(c,"partial",zt(c))}});if(c.output_markdown=f.output_markdown,c.input_tokens=f.input_tokens,c.output_tokens=f.output_tokens,o.signal.aborted)c.status="cancelled",c.endedAt=new Date().toISOString(),Wn(c,"done",zt(c)),oe({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(f.error)c.status="failed",c.endedAt=new Date().toISOString(),c.error=f.error,Wn(c,"done",zt(c)),oe({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:f.error,input_tokens:c.input_tokens,output_tokens:c.output_tokens});else{c.status="done",c.endedAt=new Date().toISOString(),Wn(c,"done",zt(c)),oe({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{Tm({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,Wn(c,"done",zt(c)),oe({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{xk(c)}})(),{jobId:r,reused:!1}}async function*Wm(e,t=0){let n=Kt.get(e);if(!n)return;let s=t;for(;;){for(;s<n.events.length;){let r=n.events[s];if(!r)break;if(s+=1,yield r,r.kind==="done")return}if(n.status!=="running")return;await new Promise(r=>n.waiters.add(r))}}function qm(e){let t=Kt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function la(e){let t=Kt.get(e);return t?zt(t):null}import{randomUUID as t0}from"node:crypto";H();xs();import{randomUUID as Vk}from"node:crypto";H();var Xm=10,Jm=20;function Wk(e){if(!e)return!1;let t=e.split(`
|
|
1726
|
+
`);if(t.length<4)return!1;let n=0,s=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?n++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&s++;return n/t.length>.7||s/t.length>.5}function qk(e){let t=e.byteLength/4;if(!Number.isInteger(t)||t<=0)return null;let n=new Float32Array(t),s=new Float32Array(e.buffer,e.byteOffset,t);return n.set(s),n}function Gm(e){let t=0;for(let s=0;s<e.length;s++)t+=e[s]*e[s];if(t<=0)return!1;let n=1/Math.sqrt(t);for(let s=0;s<e.length;s++)e[s]*=n;return!0}function ua(e){if(e.length===0)return null;let t=e[0].length,n=new Float32Array(t);for(let s of e)if(s.length===t)for(let r=0;r<t;r++)n[r]+=s[r];for(let s=0;s<t;s++)n[s]/=e.length;return Gm(n)?n:null}function Ym(e){let t=new Map;if(e.length===0)return t;let n=h(),s=e.map(()=>"?").join(","),r=[];try{r=n.prepare(`SELECT cm.rowid AS rowid, cm.session_id AS session_id,
|
|
1679
1727
|
cm.text AS text, v.embedding AS embedding
|
|
1680
1728
|
FROM chunk_meta cm
|
|
1681
1729
|
JOIN vec_chunks v ON v.rowid = cm.rowid
|
|
1682
1730
|
WHERE cm.stale = 0
|
|
1683
1731
|
AND cm.session_id IN (${s})
|
|
1684
|
-
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(
|
|
1685
|
-
`),u=
|
|
1732
|
+
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=>!Wk(M.text)),d=u.length>0?u:c,p=[];for(let M of d){let F=qk(M.embedding);F&&Gm(F)&&p.push(F)}if(p.length===0)continue;let f=Math.min(Xm,p.length),b=Math.max(0,p.length-Xm),T=p.slice(0,f),S=p.slice(b),R=ua(T),A=ua(S),I=ua(p),j=new Map;for(let M=0;M<T.length&&j.size<Jm;M++)j.set(M,T[M]);for(let M=0;M<S.length&&j.size<Jm;M++)j.set(b+M,S[M]);let J=Array.from(j.values());t.set(a,{session_id:a,full_mean:I,head_pool:R,tail_pool:A,sample_chunks:J})}return t}function Xk(e,t){if(e.length!==t.length)return 0;let n=0;for(let s=0;s<e.length;s++)n+=e[s]*t[s];return n<-1?-1:n>1?1:n}function zm(e,t=.8){let n=new Map,s=[],r=[];for(let[d,p]of e)p.full_mean&&(s.push(d),r.push(p.full_mean));if(s.length===0)return n;let o=new Int32Array(s.length);for(let d=0;d<o.length;d++)o[d]=d;let a=d=>{let p=d;for(;o[p]!==p;)p=o[p];let f=d;for(;o[f]!==p;){let b=o[f];o[f]=p,f=b}return p},c=(d,p)=>{let f=a(d),b=a(p);f!==b&&(o[f]=b)};for(let d=0;d<s.length;d++)for(let p=d+1;p<s.length;p++)Xk(r[d],r[p])>=t&&c(d,p);let u=new Map;for(let d=0;d<s.length;d++){let p=a(d),f=u.get(p);f===void 0&&(f=u.size,u.set(p,f)),n.set(s[d],f)}return n}var Xe={lo:.4,hi:.7},Vt="claude-haiku-4-5-20251001",Qt=50,Km=1500,Vm=50,Jk={same_workflow:.15,unrelated:-.2,unsure:0};function Qm(e,t,n=Xe){if(n.lo>n.hi)throw new Error(`borderline band invalid: lo=${n.lo} > hi=${n.hi}`);let s=[];for(let r of e){if(r.confidence<n.lo||r.confidence>n.hi)continue;let o=t.get(r.parent_id),a=t.get(r.child_id);!o||!a||s.push({parent:o,child:a,step1:r})}return s.sort((r,o)=>r.child.started_at_ms-o.child.started_at_ms),s}function Zm(e,t=Xe){let n=0;for(let s of e)s.confidence>=t.lo&&s.confidence<=t.hi&&n++;return n}function Gk(e,t){let n=e.replace(/\s+/g," ").trim();return n.length>t?n.slice(0,t-1)+"\u2026":n}function Yk(e,t){if(e===null)return"unknown";let n=t-e;if(n<0)return"overlap";let s=Math.round(n/6e4);if(s<60)return`${s}m`;let r=Math.round(n/36e5);return r<24?`${r}h`:`${Math.round(n/864e5)}d`}function zk(e){let n=e.parent.recent_user_messages,s=e.child.recent_user_messages,r=n.slice(0,3),o=n.length>3?n.slice(-3):[],a=s.slice(0,3),c=d=>d.length===0?" (none captured)":d.map(p=>` - ${Gk(p,500)}`).join(`
|
|
1733
|
+
`),u=Yk(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(`
|
|
1686
1734
|
`),` 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(`
|
|
1687
|
-
`)}function
|
|
1735
|
+
`)}function Kk(e){if(!e)return null;let t=e.trim();if(!t)return null;let n=t;try{let u=JSON.parse(t);typeof u.result=="string"&&(n=u.result.trim())}catch{}let s=n.match(/\{[\s\S]*\}/);if(!s)return null;let r;try{r=JSON.parse(s[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:Jk[a]}}async function eg(e,t={}){if(t.signal?.aborted)return null;let n=t.model??Vt,s=zk(e),r=t.spawn;if(!r)try{let a=await Promise.resolve().then(()=>(we(),pt));if(!a.isClaudeCliAvailable())return null;r=(c,u)=>a.spawnClaudePrompt(c,[],u)}catch{return null}let o;try{o=await Promise.race([r(s,{model:n}),new Promise(a=>{t.signal&&t.signal.addEventListener("abort",()=>a({success:!1,stdout:""}),{once:!0})})])}catch{return null}return!o.success||!o.stdout?null:Kk(o.stdout)}function tg(e){let t=[];for(let n of e.proposals){let s=`${n.parent_id}::${n.child_id}`,r=e.rescored.get(s);if(!r){t.push(n);continue}let o=Math.max(0,Math.min(1,n.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({...n,confidence:o,reasons:[...n.reasons,a]})}return t}function ng(e){return`${e.parent_id}::${e.child_id}`}async function Qk(e){if(e.signal?.aborted)return null;let t,n;try{({spawnClaudePrompt:t,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(we(),pt)))}catch{return null}if(!n())return null;let s=[];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)");s.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:
|
|
1688
1736
|
- 4 to 8 words
|
|
1689
1737
|
- Title-case, no trailing punctuation
|
|
1690
1738
|
- Capture the WORKFLOW (e.g., "Update Remotion deps + lint cleanup"), not any one session
|
|
@@ -1695,12 +1743,12 @@ Sessions:
|
|
|
1695
1743
|
`+s.join(`
|
|
1696
1744
|
`)+`
|
|
1697
1745
|
|
|
1698
|
-
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),a=null,c=new Promise(f=>{e.signal&&(a=()=>f(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 p;try{let f=JSON.parse(d);p=typeof f.result=="string"?f.result:d}catch{p=d}return p=p.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),p?p.length>80?p.slice(0,77)+"...":p:null}catch{return null}}function
|
|
1746
|
+
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),a=null,c=new Promise(f=>{e.signal&&(a=()=>f(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 p;try{let f=JSON.parse(d);p=typeof f.result=="string"?f.result:d}catch{p=d}return p=p.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),p?p.length>80?p.slice(0,77)+"...":p:null}catch{return null}}function da(e){return Zk(e)}function pa(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 n=e.map(o=>o.id),s=new Map;try{s=Ym(n)}catch{s=new Map}let r=new Map;for(let[o,a]of t){let c=new Map;for(let p of a){let f=s.get(p.id);f&&c.set(p.id,f)}let u=zm(c,.8),d=[];for(let p of a){let f=e0(p,s,u);f&&d.push(f)}r.set(o,d)}return{byProject:t,scannablesByProject:r}}function rg(e){return h().prepare(`SELECT COUNT(DISTINCT t.id) AS n
|
|
1699
1747
|
FROM threads t
|
|
1700
1748
|
JOIN thread_edges te ON te.thread_id = t.id
|
|
1701
1749
|
JOIN sessions s ON s.id = te.session_id
|
|
1702
1750
|
JOIN projects p ON p.id = s.project_id
|
|
1703
|
-
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function
|
|
1751
|
+
WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function Zk(e){let t=h(),n={},s="1=1 AND s.message_count > 2";return s+=" 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&&(s+=" AND p.name = @project",n.project=e.project),t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1704
1752
|
s.first_user_message, s.auto_title,
|
|
1705
1753
|
NULLIF(sa.alias, '') AS alias,
|
|
1706
1754
|
s.file_path
|
|
@@ -1708,21 +1756,21 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1708
1756
|
JOIN projects p ON p.id = s.project_id
|
|
1709
1757
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1710
1758
|
WHERE ${s}
|
|
1711
|
-
ORDER BY p.name ASC, s.started_at ASC`).all(n)}function
|
|
1759
|
+
ORDER BY p.name ASC, s.started_at ASC`).all(n)}function e0(e,t,n){if(!e.started_at)return null;let s=Date.parse(e.started_at);if(!Number.isFinite(s))return null;let r=e.ended_at?Date.parse(e.ended_at):null,o=fr(e.file_path,{maxUserMessages:7}),a=t?.get(e.id);return{id:e.id,started_at_ms:s,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:n?.get(e.id)??null,authored_paths:o.authored_paths,authored_content:o.authored_content}}function sg(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 og(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??Xe,n=e.rescore.cap??Qt,s=e.rescore.model??Vt,r=new Map(e.scannables.map(b=>[b.id,b])),o=Qm(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>n)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,p=0;for(let b=0;b<o.length&&!e.signal?.aborted;b++){let T=o[b],S=await eg(T,{model:s,signal:e.signal});S?(a.set(ng(T.step1),S),S.verdict==="same_workflow"?c++:S.verdict==="unrelated"?u++:d++):p++,e.onProgress?.({phase:"rescoring",current:b+1,total:o.length,verdict:S?.verdict??"failed"})}return{proposals:tg({proposals:e.proposals,rescored:a,applyThreshold:e.applyThreshold}),ran:!0,considered:o.length,promoted:c,demoted:u,unsure:d,failed:p,capped:!1}}async function ig(e){let t=h(),n=new Map(e.rows.map(d=>[d.id,d])),s=Np(e.edges,e.scannables),r=new Date().toISOString(),o=[],a=new Map,c=0;for(let d of s){if(c++,e.signal?.aborted)break;let p=n.get(d.rootId),f=sg(p);if(!e.llmNames){a.set(d.rootId,f),e.onProgress?.({phase:"naming",current:c,total:s.length,thread_name:f});continue}let T=await Qk({rootRow:p,sessionIds:d.sessionIds,rowById:n,signal:e.signal,model:e.model})??f;a.set(d.rootId,T),e.onProgress?.({phase:"naming",current:c,total:s.length,thread_name:T})}return t.transaction(()=>{for(let d of s){if(!a.has(d.rootId))continue;let p=n.get(d.rootId),f=`auto-scan-${Vk()}`,b=a.get(d.rootId)??sg(p),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(f,b,T,r),t.prepare(`INSERT INTO thread_edges
|
|
1712
1760
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1713
|
-
VALUES (?, ?, NULL, 'origin', 1.0, 'auto-scan-v1', ?)`).run(f,d.rootId,r);let
|
|
1761
|
+
VALUES (?, ?, NULL, 'origin', 1.0, 'auto-scan-v1', ?)`).run(f,d.rootId,r);let S=new Map;for(let R of e.edges)d.sessionIds.includes(R.child_id)&&S.set(R.child_id,R);for(let R of d.sessionIds){if(R===d.rootId)continue;let A=S.get(R);A&&t.prepare(`INSERT INTO thread_edges
|
|
1714
1762
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1715
|
-
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(f,R,
|
|
1716
|
-
`)}function
|
|
1763
|
+
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(f,R,A.parent_id,A.confidence,r)}o.push({thread_id:f,name:b,session_count:d.sessionIds.length})}})(),{project:e.project,threads:o}}var en=new Map,Jn=new Map,n0=300*1e3;function Zt(e,t,n){e.events.push({id:e.events.length+1,kind:t,data:n});for(let s of e.waiters)s();e.waiters.clear()}function s0(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{en.delete(e.jobId),Jn.get(e.project)===e.jobId&&Jn.delete(e.project)},n0),e.cleanupTimer.unref?.())}function kr(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 r0=500,o0=30,ma="claude-haiku-4-5-20251001",ag={"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 cg(e){return ag[e]??ag[ma]}function lg(e){let t=typeof e.threshold=="number"?e.threshold:mr;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let n=e.model??ma,s="",r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Xe.lo,hi:e.llm_rescore?.band_hi??Xe.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??Vt,c=da({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:tt(0,n),accuracy_caveat:s,model:n,llm_rescore:r?{enabled:!0,band:o,cap:Qt,estimated_borderline_pairs:0,estimated_input_tokens_max:0,estimated_output_tokens_max:0,estimated_cost_usd_max:0,plan_window_estimate:tt(0,a),exceeds_cap:!1,model:a}:null};let{byProject:u,scannablesByProject:d}=pa(c),p=u.get(e.project)??[],f=d.get(e.project)??[],b=r?Math.min(t,o.lo):t,T=Wt(f,b),S=r?T.filter(i=>i.confidence>=t):T,R=ug(S),A=R*r0,I=R*o0,j=cg(n),J=A/1e6*j.in,M=I/1e6*j.out,F=J+M,ne=tt(R,n),U=rg(e.project),v=null;if(r){let i=Zm(T,o),l=cg(a),m=i*Km,g=i*Vm,_=m/1e6*l.in+g/1e6*l.out;v={enabled:!0,band:o,cap:Qt,estimated_borderline_pairs:i,estimated_input_tokens_max:m,estimated_output_tokens_max:g,estimated_cost_usd_max:Math.round(_*1e4)/1e4,plan_window_estimate:tt(i,a),exceeds_cap:i>Qt,model:a}}return{eligible_sessions:p.length,proposed_edges:S.length,estimated_threads:R,estimated_llm_calls:R,estimated_input_tokens_max:A,estimated_output_tokens_max:I,estimated_cost_usd_max:Math.round(F*1e4)/1e4,existing_auto_scan_threads:U,plan_window_estimate:ne,accuracy_caveat:s,model:n,llm_rescore:v}}function ug(e){let t=new Map,n=o=>{let a=o;for(;t.get(a)!==a;)a=t.get(a);return a},s=(o,a)=>{let c=n(o),u=n(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),s(o.parent_id,o.child_id);let r=new Set;for(let o of t.keys())r.add(n(o));return r.size}function dg(e){let t=typeof e.threshold=="number"?e.threshold:mr;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let n=e.llm_names??!0,s=e.model??ma,r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Xe.lo,hi:e.llm_rescore?.band_hi??Xe.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??Vt,c=Jn.get(e.project);if(c){let F=en.get(c);if(F&&F.status==="running")return{jobId:c,reused:!0};Jn.delete(e.project)}let u=da({project:e.project});if(u.length===0)return{error:`no eligible sessions in project "${e.project}"`};let{byProject:d,scannablesByProject:p}=pa(u),f=d.get(e.project),b=p.get(e.project);if(!f||!b)return{error:`project "${e.project}" not found`};let T=r?Math.min(t,o.lo):t,S=Wt(b,T),R=r?S.filter(F=>F.confidence>=t):S;if(S.length===0||R.length===0&&!r)return{error:"no edges above threshold; nothing to apply"};let A=t0(),I=new AbortController,j=ug(R),J=new Date().toISOString(),M={jobId:A,project:e.project,threshold:t,llmNames:n,status:"running",startedAt:J,endedAt:null,events:[],waiters:new Set,controller:I,totalThreads:j,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 en.set(A,M),Jn.set(e.project,A),(async()=>{await Promise.resolve();try{Zt(M,"progress",{phase:"linking",edges_proposed:S.length,estimated_threads:j});let F=S;if(r){let U=await og({proposals:S,scannables:b,applyThreshold:t,rescore:{enabled:!0,band:o,model:a},signal:I.signal,onProgress:v=>{v.phase==="rescoring"&&Zt(M,"progress",{phase:"rescoring",current:v.current,total:v.total,verdict:v.verdict})}});F=U.proposals,M.rescore={enabled:!0,considered:U.considered,promoted:U.promoted,demoted:U.demoted,unsure:U.unsure,failed:U.failed,capped:U.capped}}else F=S.filter(U=>U.confidence>=t);if(F.length===0){M.status=I.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),Zt(M,"done",{...kr(M),threads:[]});return}let ne=await ig({project:e.project,rows:f,edges:F,scannables:b,llmNames:n,model:s,signal:I.signal,onProgress:U=>{U.phase==="naming"&&(M.threadsNamed=U.current,M.currentThreadName=U.thread_name??null,Zt(M,"progress",{phase:"naming",current:U.current,total:U.total,thread_name:U.thread_name}))}});M.threadsCreated=ne.threads.length,M.edgesWritten=F.length+ne.threads.length,M.status=I.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),Zt(M,"done",{...kr(M),threads:ne.threads})}catch(F){M.status="failed",M.endedAt=new Date().toISOString(),M.error=F instanceof Error?F.message:String(F??"unknown error"),Zt(M,"done",kr(M))}finally{s0(M)}})(),{jobId:A,reused:!1}}async function*pg(e,t=0){let n=en.get(e);if(!n)return;let s=t;for(;;){for(;s<n.events.length;){let r=n.events[s];if(!r)break;if(s+=1,yield r,r.kind==="done")return}if(n.status!=="running")return;await new Promise(r=>n.waiters.add(r))}}function mg(e){let t=en.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function ga(e){let t=en.get(e);return t?kr(t):null}gs();import{randomUUID as i0}from"node:crypto";var Ar=new Map;function gg(e){let t={id:i0(),status:"pending",createdAt:new Date().toISOString(),finishedAt:null,total:e,completed:0,results:[],error:null,controller:new AbortController,listeners:new Set};return Ar.set(t.id,t),t}function xr(e){return Ar.get(e)}function tn(e,t){for(let n of e.listeners)n(t)}function fg(e,t){return e.listeners.add(t),()=>{e.listeners.delete(t)}}function _g(e){let t=Ar.get(e);return t?(t.controller.abort(),t.status="cancelled",t.finishedAt=new Date().toISOString(),tn(t,{type:"status",status:"cancelled"}),!0):!1}function hg(e){return Ar.delete(e)}kt();function Nr(e){let{session:t,knownTags:n,minTags:s,maxTags:r}=e,o=n.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 ${s}-${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:",Eg(t.first_user_message," ")," message_sample:",Eg(t.message_sample," "),"","Return a single JSON object matching this schema exactly, with no prose before or after:",`{"tags": string[] length ${s}-${r}, "confidence": number 0-1, "rationale": string one short sentence}`].filter(Boolean).join(`
|
|
1764
|
+
`)}function Eg(e,t){return e.split(`
|
|
1717
1765
|
`).map(n=>t+n).join(`
|
|
1718
|
-
`)}
|
|
1766
|
+
`)}kt();import{z as Gn}from"zod";var a0=Gn.object({tags:Gn.array(Gn.string()).min(1),confidence:Gn.number().min(0).max(1),rationale:Gn.string().min(1).max(500)});function c0(e){let t=e.trim();return t.startsWith("```")?t.replace(/^```(?:json)?\s*/i,"").replace(/```$/i,"").trim():t}function Or(e,t={}){let n=t.maxTags??10,s;try{s=JSON.parse(c0(e))}catch{return{ok:!1,reason:"not valid JSON"}}let r=a0.safeParse(s);if(!r.success)return{ok:!1,reason:r.error.issues.map(c=>c.message).join("; ")};let o=r.data.tags.map(c=>ut(c)).filter(c=>c.length>0),a=Array.from(new Set(o)).slice(0,n);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 l0 from"@anthropic-ai/sdk";async function Lr(e){let s=(await new l0({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(!s||s.type!=="text")throw new Error("Anthropic response contained no text block");return s.text}function u0(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 Cr(e,t={}){let n=t.maxAttempts??3,s=t.baseDelayMs??500,r;for(let o=0;o<n;o++)try{return await e()}catch(a){if(r=a,!u0(a)||o===n-1)throw a;await new Promise(c=>setTimeout(c,s*Math.pow(2,o)))}throw r}async function bg(e,t){e.status="running",tn(e,{type:"status",status:"running"});let n=Rt();for(let s of t.sessions){if(e.controller.signal.aborted)break;let r=Nr({session:s,knownTags:n,minTags:t.minTags,maxTags:t.maxTags}),o={sessionId:s.id,project:s.project,alias:s.alias,first_user_message:s.first_user_message,current_tags:s.current_tags,suggestion:null,error:null,applied:!1};try{let a=await Cr(()=>Lr({apiKey:t.apiKey,model:t.model,prompt:r,signal:e.controller.signal})),c=Or(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,tn(e,{type:"result",result:o}),tn(e,{type:"progress",completed:e.completed,total:e.total})}if(!e.controller.signal.aborted){e.status="completed",e.finishedAt=new Date().toISOString();let s=e.results.filter(o=>o.suggestion&&!o.error).length,r=e.results.filter(o=>o.error).length;tn(e,{type:"done",summary:{ok:s,failed:r}})}}function Sg(e,t){let n=0,s=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}=wt(r.sessionId,a);c&&(n+=1)}catch(c){console.error("[scanner] apply failed:",r.sessionId,a,c),s+=1}o.applied=!0}}return{applied:n,failed:s}}kt();gs();var ee={running:!1,status:"idle",processed:0,total:0,currentSessionId:null,lastError:null,lastRunAt:null,controller:null},fa=new Set;function zn(){return{status:ee.status,processed:ee.processed,total:ee.total,currentSessionId:ee.currentSessionId,lastError:ee.lastError,lastRunAt:ee.lastRunAt}}function Tg(e){return fa.add(e),()=>{fa.delete(e)}}function Yn(){let e=zn();for(let t of fa)t(e)}async function vr(){let e=qe();if(!(!e.enabled||!e.autopilot)&&!(e.backend!=="api"||!e.apiKey)&&!ee.running){ee.running=!0,ee.status="scanning",ee.processed=0,ee.total=0,ee.currentSessionId=null,ee.lastError=null,ee.controller=new AbortController,Yn();try{let t=At({untaggedOnly:!0,limit:200});if(ee.total=t.length,Yn(),t.length===0){ee.status="idle",ee.lastRunAt=new Date().toISOString();return}let n=Rt();for(let s of t){if(ee.controller.signal.aborted)break;let r=qe();if(!r.autopilot||!r.enabled||r.backend!=="api"||!r.apiKey)break;ee.currentSessionId=s.id,Yn();let o=Nr({session:s,knownTags:n,minTags:r.minTagsPerSession,maxTags:r.maxTagsPerSession});try{let a=await Cr(()=>Lr({apiKey:r.apiKey,model:r.model,prompt:o,signal:ee.controller.signal})),c=Or(a,{maxTags:r.maxTagsPerSession});if(c.ok)for(let u of c.data.tags)try{wt(s.id,u)}catch(d){console.error("[autopilot] addTag failed:",s.id,u,d)}}catch(a){console.error("[autopilot] scan failed for",s.id,a)}ee.processed+=1,Yn(),await new Promise(a=>setTimeout(a,1500))}ee.status="idle",ee.lastRunAt=new Date().toISOString()}catch(t){ee.status="error",ee.lastError=t instanceof Error?t.message:String(t),console.error("[autopilot] fatal:",t)}finally{ee.running=!1,ee.currentSessionId=null,ee.controller=null,Yn()}}}hs();import{copyFileSync as d0,existsSync as Ir,readFileSync as wg,writeFileSync as Rg}from"node:fs";import{homedir as p0}from"node:os";import{dirname as m0,join as g0,resolve as f0}from"node:path";import{fileURLToPath as _0}from"node:url";var Pe=g0(p0(),".claude.json"),h0=m0(_0(import.meta.url)),E0=f0(h0,"..","mcp","server.js");function _a(){if(!Ir(Pe))return{};try{let e=wg(Pe,"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 yg=new Map;function b0(e){let t=yg.get(e);if(t!==void 0)return t;let n=xt(e)!==null;return yg.set(e,n),n}function S0(){return b0("claude-recall-mcp")?{command:"claude-recall-mcp",args:[]}:{command:process.execPath,args:[E0]}}function Je(){let t=_a().mcpServers?.recall;return{configPath:Pe,configExists:Ir(Pe),installed:!!(t&&typeof t.command=="string"),command:t?.command??null,args:t?.args??null}}function ha(){let e=_a(),t=e.mcpServers??{},n=S0(),s=t.recall;if(s?.command===n.command&&JSON.stringify(s?.args??[])===JSON.stringify(n.args))return Je();let o={command:n.command};n.args.length>0&&(o.args=n.args);let a={...e,mcpServers:{...t,recall:o}};return Rg(Pe,JSON.stringify(a,null,2)),Je()}function kg(){if(!Ir(Pe))return{repointed:!1,reason:"config_missing"};let e;try{e=wg(Pe,"utf8")}catch(f){return console.error("[mcp-installer] validate: failed to read ~/.claude.json:",f),{repointed:!1,reason:"read_error"}}let t;try{t=JSON.parse(e)}catch(f){return console.error("[mcp-installer] validate: ~/.claude.json is not valid JSON \u2014 skipping (operator must fix):",f),{repointed:!1,reason:"parse_error"}}if(!t||typeof t!="object"||Array.isArray(t))return{repointed:!1,reason:"not_an_object"};let s=t.mcpServers;if(!s||typeof s!="object")return{repointed:!1,reason:"no_mcp_servers"};let r=s.recall;if(!r)return{repointed:!1,reason:"no_recall_entry"};let o=r.args;if(!Array.isArray(o)||o.length===0||typeof o[0]!="string")return{repointed:!1,reason:"no_args_path"};let a=o[0];if(Ir(a))return{repointed:!1,reason:"path_live"};let c=new Date().toISOString().replace(/[:.]/g,"-"),u=`${Pe}.bak.${c}`;try{d0(Pe,u)}catch(f){return console.error(`[mcp-installer] validate: backup write failed (${u}) \u2014 refusing to repoint to avoid risking original:`,f),{repointed:!1,reason:"backup_failed"}}try{ha()}catch(f){return console.error(`[mcp-installer] validate: installMcp() failed during repoint \u2014 backup preserved at ${u}:`,f),{repointed:!1,reason:"install_failed"}}let d=Je(),p=d.args&&d.args.length>0?d.args[0]:d.command;return console.log(`[mcp-installer] repointed stale ~/.claude.json from ${a} to ${p??"(command-only)"} (backup: ${u})`),{repointed:!0,reason:"stale_args[0]"}}function Ag(){let e=_a(),t=e.mcpServers??{};if(!t.recall)return Je();let{recall:n,...s}=t,r={...e,mcpServers:s};return Rg(Pe,JSON.stringify(r,null,2)),Je()}import{existsSync as xg,mkdirSync as T0,readFileSync as y0,writeFileSync as Ng}from"node:fs";import{homedir as w0}from"node:os";import{join as Og}from"node:path";import{z as nt}from"zod";function Lg(){return process.env.RECALL_HOME??Og(w0(),".recall")}function Cg(){let e=Lg();xg(e)||T0(e,{recursive:!0})}function ba(){return Og(Lg(),"onboarding.json")}var Mr=nt.object({version:nt.literal(1).default(1),completed:nt.boolean().default(!1),skipped:nt.boolean().default(!1),finishedAt:nt.string().nullable().default(null),completedSteps:nt.array(nt.string().max(100)).max(50).default([]),threadsIntroSeen:nt.boolean().default(!1)}),Ea={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:[],threadsIntroSeen:!1};function Dr(){let e=ba();if(!xg(e))return{...Ea};try{let t=JSON.parse(y0(e,"utf8")),n=Mr.safeParse(t);return n.success?n.data:{...Ea}}catch(t){return console.error("[onboarding-state] failed to parse onboarding.json, using defaults:",t),{...Ea}}}function vg(e){Cg();let t=Dr(),n=Mr.parse({...t,...e,completedSteps:R0([...t.completedSteps??[],...e.completedSteps??[]]),version:1});return Ng(ba(),JSON.stringify(n,null,2)),n}function Ig(){Cg();let e=Dr(),t={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:e.completedSteps,threadsIntroSeen:e.threadsIntroSeen};return Ng(ba(),JSON.stringify(t,null,2)),t}function R0(e){let t=new Set,n=[];for(let s of e)t.has(s)||(t.add(s),n.push(s));return n}we();_o();fo();H();H();var k0=500,jr=new Set,Mg=null,Pr=!1;function Dg(){return jr.size}function A0(){return`
|
|
1719
1767
|
SELECT m.uuid, m.session_id, m.timestamp, m.raw_json
|
|
1720
1768
|
FROM messages m
|
|
1721
1769
|
LEFT JOIN message_usage mu ON mu.message_uuid = m.uuid
|
|
1722
1770
|
WHERE m.role = 'assistant' AND mu.message_uuid IS NULL
|
|
1723
1771
|
AND m.uuid NOT IN (SELECT value FROM json_each(?))
|
|
1724
1772
|
LIMIT ?
|
|
1725
|
-
`}function
|
|
1773
|
+
`}function jg(e,t){let n=t.limit??Number.MAX_SAFE_INTEGER,s=Math.max(1,t.chunkSize??k0),r=e.prepare(A0()),o=e.prepare(`
|
|
1726
1774
|
INSERT INTO message_usage (
|
|
1727
1775
|
message_uuid, session_id, model,
|
|
1728
1776
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1732,7 +1780,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1732
1780
|
@input, @output, @cc, @cr, @ts
|
|
1733
1781
|
)
|
|
1734
1782
|
ON CONFLICT(message_uuid) DO NOTHING
|
|
1735
|
-
`),a=0,c=0,u=new Set;for(;a<n;){let d=Math.min(s,n-a),p=JSON.stringify([...
|
|
1783
|
+
`),a=0,c=0,u=new Set;for(;a<n;){let d=Math.min(s,n-a),p=JSON.stringify([...jr]),f=r.all(p,d);if(f.length===0)break;let b=new Set;if(e.transaction(()=>{for(let S of f){let R;try{R=JSON.parse(S.raw_json)}catch{jr.add(S.uuid);continue}let A=Uo(R.message);if(!A){jr.add(S.uuid);continue}o.run({uuid:S.uuid,session_id:S.session_id,model:R.message?.model??null,input:A.inputTokens,output:A.outputTokens,cc:A.cacheCreateTokens,cr:A.cacheReadTokens,ts:S.timestamp}),c+=1,b.add(S.session_id)}for(let S of b)yn(e,S),u.add(S)})(),a+=f.length,t.onProgress?.({scanned:a,inserted:c,sessionsTouched:u.size,done:f.length<d}),f.length<d)break}return{scanned:a,inserted:c,sessionsTouched:u.size,done:!0}}function Pg(e={}){return jg(h(),e)}function Fg(e={}){return Pr?!1:(Pr=!0,queueMicrotask(()=>{try{let t=jg(h(),e);Mg={scanned:t.scanned,inserted:t.inserted,sessionsTouched:t.sessionsTouched,finishedAt:new Date().toISOString()}}catch(t){console.error("[stats.backfill] failed:",t)}finally{Pr=!1}}),!0)}function $g(){return Pr}function Sa(){return Mg}var x0=[[/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}]],Ug={label:"unknown",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30};function st(e){if(!e)return Ug;for(let[t,n]of x0)if(t.test(e))return n;return Ug}function xe(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,p]of Object.entries(e.byModel)){let f=st(d);a.input+=p.inputTokens/1e6*f.inputCentsPerMtok,a.output+=p.outputTokens/1e6*f.outputCentsPerMtok,a.cacheCreate+=p.cacheCreateTokens/1e6*f.cacheCreateCentsPerMtok,a.cacheRead+=p.cacheReadTokens/1e6*f.cacheReadCentsPerMtok,c+=p.inputTokens+p.outputTokens+p.cacheCreateTokens+p.cacheReadTokens}let u=a.input+a.output+a.cacheCreate+a.cacheRead;return{cents:u,dollars:u/100,totalTokens:c,parts:a}}let n=st(t),s={input:e.inputTokens/1e6*n.inputCentsPerMtok,output:e.outputTokens/1e6*n.outputCentsPerMtok,cacheCreate:e.cacheCreateTokens/1e6*n.cacheCreateCentsPerMtok,cacheRead:e.cacheReadTokens/1e6*n.cacheReadCentsPerMtok},r=s.input+s.output+s.cacheCreate+s.cacheRead,o=e.inputTokens+e.outputTokens+e.cacheCreateTokens+e.cacheReadTokens;return{cents:r,dollars:r/100,totalTokens:o,parts:s}}function nn(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 sn(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 Ta(e){let t=new Map;for(let s of e){let r=s.model??null,o=t.get(r)??{inputTokens:0,outputTokens:0,cacheCreateTokens:0,cacheReadTokens:0,messageCount:0};o.inputTokens+=s.input_tokens,o.outputTokens+=s.output_tokens,o.cacheCreateTokens+=s.cache_create_tokens,o.cacheReadTokens+=s.cache_read_tokens,o.messageCount+=s.n,t.set(r,o)}let n=[];for(let[s,r]of t.entries()){let o=xe({inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens},s);n.push({model:s,modelLabel:st(s).label,inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens,messageCount:r.messageCount,cost:o})}return n.sort((s,r)=>r.cost.cents-s.cost.cents)}function ya(e){let t={};for(let n of e)t[n.model??"__unknown__"]={inputTokens:n.inputTokens,outputTokens:n.outputTokens,cacheCreateTokens:n.cacheCreateTokens,cacheReadTokens:n.cacheReadTokens};return{byModel:t}}function Hg(e){let t=h(),n=t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1736
1784
|
s.message_count,
|
|
1737
1785
|
s.total_input_tokens, s.total_output_tokens,
|
|
1738
1786
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
@@ -1747,7 +1795,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1747
1795
|
COUNT(*) AS n
|
|
1748
1796
|
FROM message_usage
|
|
1749
1797
|
WHERE session_id = ?
|
|
1750
|
-
GROUP BY model`).all(e),r=
|
|
1798
|
+
GROUP BY model`).all(e),r=Ta(s),o=n.total_input_tokens??0,a=n.total_output_tokens??0,c=n.total_cache_create_tokens??0,u=n.total_cache_read_tokens??0,d=xe({inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:u,...ya(r)},n.primary_model);return{sessionId:n.id,project:n.project,startedAt:n.started_at,endedAt:n.ended_at,messageCount:n.message_count,primaryModel:n.primary_model,primaryModelLabel:st(n.primary_model).label,inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:u,totalTokens:d.totalTokens,cost:d,byModel:r,display:{dollars:nn(d.cents),tokens:sn(d.totalTokens),model:st(n.primary_model).label}}}function Bg(e){let t=h(),n=t.prepare("SELECT id, name FROM projects WHERE name = ?").get(e);if(!n)return null;let s=t.prepare(`SELECT mu.model,
|
|
1751
1799
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1752
1800
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1753
1801
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1756,12 +1804,12 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1756
1804
|
FROM message_usage mu
|
|
1757
1805
|
JOIN sessions s ON s.id = mu.session_id
|
|
1758
1806
|
WHERE s.project_id = ?
|
|
1759
|
-
GROUP BY mu.model`).all(n.id),r=
|
|
1807
|
+
GROUP BY mu.model`).all(n.id),r=Ta(s),o=t.prepare(`SELECT COALESCE(SUM(total_input_tokens), 0) AS input_tokens,
|
|
1760
1808
|
COALESCE(SUM(total_output_tokens), 0) AS output_tokens,
|
|
1761
1809
|
COALESCE(SUM(total_cache_create_tokens), 0) AS cache_create_tokens,
|
|
1762
1810
|
COALESCE(SUM(total_cache_read_tokens), 0) AS cache_read_tokens,
|
|
1763
1811
|
COUNT(*) AS session_count
|
|
1764
|
-
FROM sessions WHERE project_id = ?`).get(n.id),a=
|
|
1812
|
+
FROM sessions WHERE project_id = ?`).get(n.id),a=xe({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...ya(r)},null),u=t.prepare(`SELECT s.id, sa.alias, s.started_at,
|
|
1765
1813
|
s.total_input_tokens, s.total_output_tokens,
|
|
1766
1814
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
1767
1815
|
s.primary_model
|
|
@@ -1772,7 +1820,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1772
1820
|
+ COALESCE(s.total_output_tokens,0)
|
|
1773
1821
|
+ COALESCE(s.total_cache_create_tokens,0)
|
|
1774
1822
|
+ COALESCE(s.total_cache_read_tokens,0)) DESC
|
|
1775
|
-
LIMIT 10`).all(n.id).map(d=>{let p=
|
|
1823
|
+
LIMIT 10`).all(n.id).map(d=>{let p=xe({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:p.totalTokens,cost:p}});return{project:n.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:nn(a.cents),tokens:sn(a.totalTokens)}}}function Wg(e="all"){let t=h(),n=e==="7d"?new Date(Date.now()-7*864e5).toISOString():e==="30d"?new Date(Date.now()-30*864e5).toISOString():null,s=n?"WHERE mu.timestamp >= @since OR (mu.timestamp IS NULL AND s.started_at >= @since)":"",r=n?"WHERE m.timestamp >= @since OR (m.timestamp IS NULL AND s2.started_at >= @since)":"",o=n?{since:n}:{},a=l=>n?t.prepare(l).get(o):t.prepare(l).get(),c=l=>n?t.prepare(l).all(o):t.prepare(l).all(),u=c(`SELECT mu.model,
|
|
1776
1824
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1777
1825
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1778
1826
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1781,7 +1829,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1781
1829
|
FROM message_usage mu
|
|
1782
1830
|
JOIN sessions s ON s.id = mu.session_id
|
|
1783
1831
|
${s}
|
|
1784
|
-
GROUP BY mu.model`),d=
|
|
1832
|
+
GROUP BY mu.model`),d=Ta(u),p=0,f=0,b=0,T=0;for(let l of d)p+=l.inputTokens,f+=l.outputTokens,b+=l.cacheCreateTokens,T+=l.cacheReadTokens;let S=xe({inputTokens:p,outputTokens:f,cacheCreateTokens:b,cacheReadTokens:T,...ya(d)},null),R=n?a(`SELECT
|
|
1785
1833
|
(SELECT COUNT(DISTINCT m.session_id)
|
|
1786
1834
|
FROM messages m
|
|
1787
1835
|
JOIN sessions s2 ON s2.id = m.session_id
|
|
@@ -1795,7 +1843,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1795
1843
|
WHERE (COALESCE(total_input_tokens,0)
|
|
1796
1844
|
+COALESCE(total_output_tokens,0)
|
|
1797
1845
|
+COALESCE(total_cache_create_tokens,0)
|
|
1798
|
-
+COALESCE(total_cache_read_tokens,0)) > 0) AS sessions_with_usage`).get(),
|
|
1846
|
+
+COALESCE(total_cache_read_tokens,0)) > 0) AS sessions_with_usage`).get(),A=c(`SELECT substr(datetime(COALESCE(mu.timestamp, s.started_at, ''), 'localtime'), 1, 10) AS day,
|
|
1799
1847
|
mu.model,
|
|
1800
1848
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1801
1849
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
@@ -1805,7 +1853,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1805
1853
|
JOIN sessions s ON s.id = mu.session_id
|
|
1806
1854
|
${s}
|
|
1807
1855
|
GROUP BY day, mu.model
|
|
1808
|
-
ORDER BY day ASC`),
|
|
1856
|
+
ORDER BY day ASC`),I=new Map;for(let l of A){if(!l.day)continue;let m=xe({inputTokens:l.input_tokens,outputTokens:l.output_tokens,cacheCreateTokens:l.cache_create_tokens,cacheReadTokens:l.cache_read_tokens},l.model),g=I.get(l.day)??{tokens:0,cents:0};g.tokens+=m.totalTokens,g.cents+=m.cents,I.set(l.day,g)}let j=[...I.entries()].map(([l,m])=>({day:l,tokens:m.tokens,cents:m.cents})).sort((l,m)=>l.day.localeCompare(m.day)),M=c(`SELECT s.id, p.name AS project, sa.alias, s.started_at,
|
|
1809
1857
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1810
1858
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1811
1859
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1821,7 +1869,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1821
1869
|
+ COALESCE(SUM(mu.output_tokens),0)
|
|
1822
1870
|
+ COALESCE(SUM(mu.cache_create_tokens),0)
|
|
1823
1871
|
+ COALESCE(SUM(mu.cache_read_tokens),0)) DESC
|
|
1824
|
-
LIMIT 10`).map(l=>{let m=
|
|
1872
|
+
LIMIT 10`).map(l=>{let m=xe({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:m.totalTokens,cost:m}}),F=c(`SELECT p.id AS project_id,
|
|
1825
1873
|
p.name AS project,
|
|
1826
1874
|
mu.model,
|
|
1827
1875
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
@@ -1833,29 +1881,29 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1833
1881
|
JOIN sessions s ON s.id = mu.session_id
|
|
1834
1882
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1835
1883
|
${s}
|
|
1836
|
-
GROUP BY p.id, mu.model`),
|
|
1884
|
+
GROUP BY p.id, mu.model`),ne=new Map;for(let l of F){let m=l.project_id??"__none__",g=ne.get(m);g||(g={project:l.project??"(no project)",sessionIds:new Set,sessionsApprox:0,byModel:{}},ne.set(m,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 U=[...ne.values()].map(l=>{let m=0,g=0,_=0,E=0;for(let k of Object.values(l.byModel))m+=k.inputTokens,g+=k.outputTokens,_+=k.cacheCreateTokens,E+=k.cacheReadTokens;let y=xe({inputTokens:m,outputTokens:g,cacheCreateTokens:_,cacheReadTokens:E,byModel:l.byModel},null);return{project:l.project,sessions:l.sessionsApprox,totalTokens:y.totalTokens,cost:y}});U.sort((l,m)=>m.totalTokens-l.totalTokens);let v=U.slice(0,20),i=t.prepare(`SELECT
|
|
1837
1885
|
(SELECT COUNT(*) FROM messages WHERE role='assistant') AS assistant_messages,
|
|
1838
|
-
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:R.total_sessions,sessionsWithUsage:R.sessions_with_usage,inputTokens:p,outputTokens:f,cacheCreateTokens:
|
|
1886
|
+
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:R.total_sessions,sessionsWithUsage:R.sessions_with_usage,inputTokens:p,outputTokens:f,cacheCreateTokens:b,cacheReadTokens:T,totalTokens:S.totalTokens,cost:S,daily:j,byModel:d,topSessions:M,topRepos:v,backfill:{assistantMessages:i.assistant_messages,messagesWithUsage:i.messages_with_usage,pending:Math.max(0,i.assistant_messages-i.messages_with_usage),unrecoverable:Math.min(Dg(),Math.max(0,i.assistant_messages-i.messages_with_usage))},display:{dollars:nn(S.cents),tokens:sn(S.totalTokens)}}}H();function Kn(e){return Math.max(0,Math.min(1,e))}function wa(e){let t=h(),n=t.prepare("SELECT id, name FROM projects WHERE id = ?").get(e);if(!n)return null;let s=t.prepare(`SELECT COUNT(*) AS cnt,
|
|
1839
1887
|
MAX(started_at) AS latest
|
|
1840
|
-
FROM sessions WHERE project_id = ?`).get(e),r=s.cnt;if(r===0)return{projectId:e,projectName:n.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=
|
|
1841
|
-
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,p=
|
|
1888
|
+
FROM sessions WHERE project_id = ?`).get(e),r=s.cnt;if(r===0)return{projectId:e,projectName:n.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=Kn(r/10),a=s.latest?(Date.now()-new Date(s.latest).getTime())/(1e3*60*60*24):90,c=Kn(1-a/90),d=t.prepare(`SELECT AVG(message_count) AS avg_msgs
|
|
1889
|
+
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,p=Kn((d-2)/3),f=t.prepare(`SELECT COUNT(*) AS total,
|
|
1842
1890
|
SUM(CASE WHEN m.content_text IS NOT NULL AND m.content_text != '' THEN 1 ELSE 0 END) AS covered
|
|
1843
1891
|
FROM messages m
|
|
1844
1892
|
JOIN sessions s ON s.id = m.session_id
|
|
1845
|
-
WHERE s.project_id = ?`).get(e),
|
|
1893
|
+
WHERE s.project_id = ?`).get(e),b=f.total>0?f.covered/f.total:.5,T=Kn(b),S=t.prepare(`SELECT COUNT(DISTINCT s.id) AS total,
|
|
1846
1894
|
COUNT(DISTINCT st.session_id) AS tagged
|
|
1847
1895
|
FROM sessions s
|
|
1848
1896
|
LEFT JOIN session_tags st ON st.session_id = s.id
|
|
1849
|
-
WHERE s.project_id = ?`).get(e),R=
|
|
1897
|
+
WHERE s.project_id = ?`).get(e),R=S.total>0?S.tagged/S.total:0,A=Kn(R),I=Math.round((o*.2+c*.25+p*.15+T*.2+A*.2)*100);return{projectId:e,projectName:n.name,score:I,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:p,weight:.15},searchCoverage:{ratio:Math.round(b*100)/100,score:T,weight:.2},tagCoverage:{ratio:Math.round(R*100)/100,score:A,weight:.2}}}}function qg(){let t=h().prepare("SELECT id FROM projects ORDER BY name").all(),n=[];for(let s of t){let r=wa(s.id);r&&n.push(r)}return n}H();import{execFile as N0}from"node:child_process";import{promisify as O0}from"node:util";import{stat as L0}from"node:fs/promises";var C0=O0(N0),v0=60,I0=7,M0=7,D0=5e3;function j0(){let e=h(),t=n=>{try{return!!e.prepare(n).get()}catch{return!1}};return{semantic:t("SELECT 1 FROM session_semantic LIMIT 1"),cost:t(`SELECT 1 FROM sessions
|
|
1850
1898
|
WHERE (COALESCE(total_input_tokens,0)
|
|
1851
1899
|
+ COALESCE(total_output_tokens,0)
|
|
1852
1900
|
+ COALESCE(total_cache_create_tokens,0)
|
|
1853
1901
|
+ COALESCE(total_cache_read_tokens,0)) > 0
|
|
1854
|
-
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function
|
|
1902
|
+
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function Ra(e){if(!e)return[];let t=new Set,n=[];for(let s of e.split(",")){let r=s.trim().toLowerCase();!r||t.has(r)||(t.add(r),n.push(r))}return n}function Xg(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 P0(){let e=h(),t=e.prepare(`SELECT ss.keywords
|
|
1855
1903
|
FROM session_semantic ss
|
|
1856
1904
|
JOIN sessions s ON s.id = ss.session_id
|
|
1857
1905
|
WHERE s.started_at IS NOT NULL
|
|
1858
|
-
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:
|
|
1906
|
+
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:I0});if(t.length===0)return null;let n=new Set;for(let o of t)for(let a of Ra(o.keywords))n.add(a);if(n.size===0)return null;let s=e.prepare(`SELECT ss.session_id AS session_id,
|
|
1859
1907
|
ss.summary AS summary,
|
|
1860
1908
|
ss.keywords AS keywords,
|
|
1861
1909
|
p.name AS project,
|
|
@@ -1871,7 +1919,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1871
1919
|
WHERE s.started_at IS NOT NULL
|
|
1872
1920
|
AND s.message_count > 2
|
|
1873
1921
|
AND julianday('now') - julianday(s.started_at) >= @ageDays
|
|
1874
|
-
ORDER BY s.started_at ASC`).all({ageDays:
|
|
1922
|
+
ORDER BY s.started_at ASC`).all({ageDays:v0});if(s.length===0)return null;let r=null;for(let o of s){let c=Ra(o.keywords).filter(u=>n.has(u));c.length!==0&&(!r||c.length>r.overlap.length)&&(r={row:o,overlap:c})}return r?{...Xg(r.row),summary:r.row.summary,keywords:Ra(r.row.keywords),matchedKeywords:r.overlap,daysAgo:Math.max(0,Math.round(r.row.days_old))}:null}function F0(){let t=h().prepare(`SELECT s.id AS session_id,
|
|
1875
1923
|
p.name AS project,
|
|
1876
1924
|
NULLIF(sa.alias, '') AS alias,
|
|
1877
1925
|
s.started_at AS started_at,
|
|
@@ -1890,50 +1938,50 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1890
1938
|
AND (COALESCE(s.total_input_tokens, 0)
|
|
1891
1939
|
+ COALESCE(s.total_output_tokens, 0)
|
|
1892
1940
|
+ COALESCE(s.total_cache_create_tokens, 0)
|
|
1893
|
-
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:
|
|
1941
|
+
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:M0});if(t.length===0)return null;let n=null;for(let s of t){let r=xe({inputTokens:s.input_tokens,outputTokens:s.output_tokens,cacheCreateTokens:s.cache_create_tokens,cacheReadTokens:s.cache_read_tokens},s.primary_model);r.cents<=0||(!n||r.cents>n.cents)&&(n={row:s,cents:r.cents,totalTokens:r.totalTokens})}return n?{...Xg(n.row),totalTokens:n.totalTokens,costCents:n.cents,costDisplay:nn(n.cents),tokensDisplay:sn(n.totalTokens),primaryModel:n.row.primary_model,primaryModelLabel:st(n.row.primary_model).label}:null}async function $0(e){try{if(!(await L0(e)).isDirectory())return null}catch{return null}try{let{stdout:t}=await C0("git",["rev-parse","HEAD"],{cwd:e,timeout:D0}),n=t.trim();return/^[0-9a-f]{40}$/.test(n)?n:null}catch{return null}}async function U0(){let e=h(),t=e.prepare(`SELECT s.id AS id, s.cwd AS cwd
|
|
1894
1942
|
FROM sessions s
|
|
1895
1943
|
WHERE s.cwd IS NOT NULL AND s.started_at IS NOT NULL
|
|
1896
1944
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1897
|
-
LIMIT 1`).get();if(!t?.cwd)return null;let n=await
|
|
1945
|
+
LIMIT 1`).get();if(!t?.cwd)return null;let n=await $0(t.cwd);if(!n)return null;let s=js(n);if(s.length===0)return null;let r=s[0],o=e.prepare(`SELECT s.first_user_message AS first_user_message, s.ended_at AS ended_at
|
|
1898
1946
|
FROM sessions s
|
|
1899
|
-
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
|
|
1947
|
+
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 Jg(){let e=j0(),t=e.semantic?Promise.resolve().then(()=>{try{return P0()}catch(c){return console.error("[discover.rediscovered]",c),null}}):Promise.resolve(null),n=e.cost?Promise.resolve().then(()=>{try{return F0()}catch(c){return console.error("[discover.expensive]",c),null}}):Promise.resolve(null),s=e.git?U0().catch(c=>(console.error("[discover.authored]",c),null)):Promise.resolve(null),[r,o,a]=await Promise.all([t,n,s]);return{rediscovered:r,expensive:o,authored:a,availability:e,generatedAt:new Date().toISOString()}}We();H();es();var ka=class extends Error{name="CorpusTooLargeError";code="CORPUS_TOO_LARGE";rowCount;limit;constructor(t,n){super(`semantic search refused: vec_chunks has ${t} rows (cap ${n}). An unindexed kNN over this corpus can pin the SQLite WAL for hours. Workarounds: (a) scope the search to a smaller project, (b) raise the cap via RECALL_KNN_MAX_CORPUS at your own risk, (c) wait for ANN indexing (tracked in the WAL death-spiral plan).`),this.rowCount=t,this.limit=n}};function Aa(e,t={}){let n=t.limit??H0()??75e3,r=e.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get()?.n??0;if(r>n)throw new ka(r,n)}function H0(){let e=process.env.RECALL_KNN_MAX_CORPUS;if(!e)return;let t=Number(e);if(!(!Number.isFinite(t)||t<=0||t>1e7))return Math.floor(t)}ns();import{Worker as B0}from"node:worker_threads";import{join as W0}from"node:path";var xa=class extends Error{constructor(n){super(`semantic kNN exceeded ${n}ms wall-clock budget -- query aborted`);this.timeoutMs=n}timeoutMs;name="KnnTimeoutError";code="KNN_TIMEOUT"},q0=1e4;function X0(){let e=process.env.RECALL_KNN_TIMEOUT_MS;if(!e)return;let t=Number(e);if(!(!Number.isFinite(t)||t<=0||t>36e5))return Math.floor(t)}async function Na(e){let t=e.timeoutMs??X0()??q0,n=(e.workerFactory??J0)();return new Promise((s,r)=>{let o=setTimeout(()=>{n.terminate().catch(()=>{}),r(new xa(t))},t);n.once("message",a=>{clearTimeout(o);let c=a;c&&c.ok===!0&&Array.isArray(c.hits)?s(c.hits):r(new Error(c?.error??"worker returned malformed reply")),n.terminate().catch(()=>{})}),n.once("error",a=>{clearTimeout(o),n.terminate().catch(()=>{}),r(a)}),n.postMessage({query:e.query,precomputedVector:e.precomputedVector,limit:e.limit})})}function J0(){let e=W0(Tt(),"dist","daemon","query-worker.js"),t={...process.env,RECALL_DB_PROFILE:"worker"};return new B0(e,{env:t})}async function Gg(e,t=50){let n=h();return at(n),Aa(n),Na({query:e,limit:t})}async function Yg(e,t=10,n=.65){let s=h();at(s),Aa(s);let r=s.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!r)return[];let o=s.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(r.rowid);if(!o)return[];let a=await Na({precomputedVector:o.embedding,limit:t*5}),c=new Map;for(let d of a){if(d.sessionId===e)continue;let p=c.get(d.sessionId);(p===void 0||d.distance<p)&&c.set(d.sessionId,d.distance)}let u=[];for(let[d,p]of c){let f=1-p;f>=n&&u.push({sessionId:d,similarity:f})}return u.sort((d,p)=>p.similarity-d.similarity),u.slice(0,t)}function G0(){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 zg(e){let t=G0(),n=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=n.get(a.id);u?(u.score+=c,u.lanes.push(a.lane),o+1<u.bestRank&&(u.bestRank=o+1,u.bestData=a.data)):n.set(a.id,{score:c,lanes:[a.lane],bestRank:o+1,bestData:a.data})}let s=[];for(let[r,o]of n)s.push({id:r,score:o.score,lanes:o.lanes,data:o.bestData});return s.sort((r,o)=>o.score-r.score),s}Z();import{existsSync as Qg,mkdirSync as Kg,rmSync as Vg,createWriteStream as Y0,statSync as z0}from"node:fs";import{join as Fr}from"node:path";import{createHash as K0}from"node:crypto";import{readFile as V0}from"node:fs/promises";var Q0="https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/",Zg=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function ef(){return Fr(W,"models","BAAI","bge-base-en-v1.5")}function Ge(){let e=ef();return Zg.every(t=>Qg(Fr(e,t.path)))}async function tf(e){let t=ef();Kg(t,{recursive:!0}),Kg(Fr(t,"onnx"),{recursive:!0});for(let n of Zg){let s=Fr(t,n.path),r=Q0+n.path,o=0;Qg(s)&&(o=z0(s).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 ${n.path}: HTTP ${c.status}`);let u=c.headers.get("content-length"),d=u?o+Number(u):0,p=c.body;if(!p)throw new Error(`No response body for ${n.path}`);let f=Y0(s,{flags:o>0?"a":"w"}),b=p.getReader(),T=o;for(;;){let{done:A,value:I}=await b.read();if(A)break;f.write(Buffer.from(I)),T+=I.byteLength,e?.(n.path,T,d)}if(f.end(),await new Promise((A,I)=>{f.on("finish",A),f.on("error",I)}),n.sha256==="TODO_PLACEHOLDER")throw Vg(s),new Error(`Refusing to install: SHA-256 not pinned for ${n.path}. Update model-download.ts.`);let S=await V0(s);if(K0("sha256").update(S).digest("hex")!==n.sha256)throw Vg(s),new Error(`SHA-256 mismatch for ${n.path}`)}}H();var Z0=[/\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],eA=1440*60*1e3;function tA(e){let t=h(),n=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
1900
1948
|
WHERE session_id = ? AND role = 'assistant'
|
|
1901
|
-
ORDER BY timestamp DESC LIMIT 5`).all(e),s=!1;for(let u of n)if(u.content_text&&
|
|
1902
|
-
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let u of r){let d=u.tool_names??"",p=u.content_text??"";/\bWrite\b|\bEdit\b/.test(d)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(p)&&/pass|ok|✓/i.test(p)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(p)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(p)||/tsc.*(?:0 errors|no errors)/i.test(p))&&(o.buildSuccess=!0)}return s?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:s}:{status:"neutral",evidence:o,claimFound:s}}function
|
|
1903
|
-
`,"utf-8"),
|
|
1949
|
+
ORDER BY timestamp DESC LIMIT 5`).all(e),s=!1;for(let u of n)if(u.content_text&&Z0.some(d=>d.test(u.content_text))){s=!0;break}let r=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
1950
|
+
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let u of r){let d=u.tool_names??"",p=u.content_text??"";/\bWrite\b|\bEdit\b/.test(d)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(p)&&/pass|ok|✓/i.test(p)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(p)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(p)||/tsc.*(?:0 errors|no errors)/i.test(p))&&(o.buildSuccess=!0)}return s?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:s}:{status:"neutral",evidence:o,claimFound:s}}function nA(e){let t=tA(e);return h().prepare("UPDATE sessions SET verification_status = ?, verification_computed_at = ? WHERE id = ?").run(t.status,Date.now(),e),t}function nf(e){let n=h().prepare("SELECT verification_status, verification_computed_at FROM sessions WHERE id = ?").get(e);if(n?.verification_status&&n.verification_computed_at&&Date.now()-n.verification_computed_at<eA){let s={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};return{status:n.verification_status,evidence:s,claimFound:n.verification_status!=="neutral"}}return nA(e)}import{readFileSync as sA,writeFileSync as rA,mkdirSync as oA,chmodSync as iA}from"node:fs";import{join as sf}from"node:path";import{homedir as rf}from"node:os";function of(){return sf(rf(),".recall","config.json")}function af(){try{return JSON.parse(sA(of(),"utf-8"))}catch{return{}}}function aA(e){let t=of();oA(sf(rf(),".recall"),{recursive:!0}),rA(t,JSON.stringify(e,null,2)+`
|
|
1951
|
+
`,"utf-8"),iA(t,384)}function Oa(){let t=af().verification;return typeof t=="object"&&t!==null&&"enabled"in t?!!t.enabled:!1}function cf(e){let t=af();t.verification={...typeof t.verification=="object"&&t.verification!==null?t.verification:{},enabled:e},aA(t)}var _A=5e3,Pa={scanned:0,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0},La=0,Ca=Pa,va=null;async function hA(){return Date.now()-La>=_A&&!va&&(va=Ds().then(n=>(Ca=n,La=Date.now(),n)).catch(()=>(La=Date.now(),Ca=Pa,Pa)).finally(()=>{va=null})),Ca}var EA=2e3,bA=6,Hr=new Map;function Vn(e){return e.replace(/[\\%_]/g,t=>"\\"+t)}function SA(e,t){let n=Date.now(),s=(Hr.get(e)??[]).filter(a=>n-a.ts<EA);return Hr.set(e,s),s.length<2||s[s.length-1].name===t?!1:s.slice(0,-1).some(a=>a.name===t)}function TA(e,t){let n=Hr.get(e)??[];for(n.push({name:t,ts:Date.now()});n.length>bA;)n.shift();Hr.set(e,n)}function uf(e,t){let n=t.trim();if(!n)return 0;if(ue(n)){let o=vt(n);if(!o)return 0;n=o}if(pe(n))return 0;if(SA(e,n))return console.log(`[terminal] dropping rename of pid ${e} \u2192 "${n}", flap signature (competing editor sync sources)`),0;TA(e,n);let s=D.sessionsFor(e),r=0;for(let o of s)try{if(Se(o)===n)continue;_e(o,n),r++}catch{}return r>0&&console.log(`[terminal] rename of pid ${e} \u2192 "${n}" propagated to ${r} session(s)`),r}var df=(()=>{try{let e=Br($a(Ua(import.meta.url)),"..","..","package.json");return JSON.parse(ja(e,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})(),Ia=!1,Ma=!1,Da=!1,wA=$a(Ua(import.meta.url)),Fa=Br(wA,"..","web"),gf=Br(Fa,"index.html"),RA=uA(gf);function pf(){return h().prepare(`SELECT
|
|
1904
1952
|
(SELECT COUNT(*) FROM projects) AS projects,
|
|
1905
1953
|
(SELECT COUNT(DISTINCT COALESCE(repo_root, decoded_path)) FROM projects) AS repos,
|
|
1906
1954
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
1907
1955
|
(SELECT COUNT(*) FROM messages) AS messages,
|
|
1908
1956
|
(SELECT MIN(started_at) FROM sessions WHERE started_at IS NOT NULL) AS earliest,
|
|
1909
|
-
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var
|
|
1910
|
-
WHERE session_id = ? AND tool_names IS NOT NULL AND tool_names != ''`).all(l),_=g.length,E=new Set;for(let
|
|
1957
|
+
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var kA=/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i,AA=/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i;async function Fe(e,t){if((await it()).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 $r=new Map,ff=0,mf=0,_f=null,xA=6e4;function NA(){ff+=1;let e=Date.now();e-mf<xA||(mf=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 OA(){_f=new Date().toISOString()}var LA=6e4,CA=5,Ye=[];function vA(e){let t=Date.now(),n=Ye[Ye.length-1];if(!n||t-n.ts>=LA)for(Ye.push({ts:t,remainingChunks:e});Ye.length>CA;)Ye.shift();if(Ye.length<2)return{deltaPerSec:0};let s=Ye[0],r=Ye[Ye.length-1],o=Math.max(1,(r.ts-s.ts)/1e3),a=r.remainingChunks-s.remainingChunks;return{deltaPerSec:Math.max(0,a/o)}}function IA(){let e=Pl();return{silentTerminalRejections:ff,lastTerminalSyncAt:_f,autoExtract:{circuitBroken:e.broken,brokenAt:e.brokenAt,reason:e.reason,consecutiveZeroTokenRuns:e.consecutiveZeroTokenRuns},watcherReindexHotFiles:er(10)}}function Ur(e,t){if(t)return"";let n=e;return` AND COALESCE(${n}.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(${n}.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(${n}.auto_title, '') NOT LIKE '[skill]%' AND COALESCE(${n}.auto_title, '') NOT LIKE 'You are summarizing a Claude Code session%' AND COALESCE(${n}.auto_title, '') NOT LIKE 'You are extracting a structured Output Index%' AND COALESCE(${n}.title_quality, '') != 'programmatic'`}function MA(e){let t=new cA;if(t.use("*",yA({maxSize:1*1024*1024})),t.use("*",async(i,l)=>{let m=i.req.raw.headers.get("host")??"";if(!kA.test(m))return i.text("Forbidden: invalid Host header",403);let g=i.req.raw.headers.get("origin");if(g&&!AA.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,m)=>{if(l.req.method==="GET"&&l.req.path==="/api/health")return m();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{_=gA(Buffer.from(g,"utf8"),i)}catch{_=!1}return _?m():(l.req.path.startsWith("/api/terminal/")&&NA(),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:df,uptimeSeconds:Math.round(process.uptime()),pipeline:IA()})),t.get("/api/stats",i=>i.json(pf())),t.get("/api/stats/session/:id",i=>{let l=Hg(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=Bg(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"),m=l==="7d"?"7d":l==="30d"?"30d":"all";return i.json(Wg(m))}),t.post("/api/stats/backfill",async i=>{let l=await i.req.json().catch(()=>({})),m=l.limit?Math.max(1,Math.min(1e5,Number(l.limit))):5e3;if(m>5e3){let _=Fg({limit:m});return i.json({mode:"background",started:_,alreadyRunning:!_&&$g(),limit:m,lastRun:Sa()})}let g=Pg({limit:m});return i.json({mode:"sync",started:!1,alreadyRunning:!1,limit:m,result:g,lastRun:Sa()})}),t.get("/api/stats/health",i=>i.json(qg())),t.get("/api/stats/health/:projectId",i=>{let l=Number(i.req.param("projectId")),m=wa(l);return m?i.json(m):i.json({error:"project not found"},404)}),t.get("/api/config/verification",i=>i.json({enabled:Oa()})),t.put("/api/config/verification",async i=>{let l=await i.req.json();return typeof l.enabled=="boolean"&&cf(l.enabled),i.json({enabled:Oa()})}),t.get("/api/sessions/:id/verification",i=>{let l=i.req.param("id"),m=nf(l);return i.json(m)}),t.get("/api/sessions/:id/share-stats",i=>{let l=i.req.param("id"),g=h().prepare(`SELECT tool_names, raw_json FROM messages
|
|
1958
|
+
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 O of k){let N=O.match(/":\s*"([^"]+)"/);N&&E.add(N[1])}}return i.json({filesReferenced:E.size,toolCallCount:_})}),t.get("/api/sessions/:id/commits",async i=>{let l=i.req.param("id"),m=Yo(l);if(m.length>0||i.req.query("refresh")!=="1")return i.json({commits:m});let g=await Go(l);return i.json({commits:Yo(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:js(l)}):i.json({error:"invalid sha format"},400)}),t.get("/api/license/status",async i=>{let l=await it();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 m=process.env.RECALL_FEEDBACK_API??"https://clauderecall.com/api/feedback",g=m==="https://clauderecall.com/api/feedback",_=await it(),E=an(),y=g&&_.tier==="pro"&&E?E.license_jwt:null,k=(()=>{try{let x=Br($a(Ua(import.meta.url)),"..","..","package.json");return JSON.parse(ja(x,"utf8")).version}catch{return"unknown"}})(),O=l,N={score:O.score,comment:O.comment??null,surface:"web",version:typeof O.version=="string"?O.version:k,os:typeof O.os=="string"?O.os:process.platform,trigger_kind:typeof O.trigger_kind=="string"?O.trigger_kind:"manual",license_jwt:y};try{let x=await fetch(m,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(N)}),w=await x.json().catch(()=>({}));return i.json(w,x.status)}catch(x){let w=x instanceof Error?x.message:"network error";return i.json({error:"upstream_unreachable",detail:w},502)}}),t.get("/api/discover/today",Fe,async i=>{try{return i.json(await Jg())}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:Vi(),orphan_projects:fm()})),t.get("/api/bug-synthesis",i=>{let l=i.req.query("scope"),m=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=wm({scope:_,target_id:m??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 m=Rm(l);return i.json({counts:Array.from(m.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 m=Qi(l);return m?i.json({result:m}):i.json({error:"not found"},404)}),t.delete("/api/bug-synthesis/:id",i=>{let l=Number(i.req.param("id"));return Number.isFinite(l)?(km(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let n=P.object({name:P.string().min(1).max(100),description:P.string().max(500).nullable().optional()});t.post("/api/macro-repos",async i=>{let l=await i.req.json().catch(()=>null),m=n.safeParse(l);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);try{let g=_m({name:m.data.name,description:m.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 "${m.data.name}" already exists`},409):i.json({error:_},400)}});let s=P.object({name:P.string().min(1).max(100).optional(),description:P.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 m=await i.req.json().catch(()=>null),g=s.safeParse(m);if(!g.success)return i.json({error:"invalid request body"},400);try{let _=hm(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)?(Em(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let r=P.object({project_id:P.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 m=await i.req.json().catch(()=>null),g=r.safeParse(m);if(!g.success)return i.json({error:"invalid request body"},400);try{return bm(l,g.data.project_id),i.json({macro_repo:Jt(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")),m=Number(i.req.param("projectId"));return!Number.isFinite(l)||!Number.isFinite(m)?i.json({error:"invalid id"},400):(Sm(l,m),i.json({macro_repo:Jt(l)}))}),t.get("/api/projects",i=>{let l=h(),m=i.req.query("system")==="1"||i.req.query("system")==="true",g=Ur("s",m),_=l.prepare(`SELECT p.id, p.name, p.decoded_path,
|
|
1911
1959
|
COUNT(CASE WHEN s.id IS NOT NULL${g} THEN 1 END) AS session_count,
|
|
1912
1960
|
COALESCE(SUM(CASE WHEN s.id IS NOT NULL${g} THEN s.message_count ELSE 0 END), 0) AS message_count,
|
|
1913
1961
|
MAX(COALESCE(s.ended_at, s.started_at)) AS latest
|
|
1914
1962
|
FROM projects p
|
|
1915
1963
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1916
1964
|
GROUP BY p.id
|
|
1917
|
-
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all(),E=i.req.query("groupBy"),
|
|
1965
|
+
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all(),E=i.req.query("groupBy"),y=E==="repo"||E==="repo-flat"?E:"folder";if(y==="folder")return i.json(_);let k=y==="repo-flat"?"COALESCE(p.main_repo, p.repo_root, p.decoded_path)":"COALESCE(p.repo_root, p.decoded_path)",O=l.prepare(`SELECT ${k} AS repo_root,
|
|
1918
1966
|
COUNT(DISTINCT p.id) AS folder_count,
|
|
1919
1967
|
COUNT(CASE WHEN s.id IS NOT NULL${g} THEN 1 END) AS session_count_total,
|
|
1920
1968
|
COALESCE(SUM(CASE WHEN s.id IS NOT NULL${g} THEN s.message_count ELSE 0 END), 0) AS message_count_total,
|
|
1921
1969
|
MAX(COALESCE(s.ended_at, s.started_at)) AS latest,
|
|
1922
1970
|
MAX(p.is_repo) AS is_repo,
|
|
1923
|
-
${
|
|
1924
|
-
${
|
|
1971
|
+
${y==="repo-flat"?"0":"MAX(p.is_worktree)"} AS is_worktree,
|
|
1972
|
+
${y==="repo-flat"?"NULL":"MAX(p.main_repo)"} AS main_repo
|
|
1925
1973
|
FROM projects p
|
|
1926
1974
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1927
|
-
GROUP BY ${
|
|
1928
|
-
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all(),
|
|
1975
|
+
GROUP BY ${k}
|
|
1976
|
+
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all(),N=l.prepare(`SELECT p.id, p.name, p.decoded_path,
|
|
1929
1977
|
COUNT(CASE WHEN s.id IS NOT NULL${g} THEN 1 END) AS session_count,
|
|
1930
1978
|
COALESCE(SUM(CASE WHEN s.id IS NOT NULL${g} THEN s.message_count ELSE 0 END), 0) AS message_count,
|
|
1931
1979
|
MAX(COALESCE(s.ended_at, s.started_at)) AS latest
|
|
1932
1980
|
FROM projects p
|
|
1933
1981
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1934
|
-
WHERE ${
|
|
1982
|
+
WHERE ${k} = ?
|
|
1935
1983
|
GROUP BY p.id
|
|
1936
|
-
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`),
|
|
1984
|
+
ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`),x=O.map(w=>({repo_root:w.repo_root,name:w.repo_root?w.repo_root.split("/").filter(Boolean).pop()??w.repo_root:"(unknown)",is_repo:w.is_repo===1,is_worktree:w.is_worktree===1,main_repo:w.main_repo,session_count_total:w.session_count_total,message_count_total:w.message_count_total,latest:w.latest,folders:N.all(w.repo_root)}));return i.json({groupBy:y,groups:x})}),t.get("/api/graph/:project",i=>{let l=h(),m=i.req.param("project"),g=l.prepare(`SELECT id, name, decoded_path FROM projects
|
|
1937
1985
|
WHERE name = ? LIMIT 1`).get(m);if(!g)return i.json({error:`project "${m}" not found`},404);let _=i.req.query("system")==="1"||i.req.query("system")==="true",E=l.prepare(`SELECT s.id,
|
|
1938
1986
|
s.auto_title,
|
|
1939
1987
|
s.auto_title_source,
|
|
@@ -1944,12 +1992,12 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1944
1992
|
sa.alias AS alias
|
|
1945
1993
|
FROM sessions s
|
|
1946
1994
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1947
|
-
WHERE s.project_id = ?${
|
|
1948
|
-
ORDER BY s.started_at`).all(g.id),
|
|
1995
|
+
WHERE s.project_id = ?${Ur("s",_)}
|
|
1996
|
+
ORDER BY s.started_at`).all(g.id),y=E.map(N=>N.id),k=[];if(y.length>0){let N=y.map(()=>"?").join(",");k=l.prepare(`SELECT thread_id, session_id, parent_session_id, role
|
|
1949
1997
|
FROM thread_edges
|
|
1950
|
-
WHERE session_id IN (${
|
|
1998
|
+
WHERE session_id IN (${N})
|
|
1951
1999
|
AND (parent_session_id IS NULL
|
|
1952
|
-
OR parent_session_id IN (${
|
|
2000
|
+
OR parent_session_id IN (${N}))`).all(...y,...y)}let O=E.map(N=>{let x=N.alias??N.auto_title??N.first_user_message??N.id.slice(0,8),w=null,L=null;if(N.auto_title?.startsWith("/")){let C=N.auto_title.split(" \xB7 ");w=C[0],L=C.length>1?C.slice(1).join(" \xB7 "):null}return{id:N.id.slice(0,8),full_id:N.id,title:x,alias:N.alias,auto_title:N.auto_title,auto_title_source:N.auto_title_source,title_quality:N.title_quality,started_at:N.started_at,msgs:N.message_count,skill:w,brand:L}});return i.json({project:g,sessions:O,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,m=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",O=E?Number(E):void 0;if(O!==void 0&&(!Number.isFinite(O)||O<1))return i.json({error:"invalid limit"},400);try{let N=ps({sourceSessionId:l,targetSessionId:m,linkType:y,approvedOnly:k,limit:O});return i.json({links:N})}catch(N){return i.json({error:N.message},400)}}),t.get("/api/links/suggestions",i=>{let l=i.req.query("status"),m=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 O=E?Number(E):void 0;if(O!==void 0&&(!Number.isFinite(O)||O<1))return i.json({error:"invalid limit"},400);try{let N=yt({status:y,sourceSessionId:m,targetSessionId:g,inferredBy:k,limit:O}),x=new Set;for(let C of N)x.add(C.source_session_id),x.add(C.target_session_id);let w=new Map;if(x.size>0){let C=Array.from(x),$=C.map(()=>"?").join(","),V=h().prepare(`SELECT s.id,
|
|
1953
2001
|
NULLIF(sa.alias, '') AS alias,
|
|
1954
2002
|
s.auto_title,
|
|
1955
2003
|
s.first_user_message,
|
|
@@ -1957,7 +2005,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1957
2005
|
FROM sessions s
|
|
1958
2006
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1959
2007
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1960
|
-
WHERE s.id IN (${
|
|
2008
|
+
WHERE s.id IN (${$})`).all(...C);for(let G of V){let B=G.first_user_message?G.first_user_message.slice(0,80):null,q=G.alias??G.auto_title??B??G.id.slice(0,8);w.set(G.id,{title:q,project:G.project})}}let L=N.map(C=>{let $=w.get(C.source_session_id),X=w.get(C.target_session_id);return{...C,source_title:$?.title??C.source_session_id.slice(0,8),source_project:$?.project??null,target_title:X?.title??C.target_session_id.slice(0,8),target_project:X?.project??null}});return i.json({suggestions:L})}catch(N){return i.json({error:N.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 m=lt(l);return m?i.json(m):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 m=i.req.query("budget"),g=m!==void 0?Number(m):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 O,N=i.req.query("edge_types");if(N){let $=N.split(",").map(X=>X.trim()).filter(Boolean);for(let X of $)if(!o.has(X))return i.json({error:`invalid edge_type: ${X}`},400);O=$}let x=i.req.query("include_wiki_links"),w=x===void 0?!0:!(x==="0"||x==="false"),L=i.req.query("include_suggestions"),C=L==="1"||L==="true";try{let $=br(l,{budget:g,scoring:E,maxDepth:k,edgeTypes:O,includeWikiLinks:w,includeSuggestions:C});return i.json($)}catch($){let X=$ instanceof Error?$.message:"unknown error",V=/not found/.test(X)?404:500;return i.json({error:X},V)}}),t.get("/api/bug-patterns",i=>{let l=i.req.query("min_count"),m=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(m==="open")k=!1;else if(m==="resolved")k=!0;else if(m&&m!=="all")return i.json({error:`invalid status: ${m}; valid: open, resolved, all`},400);let O=_?Number(_):void 0;if(O!==void 0&&(!Number.isFinite(O)||O<1))return i.json({error:"invalid limit"},400);let N=E?Number(E):void 0;if(N!==void 0&&(!Number.isFinite(N)||N<0))return i.json({error:"invalid offset"},400);try{let x=hc({minOccurrenceCount:y,hasResolved:k,project:g,limit:O,offset:N});return i.json(x)}catch(x){return i.json({error:x.message},400)}}),t.get("/api/bug-patterns/setup-status",i=>{let l=h(),g=l.prepare(`SELECT p.name AS project,
|
|
1961
2009
|
COUNT(s.id) AS total_sessions,
|
|
1962
2010
|
SUM(CASE WHEN oi.session_id IS NOT NULL THEN 1 ELSE 0 END) AS extracted_sessions,
|
|
1963
2011
|
MAX(oi.extracted_at) AS last_extracted_at
|
|
@@ -1965,20 +2013,20 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1965
2013
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
1966
2014
|
LEFT JOIN session_output_index oi ON oi.session_id = s.id
|
|
1967
2015
|
GROUP BY p.id
|
|
1968
|
-
ORDER BY total_sessions DESC`).all().map(
|
|
2016
|
+
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=P.object({project:P.string().min(1),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),limit:P.number().int().positive().optional(),force:P.boolean().optional()});t.post("/api/extract-outputs/preflight",async i=>{let l=je(i);if(l)return l;let m=ji();if(m>0&&m<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(m/1024**3).toFixed(2)} GB free \u2014 extract-outputs needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:m},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??mt,limit:_.data.limit??200,force:_.data.force??!1},y=ra();if(E.limit>y.sessionCeiling)return oe({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=$n(E.project);if(!k)return i.json({error:`project "${E.project}" not found`},404);let N=Ot({projectId:k.id,limit:E.limit,force:E.force}).eligible.length,x=oa(N),w=tt(N,E.model),L=Gt(),C=x.estimated_input_tokens_max+x.estimated_output_tokens_max>L.remaining_tokens_24h;if(oe({kind:"preflight",job_id:null,project:E.project,model:E.model,limit:E.limit,origin:i.req.header("origin")??null,sessions_eligible:N}),N===0)return i.json({eligible_session_count:0,...x,plan_window_estimate:w,budget:L,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:$,expiresAt:X}=ea(E);return i.json({preflight_token:$,expires_at:new Date(X).toISOString(),eligible_session_count:N,...x,plan_window_estimate:w,budget:L,would_exceed_budget:C})});let p=P.object({preflight_token:P.string().length(64)});t.post("/api/extract-outputs/run",async i=>{let l=je(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=p.safeParse(m);if(!g.success)return i.json({error:"invalid request body"},400);let _=ta(g.data.preflight_token);if(!_)return oe({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=Gt(),k=(()=>{let w=$n(_.project);return w?Ot({projectId:w.id,limit:_.limit,force:_.force}):null})()?.eligible.length??0,O=oa(k),N=O.estimated_input_tokens_max+O.estimated_output_tokens_max;if(N>E.remaining_tokens_24h)return oe({kind:"run-rejected",job_id:null,project:_.project,model:_.model,limit:_.limit,origin:i.req.header("origin")??null,reason:`projected spend ${N} 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:N},429);let x=Dm({project:_.project,model:_.model,limit:_.limit,force:_.force,origin:i.req.header("origin")??null});return"error"in x?i.json({error:x.error},400):i.json({jobId:x.jobId,reused:x.reused},x.reused?409:200)}),t.get("/api/extract-outputs/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!ca(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return rt(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of jm(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=ca(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=je(i);return l||(Pm(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(Gt()));let f=P.object({scope:P.enum(["cluster","project"]),target_id:P.string().min(1),mode:P.enum(["synopsis","priorities","root_cause"]),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()});t.post("/api/bug-patterns/synthesize/preflight",async i=>{{let X=ji();if(X>0&&X<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(X/1024**3).toFixed(2)} GB free \u2014 bug synthesis needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:X},507)}let l=je(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=f.safeParse(m);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??Fm},E=Rr(_);if(!E)return oe({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=ia({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,O=Mm(k),N=tt(O,_.model),x=Gt(),L=y.estimated_input_tokens_max+y.estimated_output_tokens_max>x.remaining_tokens_24h;oe({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:C,expiresAt:$}=ea({project:_.target_id,model:_.model,limit:1,force:!1});return $r.set(C,_),setTimeout(()=>$r.delete(C),9e4).unref?.(),i.json({preflight_token:C,expires_at:new Date($).toISOString(),estimated_input_tokens_max:y.estimated_input_tokens_max,estimated_output_tokens_max:y.estimated_output_tokens_max,plan_window_estimate:N,budget:x,would_exceed_budget:L,context_summary:E.context_summary})});let b=P.object({preflight_token:P.string().length(64)});t.post("/api/bug-patterns/synthesize/run",async i=>{let l=je(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=b.safeParse(m);if(!g.success)return i.json({error:"invalid request body"},400);let _=ta(g.data.preflight_token),E=$r.get(g.data.preflight_token)??null;if($r.delete(g.data.preflight_token),!E||!_)return oe({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=Gt(),k=Rr(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 O=ia({scope:E.scope,mode:E.mode,member_session_count:k.context_summary.session_count,cluster_count:k.context_summary.cluster_count}),N=O.estimated_input_tokens_max+O.estimated_output_tokens_max;if(N>y.remaining_tokens_24h)return oe({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 ${N} 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:N},429);let x=Bm({intent:E,origin:i.req.header("origin")??null});return"error"in x?i.json({error:x.error},400):i.json({jobId:x.jobId,reused:x.reused},x.reused?409:200)}),t.get("/api/bug-patterns/synthesize/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!la(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return rt(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of Wm(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=la(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=je(i);return l||(qm(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,m=Math.min(Math.max(1,Number(i.req.query("limit")??100)),500),g=h(),_=["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,
|
|
1969
2017
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1970
2018
|
FROM session_output_index oi
|
|
1971
2019
|
JOIN sessions s ON s.id = oi.session_id
|
|
1972
2020
|
JOIN projects p ON p.id = s.project_id
|
|
1973
2021
|
WHERE ${_.join(" AND ")}
|
|
1974
2022
|
ORDER BY oi.extracted_at DESC
|
|
1975
|
-
LIMIT ?`).all(...E,m).map(
|
|
2023
|
+
LIMIT ?`).all(...E,m).map(L=>{let C=[];try{let $=JSON.parse(L.bug_signatures);Array.isArray($)&&(C=$)}catch{C=[]}return{session_id:L.session_id,project:L.project,auto_title:L.auto_title,started_at:L.started_at,extracted_at:L.extracted_at,rawSignatures:C}}),O=k.flatMap(L=>L.rawSignatures.map(C=>C.message_hash).filter(C=>typeof C=="string")),N=zi(O),x=k.map(L=>({session_id:L.session_id,project:L.project,auto_title:L.auto_title,started_at:L.started_at,extracted_at:L.extracted_at,signatures:L.rawSignatures.map(C=>{let $=C.message_hash?N.get(C.message_hash)??null:null;return{...C,resolved:Ki($),resolution:$}}),signature_count:L.rawSignatures.length})),w=x.reduce((L,C)=>(L.sessions_total+=1,C.signature_count>0?(L.sessions_with_findings+=1,L.total_findings+=C.signature_count):L.sessions_empty+=1,L),{sessions_total:0,sessions_with_findings:0,sessions_empty:0,total_findings:0});return i.json({sessions:x,totals:w})}),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 m=await i.req.json().catch(()=>({})),g=mm({messageHash:l,resolvedInSessionId:m.resolved_in_session_id??null,fixSummary:m.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):(gm(l),i.json({ok:!0}))}),t.get("/api/bug-patterns/graph",i=>{let l=i.req.query("project")??null,m=i.req.query("include_resolved")!=="0",g=h(),_=["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,
|
|
1976
2024
|
s.started_at, oi.extracted_at, oi.bug_signatures
|
|
1977
2025
|
FROM session_output_index oi
|
|
1978
2026
|
JOIN sessions s ON s.id = oi.session_id
|
|
1979
2027
|
JOIN projects p ON p.id = s.project_id
|
|
1980
2028
|
WHERE ${_.join(" AND ")}
|
|
1981
|
-
ORDER BY oi.extracted_at DESC`).all(...E),
|
|
2029
|
+
ORDER BY oi.extracted_at DESC`).all(...E),k=[];for(let B of y){let q=[];try{let z=JSON.parse(B.bug_signatures);Array.isArray(z)&&(q=z)}catch{continue}for(let z of q)k.push({sig:z,session_id:B.session_id,project:B.project,auto_title:B.auto_title})}let O=new Map;for(let B of k){let q=B.sig.message_hash??`nohash:${(B.sig.snippet??"").slice(0,64)}`,z=O.get(q);z?z.push(B):O.set(q,[B])}let N=Array.from(O.keys()).filter(B=>!B.startsWith("nohash:")),x=zi(N),w=[],L=new Map,C=[];for(let[B,q]of O){let z=q[0],te=z.sig.message_hash??null,Q=te?x.get(te)??null:null,Y=Ki(Q);if(!m&&Y)continue;let ie=Array.from(new Set(q.map(ge=>ge.project))).sort(),ot=Array.from(new Set(q.map(ge=>ge.session_id))),$e={id:te??B,message_hash:te,error_type:z.sig.error_type??null,snippet:(z.sig.snippet??"").slice(0,200),file:z.sig.file??null,occurrence_count:q.length,projects:ie,resolved:Y,fix_summary:Q?.fix_summary??null,member_session_ids:ot};w.push($e);for(let ge of q)L.has(ge.session_id)||L.set(ge.session_id,{session_id:ge.session_id,project:ge.project,auto_title:ge.auto_title}),C.push({cluster_id:$e.id,session_id:ge.session_id})}let $=[],X=4,V=new Map;function G(B){let q=V.get(B)??0;return q>=X?!1:(V.set(B,q+1),!0)}for(let B=0;B<w.length;B+=1)for(let q=B+1;q<w.length;q+=1){let z=w[B],te=w[q],Q=null;z.error_type&&z.error_type!=="unknown"&&z.error_type===te.error_type?Q="same_error_type":z.file&&te.file&&z.file===te.file&&(Q="same_file"),Q&&(!G(z.id)||!G(te.id)||$.push({a:z.id,b:te.id,reason:Q}))}return i.json({clusters:w,sessions:Array.from(L.values()),member_edges:C,related_edges:$,totals:{cluster_count:w.length,singleton_count:w.filter(B=>B.occurrence_count===1).length,recurring_count:w.filter(B=>B.occurrence_count>1).length,session_count:L.size,resolved_count:w.filter(B=>B.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 m=Ec(l);return m?i.json(m):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 m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);let g=m.resolved_in_session_id,_=m.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=bc(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 m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);let g=m.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=Sc(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 m=l.source_session_id,g=l.target_session_id,_=l.link_type;if(typeof m!="string"||m.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(m===g)return i.json({error:"a session cannot link to itself"},400);let E=h();if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(m))return i.json({error:`source session not found: ${m}`},404);if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(g))return i.json({error:`target session not found: ${g}`},404);let O=ps({sourceSessionId:g,targetSessionId:m,linkType:"wiki_link"});if(O.length>0)return i.json({link:O[0]});try{let N=Uc({source_session_id:m,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:N})}catch(N){return i.json({error:N.message},400)}}),t.delete("/api/links/:id",i=>{let l=i.req.param("id"),m=Number(l);if(!Number.isInteger(m)||m<=0)return i.json({error:"id must be a positive integer"},400);let g=Hc(m);return g.removed===0?i.json({error:`link ${m} 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 m=h();if(!m.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=mn(l).filter(L=>L.link_type===E),k=Wp(l,y);if(k.length===0)return i.json({links:[]});let O=k.map(L=>L.otherSessionId),N=O.map(()=>"?").join(","),x=m.prepare(`SELECT s.id,
|
|
1982
2030
|
NULLIF(sa.alias, '') AS alias,
|
|
1983
2031
|
s.auto_title,
|
|
1984
2032
|
s.first_user_message,
|
|
@@ -1986,7 +2034,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1986
2034
|
FROM sessions s
|
|
1987
2035
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1988
2036
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1989
|
-
WHERE s.id IN (${
|
|
2037
|
+
WHERE s.id IN (${N})`).all(...O),w=new Map(x.map(L=>[L.id,L]));return i.json({links:k.map(L=>{let C=w.get(L.otherSessionId),$=C?.alias?.trim()||C?.auto_title?.trim()||(C?.first_user_message?C.first_user_message.slice(0,80):"")||L.otherSessionId.slice(0,8);return{linkId:L.linkId,otherSessionId:L.otherSessionId,direction:L.direction,updatedAt:L.updatedAt,title:$,project:C?.project??null}})})}),t.patch("/api/links/suggestions/:id",async i=>{let l=i.req.param("id"),m=Number(l);if(!Number.isInteger(m)||m<=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 _=ao(m,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 m=Array.isArray(l.ids)?l.ids:null;if(!m||m.length===0)return i.json({error:"ids must be a non-empty array"},400);if(m.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 m){let O=Number(k);if(!Number.isInteger(O)||O<=0){y.push({id:Number.isFinite(Number(k))?Number(k):-1,error:"invalid id"});continue}try{ao(O,g),_+=1}catch(N){let x=N.message;/already decided/.test(x)?E+=1:y.push({id:O,error:x})}}return i.json({decided:_,skipped:E,errors:y})}),t.get("/api/sessions",i=>{let l=h(),m=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))),O=i.req.query("cursor"),N=null;if(O)try{let V=Buffer.from(O,"base64url").toString("utf8"),G=JSON.parse(V);typeof G.ts=="string"&&typeof G.id=="string"&&(N={ts:G.ts,id:G.id})}catch{}let x=i.req.query("system")==="1"||i.req.query("system")==="true",w={limit:k},L="s.message_count > 2"+Ur("s",x);if(m&&(L+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",w.proj=`%${Vn(m)}%`),g&&(L+=" AND s.started_at >= @since",w.since=g),_&&(L+=" AND s.started_at <= @until",/^\d{4}-\d{2}-\d{2}$/.test(_)?w.until=`${_}T23:59:59.999Z`:w.until=_),E.length>0&&E.map(G=>ut(G)).filter(Boolean).forEach((G,B)=>{L+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${B})`,w[`tag_${B}`]=G}),y){let V=ni(y);if(V.length===0)return i.json({items:[],nextCursor:null});let G=V.map((B,q)=>`@col_${q}`).join(",");L+=` AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id IN (${G}))`,V.forEach((B,q)=>{w[`col_${q}`]=B})}N&&(L+=" 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))",w.cursor_ts=N.ts,w.cursor_id=N.id);let C=l.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1990
2038
|
s.message_count, s.first_user_message, s.git_branch,
|
|
1991
2039
|
s.auto_title, s.auto_title_source, s.verification_status,
|
|
1992
2040
|
COALESCE(s.ended_at, s.started_at, '') AS _cursor_ts,
|
|
@@ -2005,17 +2053,17 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2005
2053
|
JOIN projects p ON p.id = s.project_id
|
|
2006
2054
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2007
2055
|
LEFT JOIN session_notes sn ON sn.session_id = s.id
|
|
2008
|
-
WHERE ${
|
|
2056
|
+
WHERE ${L}
|
|
2009
2057
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC, s.id DESC
|
|
2010
|
-
LIMIT @limit`).all(C
|
|
2058
|
+
LIMIT @limit`).all(w),$=C.map(({tags_csv:V,_cursor_ts:G,...B})=>{let q=B.id,z=D.getOrigin(q),te=B.alias,Q=te==null?null:D.isSessionAutoLinked(q)?"auto":"manual",Y=wn({auto_title:B.auto_title,auto_title_source:B.auto_title_source??null,has_alias:te!=null&&Q==="manual"});return{...B,tags:V?V.split(","):[],origin:z?{editor:z.editor,label:z.label}:null,alias_source:Q,title_quality:Y}}),X=null;if(C.length===k&&C.length>0){let V=C[C.length-1],G=JSON.stringify({ts:V._cursor_ts??"",id:V.id});X=Buffer.from(G,"utf8").toString("base64url")}return i.json({items:$,nextCursor:X})}),t.get("/api/sessions/:id",i=>{let l=h(),m=i.req.param("id"),g=l.prepare(`SELECT s.*, p.name AS project_name, p.decoded_path,
|
|
2011
2059
|
NULLIF(sa.alias, '') AS alias
|
|
2012
2060
|
FROM sessions s
|
|
2013
2061
|
JOIN projects p ON p.id = s.project_id
|
|
2014
2062
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2015
|
-
WHERE s.id = ?`).get(m);if(!g)return i.json({error:"not found"},404);let _=
|
|
2063
|
+
WHERE s.id = ?`).get(m);if(!g)return i.json({error:"not found"},404);let _=_n(m),E=D.getOrigin(m),y=E?{editor:E.editor,label:E.label}:null,k=g.alias==null?null:D.isSessionAutoLinked(m)?"auto":"manual",O=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
2016
2064
|
FROM messages
|
|
2017
2065
|
WHERE session_id = ?
|
|
2018
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(m);return i.json({session:{...g,tags:_,origin:
|
|
2066
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(m);return i.json({session:{...g,tags:_,origin:y,alias_source:k},messages:O})}),t.get("/api/tags",i=>i.json(Rt())),t.get("/api/sessions/:id/tags",i=>i.json({tags:_n(i.req.param("id"))})),t.post("/api/sessions/:id/tags",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.tag!="string")return i.json({error:"tag required"},400);try{let g=wt(l,m.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"),m=i.req.param("tag");return i.json(Kc(l,m))}),t.get("/api/config/auto-tag",i=>i.json(Gi(qe()))),t.put("/api/config/auto-tag",async i=>{let l=await i.req.json().catch(()=>({})),m=Tr.partial().safeParse(l);if(!m.success)return i.json({error:"invalid config",issues:m.error.issues},400);let g=m.data;g.apiKey===void 0&&delete g.apiKey;let _=lm(g);return _.autopilot&&_.enabled&&_.backend==="api"&&_.apiKey&&vr(),i.json(Gi(_))}),t.get("/api/onboarding",i=>{let m=h().prepare(`SELECT s.id,
|
|
2019
2067
|
p.name AS project,
|
|
2020
2068
|
s.started_at,
|
|
2021
2069
|
s.ended_at,
|
|
@@ -2027,7 +2075,7 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2027
2075
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2028
2076
|
WHERE s.message_count > 2
|
|
2029
2077
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
2030
|
-
LIMIT 1`).get();return i.json({state:Or(),mostRecentSession:m??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),m=Nr.partial().safeParse(l);return m.success?i.json(sg(m.data)):i.json({error:"invalid onboarding state",issues:m.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(rg())),t.get("/api/config/mcp-install",i=>i.json({...qe(),claudeCliAvailable:me()})),t.post("/api/config/mcp-install",i=>i.json({...oa(),claudeCliAvailable:me()})),t.delete("/api/config/mcp-install",i=>i.json({...Vm(),claudeCliAvailable:me()}));let y=$.object({scope:$.object({untaggedOnly:$.boolean().optional(),project:$.string().optional(),collectionId:$.string().optional(),sessionIds:$.array($.string()).optional(),limit:$.number().int().min(1).max(500).optional()}).default({}),model:$.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:$.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(ya)return i.json({error:"a scan is already running"},409);if(!me())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 m=await i.req.json().catch(()=>({})),g=y.safeParse(m);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=g.data.scope,E=Be(),T=g.data.model??E.model,w=h(),x=()=>w.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,A=x();ya=!0;let N;try{let C=g.data.scanId;N=await lo(_,{model:T,scanId:C});let I=x(),O=Math.max(0,I-A);return C&&ls(C,{type:"done",result:{success:N.success,exitCode:N.exitCode,tagsAdded:O}}),i.json({success:N.success,exitCode:N.exitCode,tagsAdded:O,model:T,stdout:Se(N.stdout.slice(0,2e3)).redacted,stderrTail:Se(N.stderr.slice(-2e3)).redacted})}finally{ya=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return tt(i,async m=>{let g=[],_={resolve:()=>{}},E=new Promise(A=>{_.resolve=A}),T=Dc(l,A=>{g.push(A);let N=_.resolve;E=new Promise(C=>{_.resolve=C}),N()}),w=!1,x=setInterval(()=>{w||m.writeSSE({event:"heartbeat",data:""}).catch(()=>{w=!0})},15e3);try{for(;!w;){g.length===0&&await E;let A=g.shift();if(A&&(await m.writeSSE({event:A.type,data:JSON.stringify(A)}),A.type==="done"))break}}finally{w=!0,clearInterval(x),T()}})}),t.get("/api/prompts",i=>i.json({prompts:io.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:me()})),t.post("/api/prompts/run",async i=>{if(!me())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 m=await i.req.json().catch(()=>({})),_=$.object({name:$.string(),args:$.record($.string(),$.unknown()).optional(),model:$.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).safeParse(m);if(!_.success)return i.json({error:"invalid request",issues:_.error.issues},400);let E=jc(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let T=E.build(_.data.args??{}),w=Be(),x=_.data.model??w.model,A=await at(T,E.allowedTools,{model:x});return i.json({success:A.success,exitCode:A.exitCode,promptName:E.name,model:x,stdout:A.stdout,stderrTail:A.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(Xn())),t.get("/api/autopilot/events",i=>tt(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(Xn())});let m=[],g=()=>{},_=new Promise(T=>g=T),E=Jm(T=>{m.push(T);let w=g;_=new Promise(x=>g=x),w()});try{for(;;){if(m.length===0){let w=new Promise(A=>setTimeout(()=>A("tick"),3e4));if(await Promise.race([_.then(()=>"event"),w])==="tick"){await l.writeSSE({event:"heartbeat",data:"1"});continue}}let T=m.shift();T&&await l.writeSSE({event:"state",data:JSON.stringify(T)})}}finally{E()}})),t.post("/api/autopilot/kick",i=>(Ar(),i.json({ok:!0,snapshot:Xn()})));let b=$.object({scope:$.object({untaggedOnly:$.boolean().optional(),project:$.string().optional(),collectionId:$.string().optional(),sessionIds:$.array($.string()).optional(),limit:$.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=Be();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 m=await i.req.json().catch(()=>({})),g=b.safeParse(m);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=Rt(g.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=$m(_.length);return qm(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=yr(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let{controller:m,listeners:g,..._}=l;return i.json(_)}),t.get("/api/tags/scan/:id/events",i=>{let l=yr(i.req.param("id"));return l?tt(i,async m=>{await m.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let w of l.results)await m.writeSSE({event:"result",data:JSON.stringify(w)});let g=[],_={resolve:()=>{}},E=new Promise(w=>{_.resolve=w}),T=Um(l,w=>{g.push(w);let x=_.resolve;E=new Promise(A=>{_.resolve=A}),x()});try{for(;l.status==="running"||l.status==="pending";){g.length===0&&await E;let w=g.shift();if(w&&(await m.writeSSE({event:w.type,data:JSON.stringify(w)}),w.type==="done"||w.type==="status"&&(w.status==="cancelled"||w.status==="failed")))break}}finally{T()}}):i.json({error:"scan not found"},404)});let R=$.object({selection:$.array($.object({sessionId:$.string(),tags:$.array($.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=yr(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let m=await i.req.json().catch(()=>({})),g=R.safeParse(m);if(!g.success)return i.json({error:"invalid selection"},400);let _=Xm(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return Hm(l),Bm(l),i.json({ok:!0})}),t.put("/api/sessions/:id/alias",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.alias!="string")return i.json({error:"alias required"},400);try{let g=he(l,m.alias);if(m.pin===!0)j.unlinkSession(l);else{let _=h().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l),E=_?.cwd?_.cwd.replace(/\/+$/,""):null,T=!1;if(E&&_?.started_at){let w=Date.parse(_.started_at),x=_.started_at,A=j.all().filter(N=>N.cwd&&N.cwd.replace(/\/+$/,"")===E&&Cn({sessionStartedAt:x,terminalOpenedAt:N.opened_at??null}).allowed);if(Number.isFinite(w)&&A.length>0){let C=A.map(I=>({t:I,gap:w-Date.parse(I.opened_at??"")})).filter(I=>Number.isFinite(I.gap)).sort((I,O)=>I.gap-O.gap)[0];C&&(j.linkSession(l,C.t.shell_pid),T=!0)}}T||j.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 ks(l),j.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:ye(l)})}),t.get("/api/config/auto-title",i=>i.json(dt())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),m=Ds.partial().safeParse(l);return m.success?i.json(ru(m.data)):i.json({error:"invalid config",issues:m.error.issues},400)}),t.get("/api/sessions/:id/auto-title",i=>{let l=i.req.param("id"),m=ve(l);return m?i.json(m):i.json({error:"session not found"},404)}),t.post("/api/sessions/:id/auto-title",async i=>{let l=i.req.param("id");if(!dt().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);if(!h().prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:"session not found"},404);try{let E=await Yl(l);return Ee(l,E,"agent"),i.json(ve(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"),m=ve(l);if(!m)return i.json({error:"session not found"},404);let g=m.auto_title_history;if(!g||g.length===0)return i.json({error:"no prior title to revert to",code:"no-history"},422);let _=g[g.length-1];return Ee(l,_.title,"agent"),i.json(ve(l))}),t.post("/api/sessions/:id/regenerate-title",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({})),g=m.model??_r;try{let _=await Di(l,{model:g,force:m.force===!0,budget:typeof m.budget=="number"?m.budget:void 0,signal:i.req.raw.signal}),E=ve(l),T=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:T})}catch(_){if(_ instanceof Bt)return i.json({error:_.message,code:"no-context-available",session_id:_.sessionId},422);let E=_ instanceof Error?_.message:"unknown error",T=/not found|unknown/i.test(E)?404:500;return i.json({error:E,code:"regenerate-failed"},T)}}),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 m=l.project;if(typeof m!="string"||m.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 T=typeof l.model=="string"&&l.model.length>0?l.model:_r,w=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,x=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,N=h().prepare(`SELECT s.id,
|
|
2078
|
+
LIMIT 1`).get();return i.json({state:Dr(),mostRecentSession:m??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),m=Mr.partial().safeParse(l);return m.success?i.json(vg(m.data)):i.json({error:"invalid onboarding state",issues:m.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(Ig())),t.get("/api/config/mcp-install",i=>i.json({...Je(),claudeCliAvailable:me()})),t.post("/api/config/mcp-install",i=>i.json({...ha(),claudeCliAvailable:me()})),t.delete("/api/config/mcp-install",i=>i.json({...Ag(),claudeCliAvailable:me()}));let T=P.object({scope:P.object({untaggedOnly:P.boolean().optional(),project:P.string().optional(),collectionId:P.string().optional(),sessionIds:P.array(P.string()).optional(),limit:P.number().int().min(1).max(500).optional()}).default({}),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:P.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(Ia)return i.json({error:"a scan is already running"},409);if(!me())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!Je().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let m=await i.req.json().catch(()=>({})),g=T.safeParse(m);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=g.data.scope,E=qe(),y=g.data.model??E.model,k=h(),O=()=>k.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,N=O();Ia=!0;let x;try{let w=g.data.scanId;x=await ho(_,{model:y,scanId:w});let L=O(),C=Math.max(0,L-N);return w&&fs(w,{type:"done",result:{success:x.success,exitCode:x.exitCode,tagsAdded:C}}),i.json({success:x.success,exitCode:x.exitCode,tagsAdded:C,model:y,stdout:be(x.stdout.slice(0,2e3)).redacted,stderrTail:be(x.stderr.slice(-2e3)).redacted})}finally{Ia=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return rt(i,async m=>{let g=[],_={resolve:()=>{}},E=new Promise(N=>{_.resolve=N}),y=Zc(l,N=>{g.push(N);let x=_.resolve;E=new Promise(w=>{_.resolve=w}),x()}),k=!1,O=setInterval(()=>{k||m.writeSSE({event:"heartbeat",data:""}).catch(()=>{k=!0})},15e3);try{for(;!k;){g.length===0&&await E;let N=g.shift();if(N&&(await m.writeSSE({event:N.type,data:JSON.stringify(N)}),N.type==="done"))break}}finally{k=!0,clearInterval(O),y()}})}),t.get("/api/prompts",i=>i.json({prompts:go.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:me()})),t.post("/api/prompts/run",async i=>{if(!me())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!Je().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let m=await i.req.json().catch(()=>({})),_=P.object({name:P.string(),args:P.record(P.string(),P.unknown()).optional(),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).safeParse(m);if(!_.success)return i.json({error:"invalid request",issues:_.error.issues},400);let E=Qc(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let y=E.build(_.data.args??{}),k=qe(),O=_.data.model??k.model,N=await dt(y,E.allowedTools,{model:O});return i.json({success:N.success,exitCode:N.exitCode,promptName:E.name,model:O,stdout:N.stdout,stderrTail:N.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(zn())),t.get("/api/autopilot/events",i=>rt(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(zn())});let m=[],g=()=>{},_=new Promise(y=>g=y),E=Tg(y=>{m.push(y);let k=g;_=new Promise(O=>g=O),k()});try{for(;;){if(m.length===0){let k=new Promise(N=>setTimeout(()=>N("tick"),3e4));if(await Promise.race([_.then(()=>"event"),k])==="tick"){await l.writeSSE({event:"heartbeat",data:"1"});continue}}let y=m.shift();y&&await l.writeSSE({event:"state",data:JSON.stringify(y)})}}finally{E()}})),t.post("/api/autopilot/kick",i=>(vr(),i.json({ok:!0,snapshot:zn()})));let S=P.object({scope:P.object({untaggedOnly:P.boolean().optional(),project:P.string().optional(),collectionId:P.string().optional(),sessionIds:P.array(P.string()).optional(),limit:P.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=qe();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 m=await i.req.json().catch(()=>({})),g=S.safeParse(m);if(!g.success)return i.json({error:"invalid scope",issues:g.error.issues},400);let _=At(g.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=gg(_.length);return bg(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=xr(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let{controller:m,listeners:g,..._}=l;return i.json(_)}),t.get("/api/tags/scan/:id/events",i=>{let l=xr(i.req.param("id"));return l?rt(i,async m=>{await m.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let k of l.results)await m.writeSSE({event:"result",data:JSON.stringify(k)});let g=[],_={resolve:()=>{}},E=new Promise(k=>{_.resolve=k}),y=fg(l,k=>{g.push(k);let O=_.resolve;E=new Promise(N=>{_.resolve=N}),O()});try{for(;l.status==="running"||l.status==="pending";){g.length===0&&await E;let k=g.shift();if(k&&(await m.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 R=P.object({selection:P.array(P.object({sessionId:P.string(),tags:P.array(P.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=xr(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let m=await i.req.json().catch(()=>({})),g=R.safeParse(m);if(!g.success)return i.json({error:"invalid selection"},400);let _=Sg(l,g.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return _g(l),hg(l),i.json({ok:!0})}),t.put("/api/sessions/:id/alias",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.alias!="string")return i.json({error:"alias required"},400);try{let g=_e(l,m.alias);if(m.pin===!0)D.unlinkSession(l);else{let _=h().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),O=_.started_at,N=D.all().filter(x=>x.cwd&&x.cwd.replace(/\/+$/,"")===E&&Mn({sessionStartedAt:O,terminalOpenedAt:x.opened_at??null}).allowed);if(Number.isFinite(k)&&N.length>0){let w=N.map(L=>({t:L,gap:k-Date.parse(L.opened_at??"")})).filter(L=>Number.isFinite(L.gap)).sort((L,C)=>L.gap-C.gap)[0];w&&(D.linkSession(l,w.t.shell_pid),y=!0)}}y||D.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 Cs(l),D.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:Se(l)})}),t.get("/api/config/auto-title",i=>i.json(ft())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),m=Hs.partial().safeParse(l);return m.success?i.json(Ou(m.data)):i.json({error:"invalid config",issues:m.error.issues},400)}),t.get("/api/sessions/:id/auto-title",i=>{let l=i.req.param("id"),m=ve(l);return m?i.json(m):i.json({error:"session not found"},404)}),t.post("/api/sessions/:id/auto-title",async i=>{let l=i.req.param("id");if(!ft().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);if(!h().prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:"session not found"},404);try{let E=await bu(l);return he(l,E,"agent"),i.json(ve(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"),m=ve(l);if(!m)return i.json({error:"session not found"},404);let g=m.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(ve(l))}),t.post("/api/sessions/:id/regenerate-title",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({})),g=m.model??yr;try{let _=await Yi(l,{model:g,force:m.force===!0,budget:typeof m.budget=="number"?m.budget:void 0,signal:i.req.raw.signal}),E=ve(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 Xt)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 m=l.project;if(typeof m!="string"||m.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 C of g){if(typeof C!="string")return i.json({error:`invalid quality_filter entry: ${C}`},400);if(!_.has(C))return i.json({error:`quality_filter must be a subset of ${[..._].join(",")}; got ${C}`},400);E.push(C)}let y=typeof l.model=="string"&&l.model.length>0?l.model:yr,k=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,O=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,x=h().prepare(`SELECT s.id,
|
|
2031
2079
|
s.auto_title,
|
|
2032
2080
|
s.auto_title_source,
|
|
2033
2081
|
NULLIF(sa.alias, '') AS alias
|
|
@@ -2036,16 +2084,16 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2036
2084
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2037
2085
|
WHERE p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\'
|
|
2038
2086
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
2039
|
-
LIMIT @limit`).all({proj:`%${
|
|
2087
|
+
LIMIT @limit`).all({proj:`%${Vn(m)}%`,limit:k}),w=new Set(E),L=x.filter(C=>{let $=C.alias==null?null:D.isSessionAutoLinked(C.id)?"auto":"manual",X=wn({auto_title:C.auto_title,auto_title_source:C.auto_title_source??null,has_alias:C.alias!=null&&$==="manual"});return w.has(X)});return rt(i,async C=>{let $=L.length,X=[],V=[],G=[],B=0,q=async(te,Q)=>{B+=1;try{await C.writeSSE({id:String(B),event:te,data:JSON.stringify(Q)})}catch{}};await q("start",{total:$,model:y});let z=0;for(let te of L){if(i.req.raw.signal.aborted)break;z+=1;try{let Q=await Yi(te.id,{model:y,budget:O,signal:i.req.raw.signal});Q.written?(X.push(te.id),await q("progress",{sessionId:te.id,title:Q.title,evidence:Q.evidence,confidence:Q.confidence,current:z,total:$})):(V.push({sessionId:te.id,reason:Q.skipped??"unknown"}),await q("skipped",{sessionId:te.id,reason:Q.skipped??"unknown",current:z,total:$}))}catch(Q){let Y=Q instanceof Error?Q.message:String(Q),ie=Q instanceof Xt?"no-context-available":"failed";G.push({sessionId:te.id,error:Y}),await q("error",{sessionId:te.id,error:Y,code:ie,current:z,total:$})}}await q("done",{generated:X,skipped:V,failed:G,cancelled:i.req.raw.signal.aborted})})}),t.get("/api/sessions/:id/notes",i=>{let l=i.req.param("id"),m=cr(l);return m?i.json(m):i.body(null,204)}),t.put("/api/sessions/:id/notes",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.content!="string")return i.json({error:"content required (string)"},400);try{let g=ap(l,m.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(!ft().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);try{let g=await cp(l),_=lp(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(So())),t.put("/api/semantic/config",async i=>{let l=await i.req.json().catch(()=>({})),m=bs.partial().safeParse(l);return m.success?(Ss(m.data,"api"),i.json(So())):i.json({error:"invalid semantic config",issues:m.error.issues},400)}),t.get("/api/semantic/config",i=>i.json(ae())),t.post("/api/semantic/backfill",Fe,async i=>{if(Da)return i.json({error:"a scan is already running"},409);if(!ae().enabled)return i.json({error:"semantic search is disabled"},400);let m=await i.req.json().catch(()=>({})),g=Math.max(1,Math.min(5e3,Number(m.limit??200)));return Da=!0,ws({limit:g,force:!!m.force}).catch(_=>console.error("[semantic.backfill] error:",_)).finally(()=>{Da=!1}),i.json({ok:!0,limit:g})}),t.post("/api/semantic/sessions/:id",Fe,async i=>{if(!ae().enabled)return i.json({error:"semantic search is disabled"},400);let m=i.req.param("id");if(!m)return i.json({error:"session id required"},400);let g=await ys(m);return i.json(g)}),t.get("/api/semantic/vector-status",i=>{let l=le(),m=Me(),g=Ge(),_=(i.req.query("project")??"").trim(),E=_.length>512?"":_,y=null,k=0,O=h();if(E){let q=O.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(E,E);q?y=O.prepare("SELECT COUNT(*) AS n FROM chunk_queue WHERE session_id IN (SELECT id FROM sessions WHERE project_id = ?)").get(q.id).n:y=0}let N=O.prepare(`SELECT COALESCE(SUM(s.message_count), 0) AS n
|
|
2040
2088
|
FROM chunk_queue cq
|
|
2041
|
-
JOIN sessions s ON s.id = cq.session_id`).get(),
|
|
2089
|
+
JOIN sessions s ON s.id = cq.session_id`).get(),x=Number(N.n)||0,w=fi(),L=w?.chunksPerSec??0,C=w?.samples??0,$=vA(x),V=L>0?Math.ceil(x/L):0,G=null;if(C>=5&&L>0){let q=L-$.deltaPerSec;q>0&&$.deltaPerSec>0?G=Math.ceil(x/q):($.deltaPerSec<=0,G=null)}let B="no-samples";return C>=5&&L>0&&(B=$.deltaPerSec>0?"live":"stable"),L>0&&y!==null&&(k=Math.ceil(y/L)),i.json({embedder:l,worker:m,modelInstalled:g,project:E||null,queueDepthForProject:y,etaForProject:k,remainingChunks:x,throughput:{chunksPerSec:L,samples:C,source:w?"local-measured":"no-samples-yet"},etaSeconds:V,etaSecondsDetail:{currentQueue:V,withGrowth:G,source:B},queueGrowthChunksPerSec:$.deltaPerSec})}),t.post("/api/semantic/install",Fe,async i=>{if(Ge())return i.json({ok:!0,status:"already_installed"});if(Ma)return i.json({error:"a scan is already running"},409);Ma=!0;try{return await tf(),await Be(),vn(),i.json({ok:!0,status:"installed"})}catch(l){let m=l instanceof Error?l.message:"unknown error";return i.json({ok:!1,error:m},500)}finally{Ma=!1}}),t.get("/api/semantic/reindex-preview",Fe,async i=>{let l=(i.req.query("project")??"").trim();if(!l)return i.json({error:"project name required"},400);let m=h(),g=m.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 _=m.prepare(`SELECT
|
|
2042
2090
|
COUNT(*) AS total,
|
|
2043
2091
|
SUM(CASE WHEN s.message_count >= 3 THEN 1 ELSE 0 END) AS eligible,
|
|
2044
2092
|
SUM(CASE WHEN s.message_count >= 3 AND EXISTS
|
|
2045
2093
|
(SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id) THEN 1 ELSE 0 END)
|
|
2046
2094
|
AS indexed
|
|
2047
|
-
FROM sessions s WHERE s.project_id = ?`).get(g.id),E=_.eligible??0,
|
|
2048
|
-
WHERE session_id NOT IN (SELECT id FROM sessions WHERE project_id = ?)`).get(g.id).n,
|
|
2095
|
+
FROM sessions s WHERE s.project_id = ?`).get(g.id),E=_.eligible??0,y=_.indexed??0,k=Math.max(0,E-y),O=m.prepare(`SELECT COUNT(*) AS n FROM chunk_queue
|
|
2096
|
+
WHERE session_id NOT IN (SELECT id FROM sessions WHERE project_id = ?)`).get(g.id).n,N=fi(),x=2,w=30,L=x,C=w,$=x*w,X="fallback-baseline",V=0;if(N)L=N.sessionsPerSec,C=N.avgChunksPerSession,$=N.chunksPerSec,X="local-measured",V=N.samples;else if(Ge()){if(!le().loaded)try{await Be()}catch{}try{let z=["[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"],te=Date.now();await Ve(z);let Q=Date.now()-te;Q>0&&($=z.length*1e3/Q,L=$/w,X="live-benchmark")}catch{}}let G=z=>m.prepare(`SELECT
|
|
2049
2097
|
COALESCE(SUM(CASE WHEN ec > 5 THEN 5 ELSE ec END), 0) AS total_quick,
|
|
2050
2098
|
COALESCE(SUM(CASE WHEN ec > 80 THEN 80 ELSE ec END), 0) AS total_standard,
|
|
2051
2099
|
COALESCE(SUM(CASE WHEN ec > 200 THEN 200 ELSE ec END), 0) AS total_full,
|
|
@@ -2058,9 +2106,9 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2058
2106
|
END AS ec
|
|
2059
2107
|
FROM sessions s
|
|
2060
2108
|
WHERE s.project_id = ? AND s.message_count >= 3 ${z}
|
|
2061
|
-
)`).get(g.id),B=
|
|
2109
|
+
)`).get(g.id),B=G("AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)"),q=G("");return i.json({project:g.name,total:_.total??0,eligible:E,indexed:y,pendingNew:k,pendingForce:E,modelInstalled:Ge(),modelName:"BAAI/bge-base-en-v1.5",embedderLoaded:le().loaded,workerRunning:Me().running,queueDepthOther:O,sessionsPerSec:L,avgChunksPerSession:C,chunksPerSec:$,throughputSource:X,throughputSamples:V,estimatedChunksByDepth:{new:{quick:B.total_quick,standard:B.total_standard,full:B.total_full,uncapped:B.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",Fe,async i=>{let l={};try{l=await i.req.json()}catch{}let m=(l.project??"").trim(),g=h(),_=0,E=null;if(m){let y=g.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(m,m);if(!y)return i.json({error:`project not found: ${m}`},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?ld(E):ud(),Me().queueDepth===0&&pd(),i.json({cleared:_,project:m||null,queueDepth:Me().queueDepth})}),t.post("/api/semantic/reindex-project",Fe,async i=>{if(!Ge())return i.json({error:"embedder not installed \u2014 run `recall semantic install` first"},503);let l={};try{l=await i.req.json()}catch{}let m=(l.project??"").trim();if(!m)return i.json({error:"project name required"},400);let g=!!l.force,_=Math.max(0,Math.floor(Number(l.maxChunks??0)));ad(_);let E=h(),y=E.prepare("SELECT id FROM projects WHERE name = ? OR decoded_path = ? LIMIT 1").get(m,m);if(!y)return i.json({error:`project not found: ${m}`},404);let k=g?"SELECT id FROM sessions WHERE project_id = ? AND message_count >= 3":`SELECT s.id FROM sessions s
|
|
2062
2110
|
WHERE s.project_id = ? AND s.message_count >= 3
|
|
2063
|
-
AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)`,
|
|
2111
|
+
AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)`,O=E.prepare(k).all(y.id);if(O.length===0)return i.json({enqueued:0,queueDepth:Me().queueDepth,message:"nothing to do \u2014 every session in this project is already vectorized (use force:true to re-embed)"});let N=E.prepare("INSERT INTO chunk_queue(session_id, action) VALUES (?, 'embed')");if(E.transaction(()=>{for(let w of O)N.run(w.id)})(),!le().loaded)try{await Be()}catch(w){let L=w instanceof Error?w.message:"unknown error";return i.json({error:`embedder load failed: ${L}`},500)}return Me().running||vn(),i.json({enqueued:O.length,queueDepth:Me().queueDepth,project:m,appliedMaxChunks:_})}),t.get("/api/sessions/:id/similar",Fe,async i=>{if(!le().loaded)return i.json({error:"vector model not loaded"},503);let l=i.req.param("id"),m=Math.max(1,Math.min(50,Number(i.req.query("limit")??10)));try{let g=await Yg(l,m);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",Fe,async i=>{let l=h(),m=i.req.query("q")?.trim();if(!m)return i.json({query:"",hits:[],tags:[]});if(m.length>500)return i.json({error:"query too long (max 500 chars)"},400);let g=i.req.query("project"),_=m.split(/\s+/).filter(Y=>Y.length>0),E=_.filter(Y=>Y.startsWith("#")).map(Y=>ut(Y)).filter(Boolean),y=_.filter(Y=>!Y.startsWith("#")),k=y.length>20,N=(k?y.slice(0,20):y).map(Y=>`"${Y.replace(/"/g,"")}"`),x=N.join(" "),w=Math.max(1,Math.min(200,Number(i.req.query("limit")??30))),L=i.req.query("system")==="1"||i.req.query("system")==="true",C=Ur("s",L);if(N.length===0&&E.length>0){let Y=`
|
|
2064
2112
|
SELECT s.id AS session_id,
|
|
2065
2113
|
s.id AS message_uuid,
|
|
2066
2114
|
p.name AS project,
|
|
@@ -2072,8 +2120,8 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2072
2120
|
FROM sessions s
|
|
2073
2121
|
JOIN projects p ON p.id = s.project_id
|
|
2074
2122
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2075
|
-
WHERE 1=1${
|
|
2076
|
-
`,ie={limit:
|
|
2123
|
+
WHERE 1=1${C}
|
|
2124
|
+
`,ie={limit:w};g&&(Y+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",ie.proj=`%${Vn(g)}%`),E.forEach(($e,ge)=>{Y+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${ge})`,ie[`tag_${ge}`]=$e}),Y+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit";let ot=l.prepare(Y).all(ie);return i.json({query:m,hits:ot,tags:E,truncated:k})}let $=`
|
|
2077
2125
|
SELECT m.session_id AS session_id,
|
|
2078
2126
|
m.uuid AS message_uuid,
|
|
2079
2127
|
p.name AS project,
|
|
@@ -2087,8 +2135,8 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2087
2135
|
JOIN sessions s ON s.id = m.session_id
|
|
2088
2136
|
JOIN projects p ON p.id = s.project_id
|
|
2089
2137
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2090
|
-
WHERE messages_fts MATCH @fts${
|
|
2091
|
-
`,
|
|
2138
|
+
WHERE messages_fts MATCH @fts${C}
|
|
2139
|
+
`,X={fts:x,limit:w};g&&($+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",X.proj=`%${Vn(g)}%`),E.forEach((Y,ie)=>{$+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${ie})`,X[`tag_${ie}`]=Y}),$+=" ORDER BY bm25(messages_fts) LIMIT @limit";let G=l.prepare($).all(X).map(Y=>({...Y,matched_via:"fts"}));if(i.req.query("mode")!=="semantic")return i.json({query:m,hits:G,tags:E,truncated:k});let q=[];try{let Y=`
|
|
2092
2140
|
SELECT s.id AS session_id,
|
|
2093
2141
|
s.id AS message_uuid,
|
|
2094
2142
|
p.name AS project,
|
|
@@ -2103,16 +2151,16 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2103
2151
|
JOIN sessions s ON s.id = ss.session_id
|
|
2104
2152
|
JOIN projects p ON p.id = s.project_id
|
|
2105
2153
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
2106
|
-
WHERE sessions_fts MATCH @fts${
|
|
2107
|
-
`,ie={fts:
|
|
2154
|
+
WHERE sessions_fts MATCH @fts${C}
|
|
2155
|
+
`,ie={fts:x,limit:w};g&&(Y+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",ie.proj=`%${Vn(g)}%`),E.forEach((ot,$e)=>{Y+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${$e})`,ie[`tag_${$e}`]=ot}),Y+=" ORDER BY rank LIMIT @limit",q=l.prepare(Y).all(ie)}catch(Y){console.error("[search.semantic] failed:",Y)}if(le().loaded)try{let Y=await Gg(m,w),ie=G.map(de=>({id:String(de.session_id),data:de,lane:"bm25"})),ot=q.map(de=>({id:String(de.session_id),data:de,lane:"summary"})),$e=Y.map(de=>({id:de.sessionId,data:{session_id:de.sessionId,snippet:de.text,matched_via:"vector"},lane:"vector"})),xf=zg([ie,ot,$e]).slice(0,w).map(de=>({...de.data,session_id:de.id,rrf_score:de.score,lanes:de.lanes,matched_via:de.lanes.length>1?"fused":de.lanes[0]}));return i.json({query:m,hits:xf,tags:E,mode:"semantic",fusion:"rrf",truncated:k})}catch(Y){console.error("[search.vector] failed, falling back:",Y)}let z=new Set(G.map(Y=>String(Y.session_id))),te=q.filter(Y=>!z.has(String(Y.session_id))).map(({rank:Y,...ie})=>({...ie,matched_via:"semantic"})),Q=[...G,...te].slice(0,w);return i.json({query:m,hits:Q,tags:E,mode:"semantic",truncated:k})}),t.get("/api/sessions/:id/context",Fe,i=>{let l=h(),m=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,
|
|
2108
2156
|
s.started_at, s.ended_at, s.message_count, s.git_branch
|
|
2109
2157
|
FROM sessions s JOIN projects p ON p.id = s.project_id
|
|
2110
|
-
WHERE s.id = ?`).get(m);if(!
|
|
2158
|
+
WHERE s.id = ?`).get(m);if(!y)return i.json({error:"not found"},404);let k=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
2111
2159
|
FROM messages
|
|
2112
2160
|
WHERE session_id = ?
|
|
2113
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(m),x=xd(T,w,{mode:g,includeSidechain:_,prelude:E});return i.text(x)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:iu(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),m=Ye(l);if(!m)return i.json({error:"not found"},404);let g=au(l,!0);return i.json({collection:m,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 m=yn({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(m,201)}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/collections/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);try{let g=lu(l,m);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 m=uu(l);return i.json(m)}catch(m){return i.json({error:m.message},404)}}),t.post("/api/collections/:id/restore",i=>{let l=i.req.param("id");try{let m=du(l);return i.json(m)}catch(m){return i.json({error:m.message},404)}}),t.post("/api/collections/:id/members",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.session_id!="string")return i.json({error:"session_id required"},400);try{let g=Tn(l,m.session_id,m.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"),m=i.req.param("sid");try{let g=pu(l,m);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:cu(l)})});let k=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:hu()})),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||!k.includes(l.type))return i.json({error:"name, type, pattern required (type must be a known matcher)"},400);try{let m=Zo({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(m,201)}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/auto-collections/rules/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);try{let g=Eu(l,m);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 m=bu(l);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.get("/api/auto-collections/suggestions",i=>{let l=i.req.query("dismissed")==="1";return i.json({suggestions:qs({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let m=yu(l);return i.json({rule:m})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/auto-collections/suggestions/:id/dismiss",i=>{let l=i.req.param("id");try{return Su(l),i.json({ok:!0})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=Xs();return i.json({suggestions:l})}),t.get("/api/auto-collections/suggestions/:id/preview",i=>{let l=i.req.param("id"),m=Math.max(1,Math.min(20,Number(i.req.query("limit"))||3)),_=qs({includeDismissed:!1}).find(T=>T.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=fu(_.type,_.pattern,m);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(Tu());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:Oi({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),m=ce(l);if(!m)return i.json({error:"thread not found"},404);let g=m.edges.map(_=>({..._,alias_source:_.alias==null?null:j.isSessionAutoLinked(_.session_id)?"auto":"manual"}));return i.json({thread:{...m,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 m=rr({name:l.name,summary:l.summary??null,originSessionId:l.originSessionId});return i.json({thread:m})}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/threads/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({}));try{m.name&&qd(l,m.name),m.close&&Xd(l),m.reopen&&Jd(l),m.archive&&Gd(l),"folder_id"in m&&mp(l,m.folder_id??null);let g=ce(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:Ii()})),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 m=ap({name:l.name,parentFolderId:l.parent_folder_id??null,projectScope:l.project_scope??null});return i.json({folder:m})}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/thread-folders/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({}));try{let g;return m.name&&(g=lp(l,m.name)),"parent_folder_id"in m&&(g=up(l,m.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 pp(l),i.json({ok:!0})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/thread-folders/reorder",async i=>{let l=await i.req.json().catch(()=>({})),m=l.ordered_ids;if(!Array.isArray(m))return i.json({error:"ordered_ids must be an array"},400);try{return dp(l.parent_folder_id??null,l.project_scope??null,m),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"),m=await i.req.json().catch(()=>({}));if(!m.sessionId)return i.json({error:"sessionId required"},400);try{let g=or({threadId:l,sessionId:m.sessionId,parentSessionId:m.parentSessionId??null,role:m.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"),m=i.req.param("sessionId"),g=Wd(l,m);return i.json(g)}),t.patch("/api/threads/:id/sessions/:sessionId",async i=>{let l=i.req.param("id"),m=i.req.param("sessionId"),g=await i.req.json().catch(()=>({}));try{let _=Ln(l,m,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"),m=await i.req.json().catch(()=>({}));if(!m.sourceId)return i.json({error:"sourceId required"},400);try{let g=Yd(m.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"),m=await i.req.json().catch(()=>({}));if(!m.sessionIds?.length||!m.newThreadName)return i.json({error:"sessionIds and newThreadName required"},400);try{let g=Kd({threadId:l,sessionIds:m.sessionIds,newThreadName:m.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:Bd(l)})});let M=$.object({enabled:$.boolean(),band_lo:$.number().min(0).max(1).optional(),band_hi:$.number().min(0).max(1).optional(),model:$.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),P=$.object({project:$.string().min(1),threshold:$.number().min(0).max(1).optional(),model:$.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:M});t.post("/api/threads/scan/preflight",async i=>{let l=De(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=P.safeParse(m);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=Mm({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 G=$.object({project:$.string().min(1),threshold:$.number().min(0).max(1).optional(),llm_names:$.boolean().optional(),model:$.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:M});t.post("/api/threads/scan/apply",async i=>{let l=De(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=G.safeParse(m);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=Dm({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(!na(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return tt(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let w of Pm(l,g))if(E||(await _.writeSSE({id:String(w.id),event:w.kind,data:JSON.stringify(w.data)}),w.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/threads/scan/jobs/:jobId",i=>{let l=na(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=De(i);return l||(Fm(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let D=$.object({project_id:$.number().int().positive(),mode:$.enum(["preflight","apply"]),window_hours:$.number().min(.5).max(168).optional(),score_threshold:$.number().min(0).max(1).optional(),use_live_pids:$.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),m=D.safeParse(l);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);try{let g=await rp(m.data.project_id,{windowHours:m.data.window_hours,scoreThreshold:m.data.score_threshold,useLivePids:m.data.use_live_pids});if(m.data.mode==="preflight")return i.json({plan:g});let _=op(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"),m=ce(l);if(!m)return i.json({error:"thread not found"},404);let g=h(),_=0;for(let E of m.edges)g.prepare("SELECT auto_title_source FROM sessions WHERE id = ?").get(E.session_id)?.auto_title_source==="agent"&&(_+=1);return i.json({total:m.edges.length,alreadyTitled:_,untitled:m.edges.length-_})}),t.post("/api/threads/:id/titles/generate",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({})),g=ce(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 _=Be(),E=m.model??_.model,T=Ap({threadId:l,force:m.force??!1,model:E});return i.json({jobId:T})}),t.get("/api/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Mi(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return tt(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let w of xp(l,g))if(E||(await _.writeSSE({id:String(w.id),event:w.kind,data:JSON.stringify(w.data)}),w.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/jobs/:jobId",i=>{let l=Mi(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>Np(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 m=j.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(m==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance",count:j.size()});let g=j.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:m,count:j.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 m=j.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(m==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance"});let g=j.rename(l.shell_pid,l.tab_name);if(!g)return i.json({error:"unknown shell_pid"},404);let _=Mg(l.shell_pid,l.tab_name);return i.json({ok:!0,ownership:m,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 m=j.remove(l.shell_pid);return i.json({ok:!0,removed:m,count:j.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):(j.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:j.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 m=l.text.length>8192?l.text.slice(-8192):l.text,g=typeof l.captured_at=="string"?l.captured_at:new Date().toISOString();return j.setOutputTail(l.shell_pid,m,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 m=new Map;for(let O of j.all())m.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=[],T=g.filter(O=>{let H=j.claimPidOwnership(O.shell_pid,_);return E.push({shell_pid:O.shell_pid,ownership:H}),H!=="rejected"}),w=j.sync(T),x=0;for(let O of T){let H=m.get(O.shell_pid),J=j.get(O.shell_pid)?.tab_name??O.tab_name,Y=!!J&&!de(J)&&!le(J)?J:O.tab_name;H!==void 0&&H!==Y&&(x+=Mg(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 N=await Mk(),C={resolved:0,expired:0};try{C=vl()}catch{}let I={rebound:0,ghosts:0,ambiguous:0};try{I=Ll()}catch{}return Jk(),i.json({ok:!0,count:j.size(),diff:w,propagated:x,live_sweep:N,deferred_resolved:C,rebound:I})}),t.get("/api/terminal/registry",i=>i.json({terminals:j.all(),count:j.size()})),t.get("/api/terminal/sessions/:shellPid",i=>{let l=i.req.param("shellPid"),m=Number(l);if(!Number.isInteger(m)||m<=0)return i.json({error:"shellPid must be a positive integer"},400);let g=j.sessionsFor(m);return i.json({shell_pid:m,sessions:g})}),t.get("/api/sessions/:id/linked-terminal",i=>{let l=i.req.param("id"),m=j.all().find(_=>j.sessionsFor(_.shell_pid).includes(l)),g=[];if(!m){let _=h().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 T=_.cwd.replace(/\/+$/,""),w=300*1e3;for(let x of j.all()){if(!x.cwd||x.cwd.replace(/\/+$/,"")!==T||de(x.tab_name))continue;let A=Date.parse(x.opened_at),N=Date.parse(x.last_seen_at);!Number.isFinite(A)||!Number.isFinite(N)||A>E||N+w<E||g.push({shell_pid:x.shell_pid,tab_name:x.tab_name,cwd:x.cwd,opened_at:x.opened_at,last_seen_at:x.last_seen_at,reason:"time-overlap"})}g.sort((x,A)=>Date.parse(A.last_seen_at)-Date.parse(x.last_seen_at))}}}return m?i.json({linked:{shell_pid:m.shell_pid,tab_name:m.tab_name,cwd:m.cwd},suggested:[]}):i.json({linked:null,suggested:g})}),t.post("/api/sessions/:id/auto-relink",async i=>{let l=i.req.param("id");if(ye(l))return i.json({applied:!1,reason:"has-alias"});if(j.all().some(T=>j.sessionsFor(T.shell_pid).includes(l)))return i.json({applied:!1,reason:"already-linked"});let g=h().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=j.all().filter(T=>T.cwd&&T.cwd.replace(/\/+$/,"")===_&&!de(T.tab_name));if(E.length===1){let T=E[0],w=Cn({sessionStartedAt:g.started_at??null,terminalOpenedAt:T.opened_at??null});if(!w.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:w.reason});let x=j.getOrigin(l),A=Lt({tabName:T.tab_name,origin:x??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return A?(he(l,A),j.linkSession(l,T.shell_pid),i.json({applied:!0,alias:A,linked_pid:T.shell_pid,linked_tab_name:T.tab_name,method:"cwd-singleton"})):i.json({applied:!1,reason:"no-usable-name"})}if(E.length>1){let T=await Cl(l);if(T){let x=j.get(T.shell_pid),A=Cn({sessionStartedAt:g.started_at??null,terminalOpenedAt:x?.opened_at??null});if(!A.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:A.reason});let N=j.getOrigin(l),C=Lt({tabName:T.tab_name,origin:N??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(C)return he(l,C),j.linkSession(l,T.shell_pid),i.json({applied:!0,alias:C,linked_pid:T.shell_pid,linked_tab_name:T.tab_name,matched_fingerprints:T.matched_fingerprints,method:"content-match"})}let w=6e4;if(g.started_at){let x=Date.parse(g.started_at);if(Number.isFinite(x)){let A=E.filter(C=>Cn({sessionStartedAt:g.started_at,terminalOpenedAt:C.opened_at??null}).allowed).map(C=>({t:C,gap:x-Date.parse(C.opened_at??"")})).filter(C=>Number.isFinite(C.gap)&&C.gap>=0&&C.gap<=w);if(A.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:A.length});let N=A[0];if(N){let C=j.getOrigin(l),I=Lt({tabName:N.t.tab_name,origin:C??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(I)return he(l,I),j.linkSession(l,N.t.shell_pid),i.json({applied:!0,alias:I,linked_pid:N.t.shell_pid,linked_tab_name:N.t.tab_name,method:"closest-before-temporal",gap_ms:N.gap})}}}return i.json({applied:!1,reason:"ambiguous",candidate_count:E.length})}return i.json({applied:!1,reason:"no-candidates"})}),t.post("/api/sessions/:id/relink",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(m?.clear)return j.unlinkSession(l),ks(l),i.json({ok:!0,alias:null,linked_pid:null});if(!m||typeof m.shell_pid!="number")return i.json({error:"shell_pid required"},400);let g=j.get(m.shell_pid);if(!g)return i.json({error:"terminal not registered"},404);let _=j.getOrigin(l),E=h().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(l),T=null,w=g.tab_name?.trim()??"";if(w&&!de(w)&&!le(w))T=w;else if(w&&le(w)){let x=Ct(w);x&&!de(x)&&(T=x)}return T?(j.unlinkSession(l),he(l,T),i.json({ok:!0,alias:T,linked_pid:m.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"),m=h().prepare("SELECT file_path FROM sessions WHERE id = ?").get(l);if(!m?.file_path)return i.json({error:"session not found"},404);j.unlinkSession(l),ks(l),await Ns(m.file_path);let g=ye(l);return i.json({ok:!0,alias:g,linked_pid:j.all().find(_=>j.sessionsFor(_.shell_pid).includes(l))?.shell_pid??null})}),t.get("/api/paste-expand",async i=>{let l=i.req.query("session"),m=i.req.query("message"),g=i.req.query("path");if(!l||!m||!g)return i.json({error:"session, message and path are required"},400);let _=h(),E=_.prepare("SELECT rowid, content_text FROM messages WHERE uuid = ? AND session_id = ?").get(m,l);if(!E)return i.json({error:"message not found in session"},404);let T=g.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");if(!new RegExp(`\\[Pasted text #\\d+ \\+\\d+ lines\\]\\s*${T}`).test(E.content_text??""))return i.json({error:"path not referenced by this message"},403);let x=_.prepare(`SELECT content_text FROM messages
|
|
2161
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(m),O=np(y,k,{mode:g,includeSidechain:_,prelude:E});return i.text(O)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:Cu(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),m=Qe(l);if(!m)return i.json({error:"not found"},404);let g=vu(l,!0);return i.json({collection:m,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 m=kn({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(m,201)}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/collections/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);try{let g=Mu(l,m);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 m=Du(l);return i.json(m)}catch(m){return i.json({error:m.message},404)}}),t.post("/api/collections/:id/restore",i=>{let l=i.req.param("id");try{let m=ju(l);return i.json(m)}catch(m){return i.json({error:m.message},404)}}),t.post("/api/collections/:id/members",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m||typeof m.session_id!="string")return i.json({error:"session_id required"},400);try{let g=An(l,m.session_id,m.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"),m=i.req.param("sid");try{let g=Pu(l,m);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:Iu(l)})});let A=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:Bu()})),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||!A.includes(l.type))return i.json({error:"name, type, pattern required (type must be a known matcher)"},400);try{let m=ci({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(m,201)}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/auto-collections/rules/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>null);if(!m)return i.json({error:"body required"},400);try{let g=Wu(l,m);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 m=qu(l);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.get("/api/auto-collections/suggestions",i=>{let l=i.req.query("dismissed")==="1";return i.json({suggestions:zs({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let m=Ju(l);return i.json({rule:m})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/auto-collections/suggestions/:id/dismiss",i=>{let l=i.req.param("id");try{return Xu(l),i.json({ok:!0})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=Ks();return i.json({suggestions:l})}),t.get("/api/auto-collections/suggestions/:id/preview",i=>{let l=i.req.param("id"),m=Math.max(1,Math.min(20,Number(i.req.query("limit"))||3)),_=zs({includeDismissed:!1}).find(y=>y.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=Uu(_.type,_.pattern,m);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(Gu());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:Hi({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),m=ce(l);if(!m)return i.json({error:"thread not found"},404);let g=m.edges.map(_=>({..._,alias_source:_.alias==null?null:D.isSessionAutoLinked(_.session_id)?"auto":"manual"}));return i.json({thread:{...m,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 m=ur({name:l.name,summary:l.summary??null,originSessionId:l.originSessionId});return i.json({thread:m})}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/threads/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({}));try{m.name&&bp(l,m.name),m.close&&Sp(l),m.reopen&&Tp(l),m.archive&&yp(l),"folder_id"in m&&Bp(l,m.folder_id??null);let g=ce(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:Xi()})),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 m=jp({name:l.name,parentFolderId:l.parent_folder_id??null,projectScope:l.project_scope??null});return i.json({folder:m})}catch(m){return i.json({error:m.message},400)}}),t.patch("/api/thread-folders/:id",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({}));try{let g;return m.name&&(g=Fp(l,m.name)),"parent_folder_id"in m&&(g=$p(l,m.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 Hp(l),i.json({ok:!0})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/thread-folders/reorder",async i=>{let l=await i.req.json().catch(()=>({})),m=l.ordered_ids;if(!Array.isArray(m))return i.json({error:"ordered_ids must be an array"},400);try{return Up(l.parent_folder_id??null,l.project_scope??null,m),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"),m=await i.req.json().catch(()=>({}));if(!m.sessionId)return i.json({error:"sessionId required"},400);try{let g=dr({threadId:l,sessionId:m.sessionId,parentSessionId:m.parentSessionId??null,role:m.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"),m=i.req.param("sessionId"),g=Ep(l,m);return i.json(g)}),t.patch("/api/threads/:id/sessions/:sessionId",async i=>{let l=i.req.param("id"),m=i.req.param("sessionId"),g=await i.req.json().catch(()=>({}));try{let _=Dn(l,m,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"),m=await i.req.json().catch(()=>({}));if(!m.sourceId)return i.json({error:"sourceId required"},400);try{let g=wp(m.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"),m=await i.req.json().catch(()=>({}));if(!m.sessionIds?.length||!m.newThreadName)return i.json({error:"sessionIds and newThreadName required"},400);try{let g=Rp({threadId:l,sessionIds:m.sessionIds,newThreadName:m.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:hp(l)})});let I=P.object({enabled:P.boolean(),band_lo:P.number().min(0).max(1).optional(),band_hi:P.number().min(0).max(1).optional(),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),j=P.object({project:P.string().min(1),threshold:P.number().min(0).max(1).optional(),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:I});t.post("/api/threads/scan/preflight",async i=>{let l=je(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=j.safeParse(m);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=lg({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=P.object({project:P.string().min(1),threshold:P.number().min(0).max(1).optional(),llm_names:P.boolean().optional(),model:P.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:I});t.post("/api/threads/scan/apply",async i=>{let l=je(i);if(l)return l;let m=await i.req.json().catch(()=>null),g=J.safeParse(m);if(!g.success)return i.json({error:"invalid request body",details:g.error.format()},400);let _=dg({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(!ga(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return rt(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of pg(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=ga(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=je(i);return l||(mg(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let M=P.object({project_id:P.number().int().positive(),mode:P.enum(["preflight","apply"]),window_hours:P.number().min(.5).max(168).optional(),score_threshold:P.number().min(0).max(1).optional(),use_live_pids:P.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),m=M.safeParse(l);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);try{let g=await Ip(m.data.project_id,{windowHours:m.data.window_hours,scoreThreshold:m.data.score_threshold,useLivePids:m.data.use_live_pids});if(m.data.mode==="preflight")return i.json({plan:g});let _=Mp(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"),m=ce(l);if(!m)return i.json({error:"thread not found"},404);let g=h(),_=0;for(let E of m.edges)g.prepare("SELECT auto_title_source FROM sessions WHERE id = ?").get(E.session_id)?.auto_title_source==="agent"&&(_+=1);return i.json({total:m.edges.length,alreadyTitled:_,untitled:m.edges.length-_})}),t.post("/api/threads/:id/titles/generate",async i=>{let l=i.req.param("id"),m=await i.req.json().catch(()=>({})),g=ce(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 _=qe(),E=m.model??_.model,y=tm({threadId:l,force:m.force??!1,model:E});return i.json({jobId:y})}),t.get("/api/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Ji(l))return i.json({error:"job not found"},404);let g=Number(i.req.header("Last-Event-ID")??0);return rt(i,async _=>{let E=!1,y=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let k of nm(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=Ji(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>sm(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 m=D.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(m==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance",count:D.size()});let g=D.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:m,count:D.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 m=D.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(m==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance"});let g=D.rename(l.shell_pid,l.tab_name);if(!g)return i.json({error:"unknown shell_pid"},404);let _=uf(l.shell_pid,l.tab_name);return i.json({ok:!0,ownership:m,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 m=D.remove(l.shell_pid);return i.json({ok:!0,removed:m,count:D.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):(D.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:D.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 m=l.text.length>8192?l.text.slice(-8192):l.text,g=typeof l.captured_at=="string"?l.captured_at:new Date().toISOString();return D.setOutputTail(l.shell_pid,m,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 m=new Map;for(let C of D.all())m.set(C.shell_pid,C.tab_name);let g=l.terminals.filter(C=>!!C&&typeof C.shell_pid=="number"&&typeof C.tab_name=="string").map(C=>({shell_pid:C.shell_pid,tab_name:C.tab_name,cwd:C.cwd??null,opened_at:C.opened_at??new Date().toISOString()})),_=l.extension_instance_id??null,E=[],y=g.filter(C=>{let $=D.claimPidOwnership(C.shell_pid,_);return E.push({shell_pid:C.shell_pid,ownership:$}),$!=="rejected"}),k=D.sync(y),O=0;for(let C of y){let $=m.get(C.shell_pid),X=D.get(C.shell_pid)?.tab_name??C.tab_name,G=!!X&&!pe(X)&&!ue(X)?X:C.tab_name;$!==void 0&&$!==G&&(O+=uf(C.shell_pid,G))}let N=E.filter(C=>C.ownership==="rejected").length;N>0&&console.log(`[terminal/sync] dropped ${N} tab_name update(s), pid(s) owned by a different extension instance`);let x=await hA(),w={resolved:0,expired:0};try{w=ru()}catch{}let L={rebound:0,ghosts:0,ambiguous:0};try{L=su()}catch{}return OA(),i.json({ok:!0,count:D.size(),diff:k,propagated:O,live_sweep:x,deferred_resolved:w,rebound:L})}),t.get("/api/terminal/registry",i=>i.json({terminals:D.all(),count:D.size()})),t.get("/api/terminal/sessions/:shellPid",i=>{let l=i.req.param("shellPid"),m=Number(l);if(!Number.isInteger(m)||m<=0)return i.json({error:"shellPid must be a positive integer"},400);let g=D.sessionsFor(m);return i.json({shell_pid:m,sessions:g})}),t.get("/api/sessions/:id/linked-terminal",i=>{let l=i.req.param("id"),m=D.all().find(_=>D.sessionsFor(_.shell_pid).includes(l)),g=[];if(!m){let _=h().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 O of D.all()){if(!O.cwd||O.cwd.replace(/\/+$/,"")!==y||pe(O.tab_name))continue;let N=Date.parse(O.opened_at),x=Date.parse(O.last_seen_at);!Number.isFinite(N)||!Number.isFinite(x)||N>E||x+k<E||g.push({shell_pid:O.shell_pid,tab_name:O.tab_name,cwd:O.cwd,opened_at:O.opened_at,last_seen_at:O.last_seen_at,reason:"time-overlap"})}g.sort((O,N)=>Date.parse(N.last_seen_at)-Date.parse(O.last_seen_at))}}}return m?i.json({linked:{shell_pid:m.shell_pid,tab_name:m.tab_name,cwd:m.cwd},suggested:[]}):i.json({linked:null,suggested:g})}),t.post("/api/sessions/:id/auto-relink",async i=>{let l=i.req.param("id");if(Se(l))return i.json({applied:!1,reason:"has-alias"});if(D.all().some(y=>D.sessionsFor(y.shell_pid).includes(l)))return i.json({applied:!1,reason:"already-linked"});let g=h().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=D.all().filter(y=>y.cwd&&y.cwd.replace(/\/+$/,"")===_&&!pe(y.tab_name));if(E.length===1){let y=E[0],k=Mn({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 O=D.getOrigin(l),N=It({tabName:y.tab_name,origin:O??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});return N?(_e(l,N),D.linkSession(l,y.shell_pid),i.json({applied:!0,alias:N,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 nu(l);if(y){let O=D.get(y.shell_pid),N=Mn({sessionStartedAt:g.started_at??null,terminalOpenedAt:O?.opened_at??null});if(!N.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:N.reason});let x=D.getOrigin(l),w=It({tabName:y.tab_name,origin:x??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(w)return _e(l,w),D.linkSession(l,y.shell_pid),i.json({applied:!0,alias:w,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 O=Date.parse(g.started_at);if(Number.isFinite(O)){let N=E.filter(w=>Mn({sessionStartedAt:g.started_at,terminalOpenedAt:w.opened_at??null}).allowed).map(w=>({t:w,gap:O-Date.parse(w.opened_at??"")})).filter(w=>Number.isFinite(w.gap)&&w.gap>=0&&w.gap<=k);if(N.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:N.length});let x=N[0];if(x){let w=D.getOrigin(l),L=It({tabName:x.t.tab_name,origin:w??null,cwd:g.cwd??null,gitBranch:g.git_branch??null});if(L)return _e(l,L),D.linkSession(l,x.t.shell_pid),i.json({applied:!0,alias:L,linked_pid:x.t.shell_pid,linked_tab_name:x.t.tab_name,method:"closest-before-temporal",gap_ms:x.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"),m=await i.req.json().catch(()=>null);if(m?.clear)return D.unlinkSession(l),Cs(l),i.json({ok:!0,alias:null,linked_pid:null});if(!m||typeof m.shell_pid!="number")return i.json({error:"shell_pid required"},400);let g=D.get(m.shell_pid);if(!g)return i.json({error:"terminal not registered"},404);let _=D.getOrigin(l),E=h().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(l),y=null,k=g.tab_name?.trim()??"";if(k&&!pe(k)&&!ue(k))y=k;else if(k&&ue(k)){let O=vt(k);O&&!pe(O)&&(y=O)}return y?(D.unlinkSession(l),_e(l,y),i.json({ok:!0,alias:y,linked_pid:m.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"),m=h().prepare("SELECT file_path FROM sessions WHERE id = ?").get(l);if(!m?.file_path)return i.json({error:"session not found"},404);D.unlinkSession(l),Cs(l),await Ms(m.file_path);let g=Se(l);return i.json({ok:!0,alias:g,linked_pid:D.all().find(_=>D.sessionsFor(_.shell_pid).includes(l))?.shell_pid??null})}),t.get("/api/paste-expand",async i=>{let l=i.req.query("session"),m=i.req.query("message"),g=i.req.query("path");if(!l||!m||!g)return i.json({error:"session, message and path are required"},400);let _=h(),E=_.prepare("SELECT rowid, content_text FROM messages WHERE uuid = ? AND session_id = ?").get(m,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 O=_.prepare(`SELECT content_text FROM messages
|
|
2114
2162
|
WHERE session_id = ? AND rowid > ?
|
|
2115
|
-
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let
|
|
2163
|
+
ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let N of O){let x=N.content_text??"";if(x.includes("**Tool result**")&&x.includes(g))return i.json({source:"tool-result",content:x});if(/^\s*1\t/.test(x)&&x.length>200)return i.json({source:"tool-result",content:x})}try{let N=await mA(g),x=fA();if(!N.startsWith(x+"/")&&!N.startsWith(x+"\\"))return i.json({error:"path outside allowed root"},403);let w=[".ssh",".gnupg",".gpg",".aws",".kube",".docker",".password-store"],C=N.slice(x.length+1).split("/")[0].split("\\")[0];if(w.includes(C))return i.json({error:"path inside sensitive directory"},403);let $=await dA(N),X=2*1024*1024;if($.size>X)return i.json({error:"file too large",size:$.size,max:X},413);let V=await pA(N,"utf8");return i.json({source:"disk",content:V})}catch(N){return i.json({source:"missing",error:N.message})}}),t.get("/api/projects/:name/stats",i=>{let l=h(),m=i.req.param("name"),g=l.prepare(`SELECT
|
|
2116
2164
|
(SELECT COUNT(*) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.message_count > 2) AS sessions,
|
|
2117
2165
|
(SELECT COALESCE(SUM(s.message_count), 0) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=?) AS messages,
|
|
2118
2166
|
(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,
|
|
@@ -2120,13 +2168,14 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
2120
2168
|
JOIN projects p ON p.id = s.project_id
|
|
2121
2169
|
WHERE p.name = ? AND s.git_branch IS NOT NULL
|
|
2122
2170
|
ORDER BY s.git_branch
|
|
2123
|
-
LIMIT 20`).all(m).map(E=>E.git_branch);return i.json({...g,branches:_})});function
|
|
2171
|
+
LIMIT 20`).all(m).map(E=>E.git_branch);return i.json({...g,branches:_})});function F(i){return i.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function ne(i){if(!e)return i;let l=`<meta name="recall-token" content="${F(e)}" />`,m=i.indexOf("</head>");return m!==-1?i.slice(0,m)+l+i.slice(m):l+i}function U(i){let l=pf();return i.html(tp({projects:l.projects,sessions:l.sessions,messages:l.messages,port:Number(i.req.raw.headers.get("host")?.split(":")[1]??0),version:df}))}function v(){try{return ja(gf,"utf8")}catch{return null}}return RA?(t.use("/assets/*",lf({root:Fa})),t.get("/favicon.svg",lf({root:Fa})),t.get("/",i=>{i.header("cache-control","no-cache, no-store, must-revalidate"),i.header("pragma","no-cache"),i.header("expires","0");let l=v();return l===null?U(i):i.html(ne(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=v();return l===null?U(i):i.html(ne(l))})):t.get("/",i=>U(i)),t}function DA(){if(vr(),!!ft().heuristicEnabled){try{let{updated:e}=Su();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}=Tu();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}=yu();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}=wu();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 hf(e,t){let n=MA(t);return new Promise((s,r)=>{try{let o=lA({fetch:n.fetch,port:e,hostname:"127.0.0.1"},()=>{s(o),setImmediate(()=>{try{DA()}catch(a){console.error("[daemon] startup maintenance crashed:",a)}})})}catch(o){r(o)}})}import{createServer as bf}from"node:net";function Ef(e){return new Promise(t=>{let n=bf();n.once("error",()=>t(!1)),n.once("listening",()=>{n.close(()=>t(!0))}),n.listen(e,"127.0.0.1")})}async function Sf(){let e=new Set([3e3,3001,4200,5e3,5173,8e3,8080,8888,9e3]),t=51370;if(!e.has(t)&&await Ef(t))return t;for(let n=0;n<50;n++){let s=49152+Math.floor(Math.random()*16383);if(!e.has(s)&&await Ef(s))return s}return new Promise((n,s)=>{let r=bf();r.once("error",s),r.listen(0,"127.0.0.1",()=>{let o=r.address();if(o&&typeof o=="object"){let a=o.port;r.close(()=>n(a))}else r.close(),s(new Error("could not determine a free port"))})})}function Tf(e){let t=Date.now(),n=e.prepare("SELECT id, decoded_path FROM projects WHERE repo_root IS NULL AND is_repo = 0").all();if(n.length===0)return{total:0,ephemeral:0,walked:0,folded:0,deferred:0,durationMs:Date.now()-t};let s=new Set,r=e.prepare("SELECT DISTINCT repo_root FROM projects WHERE repo_root IS NOT NULL AND repo_root != ?").all(Pt);for(let T of r)s.add(T.repo_root);let o=[],a=e.prepare(`UPDATE projects
|
|
2124
2172
|
SET repo_root = ?, main_repo = NULL, is_repo = 0, is_worktree = 0
|
|
2125
2173
|
WHERE id = ?`),c=e.prepare(`UPDATE projects
|
|
2126
2174
|
SET repo_root = ?, main_repo = ?, is_repo = 1, is_worktree = ?
|
|
2127
|
-
WHERE id = ?`),u=e.prepare("UPDATE projects SET repo_root = ?, is_repo = 1 WHERE id = ?"),d=0,p=0,f=0;e.transaction(()=>{for(let
|
|
2128
|
-
`)}}function
|
|
2129
|
-
sqlite3 ~/.recall/db.sqlite "UPDATE sessions SET skipped_reason='reflag_loop_breaker' WHERE file_path = '${s}';"`})}return t}function
|
|
2130
|
-
`),n=[]}let s=
|
|
2175
|
+
WHERE id = ?`),u=e.prepare("UPDATE projects SET repo_root = ?, is_repo = 1 WHERE id = ?"),d=0,p=0,f=0;e.transaction(()=>{for(let T of n)Nn(T.decoded_path)&&(a.run(Pt,T.id),d++);for(let T of n){if(Nn(T.decoded_path))continue;let S=Qs(T.decoded_path);S.isRepo?(c.run(S.root,S.mainRepo,S.isWorktree?1:0,T.id),s.add(S.root),p++):o.push(T)}for(let T of o){let S=Ku(T.decoded_path,s);S&&(u.run(S,T.id),f++)}})();let b=o.length-f;return{total:n.length,ephemeral:d,walked:p,folded:f,deferred:b,durationMs:Date.now()-t}}No();H();Z();We();function Ha(){let e=[];return rn("stale-claude-json-mcp-paths",()=>{for(let t of jA())e.push(t)}),rn("zombie-mcp",()=>{for(let t of FA())e.push(t)}),rn("chunk-queue-growth",()=>{for(let t of $A())e.push(t)}),rn("watcher-reflag-loop",()=>{for(let t of UA())e.push(t)}),rn("disk-pressure-backups",()=>{for(let t of HA())e.push(t)}),rn("daemon-state-files",()=>{for(let t of WA())e.push(t)}),e}function rn(e,t){try{t()}catch(n){let s=n instanceof Error?n.message:String(n);process.stderr.write(`[doctor-tick] ${e} check failed: ${s}
|
|
2176
|
+
`)}}function jA(){let e=ep();if(!e.configExists)return[];let t=[];for(let n of e.findings)t.push(PA(n));return t}function PA(e){return{check:"stale_claude_json_mcp_path",severity:e.severity==="HIGH"?"critical":"high",keyFacts:{name:e.name,stalePath:e.stalePath},message:`~/.claude.json mcpServers.${e.name} points at a deleted path: ${e.stalePath}`,remediation:e.remediation}}function FA(){let e=Ri();return!e.flagged||!e.message?[]:[{check:"zombie_mcp_threshold",severity:"critical",keyFacts:{countBucket:Math.floor(e.orphanCount/5)*5,aggregateGbBucket:Math.floor(e.orphanRssKb/(1024*1024))},message:e.message,remediation:"`recall mcp-prune --all` reaps. If that returns no children on a stale build, `pkill -f mcp-server.js` is the nuclear option."}]}function $A(){let e=Ci();return e.status==="ok"?[]:[{check:"chunk_queue_growth",severity:{critical:"critical",high:"high",medium:"medium"}[e.status],keyFacts:{sizeBucket:Math.floor(e.currentSize/1e3)*1e3,semanticEnabled:e.semanticEnabled},message:e.message,remediation:e.remediation??"Re-run `recall doctor` for context."}]}function UA(){let e=er(20),t=[];for(let n of e){if(n.noProgressCount<=Vd)continue;let s=n.path.replace(/'/g,"''");t.push({check:"watcher_reflag_loop",severity:"critical",keyFacts:{path:n.path},message:`Watcher reindexed ${n.path} ${n.count.toLocaleString()} times in the last hour (${n.noProgressCount.toLocaleString()} with no new content) \u2014 reflag loop.`,remediation:`Mark it skipped with a SQL one-liner:
|
|
2177
|
+
sqlite3 ~/.recall/db.sqlite "UPDATE sessions SET skipped_reason='reflag_loop_breaker' WHERE file_path = '${s}';"`})}return t}function HA(){let e=Qd();if(e.severity==="ok"||e.severity==="low")return[];let t=e.severity==="high"?"critical":"medium",n=e.severity==="high"?`Disk pressure HIGH \u2014 ${bt(e.freeBytes)} free of ${bt(e.totalBytes)} (${e.freePercent.toFixed(1)}%), backups ${bt(e.backupTotalBytes)} across ${e.backupFileCount} file(s).`:`Disk pressure MEDIUM \u2014 ${bt(e.freeBytes)} free of ${bt(e.totalBytes)} (${e.freePercent.toFixed(1)}%), backups ${bt(e.backupTotalBytes)} across ${e.backupFileCount} file(s).`;return[{check:"disk_pressure_backups",severity:t,keyFacts:{freePercentBucket:Math.floor(e.freePercent/5)*5,backupGbBucket:Math.floor(e.backupTotalBytes/(1024*1024*1024)),severity:e.severity},message:n,remediation:BA(e.oldFiles)}]}function BA(e){return e.length===0?"No snapshots older than 30 days \u2014 recent ones may still be rollback-critical, leave them alone unless you know what you are doing.":`Review snapshots older than 30 days then delete: ${e.slice(0,3).map(n=>`${n.name} (${bt(n.sizeBytes)}, ${n.ageDays}d old)`).join("; ")}. See memory \`partial_corpus_swap_bug_20260519\` \u2014 keep most recent .pre-swap for rollback.`}function WA(){let e=Zd();return!e.flagged||!e.message?[]:[{check:"daemon_state_files_missing",severity:"critical",keyFacts:{missing:[...e.missing].sort().join(",")},message:e.message,remediation:e.remediation??"recall stop && recall start"}]}function bt(e){return!Number.isFinite(e)||e<=0?"0 B":e<1024?`${e} B`:e<1024**2?`${(e/1024).toFixed(1)} KB`:e<1024**3?`${(e/1024**2).toFixed(1)} MB`:`${(e/1024**3).toFixed(2)} GB`}import{createInterface as l$}from"node:readline/promises";H();We();Ba();H();Ba();H();We();Z();var qF=768*4;We();function yf(){let e=h();if(!e.prepare("SELECT name FROM sqlite_master WHERE name='vec_chunks_v1_backup'").get())return{outcome:"absent",message:"no vec_chunks_v1_backup present"};let n=e.prepare("SELECT completed_at FROM migration_state WHERE status = 'completed' ORDER BY id DESC LIMIT 1").get();return n?e.prepare("SELECT 1 AS x FROM migration_state WHERE status = 'completed' AND completed_at > datetime('now', '-30 days') ORDER BY id DESC LIMIT 1").get()?{outcome:"kept",message:`backup retained \u2014 last migration (${n.completed_at}) is within the 30-day rollback window`}:(e.exec("DROP TABLE vec_chunks_v1_backup;"),{outcome:"pruned",message:`auto-pruned vec_chunks_v1_backup \u2014 last migration (${n.completed_at}) is older than 30 days`}):{outcome:"kept",message:"backup present but no completed migration on record \u2014 left for manual inspection"}}var wf=300*1e3;function qA(){let e=process.env.RECALL_DOCTOR_TICK_MS;if(!e)return wf;let t=Number.parseInt(e,10);return!Number.isFinite(t)||t<100?wf:t}function Rf(e=Ha,t=new Date){let n=[];try{n=e()}catch(o){let a=o instanceof Error?o.message:String(o);process.stderr.write(`[doctor-tick] check pipeline failed: ${a}
|
|
2178
|
+
`),n=[]}let s=Ii(),r=Gd(s,n,t);Mi(r);try{Wd().catch(o=>{let a=o instanceof Error?o.message:String(o);process.stderr.write(`[doctor-tick] auto-prune failed: ${a}
|
|
2131
2179
|
`)})}catch(o){let a=o instanceof Error?o.message:String(o);process.stderr.write(`[doctor-tick] auto-prune failed (sync): ${a}
|
|
2132
|
-
`)}
|
|
2180
|
+
`)}try{let o=yf();o.outcome==="pruned"&&console.log(`[doctor-tick] ${o.message}`)}catch(o){let a=o instanceof Error?o.message:String(o);process.stderr.write(`[doctor-tick] backup auto-prune failed: ${a}
|
|
2181
|
+
`)}return r}function kf(e=Ha){let t=qA(),n=setInterval(()=>{Rf(e)},t);return typeof n.unref=="function"&&n.unref(),{stop:()=>clearInterval(n),runOnce:()=>{Rf(e)}}}var JA=Math.max(1,XA().length),Af=String(Math.max(2,Math.floor(JA/2)));process.env.OMP_NUM_THREADS||(process.env.OMP_NUM_THREADS=Af);process.env.ORT_NUM_THREADS||(process.env.ORT_NUM_THREADS=Af);var GA=360*60*1e3,YA=60*1e3,zA=1440*60*1e3,KA=300*1e3,VA=300*1e3,QA=10*1e3,ZA=1440*60*1e3,ex=30*1e3,tx=500,nx=1500,sx=6e4;async function rx(){let e=await Sf(),t=Nd();(!t||t.length<32)&&(console.error("[daemon] FATAL: daemon token mint returned empty or undersized \u2014 refusing to start"),process.exit(1));try{kg().repointed&&console.log("[daemon] mcp self-heal: ~/.claude.json repointed (was stale)")}catch(v){console.error("[daemon] mcp self-heal failed (non-fatal, daemon continues):",v)}let n=await hf(e,t),s={pid:process.pid,port:e,startedAt:new Date().toISOString()};Od({...s,token:t}),Eo(ae().enabled,"boot");try{Sn();let v=Tl();v>0&&console.log(`[daemon] archive: migrated ${v} hot row(s) into archive.sqlite`)}catch(v){let i=v instanceof Error?v.message:String(v);console.error(`[daemon] archive migration failed: ${i}`)}let r=Id({db:h(),walPath:`${ze}-wal`}),o=Sd(),a=setInterval(()=>{Td()},sx),c=()=>{try{Ks()}catch(v){console.error("[daemon] suggestion scan failed:",v)}},u=setTimeout(c,YA),d=setInterval(c,GA),p=()=>{try{let v=D.reapStaleLinks();(v.pruned_pids||v.pruned_sessions)&&console.log(`[daemon] reaper: pruned ${v.pruned_pids} pid${v.pruned_pids===1?"":"s"}, ${v.pruned_sessions} session link${v.pruned_sessions===1?"":"s"}`)}catch(v){console.error("[daemon] stale-link reaper failed:",v)}},f=setTimeout(p,KA),b=setInterval(p,zA),T=()=>{try{let v=D.gcDeadPids();(v.pruned_pids||v.pruned_sessions)&&console.log(`[daemon] dead-pid gc: pruned ${v.pruned_pids} pid${v.pruned_pids===1?"":"s"}, ${v.pruned_sessions} session link${v.pruned_sessions===1?"":"s"}`)}catch(v){console.error("[daemon] dead-pid gc failed:",v)}},S=setTimeout(T,QA),R=setInterval(T,VA),A=()=>{ec().then(v=>{v.ran&&v.revoked&&console.log(`[daemon] license check: REVOKED${v.reason?` (${v.reason})`:""}`)}).catch(v=>{console.error("[daemon] license check failed:",v)})},I=setTimeout(A,ex),j=setInterval(A,ZA),J=Fl();(async()=>{try{if(!Ge())return;let v=h().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get();if(v.n===0)return;if(!ae().autoResumeWorker){console.log(`[daemon] vector-worker dormant: ${v.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 l=await it();if(l.tier!=="pro"){console.log(`[daemon] vector-worker auto-resume skipped: ${v.n} chunk(s) pending but license tier is ${l.tier}`);return}le().loaded||await Be(),Me().running||(vn(),console.log(`[daemon] vector-worker auto-resumed: ${v.n} chunk(s) pending`))}catch(v){let i=v instanceof Error?v.message:String(v);console.error(`[daemon] vector-worker auto-resume failed: ${i}`)}})();let M=kf(),F=setInterval(()=>{try{let{restored:v}=Cd({...s,token:t});v.length>0&&console.log(`[daemon] state-files heal: restored ${v.join(",")}`)}catch(v){let i=v instanceof Error?v.message:String(v);console.error(`[daemon] state-files heal failed: ${i}`)}},3e4);typeof F.unref=="function"&&F.unref();let ne=ar();console.log(ne==="enabled"?"[auto-prune] mode=enabled \u2014 orphans >10min and runaway-CPU MCPs will be killed":ne==="off"?"[auto-prune] mode=off \u2014 auto-prune fully disabled":"[auto-prune] mode=dry-run (set RECALL_AUTO_PRUNE=enabled to enforce)");let U=v=>{console.log(`[daemon] received ${v}, shutting down`),clearTimeout(u),clearInterval(d),clearTimeout(f),clearInterval(b),clearTimeout(S),clearInterval(R),clearTimeout(I),clearInterval(j),clearInterval(a),clearInterval(F),J.stop(),r.stop(),M.stop(),o.close(),n.close(),xi(),process.exit(0)};process.on("SIGTERM",()=>U("SIGTERM")),process.on("SIGINT",()=>U("SIGINT")),process.on("SIGHUP",()=>U("SIGHUP"));try{let v=Tf(h());v.total>0&&console.log(`[daemon] projects backfill: ephemeral=${v.ephemeral} walked=${v.walked} folded=${v.folded} deferred=${v.deferred} (total=${v.total}, ${v.durationMs}ms)`)}catch(v){console.error("[daemon] projects backfill failed:",v)}console.log(`[daemon] ready on http://127.0.0.1:${e} pid=${process.pid}`),setTimeout(()=>{Ds().then(v=>{console.log(`[daemon] boot sweep: scanned ${v.scanned} live claude(s), linked ${v.linked}, renamed ${v.renamed}, ambiguous_cwd ${v.ambiguous_cwd}`)}).catch(v=>{console.error("[daemon] boot sweep failed:",v)})},tx),setTimeout(()=>{yi().then(v=>{console.log(`[daemon] ingestion sweep: scanned=${v.scanned} reindexed=${v.reindexed} up-to-date=${v.upToDate} skipped=${v.skipped} errors=${v.errors} (${v.durationMs}ms)`)}).catch(v=>{console.error("[daemon] ingestion sweep failed:",v)})},nx)}rx().catch(e=>{console.error("[daemon] fatal:",e),xi(),process.exit(1)});
|