@openchambery/web 2.1.9-beta.6 → 2.1.9-beta.7
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/assets/{AssistantView-3Rfvw4sl.js → AssistantView-CuT6bDtM.js} +3 -3
- package/dist/assets/{DiagramView-CgmWfIrQ.js → DiagramView-D8kZ5sgP.js} +1 -1
- package/dist/assets/{MarkdownRenderer-CptQecYA.js → MarkdownRenderer-3Cm5B3ni.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-BsOrNJni.js → MarkdownRendererImpl-BSYMaqFX.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-0EuyxKix.js → MarkstreamRendererImpl-B9VRdRju.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-CI00mBRX.js → MobileDetailNavigation-ZFfIEYTU.js} +1 -1
- package/dist/assets/{MobileShareBridge-BSRHQmvf.js → MobileShareBridge-DpuXJdlq.js} +1 -1
- package/dist/assets/{MobileTabPageHeader-CdgleYWQ.js → MobileTabPageHeader-DYRGHein.js} +1 -1
- package/dist/assets/{MultiRunWindow-D6IK1Zkp.js → MultiRunWindow-BaJgbZP9.js} +1 -1
- package/dist/assets/{SettingsView-D_lZOSSF.js → SettingsView-C18d1an5.js} +1 -1
- package/dist/assets/{SettingsWindow-ByLmdF2a.js → SettingsWindow-B07X5mru.js} +1 -1
- package/dist/assets/{TerminalView-Th-9Zt_f.js → TerminalView-BbPFQ22Z.js} +1 -1
- package/dist/assets/{ToolOutputDialog-D_fxjrzt.js → ToolOutputDialog-Bt3ou350.js} +1 -1
- package/dist/assets/{appThemeRegistry-wX31Kn4M.js → appThemeRegistry-BNncg3Lg.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-BbnpDd2Y.js → fileMentionTouchSelection-BF0RYuUw.js} +1 -1
- package/dist/assets/{gitApi-sk4tCimN.js → gitApi-KzJZseRG.js} +1 -1
- package/dist/assets/{insertionBoundaries-Dup8RNq4.js → insertionBoundaries-BoN2D2CB.js} +1 -1
- package/dist/assets/{main-D7knwcbE.js → main-C2COAbeT.js} +4 -4
- package/dist/assets/{main-BVHlGGZs.js → main-DBLCcpJe.js} +2 -2
- package/dist/assets/{mcpQueries-DLcJMbJ8.js → mcpQueries-BF9h43FH.js} +1 -1
- package/dist/assets/{miniChat-C71dZf-x.js → miniChat-Bv5ORJmD.js} +2 -2
- package/dist/assets/{mobile-BVDtJrmi.js → mobile-BzKeTq52.js} +2 -2
- package/dist/assets/{multirun-BaN9JOfb.js → multirun-BHANiphy.js} +1 -1
- package/dist/assets/{projectMeta-WqSqDsLK.js → projectMeta-CuIJ2wzX.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-BZ3mBJuQ.js → renderElectronMiniChatApp-B4NSy0ql.js} +2 -2
- package/dist/assets/{renderMobileApp-D5oYObXh.js → renderMobileApp-BnIawAK6.js} +4 -4
- package/dist/assets/{runtimeConfig-K7gJK2wK.js → runtimeConfig-BR335S7R.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-C9YZ_XHW.js → runtimeEndpointReset-grACS-Iv.js} +1 -1
- package/dist/assets/{sessionChangeSummary-Db70oATx.js → sessionChangeSummary-Y6g4Hmn3.js} +1 -1
- package/dist/assets/{sessionLookup-oPdIysdc.js → sessionLookup-Ntvtx26F.js} +1 -1
- package/dist/assets/{speechSettingsQueries-Z5qhFY5C.js → speechSettingsQueries-BS7COcv4.js} +1 -1
- package/dist/assets/{useAppFontEffects-C5__ugup.js → useAppFontEffects-DsaIbgdU.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-MTlcH4Zm.js → useDesktopWindowControlsLayout-DFuraQa1.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-Ty8RNv4U.js → useEffectiveDirectory-Bw6EMInE.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-SqU7Bc3B.js → useMobileNavigationStore-BN866WTh.js} +3 -3
- package/dist/assets/{useWorkerHighlightedLines-5ZbHo-Qd.js → useWorkerHighlightedLines-DsyP6A_g.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/index.js +1 -0
- package/server/lib/opencode/session-directory-runtime.js +14 -1
- package/server/lib/opencode/session-directory-runtime.test.js +62 -0
- package/server/lib/session-index/DOCUMENTATION.md +18 -0
- package/server/lib/session-index/service.js +9 -0
- package/server/lib/session-index/sync-runtime.js +34 -0
- package/server/lib/session-index/sync-runtime.test.js +27 -0
package/dist/assets/{useWorkerHighlightedLines-5ZbHo-Qd.js → useWorkerHighlightedLines-DsyP6A_g.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t}from"./runtime-fetch-WgH0pROs.js";import{Mn as n,ln as r}from"./persistence-BKszKaoQ.js";import{it as i,rt as a}from"./vendor-base-ui-CdzJ6My_.js";import{n as o}from"./platform-CgwxwfA4.js";import{v as s}from"./useUIStore-CEqU9Ujv.js";import{n as c}from"./vendor-react-BP2cRVqX.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-xol_6BsB.js";import{f as d,r as f,t as p}from"./button-CWSs9Baz.js";import{n as m}from"./useThemeSystem-8jhE4Zyk.js";import{n as h}from"./Icon-BCFeHDWx.js";import{i as g,n as _,t as v}from"./tooltip-CV6dvLBp.js";import{a as y,i as b,o as x,t as S}from"./image-loading-Bj11k4gM.js";import{n as C}from"./MarkdownRenderer-CptQecYA.js";import{n as w,o as T,s as E,t as D}from"./markdown-worker-DFNZ3KJ_.js";import{n as ee}from"./markdownTheme-CV7_0QAL.js";import{t as te}from"./vendor-tanstack-react-virtual-B_EO--9t.js";var O=e(i(),1),k=c(),A=a(),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)(h,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(h,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(h,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,m=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)(g,{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)(_,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(v,{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]!==m||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":m,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=m,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 S(e,t??new AbortController().signal),await G(n,t)}finally{n&&y(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=x(a,r);if(b(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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),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}=m(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=ee(l),t[0]=l,t[1]=d);let p=d,h,g;t[2]!==n||t[3]!==o||t[4]!==c?(h=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},g=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=h,t[6]=g):(h=t[5],g=t[6]),O.useLayoutEffect(h,g);let _,v;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(_=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return D(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()}},v=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=_,t[12]=v):(_=t[11],v=t[12]),O.useEffect(_,v);let y;t[13]===i?y=t[14]:(y=f(`typography-code`,i),t[13]=i,t[14]=y);let b=p,x;t[15]!==a||t[16]!==b?(x={...b,...a},t[15]=a,t[16]=b,t[17]=x):x=t[17];let S;return t[18]!==y||t[19]!==x?(S=(0,A.jsx)(`div`,{ref:u,className:y,style:x}),t[18]=y,t[19]=x,t[20]=S):S=t[20],S},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):E(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{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t}from"./runtime-fetch-WgH0pROs.js";import{Mn as n,ln as r}from"./persistence-BKszKaoQ.js";import{it as i,rt as a}from"./vendor-base-ui-CdzJ6My_.js";import{n as o}from"./platform-CgwxwfA4.js";import{v as s}from"./useUIStore-CEqU9Ujv.js";import{n as c}from"./vendor-react-BP2cRVqX.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-xol_6BsB.js";import{f as d,r as f,t as p}from"./button-CWSs9Baz.js";import{n as m}from"./useThemeSystem-8jhE4Zyk.js";import{n as h}from"./Icon-BCFeHDWx.js";import{i as g,n as _,t as v}from"./tooltip-CV6dvLBp.js";import{a as y,i as b,o as x,t as S}from"./image-loading-Bj11k4gM.js";import{n as C}from"./MarkdownRenderer-3Cm5B3ni.js";import{n as w,o as T,s as E,t as D}from"./markdown-worker-DFNZ3KJ_.js";import{n as ee}from"./markdownTheme-CV7_0QAL.js";import{t as te}from"./vendor-tanstack-react-virtual-B_EO--9t.js";var O=e(i(),1),k=c(),A=a(),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)(h,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(h,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(h,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,m=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)(g,{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)(_,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(v,{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]!==m||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":m,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=m,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 S(e,t??new AbortController().signal),await G(n,t)}finally{n&&y(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=x(a,r);if(b(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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),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}=m(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=ee(l),t[0]=l,t[1]=d);let p=d,h,g;t[2]!==n||t[3]!==o||t[4]!==c?(h=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},g=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=h,t[6]=g):(h=t[5],g=t[6]),O.useLayoutEffect(h,g);let _,v;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(_=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return D(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()}},v=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=_,t[12]=v):(_=t[11],v=t[12]),O.useEffect(_,v);let y;t[13]===i?y=t[14]:(y=f(`typography-code`,i),t[13]=i,t[14]=y);let b=p,x;t[15]!==a||t[16]!==b?(x={...b,...a},t[15]=a,t[16]=b,t[17]=x):x=t[17];let S;return t[18]!==y||t[19]!==x?(S=(0,A.jsx)(`div`,{ref:u,className:y,style:x}),t[18]=y,t[19]=x,t[20]=S):S=t[20],S},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):E(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-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-DBLCcpJe.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
@@ -572,7 +572,7 @@
|
|
|
572
572
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-rxsMWL0o.js">
|
|
573
573
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
574
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BR335S7R.js">
|
|
576
576
|
<link rel="stylesheet" crossorigin href="/assets/src-Cq3Axl32.css">
|
|
577
577
|
</head>
|
|
578
578
|
<body class="h-full bg-background text-foreground">
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-Bv5ORJmD.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-rxsMWL0o.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BR335S7R.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/src-Cq3Axl32.css">
|
|
99
99
|
</head>
|
|
100
100
|
<body class="h-full bg-background text-foreground">
|
package/dist/mobile.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-BzKeTq52.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-BpCQ-dPr.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CMiMO5Yj.js">
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-rxsMWL0o.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BR335S7R.js">
|
|
87
87
|
<link rel="stylesheet" crossorigin href="/assets/src-Cq3Axl32.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1638,6 +1638,7 @@ async function main(options = {}) {
|
|
|
1638
1638
|
getOpenCodeAuthHeaders,
|
|
1639
1639
|
waitForOpenCodeReady,
|
|
1640
1640
|
fetchFn: indexSessionDirectories.fetchSessionList,
|
|
1641
|
+
isDirectoryMissing: indexSessionDirectories.isDirectoryMissing,
|
|
1641
1642
|
resolveDirectory: indexSessionDirectories.resolveDirectory,
|
|
1642
1643
|
projectSessions: (sessions) => {
|
|
1643
1644
|
const snap = sessionMetadataStore.getSnapshotSync();
|
|
@@ -8,6 +8,7 @@ const directoryOf = (session) => session?.location?.directory ?? session?.direct
|
|
|
8
8
|
export const createSessionDirectoryRuntime = ({
|
|
9
9
|
getRuntimeUrl,
|
|
10
10
|
realpath = fs.realpathSync.native,
|
|
11
|
+
stat = fs.promises.stat,
|
|
11
12
|
fetchFn = (...args) => globalThis.fetch(...args),
|
|
12
13
|
now = Date.now,
|
|
13
14
|
} = {}) => {
|
|
@@ -38,6 +39,18 @@ export const createSessionDirectoryRuntime = ({
|
|
|
38
39
|
return resolved;
|
|
39
40
|
};
|
|
40
41
|
const resolveDirectory = (value) => localPath(value) ?? normalize(value);
|
|
42
|
+
const isDirectoryMissing = async (value) => {
|
|
43
|
+
const input = normalize(value);
|
|
44
|
+
if (!input || !isLocal()) return false;
|
|
45
|
+
const runtime = scope;
|
|
46
|
+
try {
|
|
47
|
+
await stat(input);
|
|
48
|
+
return false;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Permission and I/O failures are not proof of deletion.
|
|
51
|
+
return error?.code === 'ENOENT' && isLocal() && scope === runtime;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
41
54
|
const projectSession = (session) => {
|
|
42
55
|
if (!session || typeof session !== 'object') return session;
|
|
43
56
|
const directory = resolveDirectory(directoryOf(session));
|
|
@@ -106,5 +119,5 @@ export const createSessionDirectoryRuntime = ({
|
|
|
106
119
|
headers.set('content-type', 'application/json');
|
|
107
120
|
return new Response(JSON.stringify(body), { status: response.status, headers });
|
|
108
121
|
};
|
|
109
|
-
return { isLocal, resolveDirectory, projectSession, projectEvent, fetchSessionList };
|
|
122
|
+
return { isLocal, resolveDirectory, isDirectoryMissing, projectSession, projectEvent, fetchSessionList };
|
|
110
123
|
};
|
|
@@ -24,6 +24,68 @@ const session = (id, directory, updated = 1) => ({ id, title: id, location: { di
|
|
|
24
24
|
const json = (value, status = 200) => new Response(JSON.stringify(value), { status, headers: { 'content-type': 'application/json' } });
|
|
25
25
|
|
|
26
26
|
describe('physical session directory identity', () => {
|
|
27
|
+
it('checks deletion only on the owning local runtime and preserves uncertain failures', async () => {
|
|
28
|
+
let url = 'http://127.0.0.1:4000';
|
|
29
|
+
const stat = vi.fn(async () => { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); });
|
|
30
|
+
const runtime = createSessionDirectoryRuntime({ getRuntimeUrl: () => url, stat });
|
|
31
|
+
expect(await runtime.isDirectoryMissing('/repo')).toBe(true);
|
|
32
|
+
stat.mockRejectedValueOnce(Object.assign(new Error('denied'), { code: 'EACCES' }));
|
|
33
|
+
expect(await runtime.isDirectoryMissing('/repo')).toBe(false);
|
|
34
|
+
stat.mockRejectedValueOnce(Object.assign(new Error('I/O'), { code: 'EIO' }));
|
|
35
|
+
expect(await runtime.isDirectoryMissing('/repo')).toBe(false);
|
|
36
|
+
url = 'https://remote.example';
|
|
37
|
+
stat.mockClear();
|
|
38
|
+
expect(await runtime.isDirectoryMissing('/repo')).toBe(false);
|
|
39
|
+
expect(stat).not.toHaveBeenCalled();
|
|
40
|
+
url = 'http://127.0.0.1:4000';
|
|
41
|
+
stat.mockImplementationOnce(async () => {
|
|
42
|
+
url = 'https://remote.example';
|
|
43
|
+
throw Object.assign(new Error('missing'), { code: 'ENOENT' });
|
|
44
|
+
});
|
|
45
|
+
expect(await runtime.isDirectoryMissing('/repo')).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('cleans missing empty directories, skips scans without losing history, and recovers restored paths', async () => {
|
|
49
|
+
const { real, other } = fixture();
|
|
50
|
+
const missing = path.join(other, 'deleted');
|
|
51
|
+
const history = path.join(other, 'history');
|
|
52
|
+
const partial = path.join(other, 'partial');
|
|
53
|
+
const child = path.join(other, 'child');
|
|
54
|
+
const directories = createSessionDirectoryRuntime();
|
|
55
|
+
const service = createSessionIndexService({ dbPath: ':memory:' });
|
|
56
|
+
for (const directory of [real, missing, history, partial, child]) {
|
|
57
|
+
service.replaceDirectory({ directory, sessions: directory === history ? [session('old', history)] : [], hasMore: directory === partial });
|
|
58
|
+
}
|
|
59
|
+
service.replaceChildSessions(child, 'parent', [{ id: 'child' }]);
|
|
60
|
+
service.setPinned('old', 1);
|
|
61
|
+
const fetchFn = vi.fn(async () => json([]));
|
|
62
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
63
|
+
sessionIndexService: service,
|
|
64
|
+
isDirectoryMissing: directories.isDirectoryMissing,
|
|
65
|
+
buildOpenCodeUrl: (route) => `http://localhost${route}`,
|
|
66
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
67
|
+
fetchFn,
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
await vi.waitFor(() => expect(service.snapshot().directories.map(x => x.directory)).not.toContain(missing));
|
|
71
|
+
expect(service.snapshot().directories.map(x => x.directory)).toEqual(expect.arrayContaining([real, history, partial, child]));
|
|
72
|
+
runtime.enqueue([missing, history, partial, child]);
|
|
73
|
+
await vi.waitFor(() => expect(runtime.snapshot().sync.active).toBe(false));
|
|
74
|
+
expect(fetchFn).not.toHaveBeenCalled();
|
|
75
|
+
expect(service.findBySessionId('old')).toBeTruthy();
|
|
76
|
+
expect(service.snapshot().pinnedSessionIds).toEqual(['old']);
|
|
77
|
+
expect(service.snapshot().directories.map(x => x.directory)).not.toContain(missing);
|
|
78
|
+
fs.mkdirSync(missing);
|
|
79
|
+
runtime.enqueue([missing]);
|
|
80
|
+
await vi.waitFor(() => expect(runtime.snapshot().sync.active).toBe(false));
|
|
81
|
+
expect(fetchFn).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(service.snapshot().directories.map(x => x.directory)).toContain(missing);
|
|
83
|
+
} finally {
|
|
84
|
+
runtime.stop();
|
|
85
|
+
service.close();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
27
89
|
it('recovers unindexed alias history across pages without admitting another worktree', async () => {
|
|
28
90
|
const { real, alias, other } = fixture();
|
|
29
91
|
const requests = [];
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Session Index Runtime
|
|
2
2
|
|
|
3
|
+
## Missing local directories
|
|
4
|
+
|
|
5
|
+
The Host probes local directory existence asynchronously before background root
|
|
6
|
+
and child scans. Confirmed ENOENT skips upstream loading without replacing history
|
|
7
|
+
with an empty result. Remote upstreams never use the Host filesystem as evidence;
|
|
8
|
+
permission and I/O errors are not deletion. Checks are not negatively cached, so
|
|
9
|
+
a recreated directory participates in the next requested sync.
|
|
10
|
+
|
|
11
|
+
At startup and every 60 seconds, the sync runtime checks empty, exhausted indexed
|
|
12
|
+
directories. It removes only directory shells with no summary rows, no child rows,
|
|
13
|
+
and no more pages, using one conditional SQLite deletion. Session history and pins
|
|
14
|
+
are preserved. This maintenance does not touch code files or upstream projects.
|
|
15
|
+
Runtime switches and shutdown discard pending checks; successful cleanup publishes
|
|
16
|
+
a revision tip. Snapshot GETs do not perform filesystem scans. Old project paths
|
|
17
|
+
may still be submitted by clients, but pre-scan checks prevent recreating empty
|
|
18
|
+
shells for missing paths. App or external worktree deletion is picked up by the
|
|
19
|
+
same maintenance pass (within 60 seconds, plus filesystem check time).
|
|
20
|
+
|
|
3
21
|
## Physical directory identity
|
|
4
22
|
|
|
5
23
|
The Host injects `session-directory-runtime.js` into index writes, background
|
|
@@ -679,9 +679,18 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
679
679
|
return result.changes > 0;
|
|
680
680
|
});
|
|
681
681
|
|
|
682
|
+
const removeEmptyDirectory = (directory) => db.prepare(`
|
|
683
|
+
DELETE FROM runtime_directory WHERE runtime_key = ? AND directory = ? AND has_more = 0
|
|
684
|
+
AND NOT EXISTS (SELECT 1 FROM session_summary s
|
|
685
|
+
WHERE s.runtime_key = runtime_directory.runtime_key AND s.directory = runtime_directory.directory)
|
|
686
|
+
AND NOT EXISTS (SELECT 1 FROM session_child c
|
|
687
|
+
WHERE c.runtime_key = runtime_directory.runtime_key AND c.directory = runtime_directory.directory)
|
|
688
|
+
`).run(runtimeKey(), directory).changes > 0;
|
|
689
|
+
|
|
682
690
|
try { reconcileDirectoryAliases(runtimeKey()); } catch (error) { db.close(); throw error; }
|
|
683
691
|
return {
|
|
684
692
|
replaceDirectory,
|
|
693
|
+
removeEmptyDirectory,
|
|
685
694
|
replaceDirectories,
|
|
686
695
|
upsert,
|
|
687
696
|
upsertAndReportChange,
|
|
@@ -82,6 +82,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
82
82
|
projectSessions = null,
|
|
83
83
|
fetchFn = globalThis.fetch,
|
|
84
84
|
resolveDirectory = (value) => value,
|
|
85
|
+
isDirectoryMissing = null,
|
|
85
86
|
now = Date.now,
|
|
86
87
|
setTimer = setTimeout,
|
|
87
88
|
clearTimer = clearTimeout,
|
|
@@ -182,6 +183,12 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
182
183
|
.find((entry) => entry.directory === directory);
|
|
183
184
|
|
|
184
185
|
const fetchDirectory = async (task) => {
|
|
186
|
+
if (isDirectoryMissing && await isDirectoryMissing(task.directory)) {
|
|
187
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
188
|
+
sessionIndexService.removeEmptyDirectory(task.directory);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
185
192
|
// v2 `session.list` cursor pagination. Collect candidates across pages,
|
|
186
193
|
// then re-project once before commit so a Host archive that lands mid-sync
|
|
187
194
|
// cannot resurrect a row. Root/system filters run before the 20-slot budget.
|
|
@@ -325,6 +332,8 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
325
332
|
};
|
|
326
333
|
|
|
327
334
|
const fetchChildFlag = async (task) => {
|
|
335
|
+
if (isDirectoryMissing && await isDirectoryMissing(task.directory)) return 'discarded';
|
|
336
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
328
337
|
const controller = new AbortController();
|
|
329
338
|
currentController = controller;
|
|
330
339
|
currentWasPreempted = false;
|
|
@@ -513,6 +522,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
513
522
|
const stop = () => {
|
|
514
523
|
if (stopped) return;
|
|
515
524
|
stopped = true;
|
|
525
|
+
if (cleanupTimer !== undefined) clearTimer(cleanupTimer);
|
|
516
526
|
currentController?.abort();
|
|
517
527
|
const interruptedDirectories = [...queuedDirectories];
|
|
518
528
|
queue.length = 0;
|
|
@@ -523,5 +533,29 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
523
533
|
notify(true);
|
|
524
534
|
};
|
|
525
535
|
|
|
536
|
+
let cleanupTimer;
|
|
537
|
+
const cleanupMissingDirectories = async () => {
|
|
538
|
+
const key = sessionIndexService.getRuntimeKey();
|
|
539
|
+
let changed = false;
|
|
540
|
+
try {
|
|
541
|
+
for (const entry of sessionIndexService.snapshot().directories) {
|
|
542
|
+
if (stopped || key !== sessionIndexService.getRuntimeKey()) break;
|
|
543
|
+
if (entry.sessions.length || entry.hasMore) continue;
|
|
544
|
+
const missing = await isDirectoryMissing(entry.directory);
|
|
545
|
+
if (stopped || key !== sessionIndexService.getRuntimeKey()) break;
|
|
546
|
+
if (missing) changed = sessionIndexService.removeEmptyDirectory(entry.directory) || changed;
|
|
547
|
+
}
|
|
548
|
+
if (changed && !stopped && key === sessionIndexService.getRuntimeKey()) notify(true);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.warn('[SessionIndex] Directory cleanup failed:', error);
|
|
551
|
+
} finally {
|
|
552
|
+
if (!stopped) {
|
|
553
|
+
cleanupTimer = setTimer(cleanupMissingDirectories, 60_000);
|
|
554
|
+
cleanupTimer?.unref?.();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
if (isDirectoryMissing) void cleanupMissingDirectories();
|
|
559
|
+
|
|
526
560
|
return { enqueue, noteInteractiveRequest, publishChange, snapshot, waitForChange, stop };
|
|
527
561
|
};
|
|
@@ -41,6 +41,33 @@ const isFullyIdle = (runtime) => (
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
describe('session index background sync runtime', () => {
|
|
44
|
+
it('rechecks missing directories periodically and stops maintenance on shutdown', async () => {
|
|
45
|
+
const service = createService([{ directory: '/repo', sessions: [], hasMore: false }]);
|
|
46
|
+
service.removeEmptyDirectory = vi.fn(() => true);
|
|
47
|
+
const isDirectoryMissing = vi.fn(async () => false);
|
|
48
|
+
const timers = [];
|
|
49
|
+
const clearTimer = vi.fn();
|
|
50
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
51
|
+
sessionIndexService: service,
|
|
52
|
+
isDirectoryMissing,
|
|
53
|
+
setTimer: (fn, ms) => { const timer = { fn, ms }; timers.push(timer); return timer; },
|
|
54
|
+
clearTimer,
|
|
55
|
+
});
|
|
56
|
+
await waitUntil(() => timers.length === 1);
|
|
57
|
+
expect(timers[0].ms).toBe(60_000);
|
|
58
|
+
expect(service.removeEmptyDirectory).not.toHaveBeenCalled();
|
|
59
|
+
isDirectoryMissing.mockResolvedValue(true);
|
|
60
|
+
await timers[0].fn();
|
|
61
|
+
expect(service.removeEmptyDirectory).toHaveBeenCalledWith('/repo');
|
|
62
|
+
expect(runtime.snapshot().revision).toBe(1);
|
|
63
|
+
runtime.stop();
|
|
64
|
+
expect(clearTimer).toHaveBeenCalledWith(timers[1]);
|
|
65
|
+
const calls = isDirectoryMissing.mock.calls.length;
|
|
66
|
+
await timers[1].fn();
|
|
67
|
+
expect(isDirectoryMissing).toHaveBeenCalledTimes(calls);
|
|
68
|
+
expect(timers).toHaveLength(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
44
71
|
it('coalesces hundreds of directory/child checks and immediately publishes the final revision', async () => {
|
|
45
72
|
vi.useFakeTimers();
|
|
46
73
|
try {
|