@openchambery/web 2.1.9-beta.2 → 2.1.9-beta.4
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-CeP8V0X-.js → AssistantView-Bn-ie4vz.js} +3 -3
- package/dist/assets/{DiagramView-DRCsjVx9.js → DiagramView-tawWR3qT.js} +1 -1
- package/dist/assets/{MarkdownRenderer-BvKaN2KX.js → MarkdownRenderer-BtVmyJy4.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-D6iUJhtT.js → MarkdownRendererImpl-D2OCbzEp.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-B14yVm7-.js → MarkstreamRendererImpl-wLbde29-.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-e4vO0lXT.js → MobileDetailNavigation-DgCfvpng.js} +1 -1
- package/dist/assets/{MobileShareBridge-BnA02Mps.js → MobileShareBridge-D9esHh6J.js} +1 -1
- package/dist/assets/{MobileTabPageHeader-nApqJ-1X.js → MobileTabPageHeader-DzTewIJq.js} +1 -1
- package/dist/assets/{MultiRunWindow-C7Vgvpoz.js → MultiRunWindow-CrcHT1DW.js} +1 -1
- package/dist/assets/{SettingsView-Be7_mr8t.js → SettingsView-ChHvQPP-.js} +2 -2
- package/dist/assets/{SettingsWindow-ZnWK6Itb.js → SettingsWindow-BLmp9C90.js} +1 -1
- package/dist/assets/{TerminalView-BluEtXH8.js → TerminalView-D9TwFDxt.js} +1 -1
- package/dist/assets/{ToolOutputDialog-6gouT_GS.js → ToolOutputDialog-0S7fJLLw.js} +1 -1
- package/dist/assets/{appThemeRegistry-D59Upe3t.js → appThemeRegistry-C0lox0ME.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-DbICTM0g.js → fileMentionTouchSelection-CWtqJ0pV.js} +1 -1
- package/dist/assets/{gitApi-Bq2BWLAz.js → gitApi-D4E7MHPI.js} +1 -1
- package/dist/assets/{insertionBoundaries-n7dVxiuI.js → insertionBoundaries-fvEQLEE_.js} +1 -1
- package/dist/assets/{main-DRGmBqpD.js → main-6QL82M1U.js} +4 -4
- package/dist/assets/{main-BlZ-gMwg.js → main-DTaOcMED.js} +2 -2
- package/dist/assets/{mcpQueries-BxICKLst.js → mcpQueries-D-rsCnTq.js} +1 -1
- package/dist/assets/{miniChat-PxCQIMUm.js → miniChat-LDIwR98X.js} +2 -2
- package/dist/assets/{mobile-Bs5d4s3_.js → mobile-CTXp1iXI.js} +2 -2
- package/dist/assets/{multirun-BxUSBUb_.js → multirun-BV5qdnL3.js} +1 -1
- package/dist/assets/{projectMeta-dZJ-hOil.js → projectMeta-Cp7A0mPx.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-CGWfxiDc.js → renderElectronMiniChatApp-DrhDj1Jh.js} +2 -2
- package/dist/assets/{renderMobileApp-DJmWaXgS.js → renderMobileApp-DrG8-J7P.js} +4 -4
- package/dist/assets/{runtimeConfig-B-2tboUz.js → runtimeConfig-B1lpYz9l.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-BbJRibgL.js → runtimeEndpointReset-Dp74zZAo.js} +1 -1
- package/dist/assets/{sessionChangeSummary-DTeJLaNT.js → sessionChangeSummary-BH28eSaw.js} +1 -1
- package/dist/assets/{sessionLookup-_U9nCuBN.js → sessionLookup-Ca1342QR.js} +1 -1
- package/dist/assets/{speechSettingsQueries-CdlOc_sI.js → speechSettingsQueries-CG9u1h1Z.js} +1 -1
- package/dist/assets/src-CcT-GitE.css +2 -0
- package/dist/assets/{useAppFontEffects-YhyFj8z3.js → useAppFontEffects-Brve5y_3.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-jdIXqoD8.js → useDesktopWindowControlsLayout-DpEWNKaQ.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-COgvBW0Q.js → useEffectiveDirectory-CFr8k_MH.js} +1 -1
- package/dist/assets/useMobileNavigationStore-CSnG4CUz.js +417 -0
- package/dist/assets/{useWorkerHighlightedLines-DGH-O6ZJ.js → useWorkerHighlightedLines-uJlySvgF.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/mini-chat.html +3 -3
- package/dist/mobile.html +3 -3
- package/package.json +1 -1
- package/server/lib/session-index/DOCUMENTATION.md +23 -0
- package/server/lib/session-index/sync-runtime.js +35 -14
- package/server/lib/session-index/sync-runtime.test.js +125 -0
- package/dist/assets/src-CdGit3gf.css +0 -2
- package/dist/assets/useMobileNavigationStore-CuOiHjP1.js +0 -417
package/dist/assets/{useWorkerHighlightedLines-DGH-O6ZJ.js → useWorkerHighlightedLines-uJlySvgF.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-BvKaN2KX.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-BtVmyJy4.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-DTaOcMED.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,8 +572,8 @@
|
|
|
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-
|
|
576
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-B1lpYz9l.js">
|
|
576
|
+
<link rel="stylesheet" crossorigin href="/assets/src-CcT-GitE.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
|
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-LDIwR98X.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,8 +94,8 @@
|
|
|
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-
|
|
98
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-B1lpYz9l.js">
|
|
98
|
+
<link rel="stylesheet" crossorigin href="/assets/src-CcT-GitE.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-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-CTXp1iXI.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,8 +83,8 @@
|
|
|
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-
|
|
87
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-B1lpYz9l.js">
|
|
87
|
+
<link rel="stylesheet" crossorigin href="/assets/src-CcT-GitE.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
|
@@ -121,6 +121,29 @@ observer active after startup refresh work becomes idle, and successful
|
|
|
121
121
|
event-driven index writes with a semantic snapshot change publish a new
|
|
122
122
|
revision tip immediately.
|
|
123
123
|
|
|
124
|
+
Background directory and child-enrichment progress share a fixed 500ms trailing
|
|
125
|
+
tip window: the first pending change arms the injected timer, later changes do
|
|
126
|
+
not reset it, and delivery reads the latest revision and sync flags. New-batch
|
|
127
|
+
enqueue, idle (`active=false` and `enriching=false`), `publishChange()` (including
|
|
128
|
+
pin/rename and event-driven writes), and stop bypass the window and cancel its
|
|
129
|
+
pending delivery. These immediate edges are exceptions to the 500ms rate bound.
|
|
130
|
+
Only a `publishChange()` broadcast carries `cause: 'change'`; it replaces any
|
|
131
|
+
pending progress tip and the event-stream sink forwards it unchanged. Progress,
|
|
132
|
+
enqueue, idle and stop tips omit cause, so the marker cannot leak into later tips.
|
|
133
|
+
Stop publishes idle with interrupted directories marked failed; late worker
|
|
134
|
+
cleanup cannot emit another revision or leave a pending tip timer. Internal
|
|
135
|
+
revision increments and `waitForChange` delivery remain immediate, including the
|
|
136
|
+
final state. Full snapshots are materialized for waiting readers, not for each
|
|
137
|
+
tip. No persisted revision, failure, or runtime-isolation semantics change.
|
|
138
|
+
|
|
139
|
+
The shared renderer independently throttles busy tips to a fixed 1000ms window
|
|
140
|
+
for compatibility with older Hosts. Idle/legacy tips use 100ms debounce and
|
|
141
|
+
shorten a pending busy deadline without extending it. Stream-ready and safety
|
|
142
|
+
timeout repair retain their existing behavior; GET remains authoritative.
|
|
143
|
+
`cause: 'change'` also selects that 100ms/earlier-deadline path while busy, keeping
|
|
144
|
+
external writes responsive. Unknown causes are ignored by the client parser;
|
|
145
|
+
Hosts without cause retain the existing progress-throttling behavior.
|
|
146
|
+
|
|
124
147
|
The OpenCode proxy calls `noteInteractiveRequest()` for selected-session reads
|
|
125
148
|
and mutations. That aborts the current background list, yields for one second,
|
|
126
149
|
then resumes the same directory. Consequently a startup index refresh cannot
|
|
@@ -7,6 +7,7 @@ const FULL_RECONCILE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
|
7
7
|
const FETCH_TIMEOUT_MS = 6_000;
|
|
8
8
|
const INTERACTIVE_YIELD_MS = 1_000;
|
|
9
9
|
const LONG_POLL_MAX_MS = 25_000;
|
|
10
|
+
const REVISION_TIP_THROTTLE_MS = 500;
|
|
10
11
|
|
|
11
12
|
const normalizeDirectory = (value) => {
|
|
12
13
|
if (typeof value !== 'string') return null;
|
|
@@ -64,7 +65,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
64
65
|
buildOpenCodeUrl,
|
|
65
66
|
getOpenCodeAuthHeaders,
|
|
66
67
|
waitForOpenCodeReady,
|
|
67
|
-
/** Optional tip sink: ({ revision, sync })
|
|
68
|
+
/** Optional tip sink: ({ revision, sync }); background progress is coalesced. */
|
|
68
69
|
onRevisionTip = null,
|
|
69
70
|
/**
|
|
70
71
|
* Optional Host authority projection applied to each upstream list page
|
|
@@ -96,10 +97,11 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
96
97
|
const failedDirectories = new Set();
|
|
97
98
|
const waiters = new Set();
|
|
98
99
|
let batchDirectories = new Set();
|
|
100
|
+
let revisionTipTimer;
|
|
99
101
|
|
|
100
102
|
const syncState = () => ({
|
|
101
|
-
active: running || queue.length > 0,
|
|
102
|
-
enriching: enrichingChildFlags || childFlagQueue.length > 0,
|
|
103
|
+
active: !stopped && (running || queue.length > 0),
|
|
104
|
+
enriching: !stopped && (enrichingChildFlags || childFlagQueue.length > 0),
|
|
103
105
|
completed: completedDirectories.size,
|
|
104
106
|
total: batchDirectories.size,
|
|
105
107
|
pendingDirectories: [...queuedDirectories],
|
|
@@ -113,19 +115,20 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
113
115
|
...sessionIndexService.snapshot(),
|
|
114
116
|
});
|
|
115
117
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
const broadcastRevisionTip = (cause) => {
|
|
119
|
+
if (revisionTipTimer !== undefined) clearTimer(revisionTipTimer);
|
|
120
|
+
revisionTipTimer = undefined;
|
|
121
|
+
const sync = syncState();
|
|
120
122
|
if (typeof onRevisionTip === 'function') {
|
|
121
123
|
try {
|
|
122
124
|
onRevisionTip({
|
|
123
|
-
revision
|
|
125
|
+
revision,
|
|
124
126
|
sync: {
|
|
125
|
-
active:
|
|
126
|
-
enriching:
|
|
127
|
+
active: sync.active,
|
|
128
|
+
enriching: sync.enriching,
|
|
127
129
|
},
|
|
128
130
|
occurredAt: now(),
|
|
131
|
+
...(cause === 'change' ? { cause } : {}),
|
|
129
132
|
});
|
|
130
133
|
} catch (error) {
|
|
131
134
|
console.warn('[session-index] onRevisionTip failed:', error);
|
|
@@ -133,6 +136,21 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
133
136
|
}
|
|
134
137
|
};
|
|
135
138
|
|
|
139
|
+
const notify = (immediate = false, cause) => {
|
|
140
|
+
if (stopped && !immediate) return;
|
|
141
|
+
revision += 1;
|
|
142
|
+
if (waiters.size > 0) {
|
|
143
|
+
const value = snapshot();
|
|
144
|
+
for (const resolve of [...waiters]) resolve(value);
|
|
145
|
+
}
|
|
146
|
+
const sync = syncState();
|
|
147
|
+
if (immediate || (!sync.active && !sync.enriching)) {
|
|
148
|
+
broadcastRevisionTip(cause);
|
|
149
|
+
} else if (revisionTipTimer === undefined && typeof onRevisionTip === 'function') {
|
|
150
|
+
revisionTipTimer = setTimer(broadcastRevisionTip, REVISION_TIP_THROTTLE_MS);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
136
154
|
const delay = (ms, signal) => new Promise((resolve) => {
|
|
137
155
|
if (ms <= 0 || signal?.aborted) {
|
|
138
156
|
resolve();
|
|
@@ -414,6 +432,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
414
432
|
};
|
|
415
433
|
|
|
416
434
|
const enqueue = (directories) => {
|
|
435
|
+
if (stopped) return snapshot();
|
|
417
436
|
const nextRuntimeKey = sessionIndexService.getRuntimeKey();
|
|
418
437
|
if (nextRuntimeKey !== runtimeKey) {
|
|
419
438
|
runtimeKey = nextRuntimeKey;
|
|
@@ -427,7 +446,8 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
427
446
|
failedDirectories.clear();
|
|
428
447
|
batchDirectories = new Set();
|
|
429
448
|
}
|
|
430
|
-
|
|
449
|
+
const newBatch = !running && queue.length === 0;
|
|
450
|
+
if (newBatch) {
|
|
431
451
|
completedDirectories.clear();
|
|
432
452
|
failedDirectories.clear();
|
|
433
453
|
batchDirectories = new Set();
|
|
@@ -443,7 +463,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
443
463
|
currentWasPreempted = true;
|
|
444
464
|
currentController.abort();
|
|
445
465
|
}
|
|
446
|
-
notify();
|
|
466
|
+
notify(newBatch);
|
|
447
467
|
void run();
|
|
448
468
|
return snapshot();
|
|
449
469
|
};
|
|
@@ -458,7 +478,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
458
478
|
|
|
459
479
|
const publishChange = () => {
|
|
460
480
|
if (stopped) return;
|
|
461
|
-
notify();
|
|
481
|
+
notify(true, 'change');
|
|
462
482
|
};
|
|
463
483
|
|
|
464
484
|
const waitForChange = (since, { signal, timeoutMs = LONG_POLL_MAX_MS } = {}) => {
|
|
@@ -481,6 +501,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
481
501
|
};
|
|
482
502
|
|
|
483
503
|
const stop = () => {
|
|
504
|
+
if (stopped) return;
|
|
484
505
|
stopped = true;
|
|
485
506
|
currentController?.abort();
|
|
486
507
|
const interruptedDirectories = [...queuedDirectories];
|
|
@@ -489,7 +510,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
489
510
|
queuedDirectories.clear();
|
|
490
511
|
queuedChildFlagKeys.clear();
|
|
491
512
|
for (const directory of interruptedDirectories) failedDirectories.add(directory);
|
|
492
|
-
notify();
|
|
513
|
+
notify(true);
|
|
493
514
|
};
|
|
494
515
|
|
|
495
516
|
return { enqueue, noteInteractiveRequest, publishChange, snapshot, waitForChange, stop };
|
|
@@ -41,6 +41,131 @@ const isFullyIdle = (runtime) => (
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
describe('session index background sync runtime', () => {
|
|
44
|
+
it('coalesces hundreds of directory/child checks and immediately publishes the final revision', async () => {
|
|
45
|
+
vi.useFakeTimers();
|
|
46
|
+
try {
|
|
47
|
+
const service = createService();
|
|
48
|
+
const revisionsAtDelivery = [];
|
|
49
|
+
const tips = vi.fn(() => { revisionsAtDelivery.push(runtime.snapshot().revision); });
|
|
50
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
51
|
+
sessionIndexService: service,
|
|
52
|
+
buildOpenCodeUrl: (route) => `http://opencode.test${route}`,
|
|
53
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
54
|
+
onRevisionTip: tips,
|
|
55
|
+
setTimer: setTimeout,
|
|
56
|
+
clearTimer: clearTimeout,
|
|
57
|
+
fetchFn: async (url) => {
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
59
|
+
const directory = url.searchParams.get('directory');
|
|
60
|
+
return new Response(JSON.stringify({ data: url.searchParams.has('parentID') ? []
|
|
61
|
+
: Array.from({ length: 20 }, (_, i) => session(`${directory}-${i}`, 100 - i, directory)) }));
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
runtime.enqueue(Array.from({ length: 15 }, (_, i) => `/repo/${i}`));
|
|
65
|
+
expect(tips).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(tips.mock.calls[0][0]).toMatchObject({ revision: 1, sync: { active: true } });
|
|
67
|
+
await vi.advanceTimersByTimeAsync(499);
|
|
68
|
+
expect(tips).toHaveBeenCalledTimes(1);
|
|
69
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
70
|
+
expect(tips).toHaveBeenCalledTimes(2);
|
|
71
|
+
const progress = runtime.waitForChange(runtime.snapshot().revision);
|
|
72
|
+
await vi.advanceTimersByTimeAsync(10);
|
|
73
|
+
expect((await progress).revision).toBe(runtime.snapshot().revision);
|
|
74
|
+
const final = (async () => {
|
|
75
|
+
let value = runtime.snapshot();
|
|
76
|
+
while (value.sync.active || value.sync.enriching) value = await runtime.waitForChange(value.revision);
|
|
77
|
+
return value;
|
|
78
|
+
})();
|
|
79
|
+
await vi.advanceTimersByTimeAsync(2640);
|
|
80
|
+
expect(service.replaceDirectory).toHaveBeenCalledTimes(15);
|
|
81
|
+
expect(service.replaceChildSessions).toHaveBeenCalledTimes(300);
|
|
82
|
+
expect(isFullyIdle(runtime)).toBe(true);
|
|
83
|
+
expect(tips).toHaveBeenCalledTimes(8);
|
|
84
|
+
expect(tips.mock.calls.map(([tip]) => tip.revision)).toEqual(revisionsAtDelivery);
|
|
85
|
+
for (const [tip] of tips.mock.calls) expect(tip).not.toHaveProperty('cause');
|
|
86
|
+
expect(tips.mock.lastCall[0]).toMatchObject({ revision: runtime.snapshot().revision, sync: { active: false, enriching: false } });
|
|
87
|
+
expect((await final).revision).toBe(runtime.snapshot().revision);
|
|
88
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
89
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
90
|
+
expect(tips).toHaveBeenCalledTimes(8);
|
|
91
|
+
runtime.stop();
|
|
92
|
+
} finally {
|
|
93
|
+
vi.useRealTimers();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('publishes external changes immediately while busy and stop cancels the pending tip', async () => {
|
|
98
|
+
vi.useFakeTimers();
|
|
99
|
+
try {
|
|
100
|
+
const tips = vi.fn();
|
|
101
|
+
let releaseReady;
|
|
102
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
103
|
+
sessionIndexService: createService(),
|
|
104
|
+
onRevisionTip: tips,
|
|
105
|
+
waitForOpenCodeReady: () => new Promise((resolve) => { releaseReady = resolve; }),
|
|
106
|
+
setTimer: setTimeout,
|
|
107
|
+
clearTimer: clearTimeout,
|
|
108
|
+
});
|
|
109
|
+
runtime.enqueue(['/repo']);
|
|
110
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
111
|
+
expect(tips.mock.lastCall[0]).not.toHaveProperty('cause');
|
|
112
|
+
runtime.publishChange();
|
|
113
|
+
expect(tips).toHaveBeenCalledTimes(2);
|
|
114
|
+
expect(tips.mock.lastCall[0]).toMatchObject({ cause: 'change', sync: { active: true } });
|
|
115
|
+
expect(tips.mock.lastCall[0].revision).toBe(runtime.snapshot().revision);
|
|
116
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
117
|
+
runtime.enqueue(['/repo/second']);
|
|
118
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
119
|
+
const final = runtime.waitForChange(runtime.snapshot().revision);
|
|
120
|
+
runtime.stop();
|
|
121
|
+
const stoppedRevision = runtime.snapshot().revision;
|
|
122
|
+
expect(tips).toHaveBeenCalledTimes(3);
|
|
123
|
+
expect(tips.mock.lastCall[0]).toMatchObject({ revision: stoppedRevision, sync: { active: false, enriching: false } });
|
|
124
|
+
expect(tips.mock.lastCall[0]).not.toHaveProperty('cause');
|
|
125
|
+
await expect(final).resolves.toMatchObject({ revision: stoppedRevision, sync: { active: false, failedDirectories: ['/repo', '/repo/second'] } });
|
|
126
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
127
|
+
releaseReady(true);
|
|
128
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
129
|
+
expect(runtime.snapshot().revision).toBe(stoppedRevision);
|
|
130
|
+
expect(tips).toHaveBeenCalledTimes(3);
|
|
131
|
+
} finally {
|
|
132
|
+
vi.useRealTimers();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('stopping during enrichment flushes idle and fences late worker notifications', async () => {
|
|
137
|
+
vi.useFakeTimers();
|
|
138
|
+
try {
|
|
139
|
+
const tips = vi.fn();
|
|
140
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
141
|
+
sessionIndexService: createService(),
|
|
142
|
+
buildOpenCodeUrl: (route) => `http://opencode.test${route}`,
|
|
143
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
144
|
+
onRevisionTip: tips,
|
|
145
|
+
fetchFn: async (url, { signal }) => {
|
|
146
|
+
if (!url.searchParams.has('parentID')) return new Response(JSON.stringify([session('root', 1)]));
|
|
147
|
+
return new Promise((_resolve, reject) => {
|
|
148
|
+
signal.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')), { once: true });
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
runtime.enqueue(['/repo']);
|
|
153
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
154
|
+
expect(runtime.snapshot().sync.enriching).toBe(true);
|
|
155
|
+
const final = runtime.waitForChange(runtime.snapshot().revision);
|
|
156
|
+
runtime.stop();
|
|
157
|
+
const revision = runtime.snapshot().revision;
|
|
158
|
+
await expect(final).resolves.toMatchObject({ revision, sync: { active: false, enriching: false } });
|
|
159
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
160
|
+
expect(tips).toHaveBeenCalledTimes(2);
|
|
161
|
+
expect(tips.mock.lastCall[0]).toMatchObject({ revision, sync: { active: false, enriching: false } });
|
|
162
|
+
expect(runtime.snapshot().revision).toBe(revision);
|
|
163
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
164
|
+
} finally {
|
|
165
|
+
vi.useRealTimers();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
44
169
|
it.each([1, 20, 21])('only advertises more when terminal pages contain over 20 visible roots (%i roots)', async (rootCount) => {
|
|
45
170
|
const service = createService();
|
|
46
171
|
const rows = Array.from({ length: Math.max(20, rootCount) }, (_, i) => ({
|