@openchambery/web 2.1.8-beta.4 → 2.1.8-beta.5
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-Dx-mzVKK.js → AssistantView-DOr44dum.js} +3 -3
- package/dist/assets/{DiagramView-CDmixuZQ.js → DiagramView-DwjEsYl2.js} +1 -1
- package/dist/assets/{MarkdownRenderer-CggK1I6D.js → MarkdownRenderer-BVTJzJKH.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-Cms-VuSl.js → MarkdownRendererImpl-DKRawvvq.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-CrC7_3Y7.js → MarkstreamRendererImpl-8IdRnZ2d.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-CunQ1OIb.js → MobileDetailNavigation-WqYrphqb.js} +1 -1
- package/dist/assets/{MobileShareBridge-CoRbF_fc.js → MobileShareBridge-C6AvFHjG.js} +1 -1
- package/dist/assets/{MobileTabPageHeader-Bj_Lu8tt.js → MobileTabPageHeader-BbDsAtKl.js} +1 -1
- package/dist/assets/{MultiRunWindow-BgNee8Nh.js → MultiRunWindow-19VPj6Of.js} +1 -1
- package/dist/assets/{SettingsView-D0Rk6ERT.js → SettingsView-DruiCw4C.js} +1 -1
- package/dist/assets/{SettingsWindow-8i92GNln.js → SettingsWindow-BBfF03M7.js} +1 -1
- package/dist/assets/{TerminalView-mLwQ0UU9.js → TerminalView-BE1wI19_.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DH6ASkUk.js → ToolOutputDialog-DOXDmUxQ.js} +1 -1
- package/dist/assets/{appThemeRegistry-CHeMJdPY.js → appThemeRegistry-D4TkQDbf.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-CSelAE3Z.js → fileMentionTouchSelection-DuGw9C0F.js} +1 -1
- package/dist/assets/{gitApi-B123oHQk.js → gitApi-QxaZos8j.js} +1 -1
- package/dist/assets/{insertionBoundaries-cfka-ff_.js → insertionBoundaries-BFtbjSBb.js} +1 -1
- package/dist/assets/{main-IO37JIWf.js → main-BJckugyb.js} +2 -2
- package/dist/assets/{main-DJhLxkOQ.js → main-CSpFdmT4.js} +4 -4
- package/dist/assets/{mcpQueries-Digedyij.js → mcpQueries-WrVQC4xT.js} +1 -1
- package/dist/assets/{miniChat-mE8pRELI.js → miniChat-YVKGcjkV.js} +2 -2
- package/dist/assets/{mobile-Cia2889g.js → mobile-DtAt6Tnq.js} +2 -2
- package/dist/assets/{multirun-C1I9Attc.js → multirun-lQfWeDYv.js} +1 -1
- package/dist/assets/{projectMeta-C2WeOasQ.js → projectMeta-gv05m6Dm.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-FpoAJUKI.js → renderElectronMiniChatApp-BRhwzG7e.js} +2 -2
- package/dist/assets/{renderMobileApp-DrT0ZwHw.js → renderMobileApp-FbnzqDL3.js} +4 -4
- package/dist/assets/{runtimeConfig-yW48patn.js → runtimeConfig-Bm1EPWPq.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-CMmN7tH1.js → runtimeEndpointReset-C4gavnsn.js} +1 -1
- package/dist/assets/{sessionChangeSummary-D1YrkHbB.js → sessionChangeSummary-DkNmkmIq.js} +1 -1
- package/dist/assets/{sessionLookup-DrKLmlMv.js → sessionLookup-DWJfnR75.js} +1 -1
- package/dist/assets/{speechSettingsQueries-BNPsqHp5.js → speechSettingsQueries-CoNlugxH.js} +1 -1
- package/dist/assets/{useAppFontEffects-BUJapti0.js → useAppFontEffects-DAeZcw5Z.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-Dlh9xNoE.js → useDesktopWindowControlsLayout-ia1w5b_c.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-2dkaAaxr.js → useEffectiveDirectory-BWMFhdHo.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-BRQBoMI1.js → useMobileNavigationStore-D7wof6Tl.js} +3 -3
- package/dist/assets/{useWorkerHighlightedLines-BRAfFOVn.js → useWorkerHighlightedLines-DdBSr8AB.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/lib/opencode/DOCUMENTATION.md +1 -0
- package/server/lib/opencode/config-entities-layering.test.js +2 -1
- package/server/lib/opencode/plugin-routes.test.js +1 -1
- package/server/lib/opencode/plugins.js +31 -21
- package/server/lib/opencode/plugins.test.js +55 -10
package/dist/assets/{useWorkerHighlightedLines-BRAfFOVn.js → useWorkerHighlightedLines-DdBSr8AB.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t}from"./runtime-fetch-QCWYlAx0.js";import{Mn as n,ln as r}from"./persistence-B7ORJ371.js";import{it as i,rt as a}from"./vendor-base-ui-CdzJ6My_.js";import{n as o}from"./platform-CiBU2bCp.js";import{v as s}from"./useUIStore-CDMI_Yzu.js";import{n as c}from"./vendor-react-BP2cRVqX.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-xol_6BsB.js";import{f as d,r as f,t as p}from"./button-DOrwfkre.js";import{n as m}from"./useThemeSystem-8jhE4Zyk.js";import{n as h}from"./Icon-QASb6r3F.js";import{i as g,n as _,t as v}from"./tooltip-JgW0lJaV.js";import{a as y,i as b,o as x,t as S}from"./image-loading-qNtUCy69.js";import{n as C}from"./MarkdownRenderer-CggK1I6D.js";import{n as w,o as T,s as E,t as D}from"./markdown-worker-DFNZ3KJ_.js";import{n as ee}from"./markdownTheme-CV7_0QAL.js";import{t as te}from"./vendor-tanstack-react-virtual-B_EO--9t.js";var O=e(i(),1),k=c(),A=a(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(h,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(h,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(h,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,m=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(g,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(_,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(v,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==m||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":m,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=m,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await S(e,t??new AbortController().signal),await G(n,t)}finally{n&&y(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=x(a,r);if(b(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=m(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=ee(l),t[0]=l,t[1]=d);let p=d,h,g;t[2]!==n||t[3]!==o||t[4]!==c?(h=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},g=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=h,t[6]=g):(h=t[5],g=t[6]),O.useLayoutEffect(h,g);let _,v;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(_=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return D(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},v=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=_,t[12]=v):(_=t[11],v=t[12]),O.useEffect(_,v);let y;t[13]===i?y=t[14]:(y=f(`typography-code`,i),t[13]=i,t[14]=y);let b=p,x;t[15]!==a||t[16]!==b?(x={...b,...a},t[15]=a,t[16]=b,t[17]=x):x=t[17];let S;return t[18]!==y||t[19]!==x?(S=(0,A.jsx)(`div`,{ref:u,className:y,style:x}),t[18]=y,t[19]=x,t[20]=S):S=t[20],S},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):E(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{n as t}from"./runtime-fetch-QCWYlAx0.js";import{Mn as n,ln as r}from"./persistence-B7ORJ371.js";import{it as i,rt as a}from"./vendor-base-ui-CdzJ6My_.js";import{n as o}from"./platform-CiBU2bCp.js";import{v as s}from"./useUIStore-CDMI_Yzu.js";import{n as c}from"./vendor-react-BP2cRVqX.js";import{a as l,n as u}from"./vendor-aparajita-capacitor-secure-storage-xol_6BsB.js";import{f as d,r as f,t as p}from"./button-DOrwfkre.js";import{n as m}from"./useThemeSystem-8jhE4Zyk.js";import{n as h}from"./Icon-QASb6r3F.js";import{i as g,n as _,t as v}from"./tooltip-JgW0lJaV.js";import{a as y,i as b,o as x,t as S}from"./image-loading-qNtUCy69.js";import{n as C}from"./MarkdownRenderer-BVTJzJKH.js";import{n as w,o as T,s as E,t as D}from"./markdown-worker-DFNZ3KJ_.js";import{n as ee}from"./markdownTheme-CV7_0QAL.js";import{t as te}from"./vendor-tanstack-react-virtual-B_EO--9t.js";var O=e(i(),1),k=c(),A=a(),ne=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,j={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},M=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,N=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,re=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,P=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,F=`${P} max-h-[min(22rem,50vh)] overflow-y-auto`,ie=`${P} w-full`,ae={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},I=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,oe=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,se=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ce=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,le=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ue={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},L={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},R=e=>{let t=(0,k.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:a}=d(),o=z.has(n.status)?n.status:`pending`,s=L[o],c;t[0]!==a||t[1]!==s?(c=a(s),t[0]=a,t[1]=s,t[2]=c):c=t[2];let l=c,u;t[3]===o?u=t[4]:(u=o===`in_progress`?(0,A.jsx)(h,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):o===`completed`?(0,A.jsx)(h,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,A.jsx)(h,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=o,t[4]=u);let p=u,m=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=f(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,I,y),t[5]=y,t[6]=b);let x;t[7]!==p||t[8]!==l?(x=(0,A.jsx)(g,{asChild:!0,children:(0,A.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":l,children:p})}),t[7]=p,t[8]=l,t[9]=x):x=t[9];let S;t[10]===l?S=t[11]:(S=(0,A.jsx)(_,{side:`left`,sideOffset:6,children:l}),t[10]=l,t[11]=S);let C;t[12]!==x||t[13]!==S?(C=(0,A.jsxs)(v,{children:[x,S]}),t[12]=x,t[13]=S,t[14]=C):C=t[14];let w=ue[o],T;t[15]===w?T=t[16]:(T=f(`block whitespace-normal break-words`,w),t[15]=w,t[16]=T);let E;t[17]!==T||t[18]!==n.content?(E=(0,A.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,A.jsx)(`span`,{className:T,children:n.content})}),t[17]=T,t[18]=n.content,t[19]=E):E=t[19];let D;return t[20]!==i||t[21]!==E||t[22]!==m||t[23]!==b||t[24]!==C?(D=(0,A.jsxs)(`li`,{ref:i,"aria-current":m,className:b,children:[C,E]}),t[20]=i,t[21]=E,t[22]=m,t[23]=b,t[24]=C,t[25]=D):D=t[25],D},z=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),B=`image/png`,V=l(`OpenChamberMedia`),H=e=>{let t=e.toLowerCase().split(`;`)[0]?.trim()||B;if(t===`image/jpeg`||t===`image/jpg`)return`jpg`;if(t===`image/webp`)return`webp`;if(t===`image/gif`)return`gif`;if(t===`image/svg+xml`)return`svg`;if(t===`image/png`)return`png`;let n=t.split(`/`)[1];return n&&/^[a-z0-9.+-]+$/i.test(n)?n:`png`},U=(e,t)=>{let n=`image.${H(t)}`,r=(((e||``).trim()||n).split(/[\\/]/).pop()||n).replace(/[^\w.\- ()[\]]+/g,`_`).replace(/^\.+/,``);return r?/\.[a-z0-9]{1,8}$/i.test(r)?r:`${r}.${H(t)}`:n},W=e=>new Promise((t,n)=>{let r=new FileReader;r.onload=()=>{if(typeof r.result==`string`){t(r.result);return}n(Error(`Failed to encode image`))},r.onerror=()=>n(r.error??Error(`Failed to encode image`)),r.readAsDataURL(e)}),de=async e=>{let t=await W(e),n=t.indexOf(`,`);return n>=0?t.slice(n+1):t},fe=(e,t)=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)},G=async(e,t)=>{let n=await fetch(e,{signal:t,cache:`no-store`});if(!n.ok)throw Error(`Image fetch failed (${n.status})`);return n.blob()},pe=async(e,t=B)=>{if(!e.naturalWidth||!e.naturalHeight)return null;let n=e.currentSrc||e.src;if(n&&(/^(?:blob:|data:|openchamber-asset:)/i.test(n)||/^https?:/i.test(n)||/^\/api(?=\/|[?#]|$)/.test(n)))try{let e=await G(n);if(e.size>0)return e}catch{}try{let n=document.createElement(`canvas`);n.width=e.naturalWidth,n.height=e.naturalHeight;let r=n.getContext(`2d`);if(!r)return null;r.drawImage(e,0,0);let i=t.startsWith(`image/`)?t:B,a=await new Promise(e=>{n.toBlob(t=>e(t),i===`image/jpg`?`image/jpeg`:i)});return a&&a.size>0?a:null}catch{return null}},me=async(e,t)=>{let n=``;try{return n=await S(e,t??new AbortController().signal),await G(n,t)}finally{n&&y(n)}},he=async(e,n)=>{let r=e.effectiveDirectory??``,i=null;e.prefetchedBlob&&e.prefetchedBlob.size>0&&(i=e.prefetchedBlob);let a=e.displayUrl?.trim();if(!i&&a)try{i=await G(a,n)}catch{i=null}if(!i){let a=e.sourceUrl.trim();if(!a)throw Error(`Image source is empty`);if(/^(?:data|blob):/i.test(a)||/^https?:/i.test(a)||/^\/api(?=\/|[?#]|$)/.test(a))i=await G(a,n);else{let e=x(a,r);if(b(e))try{i=await me(e.path,n)}catch{let r=await t(`/api/fs/raw`,{method:`GET`,cache:`no-store`,query:{path:e.path},signal:n});if(!r.ok)throw Error(`Image source request failed with status ${r.status}`);i=await r.blob()}else i=await G(e.source,n)}}if(!i||i.size<=0)throw Error(`Image is empty`);let o=(e.mimeType||(i.type&&i.type.startsWith(`image/`)?i.type:``)||B).split(`;`)[0].trim()||B;return{blob:i,filename:U(e.filename,o),mimeType:o}},ge=()=>!o()||u===void 0?!1:u.isPluginAvailable(`OpenChamberMedia`),_e=async(e,t)=>{let{blob:i,filename:a,mimeType:o}=await he(e,t);if(ge()){let e=await de(i);return await V.saveImage({dataBase64:e,mimeType:o,filename:a}),`saved`}if(r()){let e=n()?.vscode;if(e?.saveImage){let t=await W(i);return await e.saveImage({fileName:a,dataUrl:t}),`saved`}}return fe(i,a),`downloaded`},ve=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),ye=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,K=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=ye(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},be=e=>`<pre><code>${ve(e)}</code></pre>`,xe=e=>{let t=(0,k.c)(21),{code:n,language:r,className:i,style:a,codeStyle:o,wrap:s}=e,c=s!==void 0&&s,{currentTheme:l}=m(),u=O.useRef(null),d;t[0]===l?d=t[1]:(d=ee(l),t[0]=l,t[1]=d);let p=d,h,g;t[2]!==n||t[3]!==o||t[4]!==c?(h=()=>{let e=u.current;e&&(e.innerHTML=be(n),K(e,c,o))},g=[n,c,o],t[2]=n,t[3]=o,t[4]=c,t[5]=h,t[6]=g):(h=t[5],g=t[6]),O.useLayoutEffect(h,g);let _,v;t[7]!==n||t[8]!==o||t[9]!==r||t[10]!==c?(_=()=>{let e=u.current;if(!e)return;let t=!0,i=new AbortController;return D(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,K(e,c,o))}),()=>{t=!1,i.abort()}},v=[n,r,c,o],t[7]=n,t[8]=o,t[9]=r,t[10]=c,t[11]=_,t[12]=v):(_=t[11],v=t[12]),O.useEffect(_,v);let y;t[13]===i?y=t[14]:(y=f(`typography-code`,i),t[13]=i,t[14]=y);let b=p,x;t[15]!==a||t[16]!==b?(x={...b,...a},t[15]=a,t[16]=b,t[17]=x):x=t[17];let S;return t[18]!==y||t[19]!==x?(S=(0,A.jsx)(`div`,{ref:u,className:y,style:x}),t[18]=y,t[19]=x,t[20]=S):S=t[20],S},Se=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},Ce=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},we=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,Te=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,Ee=(e,t)=>!e||typeof e!=`object`?String(e):E(e,t||``),q=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},De=e=>{if(!e||typeof e!=`object`)return;let t=q(e.patch)??q(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=q(e.patch)??q(e.diff);if(t)return t}},Oe=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},ke=(e,t,n)=>{let r=Se(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&we(r)&&(r=Te(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=De(n);if(e)return e}return r},Ae=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
|
|
2
2
|
`).split(`
|
|
3
3
|
`),r=e=>/\(\s*File has more lines\..*offset.*\)/i.test(e.trim());return{type:t,lines:n.map(e=>{let n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);if(t!==`directory`){let t=e.match(/^(\d+):\s?(.*)$/);if(t){let e=t[2],n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);return{lineNumber:i?null:Number(t[1]),text:e,isInfo:i}}}return{lineNumber:null,text:e,isInfo:i}}).filter((e,t,n)=>{if(e.text.trim().length>0)return!0;let r=n[t-1],i=n[t+1],a=!!(r?.isInfo||i?.isInfo),o=e.lineNumber!==null;return!(a&&o)})}},je=(e,t)=>{try{let n=e.trim().split(`
|
|
4
4
|
`).filter(Boolean);if(n.length===0)return null;let r=[];return n.forEach(e=>{let t=e.match(/^(\s*)(.+)$/);if(t){let[,e,n]=t,i=Math.floor(e.length/2),a=!n.endsWith(`/`);r.push({name:n.replace(/\/$/,``),depth:i,isFile:a})}}),(0,A.jsx)(`div`,{className:f(`w-full min-w-0 font-mono space-y-0.5`,t?.unstyled?null:`p-3 bg-muted/20 rounded-xl border border-border/30`),style:s.tool.popup,children:r.map((e,t)=>(0,A.jsx)(`div`,{className:`min-w-0`,style:{paddingLeft:`${e.depth*20}px`},children:e.isFile?(0,A.jsx)(`span`,{className:`text-foreground/90 block truncate`,children:e.name}):(0,A.jsxs)(`span`,{className:`font-semibold text-foreground block truncate`,children:[e.name,`/`]})},t))})}catch{return null}},Me={backgroundColor:`var(--status-info)`,opacity:.6},Ne=(e,t,n)=>{try{let r=e.trim().split(`
|
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-BJckugyb.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-C8w0nlKy.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-3ad3yzM7.js">
|
|
@@ -572,7 +572,7 @@
|
|
|
572
572
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-bN5IYQau.js">
|
|
573
573
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
574
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Bm1EPWPq.js">
|
|
576
576
|
<link rel="stylesheet" crossorigin href="/assets/src-C9BNQK0O.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-YVKGcjkV.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-C8w0nlKy.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-3ad3yzM7.js">
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-bN5IYQau.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Bm1EPWPq.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/src-C9BNQK0O.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-DtAt6Tnq.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-C8w0nlKy.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-3ad3yzM7.js">
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-bN5IYQau.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-KNLHDj7G.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-Bm1EPWPq.js">
|
|
87
87
|
<link rel="stylesheet" crossorigin href="/assets/src-C9BNQK0O.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
19
19
|
from the authenticated client context (null for owner UI sessions), allowing
|
|
20
20
|
connection shortcuts to exclude themselves without changing management lists.
|
|
21
21
|
- `plugins.js`: config entries and plugin-directory files are inventory, not compatibility checks. They do not synthesize `compatibility` or `compatible` fields; Settings uses the OpenCode runtime inventory for actual load status and errors.
|
|
22
|
+
- Reads native V2 `plugins` (strings or `{ package, options }`) with legacy `plugin`/tuple fallback. A native array, including an empty one, wins within each document. Entry mutations write only `plugins`, convert retained legacy tuples, and remove the shadowed legacy key in the touched source file. Options, ordering, unrelated configuration and JSONC comments are preserved through the shared atomic config writer; malformed entries or write failures do not replace the original file. Web, Electron and mobile use this host implementation; VS Code mirrors the same persistence contract.
|
|
22
23
|
- `session-directory-runtime.js`: Host-side physical directory identity for session index/list/detail/live projection. Uses a runtime-scoped, bounded 256-entry cache (60s success / 5s failed resolution), cleared when the upstream URL changes. Local directory lists use the upstream project identity and physical-directory filtering to include legacy symlink sessions without mixing worktrees. One request reads one page and preserves opaque cursors; consumers must follow continuations even after filtered-empty pages. Non-loopback upstreams keep their own path namespace.
|
|
23
24
|
- `packages/web/server/lib/opencode/index.js`: public entrypoint (currently baseline placeholder).
|
|
24
25
|
- `packages/web/server/lib/opencode/auth.js`: host-only authoritative provider credential API reads and deletion.
|
|
@@ -66,7 +66,8 @@ describe('Web/Electron entity physical provenance', () => {
|
|
|
66
66
|
fs.writeFileSync(jsonc, '{ // retained\n"plugin":["higher-plugin"]}');
|
|
67
67
|
const entry = plugins.listPluginEntries().find((item) => item.spec === 'lower-plugin');
|
|
68
68
|
plugins.updatePluginEntry(entry.id, { spec: 'updated-plugin' });
|
|
69
|
-
expect(readConfigFile(json).
|
|
69
|
+
expect(readConfigFile(json).plugins).toEqual(['updated-plugin']);
|
|
70
|
+
expect(readConfigFile(json).plugin).toBeUndefined();
|
|
70
71
|
expect(readConfigFile(jsonc).plugin).toEqual(['higher-plugin']);
|
|
71
72
|
});
|
|
72
73
|
});
|
|
@@ -316,7 +316,7 @@ describe('opencode plugin routes', () => {
|
|
|
316
316
|
|
|
317
317
|
const after = await request(app).get('/api/config/plugins').expect(200);
|
|
318
318
|
expect(after.body.entries).toEqual([]);
|
|
319
|
-
expect(readJson(userConfigPath).
|
|
319
|
+
expect(readJson(userConfigPath).plugins).toBeUndefined();
|
|
320
320
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
321
321
|
});
|
|
322
322
|
|
|
@@ -79,6 +79,25 @@ function readPluginConfigLayers(workingDirectory) {
|
|
|
79
79
|
return readConfigLayers(workingDirectory);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function readPluginArray(config) {
|
|
83
|
+
if (Array.isArray(config.plugins)) return config.plugins;
|
|
84
|
+
return Array.isArray(config.plugin) ? config.plugin : [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writePluginArray(config, plugin, filePath) {
|
|
88
|
+
const next = { ...config };
|
|
89
|
+
delete next.plugin;
|
|
90
|
+
if (plugin.length > 0) {
|
|
91
|
+
next.plugins = plugin.map((raw) => {
|
|
92
|
+
const parsed = parsePluginRaw(raw);
|
|
93
|
+
return Array.isArray(raw) ? serializePluginEntry(parsed) : raw;
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
delete next.plugins;
|
|
97
|
+
}
|
|
98
|
+
writeConfig(next, filePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
function validateFileName(fileName) {
|
|
83
102
|
if (typeof fileName !== 'string' || !fileName) {
|
|
84
103
|
throw codedError('Plugin file name is required', 'INVALID_FILENAME');
|
|
@@ -125,9 +144,8 @@ function getPluginTarget(id, workingDirectory) {
|
|
|
125
144
|
validateScope(scope);
|
|
126
145
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
127
146
|
const source = configSources(layers).find((candidate) => candidate.scope === scope
|
|
128
|
-
&&
|
|
129
|
-
|
|
130
|
-
const plugin = Array.isArray(source?.config?.plugin) ? source.config.plugin : [];
|
|
147
|
+
&& readPluginArray(candidate.config).some((raw) => parsePluginRaw(raw).spec === spec));
|
|
148
|
+
const plugin = source ? [...readPluginArray(source.config)] : [];
|
|
131
149
|
const index = plugin.findIndex((raw) => parsePluginRaw(raw).spec === spec);
|
|
132
150
|
if (!source || index === -1) {
|
|
133
151
|
return null;
|
|
@@ -181,13 +199,16 @@ function parsePluginRaw(raw) {
|
|
|
181
199
|
if (Array.isArray(raw) && raw.length === 2 && isRecord(raw[1])) {
|
|
182
200
|
return { spec: validatePluginSpec(raw[0]), options: { ...raw[1] } };
|
|
183
201
|
}
|
|
184
|
-
|
|
202
|
+
if (isRecord(raw) && (raw.options === undefined || isRecord(raw.options))) {
|
|
203
|
+
return { spec: validatePluginSpec(raw.package), ...(raw.options === undefined ? {} : { options: { ...raw.options } }) };
|
|
204
|
+
}
|
|
205
|
+
throw codedError('Plugin spec must be a string or { package, options } object', 'INVALID_SPEC');
|
|
185
206
|
}
|
|
186
207
|
|
|
187
208
|
function serializePluginEntry(entry) {
|
|
188
209
|
const spec = validatePluginSpec(entry?.spec);
|
|
189
210
|
if (hasOptions(entry?.options)) {
|
|
190
|
-
return
|
|
211
|
+
return { package: spec, options: { ...entry.options } };
|
|
191
212
|
}
|
|
192
213
|
return spec;
|
|
193
214
|
}
|
|
@@ -195,10 +216,7 @@ function serializePluginEntry(entry) {
|
|
|
195
216
|
function listPluginEntries(workingDirectory) {
|
|
196
217
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
197
218
|
return configSources(layers).flatMap((source) => {
|
|
198
|
-
|
|
199
|
-
return [];
|
|
200
|
-
}
|
|
201
|
-
return source.config.plugin.map((raw) => {
|
|
219
|
+
return readPluginArray(source.config).map((raw) => {
|
|
202
220
|
const parsed = parsePluginRaw(raw);
|
|
203
221
|
return {
|
|
204
222
|
id: encodePluginId('config', `${source.scope}:${parsed.spec}`),
|
|
@@ -225,8 +243,7 @@ function createPluginEntry(entry, workingDirectory) {
|
|
|
225
243
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
226
244
|
const existing = configSources(layers).find((source) => (
|
|
227
245
|
source.scope === scope
|
|
228
|
-
&&
|
|
229
|
-
&& source.config.plugin.some((raw) => parsePluginRaw(raw).spec === spec)
|
|
246
|
+
&& readPluginArray(source.config).some((raw) => parsePluginRaw(raw).spec === spec)
|
|
230
247
|
));
|
|
231
248
|
if (existing) {
|
|
232
249
|
throw codedError(`Plugin "${spec}" already exists`, 'ENTRY_EXISTS');
|
|
@@ -242,11 +259,7 @@ function createPluginEntry(entry, workingDirectory) {
|
|
|
242
259
|
config = getConfigForPath(layers, targetPath);
|
|
243
260
|
}
|
|
244
261
|
|
|
245
|
-
|
|
246
|
-
config.plugin = [];
|
|
247
|
-
}
|
|
248
|
-
config.plugin.push(serializePluginEntry({ spec, options: entry.options }));
|
|
249
|
-
writeConfig(config, targetPath);
|
|
262
|
+
writePluginArray(config, [...readPluginArray(config), serializePluginEntry({ spec, options: entry.options })], targetPath);
|
|
250
263
|
}
|
|
251
264
|
|
|
252
265
|
function updatePluginEntry(id, updates, workingDirectory) {
|
|
@@ -258,7 +271,7 @@ function updatePluginEntry(id, updates, workingDirectory) {
|
|
|
258
271
|
const nextSpec = updates?.spec === undefined ? existing.spec : validatePluginSpec(updates.spec);
|
|
259
272
|
const nextOptions = updates?.options === undefined ? existing.options : updates.options;
|
|
260
273
|
target.plugin[target.index] = serializePluginEntry({ spec: nextSpec, options: nextOptions });
|
|
261
|
-
|
|
274
|
+
writePluginArray(target.source.config, target.plugin, target.source.filePath);
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
function deletePluginEntry(id, workingDirectory) {
|
|
@@ -267,10 +280,7 @@ function deletePluginEntry(id, workingDirectory) {
|
|
|
267
280
|
throw codedError('Plugin entry not found', 'NOT_FOUND');
|
|
268
281
|
}
|
|
269
282
|
target.plugin.splice(target.index, 1);
|
|
270
|
-
|
|
271
|
-
delete target.source.config.plugin;
|
|
272
|
-
}
|
|
273
|
-
writeConfig(target.source.config, target.source.filePath);
|
|
283
|
+
writePluginArray(target.source.config, target.plugin, target.source.filePath);
|
|
274
284
|
}
|
|
275
285
|
|
|
276
286
|
function listPluginDirFiles(workingDirectory) {
|
|
@@ -52,6 +52,9 @@ describe('opencode plugins data layer', () => {
|
|
|
52
52
|
expect(plugins.parsePluginRaw('foo@1.0.0')).toEqual({ spec: 'foo@1.0.0' });
|
|
53
53
|
expect(plugins.parsePluginRaw(['foo', { a: 1 }])).toEqual({ spec: 'foo', options: { a: 1 } });
|
|
54
54
|
expect(plugins.parsePluginRaw(['foo', {}])).toEqual({ spec: 'foo', options: {} });
|
|
55
|
+
expect(plugins.parsePluginRaw({ package: 'foo', options: { a: 1 } })).toEqual({ spec: 'foo', options: { a: 1 } });
|
|
56
|
+
expect(plugins.parsePluginRaw({ package: 'foo' })).toEqual({ spec: 'foo' });
|
|
57
|
+
expect(() => plugins.parsePluginRaw({ package: 'foo', options: [] })).toThrow('Plugin spec');
|
|
55
58
|
expect(() => plugins.parsePluginRaw(123)).toThrow('Plugin spec');
|
|
56
59
|
});
|
|
57
60
|
|
|
@@ -59,7 +62,7 @@ describe('opencode plugins data layer', () => {
|
|
|
59
62
|
expect(plugins.serializePluginEntry({ spec: 'foo' })).toBe('foo');
|
|
60
63
|
expect(plugins.serializePluginEntry({ spec: 'foo', options: undefined })).toBe('foo');
|
|
61
64
|
expect(plugins.serializePluginEntry({ spec: 'foo', options: {} })).toBe('foo');
|
|
62
|
-
expect(plugins.serializePluginEntry({ spec: 'foo', options: { a: 1 } })).toEqual(
|
|
65
|
+
expect(plugins.serializePluginEntry({ spec: 'foo', options: { a: 1 } })).toEqual({ package: 'foo', options: { a: 1 } });
|
|
63
66
|
});
|
|
64
67
|
|
|
65
68
|
test('rejects invalid specs and file names', () => {
|
|
@@ -71,11 +74,11 @@ describe('opencode plugins data layer', () => {
|
|
|
71
74
|
expect(() => plugins.writePluginDirFile({ fileName: 'foo.txt', content: '', scope: 'project' }, projectDir)).toThrow('Plugin file name');
|
|
72
75
|
});
|
|
73
76
|
|
|
74
|
-
test('creates string and
|
|
77
|
+
test('creates V2 string and object entries with duplicate rejection', () => {
|
|
75
78
|
plugins.createPluginEntry({ spec: 'plain-plugin', scope: 'user' }, projectDir);
|
|
76
79
|
plugins.createPluginEntry({ spec: 'tuple-plugin', options: { apiKey: 'x' }, scope: 'user' }, projectDir);
|
|
77
80
|
|
|
78
|
-
expect(readJson(userConfigPath).
|
|
81
|
+
expect(readJson(userConfigPath).plugins).toEqual(['plain-plugin', { package: 'tuple-plugin', options: { apiKey: 'x' } }]);
|
|
79
82
|
expect(() => plugins.createPluginEntry({ spec: 'plain-plugin', scope: 'user' }, projectDir)).toThrow('already exists');
|
|
80
83
|
expect(thrownBy(() => plugins.createPluginEntry({ spec: 'plain-plugin', scope: 'user' }, projectDir))).toHaveProperty('code', 'ENTRY_EXISTS');
|
|
81
84
|
});
|
|
@@ -84,8 +87,8 @@ describe('opencode plugins data layer', () => {
|
|
|
84
87
|
plugins.createPluginEntry({ spec: 'user-plugin', scope: 'user' }, projectDir);
|
|
85
88
|
plugins.createPluginEntry({ spec: 'project-plugin', scope: 'project' }, projectDir);
|
|
86
89
|
|
|
87
|
-
expect(readJson(userConfigPath).
|
|
88
|
-
expect(readJson(path.join(projectDir, '.opencode', 'opencode.json')).
|
|
90
|
+
expect(readJson(userConfigPath).plugins).toEqual(['user-plugin']);
|
|
91
|
+
expect(readJson(path.join(projectDir, '.opencode', 'opencode.json')).plugins).toEqual(['project-plugin']);
|
|
89
92
|
});
|
|
90
93
|
|
|
91
94
|
test('re-resolves custom config env between calls', () => {
|
|
@@ -100,20 +103,62 @@ describe('opencode plugins data layer', () => {
|
|
|
100
103
|
plugins.createPluginEntry({ spec: 'second-plugin', scope: 'user' }, projectDir);
|
|
101
104
|
plugins.writePluginDirFile({ fileName: 'second.js', content: 'two', scope: 'user' }, projectDir);
|
|
102
105
|
|
|
103
|
-
expect(readJson(firstConfigPath).
|
|
104
|
-
expect(readJson(secondConfigPath).
|
|
106
|
+
expect(readJson(firstConfigPath).plugins).toEqual(['first-plugin']);
|
|
107
|
+
expect(readJson(secondConfigPath).plugins).toEqual(['second-plugin']);
|
|
105
108
|
expect(fs.existsSync(path.join(path.dirname(firstConfigPath), 'plugins', 'first.js'))).toBe(true);
|
|
106
109
|
expect(fs.existsSync(path.join(path.dirname(secondConfigPath), 'plugins', 'second.js'))).toBe(true);
|
|
107
110
|
});
|
|
108
111
|
|
|
109
|
-
test('
|
|
112
|
+
test('migrates legacy entries in place and transitions between string and object', () => {
|
|
110
113
|
writeJson(userConfigPath, { plugin: ['first', ['second', { a: 1 }], 'third'] });
|
|
111
114
|
|
|
112
115
|
plugins.updatePluginEntry(plugins.encodePluginId('config', 'user:second'), { spec: 'second-new', options: {} }, projectDir);
|
|
113
|
-
expect(readJson(userConfigPath)
|
|
116
|
+
expect(readJson(userConfigPath)).toEqual({ plugins: ['first', 'second-new', 'third'] });
|
|
114
117
|
|
|
115
118
|
plugins.updatePluginEntry(plugins.encodePluginId('config', 'user:first'), { spec: 'first-new', options: { b: 2 } }, projectDir);
|
|
116
|
-
expect(readJson(userConfigPath).
|
|
119
|
+
expect(readJson(userConfigPath).plugins).toEqual([{ package: 'first-new', options: { b: 2 } }, 'second-new', 'third']);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('converts untouched legacy options on save and preserves unrelated JSONC', () => {
|
|
123
|
+
fs.writeFileSync(userConfigPath, '// retained comment\n' + JSON.stringify({ model: 'example/model', plugin: [['old', { nested: { enabled: false } }]] }));
|
|
124
|
+
plugins.createPluginEntry({ spec: 'new', scope: 'user' }, projectDir);
|
|
125
|
+
const text = fs.readFileSync(userConfigPath, 'utf8');
|
|
126
|
+
expect(text).toContain('// retained comment');
|
|
127
|
+
expect(plugins.listPluginEntries(projectDir).map(({ spec, options }) => ({ spec, options }))).toEqual([
|
|
128
|
+
{ spec: 'old', options: { nested: { enabled: false } } }, { spec: 'new', options: undefined },
|
|
129
|
+
]);
|
|
130
|
+
expect(text).toContain('"plugins"');
|
|
131
|
+
expect(text).not.toContain('"plugin"');
|
|
132
|
+
expect(text).toContain('example/model');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('prefers native plugins over legacy entries including an empty native array', () => {
|
|
136
|
+
writeJson(userConfigPath, { plugin: ['shadowed'], plugins: [{ package: 'native', options: { enabled: false } }] });
|
|
137
|
+
expect(plugins.listPluginEntries(projectDir).map((entry) => entry.spec)).toEqual(['native']);
|
|
138
|
+
expect(() => plugins.createPluginEntry({ spec: 'native' }, projectDir)).toThrow('already exists');
|
|
139
|
+
plugins.deletePluginEntry(plugins.encodePluginId('config', 'user:native'), projectDir);
|
|
140
|
+
expect(readJson(userConfigPath)).toEqual({});
|
|
141
|
+
writeJson(userConfigPath, { plugin: ['shadowed'], plugins: [] });
|
|
142
|
+
expect(plugins.listPluginEntries(projectDir)).toEqual([]);
|
|
143
|
+
plugins.createPluginEntry({ spec: 'new' }, projectDir);
|
|
144
|
+
expect(readJson(userConfigPath)).toEqual({ plugins: ['new'] });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('malformed legacy data and failed atomic writes leave original config intact', () => {
|
|
148
|
+
writeJson(userConfigPath, { plugin: [['bad', null]], model: 'example/model' });
|
|
149
|
+
const malformed = fs.readFileSync(userConfigPath, 'utf8');
|
|
150
|
+
expect(() => plugins.createPluginEntry({ spec: 'new' }, projectDir)).toThrow('Plugin spec');
|
|
151
|
+
expect(fs.readFileSync(userConfigPath, 'utf8')).toBe(malformed);
|
|
152
|
+
|
|
153
|
+
writeJson(userConfigPath, { plugin: [['old', { enabled: false }]] });
|
|
154
|
+
const original = fs.readFileSync(userConfigPath, 'utf8');
|
|
155
|
+
const rename = vi.spyOn(fs, 'renameSync').mockImplementation(() => { throw new Error('write failed'); });
|
|
156
|
+
try {
|
|
157
|
+
expect(() => plugins.createPluginEntry({ spec: 'new' }, projectDir)).toThrow('write failed');
|
|
158
|
+
expect(fs.readFileSync(userConfigPath, 'utf8')).toBe(original);
|
|
159
|
+
} finally {
|
|
160
|
+
rename.mockRestore();
|
|
161
|
+
}
|
|
117
162
|
});
|
|
118
163
|
|
|
119
164
|
test('deletes entries and prunes empty plugin key', () => {
|