@openchambery/web 2.0.0-beta.21 → 2.0.0-beta.23
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-naRl3N6N.js → AssistantView-xcvH8rjG.js} +3 -3
- package/dist/assets/{DiagramView-CJVRnLLD.js → DiagramView-DNG8vo4Y.js} +1 -1
- package/dist/assets/{MarkdownRenderer-_SuOGgNO.js → MarkdownRenderer-CEJAAr1D.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-D3c0ZZOe.js → MarkdownRendererImpl-Cr2TAqhw.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-BnZaplmG.js → MarkstreamRendererImpl-nZuUzTxY.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-HLvtYuCk.js → MobileDetailNavigation-DtKMM3yi.js} +1 -1
- package/dist/assets/{MobileShareBridge-Biw4q67K.js → MobileShareBridge-B7WXWIu6.js} +1 -1
- package/dist/assets/{MobileSurface-BgE6_oyX.js → MobileSurface-DpWdHT3N.js} +1 -1
- package/dist/assets/{MultiRunWindow-BF516oDD.js → MultiRunWindow-BUKkRUqu.js} +1 -1
- package/dist/assets/{SettingsView-DbHjBwRm.js → SettingsView-8E-wbaVn.js} +1 -1
- package/dist/assets/{SettingsWindow-C8Xn95yZ.js → SettingsWindow-Bqmgbx0M.js} +1 -1
- package/dist/assets/{TerminalView-B7yuQ1AV.js → TerminalView-68VbSeR-.js} +1 -1
- package/dist/assets/{ToolOutputDialog-C5pL0tuu.js → ToolOutputDialog-CVK4WsvG.js} +1 -1
- package/dist/assets/{appThemeRegistry-BZX_tQiK.js → appThemeRegistry-9e16JGaU.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-DrYA6Sr9.js → fileMentionTouchSelection-Dn-wohRQ.js} +1 -1
- package/dist/assets/{gitApi-DYTjwOds.js → gitApi-BA9aM1SG.js} +1 -1
- package/dist/assets/{insertionBoundaries-CDqeL75o.js → insertionBoundaries-CI9ok09h.js} +1 -1
- package/dist/assets/{main-D0wxmCfZ.js → main-CcAGHLHU.js} +4 -4
- package/dist/assets/{main-eaupUQ5r.js → main-DgAK4oel.js} +2 -2
- package/dist/assets/{mcpQueries-BLAC84Tx.js → mcpQueries-2M1PZ3lm.js} +1 -1
- package/dist/assets/{miniChat-CQVvM9wj.js → miniChat-Cc9LRMjb.js} +2 -2
- package/dist/assets/{mobile-D2ek5Kqj.js → mobile-D85ParE1.js} +2 -2
- package/dist/assets/{multirun-BlwYY4Zc.js → multirun-DvtrTdrM.js} +1 -1
- package/dist/assets/{projectMeta-BKyd9XVy.js → projectMeta-D_YBAmPw.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-D8r6WLic.js → renderElectronMiniChatApp-29E_I_Wu.js} +2 -2
- package/dist/assets/{renderMobileApp-CWnwd-6g.js → renderMobileApp-VE8moPSo.js} +4 -4
- package/dist/assets/{runtimeConfig-CrstkVKq.js → runtimeConfig-CZrkgYcD.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-Dtq4kqAQ.js → runtimeEndpointReset-B71dh9nk.js} +1 -1
- package/dist/assets/{sessionChangeSummary-Cy03Ps9t.js → sessionChangeSummary-Dk4PbQaU.js} +1 -1
- package/dist/assets/{sessionLookup-DHVNgBpp.js → sessionLookup-BcQ28pj8.js} +1 -1
- package/dist/assets/{useAppFontEffects-CUB9GXLu.js → useAppFontEffects-BIH0h1v7.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-DT2ZGali.js → useDesktopWindowControlsLayout-DvKPPF8d.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-HWOd1UY6.js → useEffectiveDirectory-BXEpJMOS.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-BvjkdMg_.js → useMobileNavigationStore-Cu3jliDp.js} +5 -5
- package/dist/assets/{useSessionAutoCleanup-DZ_iSJsk.js → useSessionAutoCleanup-CH8TehHH.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-DdnhcOWA.js → useWorkerHighlightedLines-PqKAaqkW.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/question-auto-delegate/DOCUMENTATION.md +21 -6
- package/server/lib/question-auto-delegate/core.d.ts +5 -1
- package/server/lib/question-auto-delegate/core.js +21 -13
- package/server/lib/question-auto-delegate/form-io.d.ts +6 -0
- package/server/lib/question-auto-delegate/form-io.js +39 -0
- package/server/lib/question-auto-delegate/runtime.js +4 -24
- package/server/lib/question-auto-delegate/runtime.test.js +138 -0
- package/server/lib/session-goal/DOCUMENTATION.md +11 -0
- package/server/lib/session-goal/runtime.js +4 -4
- package/server/lib/session-goal/runtime.test.js +32 -3
package/dist/assets/{useWorkerHighlightedLines-DdnhcOWA.js → useWorkerHighlightedLines-PqKAaqkW.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-B0p43squ.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-e8fYWIMM.js";import{n as m}from"./Icon-D7NdMmbi.js";import{i as h,n as g,t as _}from"./tooltip-C64SdRvR.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-_SuOGgNO.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-B0p43squ.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-e8fYWIMM.js";import{n as m}from"./Icon-D7NdMmbi.js";import{i as h,n as g,t as _}from"./tooltip-C64SdRvR.js";import{n as v}from"./useThemeSystem-DMzWVJaW.js";import{n as y}from"./markdownTheme-DM5h1i18.js";import{n as b}from"./MarkdownRenderer-CEJAAr1D.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-DgAK4oel.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-CJyWCuc8.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-CZrkgYcD.js">
|
|
576
576
|
<link rel="stylesheet" crossorigin href="/assets/src-qYyYBKwA.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-Cc9LRMjb.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-CJyWCuc8.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-CZrkgYcD.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/src-qYyYBKwA.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-D85ParE1.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-CJyWCuc8.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-CZrkgYcD.js">
|
|
87
87
|
<link rel="stylesheet" crossorigin href="/assets/src-qYyYBKwA.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Server-owned automatic handling of OpenCode
|
|
5
|
+
Server-owned automatic handling of OpenCode 2 question-tool forms (`form.created`,
|
|
6
|
+
`data.form.metadata.kind === "question"`). When
|
|
6
7
|
enabled (default), pending questions are answered after 30 seconds with a fixed
|
|
7
8
|
continuation text so agents (including background/subagent sessions) can proceed
|
|
8
9
|
without a connected UI. Manual reply/reject still use the official OpenCode
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
form schema. The Host adds a single-flight claim for timer, delegate, and
|
|
11
|
+
compatibility question routes. Native manual form replies settle via
|
|
12
|
+
`form.replied` / `form.cancelled`; upstream form settlement arbitrates any race
|
|
13
|
+
with a native reply already in flight.
|
|
11
14
|
|
|
12
15
|
Permission auto-accept is intentionally out of scope — this module never
|
|
13
16
|
replies to permission requests.
|
|
@@ -42,6 +45,7 @@ Auto-submit requires **settings ready + enabled + authoritative directory**.
|
|
|
42
45
|
|---|---|
|
|
43
46
|
| `core.js` / `core.d.ts` | Platform-agnostic state machine (IO/timer injected). Shared contract for VS Code. |
|
|
44
47
|
| `runtime.js` | Web adapter: OpenCode upstream fetch, global event hub, settings, SSE tips, project + session-index directories. |
|
|
48
|
+
| `form-io.js` / `form-io.d.ts` | Shared Web/VS Code native form list, schema lookup, answer mapping and cancellation. |
|
|
45
49
|
| `routes.js` | OpenChamber routes + precise `/api/question/:id/reply\|reject` intercepts. |
|
|
46
50
|
|
|
47
51
|
## Snapshot
|
|
@@ -62,21 +66,31 @@ Tip event (not a data payload):
|
|
|
62
66
|
| `POST` | `.../requests/:id/pause` | `{ sessionID, directory, reason: 'interaction'\|'user' }` — bound identity conflicts → 409. Directory may be empty; pause still cancels the auto timer. |
|
|
63
67
|
| `POST` | `.../requests/:id/delegate` | recovers identity via scoped list; allowed when toggle off |
|
|
64
68
|
| `POST` | `/api/question/:id/reply` | manual: `answers` must be an array (incl. `[]`); never auto text |
|
|
65
|
-
| `POST` | `/api/question/:id/reject` |
|
|
69
|
+
| `POST` | `/api/question/:id/reject` | Compatibility route mapped to native form cancellation |
|
|
66
70
|
|
|
67
71
|
Concurrent claim losers: HTTP **409** + `code: "question_submission_claimed"`.
|
|
68
72
|
|
|
69
73
|
## Claim / upstream rules
|
|
70
74
|
|
|
75
|
+
OpenCode 2.0.15 upstream uses `GET /api/form?location[directory]=...` for
|
|
76
|
+
pending forms and `GET /api/session/:sessionID/form/:formID` before submitting
|
|
77
|
+
`POST .../reply` with `{ answer }`. Only question-tool forms are eligible.
|
|
78
|
+
The fresh schema owns field keys, option values and string/multiselect shape.
|
|
79
|
+
Failed schema reads are `notSent` and pause for retry. Cancellation uses
|
|
80
|
+
`POST .../cancel`. Session directory recovery reads `info.location.directory`;
|
|
81
|
+
live events read `payload.location.directory` and `payload.data`.
|
|
82
|
+
Web, Electron and mobile hosts use the Web adapter; VS Code imports the same
|
|
83
|
+
form IO and core, restoring `/api` for origin-only upstream URLs.
|
|
84
|
+
|
|
71
85
|
1. One authority owns at most one upstream POST per `requestID` (sync claim).
|
|
72
86
|
2. **Definitive reject** (400/409/422) or pre-send throw → **release claim**, pause for human retry. **No immediate auto retry.**
|
|
73
87
|
3. **Unclear post-dispatch result** (timeout/5xx/`uncertain`) → `uncertain` tombstone; no retry; still **blocks** goal.
|
|
74
88
|
4. **Settled** must not be downgraded by a late POST failure.
|
|
75
89
|
5. Successful **scoped** empty list may converge `uncertain`/pending → `external` settled **only for that directory**.
|
|
76
|
-
6.
|
|
90
|
+
6. Compatibility manual reply passes `answers` (including `[]`) to the form adapter, which maps them by authoritative fields. Fixed text is auto/delegate only.
|
|
77
91
|
7. Client `sessionID`/`directory` hints never override a bound identity (conflict → 409).
|
|
78
92
|
8. Auto-submit requires an authoritative directory binding (from event, session cache, or `getSession` write-back). Missing directory → no timer.
|
|
79
|
-
9. Unknown pause/delegate recovers via scoped `
|
|
93
|
+
9. Unknown pause/delegate recovers via scoped `form.list`, filtered to question-tool forms — never invents `questionCount=1`.
|
|
80
94
|
|
|
81
95
|
## Reconcile
|
|
82
96
|
|
|
@@ -119,6 +133,7 @@ Concurrent claim losers: HTTP **409** + `code: "question_submission_claimed"`.
|
|
|
119
133
|
```sh
|
|
120
134
|
bunx vitest run --project @openchamber/web \
|
|
121
135
|
packages/web/server/lib/question-auto-delegate/core.test.js \
|
|
136
|
+
packages/web/server/lib/question-auto-delegate/runtime.test.js \
|
|
122
137
|
packages/web/server/lib/question-auto-delegate/routes.test.js \
|
|
123
138
|
packages/web/server/lib/session-goal/runtime.test.js
|
|
124
139
|
```
|
|
@@ -114,6 +114,8 @@ export interface QuestionAutoDelegatePendingQuestion {
|
|
|
114
114
|
sessionID: string;
|
|
115
115
|
directory?: string | null;
|
|
116
116
|
questions?: unknown[] | null;
|
|
117
|
+
fields?: unknown[];
|
|
118
|
+
metadata?: { kind?: unknown };
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
export interface QuestionAutoDelegateSessionInfo {
|
|
@@ -167,11 +169,13 @@ export interface QuestionAutoDelegateIO {
|
|
|
167
169
|
requestID: string,
|
|
168
170
|
directory: string | undefined,
|
|
169
171
|
answers: string[][],
|
|
172
|
+
sessionID: string,
|
|
170
173
|
): Promise<QuestionAutoDelegateUpstreamResult>;
|
|
171
174
|
postReject(
|
|
172
175
|
requestID: string,
|
|
173
176
|
directory: string | undefined,
|
|
174
|
-
body
|
|
177
|
+
body: unknown,
|
|
178
|
+
sessionID: string,
|
|
175
179
|
): Promise<QuestionAutoDelegateUpstreamResult>;
|
|
176
180
|
/** Project directories used for reconnect/start reconcile. */
|
|
177
181
|
listDirectories(): Promise<string[]>;
|
|
@@ -25,7 +25,8 @@ const normalizeQuestion = (raw, directoryHint = '') => {
|
|
|
25
25
|
const sessionID = asTrimmedString(raw.sessionID);
|
|
26
26
|
if (!requestID || !sessionID) return null;
|
|
27
27
|
const directory = asTrimmedString(raw.directory) || asTrimmedString(directoryHint);
|
|
28
|
-
|
|
28
|
+
if (Array.isArray(raw.fields) && raw.metadata?.kind !== 'question') return null;
|
|
29
|
+
const questions = Array.isArray(raw.fields) ? raw.fields : Array.isArray(raw.questions) ? raw.questions : [];
|
|
29
30
|
return {
|
|
30
31
|
requestID,
|
|
31
32
|
sessionID,
|
|
@@ -608,18 +609,18 @@ export function createQuestionAutoDelegateCore({
|
|
|
608
609
|
try {
|
|
609
610
|
if (kind === 'reject') {
|
|
610
611
|
entry.upstreamDispatched = true;
|
|
611
|
-
upstream = await io.postReject(requestID, directory, input.body);
|
|
612
|
+
upstream = await io.postReject(requestID, directory, input.body, entry.sessionID);
|
|
612
613
|
} else if (authority === 'manual') {
|
|
613
614
|
entry.upstreamDispatched = true;
|
|
614
615
|
// Forward the caller payload exactly (answers may be empty).
|
|
615
|
-
upstream = await io.postReply(requestID, directory, input.answers);
|
|
616
|
+
upstream = await io.postReply(requestID, directory, input.answers, entry.sessionID);
|
|
616
617
|
} else {
|
|
617
618
|
const count = entry.questionCount > 0 ? entry.questionCount : 1;
|
|
618
619
|
const answers = Array.isArray(input.answers) && input.answers.length > 0
|
|
619
620
|
? input.answers
|
|
620
621
|
: buildAutoDelegateAnswers(count, autoAnswer);
|
|
621
622
|
entry.upstreamDispatched = true;
|
|
622
|
-
upstream = await io.postReply(requestID, directory, answers);
|
|
623
|
+
upstream = await io.postReply(requestID, directory, answers, entry.sessionID);
|
|
623
624
|
}
|
|
624
625
|
} catch (error) {
|
|
625
626
|
// Throw before a classified upstream result: treat as pre-send failure → release for retry.
|
|
@@ -1157,12 +1158,18 @@ export function createQuestionAutoDelegateCore({
|
|
|
1157
1158
|
const processEvent = (payload, directoryHint = '') => {
|
|
1158
1159
|
if (disposed || !payload || typeof payload !== 'object') return;
|
|
1159
1160
|
const type = typeof payload.type === 'string' ? payload.type : '';
|
|
1160
|
-
const properties = payload.
|
|
1161
|
-
|
|
1161
|
+
const properties = payload.data && typeof payload.data === 'object'
|
|
1162
|
+
? payload.data
|
|
1163
|
+
: payload.properties && typeof payload.properties === 'object' ? payload.properties : {};
|
|
1164
|
+
const hint = asTrimmedString(payload.location?.directory) || asTrimmedString(directoryHint);
|
|
1162
1165
|
|
|
1163
1166
|
if (type === 'session.created' || type === 'session.updated') {
|
|
1164
|
-
|
|
1165
|
-
|
|
1167
|
+
const info = properties.info ?? {
|
|
1168
|
+
id: properties.sessionID,
|
|
1169
|
+
parentID: properties.parentID,
|
|
1170
|
+
directory: properties.location?.directory,
|
|
1171
|
+
};
|
|
1172
|
+
rememberSession(info, hint);
|
|
1166
1173
|
const sessionId = asTrimmedString(info?.id);
|
|
1167
1174
|
const directory = asTrimmedString(info?.directory) || hint;
|
|
1168
1175
|
if (sessionId && directory) {
|
|
@@ -1176,8 +1183,9 @@ export function createQuestionAutoDelegateCore({
|
|
|
1176
1183
|
return;
|
|
1177
1184
|
}
|
|
1178
1185
|
|
|
1179
|
-
if (type === 'question.asked') {
|
|
1180
|
-
|
|
1186
|
+
if (type === 'question.asked' || type === 'form.created') {
|
|
1187
|
+
if (type === 'form.created' && properties.form?.metadata?.kind !== 'question') return;
|
|
1188
|
+
const normalized = normalizeQuestion(type === 'form.created' ? properties.form : properties, hint);
|
|
1181
1189
|
if (!normalized) return;
|
|
1182
1190
|
rememberSession({ id: normalized.sessionID, directory: normalized.directory }, normalized.directory || hint);
|
|
1183
1191
|
if (!normalized.directory) {
|
|
@@ -1191,13 +1199,13 @@ export function createQuestionAutoDelegateCore({
|
|
|
1191
1199
|
return;
|
|
1192
1200
|
}
|
|
1193
1201
|
|
|
1194
|
-
if (type === 'question.replied' || type === 'question.rejected') {
|
|
1195
|
-
const requestID = asTrimmedString(properties.id) || asTrimmedString(properties.requestID);
|
|
1202
|
+
if (type === 'question.replied' || type === 'question.rejected' || type === 'form.replied' || type === 'form.cancelled') {
|
|
1203
|
+
const requestID = asTrimmedString(properties.formID) || asTrimmedString(properties.id) || asTrimmedString(properties.requestID);
|
|
1196
1204
|
if (!requestID) return;
|
|
1197
1205
|
const entry = requests.get(requestID);
|
|
1198
1206
|
if (!entry) return;
|
|
1199
1207
|
if (entry.state === TERMINAL_SETTLED) return;
|
|
1200
|
-
const resolution = type === 'question.rejected' ? 'rejected' : 'replied';
|
|
1208
|
+
const resolution = type === 'question.rejected' || type === 'form.cancelled' ? 'rejected' : 'replied';
|
|
1201
1209
|
if (entry.state === 'uncertain' || entry.state === 'submitting') {
|
|
1202
1210
|
markSettled(entry, resolution, entry.submittedBy ?? null);
|
|
1203
1211
|
bump();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { QuestionAutoDelegateIO, QuestionAutoDelegateUpstreamResult } from './core.js';
|
|
2
|
+
|
|
3
|
+
export function createQuestionFormIO(upstream: (
|
|
4
|
+
path: string,
|
|
5
|
+
options?: { directory?: string | null; method?: string; body?: unknown },
|
|
6
|
+
) => Promise<QuestionAutoDelegateUpstreamResult>): Pick<QuestionAutoDelegateIO, 'listQuestions' | 'postReply' | 'postReject'>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Native OpenCode form IO shared by Web and the VS Code host. */
|
|
2
|
+
export function createQuestionFormIO(upstream) {
|
|
3
|
+
const formPath = (sessionID, requestID) => {
|
|
4
|
+
if (!sessionID) throw new Error('Question form requires a sessionID');
|
|
5
|
+
return `/session/${encodeURIComponent(sessionID)}/form/${encodeURIComponent(requestID)}`;
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
async listQuestions(directory) {
|
|
9
|
+
const result = await upstream('/form', { directory });
|
|
10
|
+
if (!result.ok) return null;
|
|
11
|
+
const rows = result.body?.data;
|
|
12
|
+
if (!Array.isArray(rows)) return null;
|
|
13
|
+
return rows.filter((form) => form?.metadata?.kind === 'question');
|
|
14
|
+
},
|
|
15
|
+
async postReply(requestID, directory, answers, sessionID) {
|
|
16
|
+
const path = formPath(sessionID, requestID);
|
|
17
|
+
const result = await upstream(path, { directory });
|
|
18
|
+
// A failed schema read never means the answer POST was dispatched.
|
|
19
|
+
if (!result.ok) return { ...result, notSent: true, uncertain: false };
|
|
20
|
+
const form = result.body?.data;
|
|
21
|
+
if (form?.id !== requestID || form?.sessionID !== sessionID
|
|
22
|
+
|| form.metadata?.kind !== 'question' || !Array.isArray(form.fields) || !form.fields.length) {
|
|
23
|
+
throw new Error('Invalid question form schema');
|
|
24
|
+
}
|
|
25
|
+
const answer = Object.fromEntries(form.fields.map((field, index) => {
|
|
26
|
+
if (!field.key || (field.type !== 'string' && field.type !== 'multiselect')) {
|
|
27
|
+
throw new Error('Unsupported question form field');
|
|
28
|
+
}
|
|
29
|
+
const values = (answers[index] ?? []).map((value) =>
|
|
30
|
+
field.options?.find((option) => (option.label || option.value) === value)?.value ?? value);
|
|
31
|
+
return [field.key, field.type === 'multiselect' ? values : values[0] ?? ''];
|
|
32
|
+
}));
|
|
33
|
+
return upstream(`${path}/reply`, { directory, method: 'POST', body: { answer } });
|
|
34
|
+
},
|
|
35
|
+
async postReject(requestID, directory, _body, sessionID) {
|
|
36
|
+
return upstream(`${formPath(sessionID, requestID)}/cancel`, { directory, method: 'POST' });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createQuestionAutoDelegateCore,
|
|
3
3
|
QUESTION_AUTO_DELEGATE_DELAY_MS,
|
|
4
4
|
} from './core.js';
|
|
5
|
+
import { createQuestionFormIO } from './form-io.js';
|
|
5
6
|
|
|
6
7
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
7
8
|
|
|
@@ -44,7 +45,7 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
44
45
|
} = {}) {
|
|
45
46
|
const upstream = async (path, { directory, method = 'GET', body } = {}) => {
|
|
46
47
|
const url = new URL(buildOpenCodeUrl(path, ''));
|
|
47
|
-
if (directory) url.searchParams.set('directory', directory);
|
|
48
|
+
if (directory) url.searchParams.set('location[directory]', directory);
|
|
48
49
|
let response;
|
|
49
50
|
try {
|
|
50
51
|
response = await fetchImpl(url, {
|
|
@@ -149,14 +150,7 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
149
150
|
}
|
|
150
151
|
return Array.from(dirs);
|
|
151
152
|
},
|
|
152
|
-
|
|
153
|
-
const result = await upstream('/question', { directory });
|
|
154
|
-
if (!result.ok) return null;
|
|
155
|
-
const payload = result.body;
|
|
156
|
-
if (Array.isArray(payload)) return payload;
|
|
157
|
-
if (Array.isArray(payload?.data)) return payload.data;
|
|
158
|
-
return null;
|
|
159
|
-
},
|
|
153
|
+
...createQuestionFormIO(upstream),
|
|
160
154
|
async getSession(sessionID, directory) {
|
|
161
155
|
const result = await upstream(`/session/${encodeURIComponent(sessionID)}`, { directory });
|
|
162
156
|
if (!result.ok) return null;
|
|
@@ -167,23 +161,9 @@ export function createQuestionAutoDelegateRuntime({
|
|
|
167
161
|
return {
|
|
168
162
|
id: asTrimmedString(info.id) || sessionID,
|
|
169
163
|
parentID: asTrimmedString(info.parentID) || null,
|
|
170
|
-
directory: asTrimmedString(info.directory) || asTrimmedString(directory) || null,
|
|
164
|
+
directory: asTrimmedString(info.location?.directory) || asTrimmedString(info.directory) || asTrimmedString(directory) || null,
|
|
171
165
|
};
|
|
172
166
|
},
|
|
173
|
-
async postReply(requestID, directory, answers) {
|
|
174
|
-
return upstream(`/question/${encodeURIComponent(requestID)}/reply`, {
|
|
175
|
-
directory,
|
|
176
|
-
method: 'POST',
|
|
177
|
-
body: { answers },
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
|
-
async postReject(requestID, directory, body) {
|
|
181
|
-
return upstream(`/question/${encodeURIComponent(requestID)}/reject`, {
|
|
182
|
-
directory,
|
|
183
|
-
method: 'POST',
|
|
184
|
-
body: body && typeof body === 'object' ? body : {},
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
167
|
},
|
|
188
168
|
});
|
|
189
169
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createQuestionAutoDelegateRuntime } from './runtime.js';
|
|
3
|
+
import { QUESTION_AUTO_DELEGATE_ANSWER } from './core.js';
|
|
4
|
+
|
|
5
|
+
const form = {
|
|
6
|
+
id: 'frm_question', sessionID: 'ses_test', title: 'Choose',
|
|
7
|
+
metadata: { kind: 'question' },
|
|
8
|
+
fields: [{ key: 'approach', type: 'string', custom: true, options: [{ label: 'A', value: 'a' }] }],
|
|
9
|
+
};
|
|
10
|
+
const runtimes = [];
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
runtimes.splice(0).forEach((runtime) => runtime.dispose());
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const setup = async (pending = []) => {
|
|
17
|
+
vi.useFakeTimers();
|
|
18
|
+
let onEvent;
|
|
19
|
+
const fetchImpl = vi.fn(async (url, options) => {
|
|
20
|
+
const path = url.pathname;
|
|
21
|
+
if (path === '/api/form') return Response.json({ data: pending });
|
|
22
|
+
if (path === '/api/session/ses_test') return Response.json({ data: { id: 'ses_test', location: { directory: '/project' } } });
|
|
23
|
+
if (path === '/api/session/ses_test/form/frm_question') return Response.json({ data: form });
|
|
24
|
+
if (path === '/api/session/ses_test/form/frm_question/reply' && options.method === 'POST') return new Response(null, { status: 204 });
|
|
25
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
26
|
+
});
|
|
27
|
+
const runtime = createQuestionAutoDelegateRuntime({
|
|
28
|
+
globalEventHub: { subscribeEvent: (fn) => { onEvent = fn; return () => {}; } },
|
|
29
|
+
buildOpenCodeUrl: (path) => `http://opencode.test/api${path}`,
|
|
30
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
31
|
+
readSettingsFromDiskMigrated: async () => ({ projects: [{ path: '/project' }] }),
|
|
32
|
+
fetchImpl,
|
|
33
|
+
});
|
|
34
|
+
runtimes.push(runtime);
|
|
35
|
+
runtime.start();
|
|
36
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
37
|
+
return {
|
|
38
|
+
runtime, fetchImpl,
|
|
39
|
+
emit: (type, data, directory = '/project') => onEvent({ directory: 'global', payload: {
|
|
40
|
+
type, data, ...(directory ? { location: { directory } } : {}),
|
|
41
|
+
} }),
|
|
42
|
+
posts: () => fetchImpl.mock.calls.filter(([, options]) => options.method === 'POST'),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
it('auto-delegates a native question form after exactly 30 seconds without a UI', async () => {
|
|
47
|
+
const { emit, posts, runtime } = await setup();
|
|
48
|
+
emit('form.created', { sessionID: form.sessionID, form });
|
|
49
|
+
await vi.advanceTimersByTimeAsync(29_999);
|
|
50
|
+
expect(posts()).toHaveLength(0);
|
|
51
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
52
|
+
expect(posts()).toHaveLength(1);
|
|
53
|
+
expect(posts()[0][0].pathname).toBe('/api/session/ses_test/form/frm_question/reply');
|
|
54
|
+
expect(JSON.parse(posts()[0][1].body)).toEqual({ answer: { approach: QUESTION_AUTO_DELEGATE_ANSWER } });
|
|
55
|
+
expect(runtime.snapshot().requests[0].state).toBe('settled');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('recovers pending native question forms on startup', async () => {
|
|
59
|
+
const { posts } = await setup([form]);
|
|
60
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
61
|
+
expect(posts()).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it.each(['form.replied', 'form.cancelled'])('cancels the timer on %s', async (type) => {
|
|
65
|
+
const { emit, posts, runtime } = await setup();
|
|
66
|
+
emit('form.created', { sessionID: form.sessionID, form });
|
|
67
|
+
expect(runtime.snapshot().requests).toHaveLength(1);
|
|
68
|
+
emit(type, { sessionID: form.sessionID, id: form.id });
|
|
69
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
70
|
+
expect(posts()).toHaveLength(0);
|
|
71
|
+
expect(runtime.snapshot().requests[0].state).toBe('settled');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not auto-answer non-question forms', async () => {
|
|
75
|
+
const other = { ...form, metadata: { kind: 'configuration' } };
|
|
76
|
+
const { emit, posts, runtime } = await setup([other]);
|
|
77
|
+
emit('form.created', { sessionID: other.sessionID, form: other });
|
|
78
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
79
|
+
expect(posts()).toHaveLength(0);
|
|
80
|
+
expect(runtime.snapshot().requests).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('resolves a directory-less event through the native session location', async () => {
|
|
84
|
+
const { emit, posts } = await setup();
|
|
85
|
+
emit('form.created', { form }, '');
|
|
86
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
87
|
+
expect(posts()).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('preserves pending questions when native list reconciliation fails', async () => {
|
|
91
|
+
const { emit, runtime, fetchImpl, posts } = await setup();
|
|
92
|
+
emit('form.created', { form });
|
|
93
|
+
const original = fetchImpl.getMockImplementation();
|
|
94
|
+
fetchImpl.mockImplementation((url, options) => url.pathname === '/api/form'
|
|
95
|
+
? Promise.resolve(new Response(null, { status: 503 })) : original(url, options));
|
|
96
|
+
await runtime.reconcile();
|
|
97
|
+
expect(runtime.snapshot().coverage.state).toBe('partial');
|
|
98
|
+
expect(runtime.snapshot().requests[0].state).toBe('counting');
|
|
99
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
100
|
+
expect(posts()).toHaveLength(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('pauses for retry when schema lookup fails, without sending an answer', async () => {
|
|
104
|
+
const { emit, runtime, fetchImpl, posts } = await setup();
|
|
105
|
+
emit('form.created', { form });
|
|
106
|
+
const original = fetchImpl.getMockImplementation();
|
|
107
|
+
fetchImpl.mockImplementation((url, options) => url.pathname.endsWith('/form/frm_question')
|
|
108
|
+
? Promise.resolve(new Response(null, { status: 503 })) : original(url, options));
|
|
109
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
110
|
+
expect(posts()).toHaveLength(0);
|
|
111
|
+
expect(runtime.snapshot().requests[0].state).toBe('paused');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('honors user takeover and explicit delegation of native forms', async () => {
|
|
115
|
+
const { emit, runtime, posts } = await setup();
|
|
116
|
+
emit('form.created', { form });
|
|
117
|
+
const identity = { requestID: form.id, sessionID: form.sessionID, directory: '/project' };
|
|
118
|
+
await runtime.pause({ ...identity, reason: 'interaction' });
|
|
119
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
120
|
+
expect(posts()).toHaveLength(0);
|
|
121
|
+
await runtime.delegate(identity);
|
|
122
|
+
expect(posts()).toHaveLength(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('uses the freshly read schema for multiple fields and array answers', async () => {
|
|
126
|
+
const { emit, fetchImpl, posts } = await setup();
|
|
127
|
+
const original = fetchImpl.getMockImplementation();
|
|
128
|
+
fetchImpl.mockImplementation((url, options) => url.pathname.endsWith('/form/frm_question')
|
|
129
|
+
? Promise.resolve(Response.json({ data: { ...form, fields: [
|
|
130
|
+
{ key: 'actual_key', type: 'string', custom: true },
|
|
131
|
+
{ key: 'targets', type: 'multiselect', custom: true, options: [] },
|
|
132
|
+
] } })) : original(url, options));
|
|
133
|
+
emit('form.created', { form: { ...form, fields: [...form.fields, { key: 'old', type: 'multiselect' }] } });
|
|
134
|
+
await vi.advanceTimersByTimeAsync(30_000);
|
|
135
|
+
expect(JSON.parse(posts()[0][1].body)).toEqual({ answer: {
|
|
136
|
+
actual_key: QUESTION_AUTO_DELEGATE_ANSWER, targets: [QUESTION_AUTO_DELEGATE_ANSWER],
|
|
137
|
+
} });
|
|
138
|
+
});
|
|
@@ -186,6 +186,17 @@ before touching the filesystem). Rationale: metadata rides every
|
|
|
186
186
|
|
|
187
187
|
## Flow
|
|
188
188
|
|
|
189
|
+
Recent-message reads request the newest 40 records with V2 `order: desc`, then
|
|
190
|
+
reverse that page into chronological order for tail checks, audit selection and
|
|
191
|
+
accounting. The SDK and HTTP fallback use the same ordering contract; `asc`
|
|
192
|
+
without a cursor would read the oldest page and strand long sessions after
|
|
193
|
+
compaction. Message IDs are not used to reorder the fetched page.
|
|
194
|
+
|
|
195
|
+
UI goal removal sends the Merge Patch tombstone `openchamber.goal: null`.
|
|
196
|
+
Omitting the key preserves the stored goal. Objective-file cleanup and stopping
|
|
197
|
+
the active turn follow a successful metadata write; failed writes retain the
|
|
198
|
+
goal and its objective for retry.
|
|
199
|
+
|
|
189
200
|
1. `createSessionGoalRuntime` subscribes to the global SSE hub (it needs
|
|
190
201
|
the envelope's `directory`).
|
|
191
202
|
2. `session.status: idle` arms a 15s per-session timer; `busy`/`retry` clears
|
|
@@ -722,20 +722,20 @@ export const createSessionGoalRuntime = ({
|
|
|
722
722
|
const client = openCodeClient();
|
|
723
723
|
if (typeof client?.message?.list === 'function') {
|
|
724
724
|
const listed = await client.message.list(
|
|
725
|
-
{ sessionID: sessionId, limit: MESSAGE_FETCH_LIMIT, order: '
|
|
725
|
+
{ sessionID: sessionId, limit: MESSAGE_FETCH_LIMIT, order: 'desc' },
|
|
726
726
|
directoryRequestOptions(directory),
|
|
727
727
|
);
|
|
728
728
|
const projected = projectGoalMessages(listed, sessionId);
|
|
729
|
-
if (projected) return projected;
|
|
729
|
+
if (projected) return projected.reverse();
|
|
730
730
|
}
|
|
731
731
|
} catch {
|
|
732
732
|
// Fall through to controlled HTTP seam for tests / older fixtures.
|
|
733
733
|
}
|
|
734
734
|
const raw = await openCodeFetch(`/session/${encodeURIComponent(sessionId)}/message`, {
|
|
735
735
|
directory,
|
|
736
|
-
query: { limit: String(MESSAGE_FETCH_LIMIT) },
|
|
736
|
+
query: { limit: String(MESSAGE_FETCH_LIMIT), order: 'desc' },
|
|
737
737
|
}).catch(() => null);
|
|
738
|
-
return projectGoalMessages(raw, sessionId);
|
|
738
|
+
return projectGoalMessages(raw, sessionId)?.reverse() ?? null;
|
|
739
739
|
};
|
|
740
740
|
|
|
741
741
|
/**
|