@openchambery/web 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/assets/{AssistantView-ByGdaGiF.js → AssistantView-CWviBCAP.js} +3 -3
  2. package/dist/assets/{DiagramView-PB0pBILb.js → DiagramView-Cm7xDZ5t.js} +1 -1
  3. package/dist/assets/{MarkdownRenderer-H1MIHXzt.js → MarkdownRenderer-CJ5t5GCJ.js} +2 -2
  4. package/dist/assets/{MarkdownRendererImpl-CZ8GeckU.js → MarkdownRendererImpl-De4VmTtg.js} +1 -1
  5. package/dist/assets/{MarkstreamRendererImpl-BKE6YZ2N.js → MarkstreamRendererImpl-B0zZVAuv.js} +1 -1
  6. package/dist/assets/{MobileDetailNavigation-D9KkIrWy.js → MobileDetailNavigation-BOuvF4sv.js} +1 -1
  7. package/dist/assets/{MobileShareBridge-1yCjq-60.js → MobileShareBridge-Brsf5H1u.js} +1 -1
  8. package/dist/assets/MobileTabPageHeader-BLlFC_jN.js +2 -0
  9. package/dist/assets/{MultiRunWindow-YCFM6or1.js → MultiRunWindow-BUdK_rVf.js} +1 -1
  10. package/dist/assets/SettingsView-LnqgjCmk.js +25 -0
  11. package/dist/assets/{SettingsWindow-jOZ7L6K8.js → SettingsWindow-AfOCGfXC.js} +1 -1
  12. package/dist/assets/{TerminalView-CI-KsDdH.js → TerminalView-Csox-OUH.js} +1 -1
  13. package/dist/assets/{ToolOutputDialog-izZjgkkg.js → ToolOutputDialog-DYnLpM-I.js} +1 -1
  14. package/dist/assets/{appThemeRegistry-MiWlVk4e.js → appThemeRegistry-Dh7evuFT.js} +1 -1
  15. package/dist/assets/{fileMentionTouchSelection-Bn0f83D3.js → fileMentionTouchSelection-DGcKhwNg.js} +1 -1
  16. package/dist/assets/{gitApi-DpwH7xTg.js → gitApi-DJiNTnOw.js} +1 -1
  17. package/dist/assets/{insertionBoundaries-Cst5Z6Cd.js → insertionBoundaries-BZ4y7_dO.js} +1 -1
  18. package/dist/assets/{main-Dkzuy7EG.js → main-C5K1b2p3.js} +7 -7
  19. package/dist/assets/{main-CEs8ipuu.js → main-D10YD-D5.js} +2 -2
  20. package/dist/assets/{mcpQueries-BBpCJgvK.js → mcpQueries-om4qg52L.js} +1 -1
  21. package/dist/assets/{miniChat-CR7k42bZ.js → miniChat-YtAlWDXn.js} +2 -2
  22. package/dist/assets/{mobile-rvypjUpd.js → mobile-Bix-1h3p.js} +2 -2
  23. package/dist/assets/{multirun-DqmJk0mh.js → multirun-Cl0qI_UR.js} +1 -1
  24. package/dist/assets/{projectMeta-BhKfv5cj.js → projectMeta-ClpG1g5k.js} +1 -1
  25. package/dist/assets/{renderElectronMiniChatApp-BLay6f9j.js → renderElectronMiniChatApp-knc9ALFK.js} +2 -2
  26. package/dist/assets/{renderMobileApp-BrikirqO.js → renderMobileApp-DvPGytV9.js} +7 -7
  27. package/dist/assets/{runtimeConfig-DjKsteH0.js → runtimeConfig-CkGIFPXV.js} +1 -1
  28. package/dist/assets/runtimeEndpointReset-BFrS_Yci.js +148 -0
  29. package/dist/assets/{sessionChangeSummary-Cgq9RPUq.js → sessionChangeSummary-DYAsA6Bi.js} +1 -1
  30. package/dist/assets/{sessionLookup-BiE36BTy.js → sessionLookup-Bc8f5Scn.js} +1 -1
  31. package/dist/assets/{src-BZ0tISrf.css → src-Cp_AK_QO.css} +1 -1
  32. package/dist/assets/{useAppFontEffects-CWzjhjXr.js → useAppFontEffects-CU_CeyJe.js} +34 -34
  33. package/dist/assets/{useDesktopWindowControlsLayout-Dtqco-Um.js → useDesktopWindowControlsLayout-D5pQxhEz.js} +1 -1
  34. package/dist/assets/{useEffectiveDirectory-DNbDaaNV.js → useEffectiveDirectory-Cn5YmSaO.js} +1 -1
  35. package/dist/assets/{useMobileNavigationStore-DYKC2swj.js → useMobileNavigationStore-Bww4SOiv.js} +19 -19
  36. package/dist/assets/{useSessionAutoCleanup-BI0xQcio.js → useSessionAutoCleanup--P_vAP6v.js} +1 -1
  37. package/dist/assets/{useWorkerHighlightedLines-BoyYALWC.js → useWorkerHighlightedLines-DOkM0xy4.js} +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/mini-chat.html +3 -3
  40. package/dist/mobile.html +3 -3
  41. package/package.json +1 -1
  42. package/server/lib/session-index/DOCUMENTATION.md +10 -4
  43. package/server/lib/session-index/service.js +19 -2
  44. package/server/lib/session-index/service.test.js +52 -0
  45. package/server/lib/session-index/sync-runtime.js +3 -17
  46. package/server/lib/session-index/sync-runtime.test.js +29 -0
  47. package/server/lib/session-metadata/DOCUMENTATION.md +6 -4
  48. package/dist/assets/MobileSurface-Dqn3OPVX.js +0 -2
  49. package/dist/assets/SettingsView-CbiuXtYG.js +0 -25
  50. package/dist/assets/runtimeEndpointReset-DbfaE3dL.js +0 -148
@@ -1,4 +1,4 @@
1
- import{o as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./runtime-fetch-CiqQd__Y.js";import{On as n,on as r}from"./persistence-CwHY3fDg.js";import{nt as i,rt as a}from"./vendor-base-ui-CnHfUmDN.js";import{n as o}from"./platform-Bxaalnc8.js";import{v as s}from"./useUIStore-DNkuudYO.js";import{n as c}from"./vendor-react-DLPElpoQ.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js";import{f as d,r as f,t as p}from"./button-CcOoDLE1.js";import{n as m}from"./Icon-BE2hkaYc.js";import{i as h,n as g,t as _}from"./tooltip-CBHR50fA.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-H1MIHXzt.js";import{n as x,o as S,s as C,t as w}from"./markdown-worker-bS5OgGO3.js";import{a as T,i as E,o as D,t as ee}from"./imageSource-BpUsiwFd.js";import{t as te}from"./vendor-tanstack-react-virtual-tlDgH6PG.js";var O=e(a(),1),k=c(),A=i(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(m,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(m,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(m,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,v=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(h,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(g,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(_,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==v||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":v,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=v,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await ee(e,t??new AbortController().signal),await G(n,t)}finally{n&&T(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=D(a,r);if(E(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
1
+ import{o as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./runtime-fetch-CiqQd__Y.js";import{On as n,on as r}from"./persistence-CwHY3fDg.js";import{nt as i,rt as a}from"./vendor-base-ui-CnHfUmDN.js";import{n as o}from"./platform-Bxaalnc8.js";import{v as s}from"./useUIStore-DNkuudYO.js";import{n as c}from"./vendor-react-DLPElpoQ.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js";import{f as d,r as f,t as p}from"./button-CcOoDLE1.js";import{n as m}from"./Icon-BE2hkaYc.js";import{i as h,n as g,t as _}from"./tooltip-CBHR50fA.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-CJ5t5GCJ.js";import{n as x,o as S,s as C,t as w}from"./markdown-worker-bS5OgGO3.js";import{a as T,i as E,o as D,t as ee}from"./imageSource-BpUsiwFd.js";import{t as te}from"./vendor-tanstack-react-virtual-tlDgH6PG.js";var O=e(a(),1),k=c(),A=i(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(m,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(m,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(m,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,v=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(h,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(g,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(_,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==v||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":v,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=v,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await ee(e,t??new AbortController().signal),await G(n,t)}finally{n&&T(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=D(a,r);if(E(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
2
2
  `).split(`
3
3
  `),r=e=>/\(\s*File has more lines\..*offset.*\)/i.test(e.trim());return{type:t,lines:n.map(e=>{let n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);if(t!==`directory`){let t=e.match(/^(\d+):\s?(.*)$/);if(t){let e=t[2],n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);return{lineNumber:i?null:Number(t[1]),text:e,isInfo:i}}}return{lineNumber:null,text:e,isInfo:i}}).filter((e,t,n)=>{if(e.text.trim().length>0)return!0;let r=n[t-1],i=n[t+1],a=!!(r?.isInfo||i?.isInfo),o=e.lineNumber!==null;return!(a&&o)})}},je=(e,t)=>{try{let n=e.trim().split(`
4
4
  `).filter(Boolean);if(n.length===0)return null;let r=[];return n.forEach(e=>{let t=e.match(/^(\s*)(.+)$/);if(t){let[,e,n]=t,i=Math.floor(e.length/2),a=!n.endsWith(`/`);r.push({name:n.replace(/\/$/,``),depth:i,isFile:a})}}),(0,A.jsx)(`div`,{className:f(`w-full min-w-0 font-mono space-y-0.5`,t?.unstyled?null:`p-3 bg-muted/20 rounded-xl border border-border/30`),style:s.tool.popup,children:r.map((e,t)=>(0,A.jsx)(`div`,{className:`min-w-0`,style:{paddingLeft:`${e.depth*20}px`},children:e.isFile?(0,A.jsx)(`span`,{className:`text-foreground/90 block truncate`,children:e.name}):(0,A.jsxs)(`span`,{className:`font-semibold text-foreground block truncate`,children:[e.name,`/`]})},t))})}catch{return null}},Me={backgroundColor:`var(--status-info)`,opacity:.6},Ne=(e,t,n)=>{try{let r=e.trim().split(`
package/dist/index.html CHANGED
@@ -552,7 +552,7 @@
552
552
  opacity: 0.72;
553
553
  }
554
554
  </style>
555
- <script type="module" crossorigin src="/assets/main-CEs8ipuu.js"></script>
555
+ <script type="module" crossorigin src="/assets/main-D10YD-D5.js"></script>
556
556
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
557
557
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
558
558
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -572,8 +572,8 @@
572
572
  <link rel="modulepreload" crossorigin href="/assets/terminalApi-DSJOXNRQ.js">
573
573
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
574
574
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
575
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DjKsteH0.js">
576
- <link rel="stylesheet" crossorigin href="/assets/src-BZ0tISrf.css">
575
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CkGIFPXV.js">
576
+ <link rel="stylesheet" crossorigin href="/assets/src-Cp_AK_QO.css">
577
577
  </head>
578
578
  <body class="h-full bg-background text-foreground">
579
579
  <!-- Keep the startup layer outside #root. React replaces every child of
@@ -74,7 +74,7 @@
74
74
  }
75
75
  </style>
76
76
 
77
- <script type="module" crossorigin src="/assets/miniChat-CR7k42bZ.js"></script>
77
+ <script type="module" crossorigin src="/assets/miniChat-YtAlWDXn.js"></script>
78
78
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
79
79
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
80
80
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -94,8 +94,8 @@
94
94
  <link rel="modulepreload" crossorigin href="/assets/terminalApi-DSJOXNRQ.js">
95
95
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
96
96
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
97
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DjKsteH0.js">
98
- <link rel="stylesheet" crossorigin href="/assets/src-BZ0tISrf.css">
97
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CkGIFPXV.js">
98
+ <link rel="stylesheet" crossorigin href="/assets/src-Cp_AK_QO.css">
99
99
  </head>
100
100
  <body class="h-full bg-background text-foreground">
101
101
  <div id="root" class="h-full">
package/dist/mobile.html CHANGED
@@ -63,7 +63,7 @@
63
63
  })();
64
64
  </script>
65
65
 
66
- <script type="module" crossorigin src="/assets/mobile-rvypjUpd.js"></script>
66
+ <script type="module" crossorigin src="/assets/mobile-Bix-1h3p.js"></script>
67
67
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
68
68
  <link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
69
69
  <link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
@@ -83,8 +83,8 @@
83
83
  <link rel="modulepreload" crossorigin href="/assets/terminalApi-DSJOXNRQ.js">
84
84
  <link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
85
85
  <link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
86
- <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DjKsteH0.js">
87
- <link rel="stylesheet" crossorigin href="/assets/src-BZ0tISrf.css">
86
+ <link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CkGIFPXV.js">
87
+ <link rel="stylesheet" crossorigin href="/assets/src-Cp_AK_QO.css">
88
88
  </head>
89
89
  <body class="h-full bg-background text-foreground">
90
90
  <div id="root" class="h-full"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchambery/web",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -20,15 +20,21 @@ newest 20 root-session summaries per runtime and directory. It never stores
20
20
  messages, attachments, permissions, provider data, or model metadata.
21
21
  Sessions titled `smartfetch-secondary` are temporary SmartFetch model calls;
22
22
  the index excludes them from every snapshot and clears prior summaries when a
23
- matching session update arrives. System-owned sessions are excluded by
24
- authoritative metadata only: a non-empty
23
+ matching session update arrives. Reserved leading tags `[Scheduled]`,
24
+ `[Assistant]`, and `[openchamber-llm]` also exclude background sessions,
25
+ including legacy rows without metadata. Ordinary bracketed titles are unaffected.
26
+ The same visibility predicate is used before the sync runtime's active-20 budget,
27
+ on index writes, and on native rename. Opening the index transactionally prunes
28
+ previously admitted tagged summaries, their pins and child memberships across
29
+ runtime partitions without advancing directory recency. Normal refresh refills
30
+ the bounded window. System-owned sessions are additionally excluded by a non-empty
25
31
  `metadata.openchamber.assistant.assistantID` (unless
26
32
  `metadata.openchamber.assigned.from === 'contact'` — those are visible
27
33
  coding workers the Assistant subscribed to),
28
34
  `metadata.openchamber.scheduledTask.taskID`,
29
35
  `metadata.openchamber.smallModel.purpose`, or
30
- `metadata.openchamber.llm.purpose`. Title prefixes are human labels and
31
- never participate in this filter. Metadata is the ownership/isolation signal;
36
+ `metadata.openchamber.llm.purpose`. Metadata remains the ownership/isolation signal;
37
+ reserved title tags are a separate background-list exclusion rule.
32
38
  `time.archived` is archive state (Host authority via
33
39
  `metadata.openchamber.archive.archivedAt` when present — see
34
40
  `session-metadata/`); `time.pinned` is OpenChamber-owned pin state
@@ -6,6 +6,7 @@ const require = createRequire(import.meta.url);
6
6
  const SCHEMA_VERSION = 6;
7
7
  const MAX_ROOT_SESSIONS = 20;
8
8
  const HIDDEN_SESSION_TITLES = new Set(['smartfetch-secondary']);
9
+ const BACKGROUND_SESSION_TITLE = /^\[(?:Scheduled|Assistant|openchamber-llm)\]/;
9
10
 
10
11
  const nonEmptySystemID = (value) => typeof value === 'string' && value.length > 0;
11
12
 
@@ -24,9 +25,10 @@ const isSystemSession = (session) => {
24
25
  return false;
25
26
  };
26
27
 
27
- const isVisibleSession = (session) => {
28
+ export const isVisibleSession = (session) => {
28
29
  if (!session) return false;
29
30
  if (HIDDEN_SESSION_TITLES.has(session.title)) return false;
31
+ if (typeof session.title === 'string' && BACKGROUND_SESSION_TITLE.test(session.title)) return false;
30
32
  if (isSystemSession(session)) return false;
31
33
  return true;
32
34
  };
@@ -169,6 +171,21 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
169
171
  db.prepare('INSERT OR REPLACE INTO session_index_meta(key, value) VALUES (?, ?)')
170
172
  .run('schema_version', String(SCHEMA_VERSION));
171
173
 
174
+ // Older indexes admitted title-only background runs. Prune every runtime once
175
+ // on open, before they can consume retention slots or receive activity writes.
176
+ db.transaction(() => {
177
+ const rows = db.prepare('SELECT runtime_key, session_id, title FROM session_summary').all();
178
+ const deleteSummary = db.prepare('DELETE FROM session_summary WHERE runtime_key = ? AND session_id = ?');
179
+ const deletePin = db.prepare('DELETE FROM session_pin WHERE runtime_key = ? AND session_id = ?');
180
+ const deleteChildren = db.prepare('DELETE FROM session_child WHERE runtime_key = ? AND (session_id = ? OR parent_id = ?)');
181
+ for (const row of rows) {
182
+ if (isVisibleSession(row)) continue;
183
+ deleteSummary.run(row.runtime_key, row.session_id);
184
+ deletePin.run(row.runtime_key, row.session_id);
185
+ deleteChildren.run(row.runtime_key, row.session_id, row.session_id);
186
+ }
187
+ })();
188
+
172
189
  const runtimeKey = () => runtimeKeyFor(getRuntimeConfig());
173
190
  const touchDirectory = db.prepare(`
174
191
  INSERT INTO runtime_directory(runtime_key, directory, cursor, has_more, last_synced_at, last_full_synced_at, last_accessed_at)
@@ -548,7 +565,7 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
548
565
  const timestamp = toTimestamp(observedAt);
549
566
  if (!timestamp) return false;
550
567
  const key = runtimeKey();
551
- if (HIDDEN_SESSION_TITLES.has(title)) {
568
+ if (!isVisibleSession({ title })) {
552
569
  const current = db.prepare('SELECT updated_at FROM session_summary WHERE runtime_key = ? AND session_id = ?').get(key, sessionID);
553
570
  return current && current.updated_at <= timestamp ? remove(sessionID) : false;
554
571
  }
@@ -32,6 +32,58 @@ afterEach(() => {
32
32
  });
33
33
 
34
34
  describe('Electron session index', () => {
35
+ it('excludes tagged background runs before retention and ignores their activity/status events', () => {
36
+ const service = createService({ value: 'http://runtime-a.test' });
37
+ const ordinary = Array.from({ length: 20 }, (_, i) => session(`ses_visible_${i}`, 100 - i));
38
+ const background = ['[Scheduled]', '[Assistant]', '[openchamber-llm]'].map((tag, i) => ({
39
+ ...session(`ses_background_${i}`, 200 + i), title: `${tag} Background run`,
40
+ }));
41
+ service.replaceDirectory({ directory: '/repo', sessions: [...background, ...ordinary], now: 500 });
42
+ const before = service.snapshot();
43
+ expect(before.directories[0].sessions.map((row) => row.id)).toEqual(ordinary.map((row) => row.id));
44
+ for (const row of background) {
45
+ expect(service.upsertAndReportChange(row, 600)).toBe(false);
46
+ expect(applySessionIndexEvent(service, { type: 'session.idle', properties: { sessionID: row.id } }, 700)).toBe(false);
47
+ }
48
+ expect(service.snapshot()).toEqual(before);
49
+ service.setPinned(ordinary[0].id, 1);
50
+ expect(service.rename(ordinary[0].id, '[Scheduled] Renamed', 1000)).toBe(true);
51
+ expect(service.snapshot().pinnedSessionIds).not.toContain(ordinary[0].id);
52
+ expect(service.findBySessionId(ordinary[0].id)).toBeNull();
53
+ service.close();
54
+ });
55
+
56
+ it('purges persisted tagged summaries and their pins on reopen without changing directory recency', () => {
57
+ const runtimeRef = { value: 'http://runtime-a.test' };
58
+ const service = createService(runtimeRef);
59
+ service.upsert(session('ses_background', 100), 500);
60
+ service.upsert(session('ses_visible', 90), 500);
61
+ service.setPinned('ses_background', 1);
62
+ service.setPinned('ses_visible', 2);
63
+ const before = service.snapshot().directories[0];
64
+ runtimeRef.value = 'http://runtime-b.test';
65
+ service.upsert(session('ses_background', 100), 500);
66
+ service.setPinned('ses_background', 1);
67
+ service.close();
68
+ const dbPath = path.join(tempDirectories.at(-1), 'session-index.sqlite');
69
+ const Database = createRequire(import.meta.url)('better-sqlite3');
70
+ const db = new Database(dbPath);
71
+ db.prepare('UPDATE session_summary SET title = ? WHERE session_id = ?').run('[Scheduled] Legacy run', 'ses_background');
72
+ db.close();
73
+ runtimeRef.value = 'http://runtime-a.test';
74
+ const reopened = createSessionIndexService({ dbPath, getRuntimeConfig: () => ({ apiBaseUrl: runtimeRef.value }) });
75
+ expect(reopened.findBySessionId('ses_background')).toBeNull();
76
+ expect(reopened.snapshot().pinnedSessionIds).toEqual(['ses_visible']);
77
+ expect(reopened.snapshot().directories[0]).toMatchObject({
78
+ lastAccessedAt: before.lastAccessedAt,
79
+ sessions: [expect.objectContaining({ id: 'ses_visible' })],
80
+ });
81
+ runtimeRef.value = 'http://runtime-b.test';
82
+ expect(reopened.findBySessionId('ses_background')).toBeNull();
83
+ expect(reopened.snapshot().pinnedSessionIds).toEqual([]);
84
+ reopened.close();
85
+ });
86
+
35
87
  it('ingests native renames without changing activity, status, pins or unrelated sessions', () => {
36
88
  const runtimeRef = { value: 'http://runtime-a.test' };
37
89
  const service = createService(runtimeRef);
@@ -1,3 +1,5 @@
1
+ import { isVisibleSession } from './service.js';
2
+
1
3
  const SESSION_LIMIT = 20;
2
4
  /** Max upstream list pages while skipping consecutive Host-archived rows. */
3
5
  const ARCHIVE_SKIP_PAGE_BUDGET = 5;
@@ -22,26 +24,10 @@ const isArchivedSession = (session) => {
22
24
  return typeof archived === 'number' && Number.isFinite(archived) && archived > 0;
23
25
  };
24
26
 
25
- const nonEmptySystemID = (value) => typeof value === 'string' && value.length > 0;
26
-
27
- /** Match session-index service: system sessions never consume the active-20 budget. */
28
- const isSystemSession = (session) => {
29
- const openchamber = session?.metadata?.openchamber;
30
- if (!openchamber || typeof openchamber !== 'object') return false;
31
- if (openchamber.assigned?.from === 'contact') return false;
32
- if (nonEmptySystemID(openchamber.assistant?.assistantID)) return true;
33
- if (nonEmptySystemID(openchamber.scheduledTask?.taskID)) return true;
34
- if (nonEmptySystemID(openchamber.smallModel?.purpose)) return true;
35
- if (nonEmptySystemID(openchamber.llm?.purpose)) return true;
36
- return false;
37
- };
38
-
39
27
  const isRootActiveCandidate = (session) => {
40
28
  if (!session?.id || isArchivedSession(session)) return false;
41
29
  if (typeof session.parentID === 'string' && session.parentID) return false;
42
- if (session.title === 'smartfetch-secondary') return false;
43
- if (isSystemSession(session)) return false;
44
- return true;
30
+ return isVisibleSession(session);
45
31
  };
46
32
 
47
33
  /**
@@ -41,6 +41,35 @@ const isFullyIdle = (runtime) => (
41
41
  );
42
42
 
43
43
  describe('session index background sync runtime', () => {
44
+ it('skips a full background-tagged page without consuming the active session budget', async () => {
45
+ const service = createService();
46
+ const cursors = [];
47
+ const runtime = createSessionIndexSyncRuntime({
48
+ sessionIndexService: service,
49
+ buildOpenCodeUrl: (route) => `http://opencode.test${route}`,
50
+ getOpenCodeAuthHeaders: () => ({}),
51
+ waitForOpenCodeReady: async () => true,
52
+ fetchFn: async (url) => {
53
+ if (url.pathname !== '/session' || url.searchParams.has('parentID')) return new Response(JSON.stringify([]));
54
+ const cursor = url.searchParams.get('cursor');
55
+ cursors.push(cursor);
56
+ return new Response(JSON.stringify(cursor ? {
57
+ data: Array.from({ length: 20 }, (_, i) => session(`visible-${i}`, 100 - i)), cursor: null,
58
+ } : {
59
+ data: Array.from({ length: 20 }, (_, i) => ({ ...session(`background-${i}`, 200 - i), title: '[Scheduled] Run' })),
60
+ cursor: { next: 'ordinary' },
61
+ }));
62
+ },
63
+ });
64
+ runtime.enqueue(['/repo']);
65
+ await waitUntil(() => isFullyIdle(runtime));
66
+ expect(cursors).toEqual([null, 'ordinary']);
67
+ expect(service.replaceDirectory.mock.calls[0][0].sessions.map((row) => row.id)).toEqual(
68
+ Array.from({ length: 20 }, (_, i) => `visible-${i}`),
69
+ );
70
+ runtime.stop();
71
+ });
72
+
44
73
  it('runs directory sync sequentially and publishes long-poll progress', async () => {
45
74
  const service = createService();
46
75
  let active = 0;
@@ -94,9 +94,11 @@ routes, and scheduled-task goal create all persist `openchamber.goal` through
94
94
  this store. After first create/resume persist, the server notifies the goal
95
95
  runtime with a synthetic `session.updated` so the loop can arm.
96
96
 
97
- Sidebar / session-index / notifications hide a session only when Host metadata
97
+ System ownership for sidebar / session-index / notifications is established when Host metadata
98
98
  carries a non-empty `openchamber.assistant.assistantID` (unless
99
99
  `assigned.from === 'contact'`), `openchamber.scheduledTask.taskID`,
100
- `openchamber.smallModel.purpose`, or `openchamber.llm.purpose`. Title prefixes
101
- are human labels and never participate. Every system session creator must
102
- persist the matching patch through this store.
100
+ `openchamber.smallModel.purpose`, or `openchamber.llm.purpose`. Every system
101
+ session creator must persist the matching patch through this store. Separately,
102
+ ordinary sidebar/session-index lists also exclude reserved leading title tags
103
+ `[Scheduled]`, `[Assistant]`, and `[openchamber-llm]`, including legacy sessions
104
+ without metadata; these tags do not establish metadata ownership.