@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.
Files changed (46) hide show
  1. package/dist/assets/{AssistantView-DfdYRwe9.js → AssistantView-CV7caMrh.js} +3 -3
  2. package/dist/assets/{DiagramView-DgPSfFEr.js → DiagramView-CDfSTjyI.js} +1 -1
  3. package/dist/assets/{MarkdownRenderer-Cgoat7nw.js → MarkdownRenderer-TW-h0HHu.js} +2 -2
  4. package/dist/assets/{MarkdownRendererImpl-DMhCX4I4.js → MarkdownRendererImpl-BDhsNSZZ.js} +1 -1
  5. package/dist/assets/{MarkstreamRendererImpl-BiEn8urF.js → MarkstreamRendererImpl-CTRQuEcK.js} +1 -1
  6. package/dist/assets/{MobileDetailNavigation-CRb0QjWl.js → MobileDetailNavigation-Ba4FGle-.js} +1 -1
  7. package/dist/assets/{MobileShareBridge-C1KkUsa1.js → MobileShareBridge-CbLWGJA7.js} +1 -1
  8. package/dist/assets/{MobileSurface-GAmsjEQX.js → MobileSurface-C62mL-z4.js} +1 -1
  9. package/dist/assets/{MultiRunWindow-1J5N1_xP.js → MultiRunWindow--Kog5jbS.js} +1 -1
  10. package/dist/assets/{SettingsView-BTWtI6gk.js → SettingsView-DrHDWxUP.js} +1 -1
  11. package/dist/assets/{SettingsWindow-5NrNotTs.js → SettingsWindow-Dj3oYcuF.js} +1 -1
  12. package/dist/assets/{TerminalView-CNHjj_km.js → TerminalView-BrQT3zTi.js} +1 -1
  13. package/dist/assets/{ToolOutputDialog-DDVMsHzT.js → ToolOutputDialog-zTkbimFp.js} +1 -1
  14. package/dist/assets/{appThemeRegistry-D7-4hz-r.js → appThemeRegistry-Dd-Mc_ae.js} +1 -1
  15. package/dist/assets/{fileMentionTouchSelection-CxmALWLE.js → fileMentionTouchSelection-CZkJTpTW.js} +1 -1
  16. package/dist/assets/{gitApi-Bx8o0qYf.js → gitApi-DDZeOQ0N.js} +1 -1
  17. package/dist/assets/{insertionBoundaries-B7WuxOpo.js → insertionBoundaries-6cEF7UND.js} +1 -1
  18. package/dist/assets/{main-fFwuYFwA.js → main-B839_vtF.js} +2 -2
  19. package/dist/assets/{main-Df1hgMj6.js → main-CboaV0z3.js} +4 -4
  20. package/dist/assets/{mcpQueries-5RQLxGkS.js → mcpQueries-Cirhjpkq.js} +1 -1
  21. package/dist/assets/{miniChat-ByXFq9Gm.js → miniChat-B0lH1mgT.js} +2 -2
  22. package/dist/assets/{mobile-DzYPMFOV.js → mobile-DCMqyVbF.js} +2 -2
  23. package/dist/assets/{multirun-CbKpafUJ.js → multirun-D45JAYPn.js} +1 -1
  24. package/dist/assets/{projectMeta-DR8jWQPj.js → projectMeta-BZj0Lqsc.js} +1 -1
  25. package/dist/assets/{renderElectronMiniChatApp-BH_HOhiH.js → renderElectronMiniChatApp-KxK13wGq.js} +2 -2
  26. package/dist/assets/{renderMobileApp-CdvHmHtu.js → renderMobileApp-DLwJp7Ef.js} +4 -4
  27. package/dist/assets/{runtimeConfig-C2r3lwvo.js → runtimeConfig-Dfmu3VWa.js} +1 -1
  28. package/dist/assets/{runtimeEndpointReset-C4E7DYmZ.js → runtimeEndpointReset-D4tNIDTD.js} +1 -1
  29. package/dist/assets/{sessionChangeSummary-BvaUzmH5.js → sessionChangeSummary-BUya3rIx.js} +1 -1
  30. package/dist/assets/{sessionLookup-D6SUfHqi.js → sessionLookup-Ba9vzhFN.js} +1 -1
  31. package/dist/assets/{useAppFontEffects-DO-sih8X.js → useAppFontEffects-C4YijXq2.js} +33 -33
  32. package/dist/assets/{useDesktopWindowControlsLayout-DFAvkRtr.js → useDesktopWindowControlsLayout-hhGCbHKE.js} +1 -1
  33. package/dist/assets/{useEffectiveDirectory-o1wBFa-2.js → useEffectiveDirectory-DB10GDme.js} +1 -1
  34. package/dist/assets/{useMobileNavigationStore-Brjbv-jq.js → useMobileNavigationStore-DXxkpRmA.js} +3 -3
  35. package/dist/assets/{useSessionAutoCleanup-uEly8Hxf.js → useSessionAutoCleanup-C7ONq7C-.js} +1 -1
  36. package/dist/assets/{useWorkerHighlightedLines-DWYhz9_o.js → useWorkerHighlightedLines-DWR_fwgu.js} +1 -1
  37. package/dist/index.html +2 -2
  38. package/dist/mini-chat.html +2 -2
  39. package/dist/mobile.html +2 -2
  40. package/package.json +1 -1
  41. package/server/lib/opencode/DOCUMENTATION.md +4 -0
  42. package/server/lib/opencode/config-entity-routes.js +1 -1
  43. package/server/lib/opencode/config-entity-routes.test.js +16 -0
  44. package/server/lib/scheduled-tasks/DOCUMENTATION.md +7 -1
  45. package/server/lib/scheduled-tasks/runtime.js +10 -1
  46. package/server/lib/scheduled-tasks/runtime.test.js +81 -6
@@ -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,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
1
+ import{o as e}from"./rolldown-runtime-C0FnF6B9.js";import{n as t}from"./runtime-fetch-CiqQd__Y.js";import{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,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&#39;`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=v(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=y(l),t[0]=l,t[1]=d);let p=d,m,h;t[2]!==n||t[3]!==o||t[4]!==c?(m=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},h=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=m,t[6]=h):(m=t[5],h=t[6]),O.useLayoutEffect(m,h);let g,_;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(g=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return w(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},_=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=g,t[12]=_):(g=t[11],_=t[12]),O.useEffect(g,_);let b;t[13]===i?b=t[14]:(b=f(`typography-code`,i),t[13]=i,t[14]=b);let x=p,S;t[15]!==a||t[16]!==x?(S={...x,...a},t[15]=a,t[16]=x,t[17]=S):S=t[17];let C;return t[18]!==b||t[19]!==S?(C=(0,A.jsx)(`div`,{ref:u,className:b,style:S}),t[18]=b,t[19]=S,t[20]=C):C=t[20],C},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):C(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
2
2
  `).split(`
3
3
  `),r=e=>/\(\s*File has more lines\..*offset.*\)/i.test(e.trim());return{type:t,lines:n.map(e=>{let n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);if(t!==`directory`){let t=e.match(/^(\d+):\s?(.*)$/);if(t){let e=t[2],n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);return{lineNumber:i?null:Number(t[1]),text:e,isInfo:i}}}return{lineNumber:null,text:e,isInfo:i}}).filter((e,t,n)=>{if(e.text.trim().length>0)return!0;let r=n[t-1],i=n[t+1],a=!!(r?.isInfo||i?.isInfo),o=e.lineNumber!==null;return!(a&&o)})}},je=(e,t)=>{try{let n=e.trim().split(`
4
4
  `).filter(Boolean);if(n.length===0)return null;let r=[];return n.forEach(e=>{let t=e.match(/^(\s*)(.+)$/);if(t){let[,e,n]=t,i=Math.floor(e.length/2),a=!n.endsWith(`/`);r.push({name:n.replace(/\/$/,``),depth:i,isFile:a})}}),(0,A.jsx)(`div`,{className:f(`w-full min-w-0 font-mono space-y-0.5`,t?.unstyled?null:`p-3 bg-muted/20 rounded-xl border border-border/30`),style:s.tool.popup,children:r.map((e,t)=>(0,A.jsx)(`div`,{className:`min-w-0`,style:{paddingLeft:`${e.depth*20}px`},children:e.isFile?(0,A.jsx)(`span`,{className:`text-foreground/90 block truncate`,children:e.name}):(0,A.jsxs)(`span`,{className:`font-semibold text-foreground block truncate`,children:[e.name,`/`]})},t))})}catch{return null}},Me={backgroundColor:`var(--status-info)`,opacity:.6},Ne=(e,t,n)=>{try{let r=e.trim().split(`
package/dist/index.html CHANGED
@@ -552,7 +552,7 @@
552
552
  opacity: 0.72;
553
553
  }
554
554
  </style>
555
- <script type="module" crossorigin src="/assets/main-fFwuYFwA.js"></script>
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-C2r3lwvo.js">
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">
@@ -74,7 +74,7 @@
74
74
  }
75
75
  </style>
76
76
 
77
- <script type="module" crossorigin src="/assets/miniChat-ByXFq9Gm.js"></script>
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-C2r3lwvo.js">
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-DzYPMFOV.js"></script>
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-C2r3lwvo.js">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchambery/web",
3
- "version": "2.0.0-beta.17",
3
+ "version": "2.0.0-beta.18",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -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
- - Real terminal success requires an assistant tail with `time.completed`
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
- * - only a completed assistant with a real terminal finish succeeds
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('does not succeed when the latest message is model-switched', async () => {
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
- id: 'msg_switch',
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 on idle after a continuation, without rewriting history', async () => {
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: [completedAssistant('msg_ok')],
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({