@openchambery/web 2.0.0-beta.17 → 2.0.0-beta.18
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-DfdYRwe9.js → AssistantView-CV7caMrh.js} +3 -3
- package/dist/assets/{DiagramView-DgPSfFEr.js → DiagramView-CDfSTjyI.js} +1 -1
- package/dist/assets/{MarkdownRenderer-Cgoat7nw.js → MarkdownRenderer-TW-h0HHu.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-DMhCX4I4.js → MarkdownRendererImpl-BDhsNSZZ.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-BiEn8urF.js → MarkstreamRendererImpl-CTRQuEcK.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-CRb0QjWl.js → MobileDetailNavigation-Ba4FGle-.js} +1 -1
- package/dist/assets/{MobileShareBridge-C1KkUsa1.js → MobileShareBridge-CbLWGJA7.js} +1 -1
- package/dist/assets/{MobileSurface-GAmsjEQX.js → MobileSurface-C62mL-z4.js} +1 -1
- package/dist/assets/{MultiRunWindow-1J5N1_xP.js → MultiRunWindow--Kog5jbS.js} +1 -1
- package/dist/assets/{SettingsView-BTWtI6gk.js → SettingsView-DrHDWxUP.js} +1 -1
- package/dist/assets/{SettingsWindow-5NrNotTs.js → SettingsWindow-Dj3oYcuF.js} +1 -1
- package/dist/assets/{TerminalView-CNHjj_km.js → TerminalView-BrQT3zTi.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DDVMsHzT.js → ToolOutputDialog-zTkbimFp.js} +1 -1
- package/dist/assets/{appThemeRegistry-D7-4hz-r.js → appThemeRegistry-Dd-Mc_ae.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-CxmALWLE.js → fileMentionTouchSelection-CZkJTpTW.js} +1 -1
- package/dist/assets/{gitApi-Bx8o0qYf.js → gitApi-DDZeOQ0N.js} +1 -1
- package/dist/assets/{insertionBoundaries-B7WuxOpo.js → insertionBoundaries-6cEF7UND.js} +1 -1
- package/dist/assets/{main-fFwuYFwA.js → main-B839_vtF.js} +2 -2
- package/dist/assets/{main-Df1hgMj6.js → main-CboaV0z3.js} +4 -4
- package/dist/assets/{mcpQueries-5RQLxGkS.js → mcpQueries-Cirhjpkq.js} +1 -1
- package/dist/assets/{miniChat-ByXFq9Gm.js → miniChat-B0lH1mgT.js} +2 -2
- package/dist/assets/{mobile-DzYPMFOV.js → mobile-DCMqyVbF.js} +2 -2
- package/dist/assets/{multirun-CbKpafUJ.js → multirun-D45JAYPn.js} +1 -1
- package/dist/assets/{projectMeta-DR8jWQPj.js → projectMeta-BZj0Lqsc.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-BH_HOhiH.js → renderElectronMiniChatApp-KxK13wGq.js} +2 -2
- package/dist/assets/{renderMobileApp-CdvHmHtu.js → renderMobileApp-DLwJp7Ef.js} +4 -4
- package/dist/assets/{runtimeConfig-C2r3lwvo.js → runtimeConfig-Dfmu3VWa.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-C4E7DYmZ.js → runtimeEndpointReset-D4tNIDTD.js} +1 -1
- package/dist/assets/{sessionChangeSummary-BvaUzmH5.js → sessionChangeSummary-BUya3rIx.js} +1 -1
- package/dist/assets/{sessionLookup-D6SUfHqi.js → sessionLookup-Ba9vzhFN.js} +1 -1
- package/dist/assets/{useAppFontEffects-DO-sih8X.js → useAppFontEffects-C4YijXq2.js} +33 -33
- package/dist/assets/{useDesktopWindowControlsLayout-DFAvkRtr.js → useDesktopWindowControlsLayout-hhGCbHKE.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-o1wBFa-2.js → useEffectiveDirectory-DB10GDme.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-Brjbv-jq.js → useMobileNavigationStore-DXxkpRmA.js} +3 -3
- package/dist/assets/{useSessionAutoCleanup-uEly8Hxf.js → useSessionAutoCleanup-C7ONq7C-.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-DWYhz9_o.js → useWorkerHighlightedLines-DWR_fwgu.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/lib/opencode/DOCUMENTATION.md +4 -0
- package/server/lib/opencode/config-entity-routes.js +1 -1
- package/server/lib/opencode/config-entity-routes.test.js +16 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +7 -1
- package/server/lib/scheduled-tasks/runtime.js +10 -1
- package/server/lib/scheduled-tasks/runtime.test.js +81 -6
package/dist/assets/{useWorkerHighlightedLines-DWYhz9_o.js → useWorkerHighlightedLines-DWR_fwgu.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{o as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./runtime-fetch-CiqQd__Y.js";import{En as n,in as r}from"./persistence-CMWO3jFo.js";import{nt as i,rt as a}from"./vendor-base-ui-CnHfUmDN.js";import{n as o}from"./platform-BxU3ZvQ0.js";import{v as s}from"./useUIStore-Brsa2mKs.js";import{n as c}from"./vendor-react-DLPElpoQ.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js";import{f as d,r as f,t as p}from"./button-EzCcYrQl.js";import{n as m}from"./Icon-DKd015RD.js";import{i as h,n as g,t as _}from"./tooltip-DwUy2erJ.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-Cgoat7nw.js";import{n as x,o as S,s as C,t as w}from"./markdown-worker-bS5OgGO3.js";import{a as T,i as E,o as D,t as ee}from"./imageSource-BpUsiwFd.js";import{t as te}from"./vendor-tanstack-react-virtual-tlDgH6PG.js";var O=e(a(),1),k=c(),A=i(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(m,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(m,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(m,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,v=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(h,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(g,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(_,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==v||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":v,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=v,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await ee(e,t??new AbortController().signal),await G(n,t)}finally{n&&T(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=D(a,r);if(E(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&`).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}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
|
|
1
|
+
import{o as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./runtime-fetch-CiqQd__Y.js";import{En as n,in as r}from"./persistence-CMWO3jFo.js";import{nt as i,rt as a}from"./vendor-base-ui-CnHfUmDN.js";import{n as o}from"./platform-BxU3ZvQ0.js";import{v as s}from"./useUIStore-Brsa2mKs.js";import{n as c}from"./vendor-react-DLPElpoQ.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js";import{f as d,r as f,t as p}from"./button-EzCcYrQl.js";import{n as m}from"./Icon-DKd015RD.js";import{i as h,n as g,t as _}from"./tooltip-DwUy2erJ.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-TW-h0HHu.js";import{n as x,o as S,s as C,t as w}from"./markdown-worker-bS5OgGO3.js";import{a as T,i as E,o as D,t as ee}from"./imageSource-BpUsiwFd.js";import{t as te}from"./vendor-tanstack-react-virtual-tlDgH6PG.js";var O=e(a(),1),k=c(),A=i(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(m,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(m,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(m,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,v=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(h,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(g,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(_,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==v||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":v,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=v,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await ee(e,t??new AbortController().signal),await G(n,t)}finally{n&&T(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=D(a,r);if(E(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&`).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}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
|
|
2
2
|
`).split(`
|
|
3
3
|
`),r=e=>/\(\s*File has more lines\..*offset.*\)/i.test(e.trim());return{type:t,lines:n.map(e=>{let n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);if(t!==`directory`){let t=e.match(/^(\d+):\s?(.*)$/);if(t){let e=t[2],n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);return{lineNumber:i?null:Number(t[1]),text:e,isInfo:i}}}return{lineNumber:null,text:e,isInfo:i}}).filter((e,t,n)=>{if(e.text.trim().length>0)return!0;let r=n[t-1],i=n[t+1],a=!!(r?.isInfo||i?.isInfo),o=e.lineNumber!==null;return!(a&&o)})}},je=(e,t)=>{try{let n=e.trim().split(`
|
|
4
4
|
`).filter(Boolean);if(n.length===0)return null;let r=[];return n.forEach(e=>{let t=e.match(/^(\s*)(.+)$/);if(t){let[,e,n]=t,i=Math.floor(e.length/2),a=!n.endsWith(`/`);r.push({name:n.replace(/\/$/,``),depth:i,isFile:a})}}),(0,A.jsx)(`div`,{className:f(`w-full min-w-0 font-mono space-y-0.5`,t?.unstyled?null:`p-3 bg-muted/20 rounded-xl border border-border/30`),style:s.tool.popup,children:r.map((e,t)=>(0,A.jsx)(`div`,{className:`min-w-0`,style:{paddingLeft:`${e.depth*20}px`},children:e.isFile?(0,A.jsx)(`span`,{className:`text-foreground/90 block truncate`,children:e.name}):(0,A.jsxs)(`span`,{className:`font-semibold text-foreground block truncate`,children:[e.name,`/`]})},t))})}catch{return null}},Me={backgroundColor:`var(--status-info)`,opacity:.6},Ne=(e,t,n)=>{try{let r=e.trim().split(`
|
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-B839_vtF.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
|
|
@@ -572,7 +572,7 @@
|
|
|
572
572
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-C6zlKnK1.js">
|
|
573
573
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
574
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Dfmu3VWa.js">
|
|
576
576
|
<link rel="stylesheet" crossorigin href="/assets/src-iyTfdRD7.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-B0lH1mgT.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-C6zlKnK1.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Dfmu3VWa.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/src-iyTfdRD7.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-DCMqyVbF.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-BJM2k_uH.js">
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-C6zlKnK1.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Dfmu3VWa.js">
|
|
87
87
|
<link rel="stylesheet" crossorigin href="/assets/src-iyTfdRD7.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -321,6 +321,10 @@ restart, then terminates only its own child and removes its temporary files.
|
|
|
321
321
|
It makes no model requests and never reads real user credentials.
|
|
322
322
|
|
|
323
323
|
## Public exports (config-entity-routes.js)
|
|
324
|
+
The provider catalog's optional `model.default` lookup may fail independently:
|
|
325
|
+
available provider/model lists still return successfully without defaults.
|
|
326
|
+
Failures of either required list still return HTTP 502 for the caller to retry.
|
|
327
|
+
|
|
324
328
|
- `registerConfigEntityRoutes(app, dependencies)`: registers configuration entity routes:
|
|
325
329
|
- `GET /api/config/catalog/providers` resolves the request project directory, composes official v2 `provider.list` + `model.list` (+ `model.default`), and returns schema version `1` with an allowlisted provider/model catalog. Catalog model ids and defaults use `ModelInfo.id` (the external generate/session id). `ModelInfo.modelID` is an internal field and is not the catalog key. The route is available through the shared web host used by Web, Electron, hosted mobile, and Capacitor mobile, before generic OpenCode proxy handling.
|
|
326
330
|
- Provider catalog responses include only provider `id`, `name`, and safe models. Safe models include `id`, `name`, fixed text/audio/image/video/pdf capability modalities, bounded cost/limit fields, `release_date`, and variant names with `{}` values. v2 `ModelInfo.variants` is an array of `{ id, settings?, headers?, body? }`; projection keeps ids in array order as empty-object keys and drops request payloads. v2 `capabilities.tools` maps to `toolcall`, and `input`/`output` modality name arrays map to boolean maps. v2 `cost` tier arrays keep the untiered base price. Identifiers are bounded to 512 characters, display names to 1024, release dates to 64, numeric values to absolute 1e9, and catalog collections to 200 providers, 500 models per provider, and 100 defaults or variants. Provider credentials/configuration and unallowlisted model fields stay server-side. Malformed catalog roots and SDK error envelopes return HTTP 502. `partial: true` is structural only: provider/model/default truncation, dropped providers/models/defaults, or truncated variant dictionaries. Soft allowlist stripping of optional metadata (unknown modalities, non-boolean capability flags, out-of-range cost/limit numbers, empty/null/invalid `release_date`, invalid variant ids) keeps valid models and must not set `partial: true`, or UI refresh retains a stale complete snapshot after OpenCode provider updates. The same input shapes are accepted by the VS Code projector and `packages/ui/src/lib/configCatalogParser.ts`.
|
|
@@ -186,7 +186,7 @@ export const registerConfigEntityRoutes = (app, dependencies) => {
|
|
|
186
186
|
const [providersResult, modelsResult, defaultResult] = await Promise.all([
|
|
187
187
|
client.provider.list({ location }),
|
|
188
188
|
client.model.list({ location }),
|
|
189
|
-
client.model.default({ location }),
|
|
189
|
+
client.model.default({ location }).catch(() => undefined),
|
|
190
190
|
]);
|
|
191
191
|
const source = composeV2ProviderCatalogSource(
|
|
192
192
|
providersResult?.data,
|
|
@@ -211,6 +211,22 @@ describe('config entity command metadata route', () => {
|
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
describe('provider catalog route', () => {
|
|
214
|
+
it('keeps the model catalog available when the optional default request fails', async () => {
|
|
215
|
+
const app = express();
|
|
216
|
+
createOpencodeClient.mockReturnValue({
|
|
217
|
+
provider: { list: vi.fn(async () => ({ data: [{ id: 'openai', name: 'OpenAI' }] })) },
|
|
218
|
+
model: {
|
|
219
|
+
list: vi.fn(async () => ({ data: [{ id: 'model', providerID: 'openai', name: 'Model' }] })),
|
|
220
|
+
default: vi.fn(async () => { throw new Error('default unavailable'); }),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
registerConfigEntityRoutes(app, createDependencies(vi.fn()));
|
|
224
|
+
const response = await request(app).get('/api/config/catalog/providers').expect(200);
|
|
225
|
+
expect(response.body.providers[0].models.model).toEqual({ id: 'model', name: 'Model' });
|
|
226
|
+
expect(response.body.default).toEqual({});
|
|
227
|
+
expect(response.body.partial).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
214
230
|
it('uses the resolved directory, OpenCode auth, SDK providers call, and explicit catalog route', async () => {
|
|
215
231
|
const app = express();
|
|
216
232
|
const resolveProjectDirectory = vi.fn(async () => ({ directory: '/project/from-header' }));
|
|
@@ -215,7 +215,13 @@ Every actual run (timer or manual):
|
|
|
215
215
|
`message.list({ limit: 50, order: 'desc' })` (newest first) — never
|
|
216
216
|
`order: 'asc'` page-0 + `at(-1)`, which reads an old page once the
|
|
217
217
|
session exceeds the limit.
|
|
218
|
-
|
|
218
|
+
- OpenCode 2.0.16 emits a native `idle` tail whose `outcome` is authoritative:
|
|
219
|
+
`succeeded` → success; `failed` / `interrupted` → error. An unknown idle
|
|
220
|
+
outcome keeps polling. Read it only after `session.active` confirms the
|
|
221
|
+
session is inactive; an older terminal record cannot settle live work.
|
|
222
|
+
This also applies to post-run continuation snapshots. Earlier servers
|
|
223
|
+
without idle records use the assistant-tail rules below.
|
|
224
|
+
- Assistant-tail terminal success requires `time.completed`
|
|
219
225
|
and finish not `tool-calls` (prefer `finish: 'stop'`). `finish:
|
|
220
226
|
'tool-calls'`, incomplete assistants (no `time.completed`), and
|
|
221
227
|
non-assistant tails such as `model-switched` never succeed — including
|
|
@@ -304,7 +304,7 @@ const isUserMessage = (info) => (
|
|
|
304
304
|
* - finish tool-calls is mid-loop, never success
|
|
305
305
|
* - incomplete (no time.completed) is never success
|
|
306
306
|
* - model-switched / non-assistant tails are not success
|
|
307
|
-
* -
|
|
307
|
+
* - native idle outcomes settle the execution; older servers use assistant finish
|
|
308
308
|
*/
|
|
309
309
|
const classifyMessageTail = (messages) => {
|
|
310
310
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
@@ -317,6 +317,15 @@ const classifyMessageTail = (messages) => {
|
|
|
317
317
|
if (!latest) {
|
|
318
318
|
return { outcome: 'unknown' };
|
|
319
319
|
}
|
|
320
|
+
if (latest.type === 'idle') {
|
|
321
|
+
if (latest.outcome === 'succeeded') {
|
|
322
|
+
return { outcome: 'success' };
|
|
323
|
+
}
|
|
324
|
+
if (latest.outcome === 'failed' || latest.outcome === 'interrupted') {
|
|
325
|
+
return { outcome: 'error', error: `session execution ${latest.outcome}` };
|
|
326
|
+
}
|
|
327
|
+
return { outcome: 'unknown' };
|
|
328
|
+
}
|
|
320
329
|
if (isAssistantMessage(latest)) {
|
|
321
330
|
if (latest.error) {
|
|
322
331
|
return {
|
|
@@ -1108,6 +1108,78 @@ describe('scheduled-tasks run history and session lifecycle', () => {
|
|
|
1108
1108
|
expect(client.prompt).not.toHaveBeenCalled();
|
|
1109
1109
|
});
|
|
1110
1110
|
|
|
1111
|
+
it.each([
|
|
1112
|
+
['succeeded', 'success', null],
|
|
1113
|
+
['failed', 'error', 'session execution failed'],
|
|
1114
|
+
['interrupted', 'error', 'session execution interrupted'],
|
|
1115
|
+
])('settles native idle outcome %s instead of timing out', async (outcome, status, error) => {
|
|
1116
|
+
vi.useFakeTimers();
|
|
1117
|
+
try {
|
|
1118
|
+
const history = createHistoryStore();
|
|
1119
|
+
const client = createSuccessfulClient({
|
|
1120
|
+
messageListImpl: async () => ({
|
|
1121
|
+
data: [
|
|
1122
|
+
{ id: 'msg_idle', type: 'idle', outcome, time: { created: 1_200 } },
|
|
1123
|
+
outcome === 'succeeded' ? erroredAssistant() : completedAssistant(),
|
|
1124
|
+
],
|
|
1125
|
+
}),
|
|
1126
|
+
});
|
|
1127
|
+
const updateState = vi.fn(async (_projectID, _taskID, state) => ({
|
|
1128
|
+
task: { ...scheduledTask, state: { ...scheduledTask.state, ...state } },
|
|
1129
|
+
}));
|
|
1130
|
+
const runtime = createRuntime(updateState, {
|
|
1131
|
+
runHistoryStore: history,
|
|
1132
|
+
waitForOpenCodeReady: vi.fn(async () => {}),
|
|
1133
|
+
maxRunDurationMs: 2_500,
|
|
1134
|
+
});
|
|
1135
|
+
await runtime.syncProject('project-1');
|
|
1136
|
+
const pending = runtime.runNow('project-1', 'task-1');
|
|
1137
|
+
await vi.advanceTimersByTimeAsync(2_500);
|
|
1138
|
+
const result = await pending;
|
|
1139
|
+
expect(result.status).toBe(status);
|
|
1140
|
+
expect(result.error ?? null).toBe(error);
|
|
1141
|
+
expect(history.finishRun).toHaveBeenCalledWith(
|
|
1142
|
+
expect.any(String), expect.objectContaining({ status }),
|
|
1143
|
+
);
|
|
1144
|
+
expect(client.interrupt).not.toHaveBeenCalled();
|
|
1145
|
+
expect(client.messageList).toHaveBeenCalledTimes(1);
|
|
1146
|
+
expect(history.finishRun.mock.calls[0][1].durationMs).toBeLessThan(2_500);
|
|
1147
|
+
runtime.stop();
|
|
1148
|
+
} finally {
|
|
1149
|
+
vi.useRealTimers();
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('does not settle an older idle record while the session is active', async () => {
|
|
1154
|
+
vi.useFakeTimers();
|
|
1155
|
+
try {
|
|
1156
|
+
const history = createHistoryStore();
|
|
1157
|
+
let busy = true;
|
|
1158
|
+
const client = createSuccessfulClient({
|
|
1159
|
+
activeImpl: async () => busy ? { ses_1: { type: 'running' } } : {},
|
|
1160
|
+
messageListImpl: async () => ({
|
|
1161
|
+
data: [{ id: 'msg_idle', type: 'idle', outcome: 'succeeded', time: { created: 1_200 } }],
|
|
1162
|
+
}),
|
|
1163
|
+
});
|
|
1164
|
+
const runtime = createRuntime(vi.fn(async () => ({ task: scheduledTask })), {
|
|
1165
|
+
runHistoryStore: history,
|
|
1166
|
+
waitForOpenCodeReady: vi.fn(async () => {}),
|
|
1167
|
+
maxRunDurationMs: 5_000,
|
|
1168
|
+
});
|
|
1169
|
+
await runtime.syncProject('project-1');
|
|
1170
|
+
const pending = runtime.runNow('project-1', 'task-1');
|
|
1171
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
1172
|
+
expect(history.finishRun).not.toHaveBeenCalled();
|
|
1173
|
+
expect(client.messageList).not.toHaveBeenCalled();
|
|
1174
|
+
busy = false;
|
|
1175
|
+
await vi.advanceTimersByTimeAsync(1_000);
|
|
1176
|
+
expect((await pending).status).toBe('success');
|
|
1177
|
+
runtime.stop();
|
|
1178
|
+
} finally {
|
|
1179
|
+
vi.useRealTimers();
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1111
1183
|
it('ordinary non-goal runs still create a session and settle on the assistant tail', async () => {
|
|
1112
1184
|
const history = createHistoryStore();
|
|
1113
1185
|
const client = createSuccessfulClient({ sessionID: 'ses_plain' });
|
|
@@ -1182,16 +1254,17 @@ describe('scheduled-tasks run history and session lifecycle', () => {
|
|
|
1182
1254
|
);
|
|
1183
1255
|
});
|
|
1184
1256
|
|
|
1185
|
-
it(
|
|
1257
|
+
it.each([
|
|
1258
|
+
{ id: 'msg_switch', type: 'model-switched', model: { providerID: 'openai', id: 'gpt-4.1' } },
|
|
1259
|
+
{ id: 'msg_idle', type: 'idle', outcome: 'unknown' },
|
|
1260
|
+
])('does not succeed on an unrecognized terminal: $type', async (tail) => {
|
|
1186
1261
|
vi.useFakeTimers();
|
|
1187
1262
|
const history = createHistoryStore();
|
|
1188
1263
|
createSuccessfulClient({
|
|
1189
1264
|
messageListImpl: async () => ({
|
|
1190
1265
|
data: [{
|
|
1191
|
-
|
|
1192
|
-
type: 'model-switched',
|
|
1266
|
+
...tail,
|
|
1193
1267
|
time: { created: Date.now() },
|
|
1194
|
-
model: { providerID: 'openai', id: 'gpt-4.1' },
|
|
1195
1268
|
}],
|
|
1196
1269
|
}),
|
|
1197
1270
|
});
|
|
@@ -1442,7 +1515,7 @@ describe('scheduled-tasks run history and session lifecycle', () => {
|
|
|
1442
1515
|
vi.unstubAllGlobals();
|
|
1443
1516
|
});
|
|
1444
1517
|
|
|
1445
|
-
it('corrects lastStatus error → success
|
|
1518
|
+
it.each(['assistant', 'idle'])('corrects lastStatus error → success from %s after a continuation, without rewriting history', async (tailType) => {
|
|
1446
1519
|
const history = createHistoryStore();
|
|
1447
1520
|
const client = createSuccessfulClient({ settlement: 'assistant-error' });
|
|
1448
1521
|
const fetchMock = vi.fn(async (url) => {
|
|
@@ -1468,7 +1541,9 @@ describe('scheduled-tasks run history and session lifecycle', () => {
|
|
|
1468
1541
|
expect(history.finishRun).toHaveBeenCalledTimes(1);
|
|
1469
1542
|
|
|
1470
1543
|
client.messageList.mockImplementation(async () => ({
|
|
1471
|
-
data: [
|
|
1544
|
+
data: [tailType === 'idle'
|
|
1545
|
+
? { id: 'msg_idle', type: 'idle', outcome: 'succeeded', time: { created: 1_200 } }
|
|
1546
|
+
: completedAssistant('msg_ok')],
|
|
1472
1547
|
}));
|
|
1473
1548
|
|
|
1474
1549
|
await runtime.observeSessionEvent({
|