@shawnstack/quickforge 1.7.2 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +1 -0
  2. package/bin/quickforge.mjs +2 -0
  3. package/dist/assets/{AgentProfilesPage-BFgyFa5e.js → AgentProfilesPage-D7pbg8nm.js} +1 -1
  4. package/dist/assets/{ChatPanelHost-Cld9HcVz.js → ChatPanelHost-wfGCaCzD.js} +62 -62
  5. package/dist/assets/{PluginsPage-DEMqwhOA.js → PluginsPage-NAGroBm3.js} +1 -1
  6. package/dist/assets/{ScheduledTasksPage-DTP_gedp.js → ScheduledTasksPage-Bp0MNfQD.js} +2 -2
  7. package/dist/assets/{SettingsWorkspacePage-Dr9zT5Bs.js → SettingsWorkspacePage-BtJZMIsZ.js} +350 -269
  8. package/dist/assets/{SharedConversationPage-op3DRwcw.js → SharedConversationPage-Ce3KoVZt.js} +1 -1
  9. package/dist/assets/{TerminalDock-BL_UovwU.js → TerminalDock-4eUuXP7X.js} +2 -2
  10. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +13 -0
  11. package/dist/assets/{index-yV1wtqTr.js → index-C6k5taeb.js} +8 -8
  12. package/dist/assets/index-CzN8NSKC.css +3 -0
  13. package/dist/assets/{mcp-servers-dialog-Bp6kbIup.js → mcp-servers-dialog-BQbFywVL.js} +2 -2
  14. package/dist/assets/{skills-dialog-DiJAUfvW.js → skills-dialog-REBeTFSH.js} +1 -1
  15. package/dist/index.html +4 -4
  16. package/package.json +4 -1
  17. package/server/ai-http-logger.mjs +6 -6
  18. package/server/ai-provider-options.mjs +8 -0
  19. package/server/index.mjs +7 -2
  20. package/server/network-proxy.mjs +384 -0
  21. package/server/public-api.mjs +4 -0
  22. package/server/routes/agent-profiles.mjs +2 -0
  23. package/server/routes/models.mjs +1 -0
  24. package/server/routes/scheduled-tasks.mjs +2 -0
  25. package/server/routes/system.mjs +26 -0
  26. package/server/routes/workspace.mjs +2 -0
  27. package/server/session-utils.mjs +2 -0
  28. package/dist/assets/WorkspaceInspector-73dhAime.js +0 -13
  29. package/dist/assets/index-Cu2bBHLv.css +0 -3
@@ -1,5 +1,5 @@
1
- import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{B as t,E as n,Pt as r,S as i,a,h as o,jt as s,tt as c,v as l}from"./icons-BP8YOS-Z.js";import{n as u}from"./react-vendor-Dr5xvL-e.js";import{V as d,b as f,ft as p,ht as m,mt as h,yt as g}from"./index-yV1wtqTr.js";var _=e(r(),1),v=u();function y(e){return e===`connected`?`bg-emerald-500/12 text-emerald-700 dark:text-emerald-300`:e===`error`?`bg-destructive/12 text-destructive`:e===`disabled`?`bg-muted text-muted-foreground`:`bg-amber-500/12 text-amber-700 dark:text-amber-300`}var b=12;function x({server:e,toggling:r,reconnecting:i,onToggle:o,onEdit:s,onDelete:c,onReconnect:u}){let d=e.tools?.slice(0,b)??[],f=e.toolCount??e.tools?.length??0,p=Math.max(0,f-d.length),_=e.enabled&&e.status!==`connected`;return(0,v.jsxs)(`div`,{className:`rounded-lg border border-border p-3`,children:[(0,v.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,v.jsxs)(`div`,{className:`min-w-0`,children:[(0,v.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,v.jsx)(`div`,{className:`truncate text-sm font-medium text-foreground/90`,children:e.name}),(0,v.jsx)(`span`,{className:m(`rounded-full px-2 py-0.5 text-[11px]`,y(e.status)),children:e.status||`unknown`}),(0,v.jsx)(`span`,{className:`text-[11px] text-muted-foreground/60`,children:g(`mcpToolsCount`,{count:f})})]}),(0,v.jsx)(`div`,{className:`mt-1 truncate text-xs text-muted-foreground/65`,children:e.transport===`stdio`?`${e.command} ${(e.args||[]).join(` `)}`:e.url}),e.error?(0,v.jsx)(`div`,{className:`mt-1 text-xs text-destructive`,children:e.error}):null]}),(0,v.jsxs)(`div`,{className:`flex shrink-0 items-center gap-1`,children:[(0,v.jsx)(`button`,{type:`button`,role:`switch`,"aria-checked":e.enabled,disabled:r,className:m(`relative h-6 w-11 rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60`,e.enabled?`bg-emerald-500`:`bg-muted-foreground/30`),onClick:()=>o(e),title:e.enabled?g(`pauseTask`):g(`enable`),children:(0,v.jsx)(`span`,{className:m(`absolute left-0.5 top-0.5 size-5 rounded-full bg-white shadow transition-transform`,e.enabled?`translate-x-5`:`translate-x-0`)})}),_?(0,v.jsx)(h,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>u(e.name),disabled:i,"aria-label":g(`mcpReconnectServer`),title:g(`mcpReconnectServer`),children:i?(0,v.jsx)(t,{className:`size-4 animate-spin`}):(0,v.jsx)(l,{className:`size-4`})}):null,(0,v.jsx)(h,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>s(e),"aria-label":g(`editTask`),title:g(`editTask`),children:(0,v.jsx)(n,{className:`size-4`})}),(0,v.jsx)(h,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-destructive`,onClick:()=>c(e.name),"aria-label":g(`delete`),title:g(`delete`),children:(0,v.jsx)(a,{className:`size-4`})})]})]}),d.length>0?(0,v.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[d.map(e=>(0,v.jsx)(`span`,{className:`rounded-md bg-muted/28 px-1.5 py-0.5 text-[11px] text-muted-foreground/75`,title:e.quickForgeName,children:e.name},e.quickForgeName)),p>0?(0,v.jsx)(`span`,{className:`rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground/55`,children:g(`mcpMoreTools`,{count:p})}):null]}):null]})}function S(){return{name:``,transport:`stdio`,command:``,args:[],url:``,cwd:``,env:{}}}function C(e){return{name:e.name,transport:e.transport,command:e.command,args:e.args||[],url:e.url||``,cwd:e.cwd||``,env:e.env||{}}}function w(e){return(e||[]).join(`
1
+ import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{B as t,E as n,Pt as r,S as i,a,h as o,jt as s,tt as c,v as l}from"./icons-BP8YOS-Z.js";import{n as u}from"./react-vendor-Dr5xvL-e.js";import{Ct as d,Ot as f,V as p,b as m,wt as h,xt as g}from"./index-C6k5taeb.js";var _=e(r(),1),v=u();function y(e){return e===`connected`?`bg-emerald-500/12 text-emerald-700 dark:text-emerald-300`:e===`error`?`bg-destructive/12 text-destructive`:e===`disabled`?`bg-muted text-muted-foreground`:`bg-amber-500/12 text-amber-700 dark:text-amber-300`}var b=12;function x({server:e,toggling:r,reconnecting:i,onToggle:o,onEdit:s,onDelete:c,onReconnect:u}){let p=e.tools?.slice(0,b)??[],m=e.toolCount??e.tools?.length??0,g=Math.max(0,m-p.length),_=e.enabled&&e.status!==`connected`;return(0,v.jsxs)(`div`,{className:`rounded-lg border border-border p-3`,children:[(0,v.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,v.jsxs)(`div`,{className:`min-w-0`,children:[(0,v.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,v.jsx)(`div`,{className:`truncate text-sm font-medium text-foreground/90`,children:e.name}),(0,v.jsx)(`span`,{className:h(`rounded-full px-2 py-0.5 text-[11px]`,y(e.status)),children:e.status||`unknown`}),(0,v.jsx)(`span`,{className:`text-[11px] text-muted-foreground/60`,children:f(`mcpToolsCount`,{count:m})})]}),(0,v.jsx)(`div`,{className:`mt-1 truncate text-xs text-muted-foreground/65`,children:e.transport===`stdio`?`${e.command} ${(e.args||[]).join(` `)}`:e.url}),e.error?(0,v.jsx)(`div`,{className:`mt-1 text-xs text-destructive`,children:e.error}):null]}),(0,v.jsxs)(`div`,{className:`flex shrink-0 items-center gap-1`,children:[(0,v.jsx)(`button`,{type:`button`,role:`switch`,"aria-checked":e.enabled,disabled:r,className:h(`relative h-6 w-11 rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60`,e.enabled?`bg-emerald-500`:`bg-muted-foreground/30`),onClick:()=>o(e),title:e.enabled?f(`pauseTask`):f(`enable`),children:(0,v.jsx)(`span`,{className:h(`absolute left-0.5 top-0.5 size-5 rounded-full bg-white shadow transition-transform`,e.enabled?`translate-x-5`:`translate-x-0`)})}),_?(0,v.jsx)(d,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>u(e.name),disabled:i,"aria-label":f(`mcpReconnectServer`),title:f(`mcpReconnectServer`),children:i?(0,v.jsx)(t,{className:`size-4 animate-spin`}):(0,v.jsx)(l,{className:`size-4`})}):null,(0,v.jsx)(d,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>s(e),"aria-label":f(`editTask`),title:f(`editTask`),children:(0,v.jsx)(n,{className:`size-4`})}),(0,v.jsx)(d,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-destructive`,onClick:()=>c(e.name),"aria-label":f(`delete`),title:f(`delete`),children:(0,v.jsx)(a,{className:`size-4`})})]})]}),p.length>0?(0,v.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[p.map(e=>(0,v.jsx)(`span`,{className:`rounded-md bg-muted/28 px-1.5 py-0.5 text-[11px] text-muted-foreground/75`,title:e.quickForgeName,children:e.name},e.quickForgeName)),g>0?(0,v.jsx)(`span`,{className:`rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground/55`,children:f(`mcpMoreTools`,{count:g})}):null]}):null]})}function S(){return{name:``,transport:`stdio`,command:``,args:[],url:``,cwd:``,env:{}}}function C(e){return{name:e.name,transport:e.transport,command:e.command,args:e.args||[],url:e.url||``,cwd:e.cwd||``,env:e.env||{}}}function w(e){return(e||[]).join(`
2
2
  `)}function T(e){return Object.entries(e||{}).map(([e,t])=>`${e}=${t}`).join(`
3
3
  `)}function E(e){return e.split(`
4
4
  `).map(e=>e.trim()).filter(Boolean)}function D(e){let t={};for(let n of e.split(`
5
- `)){let e=n.trim();if(!e)continue;let r=e.indexOf(`=`);if(r<=0)continue;let i=e.slice(0,r).trim(),a=e.slice(r+1).trim();i&&(t[i]=a)}return t}function O(e){let t={type:e.transport||`stdio`};e.transport===`stdio`?(t.command=e.command||``,t.args=e.args||[],e.cwd&&(t.cwd=e.cwd),e.env&&Object.keys(e.env).length>0&&(t.env=e.env)):(t.url=e.url||``,e.env&&Object.keys(e.env).length>0&&(t.headers=e.env));let n=e.name||`server`;return JSON.stringify({mcpServers:{[n]:t}},null,2)}function k(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function A(e){let t;try{t=JSON.parse(e)}catch{throw Error(g(`mcpInvalidJson`))}if(!k(t))throw Error(g(`mcpInvalidConfigJson`));let n=``,r=null;if(k(t.mcpServers)){let e=Object.entries(t.mcpServers);if(e.length===0)throw Error(g(`mcpEmptyConfigJson`));let[i,a]=e[0];n=i,k(a)&&(r=a)}else r=t;if(!r)throw Error(g(`mcpInvalidConfigJson`));let i=String(r.transport||r.type||`stdio`);if(![`stdio`,`http`,`sse`].includes(i))throw Error(g(`mcpInvalidTransport`,{name:n||`server`}));let a=r.env??r.headers??{};return{name:n,transport:i,command:String(r.command||``),args:Array.isArray(r.args)?r.args:[],url:String(r.url||``),cwd:String(r.cwd||``),env:k(a)?a:{}}}var j=`mb-1 block text-xs text-muted-foreground/72`,M=`min-h-20 w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs text-foreground outline-none focus:border-ring`,N=`flex h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus:border-ring`;function P({value:e,onChange:t,isEdit:n,disabled:r}){let i=e.transport===`stdio`,a=n=>t({...e,...n});return(0,v.jsxs)(`div`,{className:`space-y-3 p-3`,children:[(0,v.jsx)(`div`,{className:`text-xs text-muted-foreground/60`,children:g(n?`mcpEditServer`:`mcpAddServer`)}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-name`,children:g(`mcpServerName`)}),(0,v.jsx)(f,{id:`mcp-server-name`,value:e.name,onChange:e=>a({name:e.target.value}),placeholder:g(`mcpNamePlaceholder`),disabled:n||r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-transport`,children:g(`mcpTransport`)}),(0,v.jsxs)(`select`,{id:`mcp-server-transport`,className:N,value:e.transport,onChange:e=>a({transport:e.target.value}),disabled:r,children:[(0,v.jsx)(`option`,{value:`stdio`,children:`stdio`}),(0,v.jsx)(`option`,{value:`http`,children:`http`}),(0,v.jsx)(`option`,{value:`sse`,children:`sse`})]})]}),i?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-command`,children:g(`mcpCommand`)}),(0,v.jsx)(f,{id:`mcp-server-command`,value:e.command,onChange:e=>a({command:e.target.value}),placeholder:g(`mcpCommandPlaceholder`),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-args`,children:g(`mcpArgs`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-args`,className:M,value:w(e.args),onChange:e=>a({args:E(e.target.value)}),spellCheck:!1,disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-cwd`,children:g(`mcpCwd`)}),(0,v.jsx)(f,{id:`mcp-server-cwd`,value:e.cwd,onChange:e=>a({cwd:e.target.value}),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-env`,children:g(`mcpEnv`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-env`,className:M,value:T(e.env),onChange:e=>a({env:D(e.target.value)}),spellCheck:!1,disabled:r})]})]}):(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-url`,children:g(`mcpUrl`)}),(0,v.jsx)(f,{id:`mcp-server-url`,value:e.url,onChange:e=>a({url:e.target.value}),placeholder:g(`mcpUrlPlaceholder`),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-headers`,children:g(`mcpHeaders`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-headers`,className:M,value:T(e.env),onChange:e=>a({env:D(e.target.value)}),spellCheck:!1,disabled:r})]})]})]})}async function F(e){let t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||`HTTP ${e.status}`);return t}function I({active:e=!0,className:n}){let[r,a]=(0,_.useState)([]),[l,u]=(0,_.useState)(!1),[f,y]=(0,_.useState)(!1),[b,w]=(0,_.useState)(new Set),[T,E]=(0,_.useState)(null),[D,k]=(0,_.useState)(``),[j,M]=(0,_.useState)(!1),[N,I]=(0,_.useState)(null),[L,R]=(0,_.useState)(S),[z,B]=(0,_.useState)(`form`),[V,H]=(0,_.useState)(``),[U,W]=(0,_.useState)(``),G=(0,_.useCallback)(e=>{a(e?.servers??[])},[]),K=(0,_.useCallback)(async()=>{u(!0),k(``);try{G(await F(await fetch(`/api/mcp/servers`)))}catch(e){k(e instanceof Error?e.message:g(`mcpLoadFailed`))}finally{u(!1)}},[G]);if((0,_.useEffect)(()=>{if(!e)return;let t=window.setTimeout(()=>{K()},0);return()=>window.clearTimeout(t)},[e,K]),!e)return null;let q=e=>{e===`json`&&(H(O(L)),W(``)),B(e)},J=()=>{I(null),R(S()),B(`form`),M(!0),k(``)},Y=e=>{let t=C(e);I(e),R(t),H(O(t)),W(``),B(`form`),M(!0),k(``)},X=()=>{M(!1),I(null),H(``),W(``),k(``)},Z=e=>{R(e)},Q=e=>{H(e);try{let t=A(e);R(e=>({...t,name:t.name||e.name})),W(``)}catch(e){W(e instanceof Error?e.message:g(`mcpInvalidJson`))}},ee=async()=>{if(!f){if(U&&z===`json`){k(U);return}y(!0),k(``);try{G(await F(await fetch(`/api/mcp/servers`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify({server:{...L,enabled:N?.enabled??!0}})}))),X()}catch(e){k(e instanceof Error?e.message:g(`mcpSaveFailed`))}finally{y(!1)}}},te=async e=>{if(await d({description:g(`mcpDeleteConfirm`,{name:e}),confirmLabel:g(`confirmDelete`),cancelLabel:g(`cancel`),variant:`destructive`})){k(``);try{G(await F(await fetch(`/api/mcp/servers/${encodeURIComponent(e)}`,{method:`DELETE`})))}catch(e){k(e instanceof Error?e.message:g(`mcpDeleteFailed`))}}},ne=async e=>{if(!b.has(e.name)){w(t=>new Set(t).add(e.name)),k(``);try{G(await F(await fetch(`/api/mcp/servers/${encodeURIComponent(e.name)}/enabled`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify({enabled:!e.enabled})})))}catch(e){k(e instanceof Error?e.message:g(`mcpSaveFailed`))}finally{w(t=>{let n=new Set(t);return n.delete(e.name),n})}}},re=async e=>{if(!T){E(e),k(``);try{G(await F(await fetch(`/api/mcp/reconnect/${encodeURIComponent(e)}`,{method:`POST`})))}catch(e){k(e instanceof Error?e.message:g(`mcpReconnectFailed`))}finally{E(null)}}},$=!!N,ie=!!L.name.trim()&&!!(L.transport===`stdio`?L.command.trim():L.url.trim());return j?(0,v.jsxs)(`div`,{className:m(`flex min-h-0 flex-1 flex-col overflow-hidden bg-background`,n),children:[(0,v.jsx)(`div`,{className:`border-b border-border px-6 py-5`,children:(0,v.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,v.jsxs)(`button`,{type:`button`,className:`inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground`,onClick:X,children:[(0,v.jsx)(s,{className:`size-4`}),g(`mcpConfiguredServers`)]}),(0,v.jsx)(`span`,{className:`text-sm font-medium text-foreground/90`,children:g($?`mcpEditServer`:`mcpAddServer`)})]})}),D?(0,v.jsx)(`div`,{className:`m-6 mb-0 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:D}):null,(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-6`,children:(0,v.jsxs)(`div`,{className:`mx-auto max-w-5xl space-y-5`,children:[(0,v.jsxs)(`div`,{className:`flex border-b border-border`,children:[(0,v.jsxs)(`button`,{type:`button`,onClick:()=>q(`form`),className:m(`flex flex-1 items-center justify-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors`,z===`form`?`border-b-2 border-primary text-foreground/90 -mb-px`:`text-muted-foreground/60 hover:text-foreground/85`),children:[(0,v.jsx)(o,{className:`size-3.5`}),g(`mcpTabServer`)]}),(0,v.jsxs)(`button`,{type:`button`,onClick:()=>q(`json`),className:m(`flex flex-1 items-center justify-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors`,z===`json`?`border-b-2 border-primary text-foreground/90 -mb-px`:`text-muted-foreground/60 hover:text-foreground/85`),children:[(0,v.jsx)(c,{className:`size-3.5`}),g(`mcpTabJson`)]})]}),(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:z===`form`?(0,v.jsx)(P,{value:L,onChange:Z,isEdit:$,disabled:f}):(0,v.jsxs)(`div`,{className:`space-y-2`,children:[(0,v.jsx)(`textarea`,{className:`min-h-96 w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs text-foreground outline-none focus:border-ring`,value:V,onChange:e=>Q(e.target.value),spellCheck:!1}),U?(0,v.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive`,children:U}):null,(0,v.jsxs)(`div`,{className:`inline-flex items-center gap-1.5 text-[11px] font-medium text-foreground/90`,children:[g(`mcpTabJson`),(0,v.jsx)(p,{label:g(`mcpImportConfigDescription`)})]})]})}),(0,v.jsxs)(`div`,{className:`flex justify-end gap-2 border-t border-border pt-3`,children:[(0,v.jsx)(h,{type:`button`,variant:`outline`,size:`sm`,onClick:X,disabled:f,children:g(`cancel`)}),(0,v.jsxs)(h,{type:`button`,size:`sm`,onClick:()=>{ee()},disabled:f||!ie,children:[f?(0,v.jsx)(t,{className:`mr-1.5 size-3.5 animate-spin`}):null,g(`save`)]})]})]})})]}):(0,v.jsxs)(`div`,{className:m(`flex min-h-0 flex-1 flex-col overflow-hidden bg-background`,n),children:[(0,v.jsx)(`div`,{className:`border-b border-border px-6 py-5`,children:(0,v.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[(0,v.jsx)(`h3`,{className:`text-sm font-medium text-foreground/90`,children:g(`mcpConfiguredServers`)}),(0,v.jsxs)(h,{type:`button`,variant:`ghost`,size:`sm`,onClick:J,children:[(0,v.jsx)(i,{className:`mr-1.5 size-3.5`}),g(`mcpAddServer`)]})]})}),D?(0,v.jsx)(`div`,{className:`m-6 mb-0 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:D}):null,(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-6`,children:(0,v.jsx)(`div`,{className:`mx-auto max-w-5xl space-y-5`,children:l&&r.length===0?(0,v.jsxs)(`div`,{className:`flex items-center justify-center gap-2 rounded-lg border border-border py-8 text-sm text-muted-foreground`,children:[(0,v.jsx)(t,{className:`size-4 animate-spin`}),g(`loading`)]}):r.length===0?(0,v.jsx)(`div`,{className:`rounded-lg border border-border bg-muted/15 p-4 text-sm text-muted-foreground/72`,children:g(`mcpNoServersDescription`)}):r.map(e=>(0,v.jsx)(x,{server:e,toggling:b.has(e.name),reconnecting:T===e.name,onToggle:e=>{ne(e)},onEdit:Y,onDelete:e=>{te(e)},onReconnect:e=>{re(e)}},e.name))})})]})}export{I as McpServersPanel};
5
+ `)){let e=n.trim();if(!e)continue;let r=e.indexOf(`=`);if(r<=0)continue;let i=e.slice(0,r).trim(),a=e.slice(r+1).trim();i&&(t[i]=a)}return t}function O(e){let t={type:e.transport||`stdio`};e.transport===`stdio`?(t.command=e.command||``,t.args=e.args||[],e.cwd&&(t.cwd=e.cwd),e.env&&Object.keys(e.env).length>0&&(t.env=e.env)):(t.url=e.url||``,e.env&&Object.keys(e.env).length>0&&(t.headers=e.env));let n=e.name||`server`;return JSON.stringify({mcpServers:{[n]:t}},null,2)}function k(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function A(e){let t;try{t=JSON.parse(e)}catch{throw Error(f(`mcpInvalidJson`))}if(!k(t))throw Error(f(`mcpInvalidConfigJson`));let n=``,r=null;if(k(t.mcpServers)){let e=Object.entries(t.mcpServers);if(e.length===0)throw Error(f(`mcpEmptyConfigJson`));let[i,a]=e[0];n=i,k(a)&&(r=a)}else r=t;if(!r)throw Error(f(`mcpInvalidConfigJson`));let i=String(r.transport||r.type||`stdio`);if(![`stdio`,`http`,`sse`].includes(i))throw Error(f(`mcpInvalidTransport`,{name:n||`server`}));let a=r.env??r.headers??{};return{name:n,transport:i,command:String(r.command||``),args:Array.isArray(r.args)?r.args:[],url:String(r.url||``),cwd:String(r.cwd||``),env:k(a)?a:{}}}var j=`mb-1 block text-xs text-muted-foreground/72`,M=`min-h-20 w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs text-foreground outline-none focus:border-ring`,N=`flex h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus:border-ring`;function P({value:e,onChange:t,isEdit:n,disabled:r}){let i=e.transport===`stdio`,a=n=>t({...e,...n});return(0,v.jsxs)(`div`,{className:`space-y-3 p-3`,children:[(0,v.jsx)(`div`,{className:`text-xs text-muted-foreground/60`,children:f(n?`mcpEditServer`:`mcpAddServer`)}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-name`,children:f(`mcpServerName`)}),(0,v.jsx)(m,{id:`mcp-server-name`,value:e.name,onChange:e=>a({name:e.target.value}),placeholder:f(`mcpNamePlaceholder`),disabled:n||r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-transport`,children:f(`mcpTransport`)}),(0,v.jsxs)(`select`,{id:`mcp-server-transport`,className:N,value:e.transport,onChange:e=>a({transport:e.target.value}),disabled:r,children:[(0,v.jsx)(`option`,{value:`stdio`,children:`stdio`}),(0,v.jsx)(`option`,{value:`http`,children:`http`}),(0,v.jsx)(`option`,{value:`sse`,children:`sse`})]})]}),i?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-command`,children:f(`mcpCommand`)}),(0,v.jsx)(m,{id:`mcp-server-command`,value:e.command,onChange:e=>a({command:e.target.value}),placeholder:f(`mcpCommandPlaceholder`),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-args`,children:f(`mcpArgs`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-args`,className:M,value:w(e.args),onChange:e=>a({args:E(e.target.value)}),spellCheck:!1,disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-cwd`,children:f(`mcpCwd`)}),(0,v.jsx)(m,{id:`mcp-server-cwd`,value:e.cwd,onChange:e=>a({cwd:e.target.value}),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-env`,children:f(`mcpEnv`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-env`,className:M,value:T(e.env),onChange:e=>a({env:D(e.target.value)}),spellCheck:!1,disabled:r})]})]}):(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-url`,children:f(`mcpUrl`)}),(0,v.jsx)(m,{id:`mcp-server-url`,value:e.url,onChange:e=>a({url:e.target.value}),placeholder:f(`mcpUrlPlaceholder`),disabled:r})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{className:j,htmlFor:`mcp-server-headers`,children:f(`mcpHeaders`)}),(0,v.jsx)(`textarea`,{id:`mcp-server-headers`,className:M,value:T(e.env),onChange:e=>a({env:D(e.target.value)}),spellCheck:!1,disabled:r})]})]})]})}async function F(e){let t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||`HTTP ${e.status}`);return t}function I({active:e=!0,className:n}){let[r,a]=(0,_.useState)([]),[l,u]=(0,_.useState)(!1),[m,y]=(0,_.useState)(!1),[b,w]=(0,_.useState)(new Set),[T,E]=(0,_.useState)(null),[D,k]=(0,_.useState)(``),[j,M]=(0,_.useState)(!1),[N,I]=(0,_.useState)(null),[L,R]=(0,_.useState)(S),[z,B]=(0,_.useState)(`form`),[V,H]=(0,_.useState)(``),[U,W]=(0,_.useState)(``),G=(0,_.useCallback)(e=>{a(e?.servers??[])},[]),K=(0,_.useCallback)(async()=>{u(!0),k(``);try{G(await F(await fetch(`/api/mcp/servers`)))}catch(e){k(e instanceof Error?e.message:f(`mcpLoadFailed`))}finally{u(!1)}},[G]);if((0,_.useEffect)(()=>{if(!e)return;let t=window.setTimeout(()=>{K()},0);return()=>window.clearTimeout(t)},[e,K]),!e)return null;let q=e=>{e===`json`&&(H(O(L)),W(``)),B(e)},J=()=>{I(null),R(S()),B(`form`),M(!0),k(``)},Y=e=>{let t=C(e);I(e),R(t),H(O(t)),W(``),B(`form`),M(!0),k(``)},X=()=>{M(!1),I(null),H(``),W(``),k(``)},Z=e=>{R(e)},Q=e=>{H(e);try{let t=A(e);R(e=>({...t,name:t.name||e.name})),W(``)}catch(e){W(e instanceof Error?e.message:f(`mcpInvalidJson`))}},ee=async()=>{if(!m){if(U&&z===`json`){k(U);return}y(!0),k(``);try{G(await F(await fetch(`/api/mcp/servers`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify({server:{...L,enabled:N?.enabled??!0}})}))),X()}catch(e){k(e instanceof Error?e.message:f(`mcpSaveFailed`))}finally{y(!1)}}},te=async e=>{if(await p({description:f(`mcpDeleteConfirm`,{name:e}),confirmLabel:f(`confirmDelete`),cancelLabel:f(`cancel`),variant:`destructive`})){k(``);try{G(await F(await fetch(`/api/mcp/servers/${encodeURIComponent(e)}`,{method:`DELETE`})))}catch(e){k(e instanceof Error?e.message:f(`mcpDeleteFailed`))}}},ne=async e=>{if(!b.has(e.name)){w(t=>new Set(t).add(e.name)),k(``);try{G(await F(await fetch(`/api/mcp/servers/${encodeURIComponent(e.name)}/enabled`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify({enabled:!e.enabled})})))}catch(e){k(e instanceof Error?e.message:f(`mcpSaveFailed`))}finally{w(t=>{let n=new Set(t);return n.delete(e.name),n})}}},re=async e=>{if(!T){E(e),k(``);try{G(await F(await fetch(`/api/mcp/reconnect/${encodeURIComponent(e)}`,{method:`POST`})))}catch(e){k(e instanceof Error?e.message:f(`mcpReconnectFailed`))}finally{E(null)}}},$=!!N,ie=!!L.name.trim()&&!!(L.transport===`stdio`?L.command.trim():L.url.trim());return j?(0,v.jsxs)(`div`,{className:h(`flex min-h-0 flex-1 flex-col overflow-hidden bg-background`,n),children:[(0,v.jsx)(`div`,{className:`border-b border-border px-6 py-5`,children:(0,v.jsxs)(`div`,{className:`flex items-center justify-between gap-2`,children:[(0,v.jsxs)(`button`,{type:`button`,className:`inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground`,onClick:X,children:[(0,v.jsx)(s,{className:`size-4`}),f(`mcpConfiguredServers`)]}),(0,v.jsx)(`span`,{className:`text-sm font-medium text-foreground/90`,children:f($?`mcpEditServer`:`mcpAddServer`)})]})}),D?(0,v.jsx)(`div`,{className:`m-6 mb-0 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:D}):null,(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-6`,children:(0,v.jsxs)(`div`,{className:`mx-auto max-w-5xl space-y-5`,children:[(0,v.jsxs)(`div`,{className:`flex border-b border-border`,children:[(0,v.jsxs)(`button`,{type:`button`,onClick:()=>q(`form`),className:h(`flex flex-1 items-center justify-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors`,z===`form`?`border-b-2 border-primary text-foreground/90 -mb-px`:`text-muted-foreground/60 hover:text-foreground/85`),children:[(0,v.jsx)(o,{className:`size-3.5`}),f(`mcpTabServer`)]}),(0,v.jsxs)(`button`,{type:`button`,onClick:()=>q(`json`),className:h(`flex flex-1 items-center justify-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors`,z===`json`?`border-b-2 border-primary text-foreground/90 -mb-px`:`text-muted-foreground/60 hover:text-foreground/85`),children:[(0,v.jsx)(c,{className:`size-3.5`}),f(`mcpTabJson`)]})]}),(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:z===`form`?(0,v.jsx)(P,{value:L,onChange:Z,isEdit:$,disabled:m}):(0,v.jsxs)(`div`,{className:`space-y-2`,children:[(0,v.jsx)(`textarea`,{className:`min-h-96 w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs text-foreground outline-none focus:border-ring`,value:V,onChange:e=>Q(e.target.value),spellCheck:!1}),U?(0,v.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive`,children:U}):null,(0,v.jsxs)(`div`,{className:`inline-flex items-center gap-1.5 text-[11px] font-medium text-foreground/90`,children:[f(`mcpTabJson`),(0,v.jsx)(g,{label:f(`mcpImportConfigDescription`)})]})]})}),(0,v.jsxs)(`div`,{className:`flex justify-end gap-2 border-t border-border pt-3`,children:[(0,v.jsx)(d,{type:`button`,variant:`outline`,size:`sm`,onClick:X,disabled:m,children:f(`cancel`)}),(0,v.jsxs)(d,{type:`button`,size:`sm`,onClick:()=>{ee()},disabled:m||!ie,children:[m?(0,v.jsx)(t,{className:`mr-1.5 size-3.5 animate-spin`}):null,f(`save`)]})]})]})})]}):(0,v.jsxs)(`div`,{className:h(`flex min-h-0 flex-1 flex-col overflow-hidden bg-background`,n),children:[(0,v.jsx)(`div`,{className:`border-b border-border px-6 py-5`,children:(0,v.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[(0,v.jsx)(`h3`,{className:`text-sm font-medium text-foreground/90`,children:f(`mcpConfiguredServers`)}),(0,v.jsxs)(d,{type:`button`,variant:`ghost`,size:`sm`,onClick:J,children:[(0,v.jsx)(i,{className:`mr-1.5 size-3.5`}),f(`mcpAddServer`)]})]})}),D?(0,v.jsx)(`div`,{className:`m-6 mb-0 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:D}):null,(0,v.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-6`,children:(0,v.jsx)(`div`,{className:`mx-auto max-w-5xl space-y-5`,children:l&&r.length===0?(0,v.jsxs)(`div`,{className:`flex items-center justify-center gap-2 rounded-lg border border-border py-8 text-sm text-muted-foreground`,children:[(0,v.jsx)(t,{className:`size-4 animate-spin`}),f(`loading`)]}):r.length===0?(0,v.jsx)(`div`,{className:`rounded-lg border border-border bg-muted/15 p-4 text-sm text-muted-foreground/72`,children:f(`mcpNoServersDescription`)}):r.map(e=>(0,v.jsx)(x,{server:e,toggling:b.has(e.name),reconnecting:T===e.name,onToggle:e=>{ne(e)},onEdit:Y,onDelete:e=>{te(e)},onReconnect:e=>{re(e)}},e.name))})})]})}export{I as McpServersPanel};
@@ -1 +1 @@
1
- import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{B as n,Ot as r,Pt as i,_ as a,jt as o,n as s}from"./icons-BP8YOS-Z.js";import{n as c}from"./react-vendor-Dr5xvL-e.js";import{ft as l,ht as u,yt as d}from"./index-yV1wtqTr.js";var f=t({SkillsDialog:()=>_,SkillsManagerPanel:()=>g}),p=e(i(),1),m=c();async function h(e){let t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||`HTTP ${e.status}`);return t}function g({active:e=!0,scope:t,project:i,embedded:c=!1,onClose:f,onSaved:g}){let[_,v]=(0,p.useState)([]),[y,b]=(0,p.useState)(()=>new Set),[x,S]=(0,p.useState)(``),[C,w]=(0,p.useState)([]),[T,E]=(0,p.useState)(!1),[D,O]=(0,p.useState)(!1),[k,A]=(0,p.useState)(``),j=t===`project`,[M,N]=(0,p.useState)(null),[P,F]=(0,p.useState)(null),[I,L]=(0,p.useState)(!1),[R,z]=(0,p.useState)(``),B=(0,p.useCallback)(()=>{N(null),F(null),z(``)},[]),V=(0,p.useCallback)(()=>{B(),f?.()},[f,B]);(0,p.useEffect)(()=>{if(!e||j&&!i)return;let t=!1;return(async()=>{E(!0),A(``);try{let e=j?`/api/skills?projectId=${encodeURIComponent(i.id)}`:`/api/skills?scope=global`,n=await h(await fetch(e));if(t)return;v(Array.isArray(n.skills)?n.skills:[]),b(new Set(Array.isArray(n.selectedSkills)?n.selectedSkills:[])),w(Array.isArray(n.searchPaths)?n.searchPaths:[])}catch(e){t||A(e instanceof Error?e.message:d(`failedToLoadSkills`))}finally{t||E(!1)}})(),()=>{t=!0}},[e,i,j]),(0,p.useEffect)(()=>{if(!e||c)return;let t=e=>{e.key===`Escape`&&(M?B():D||V())};return document.addEventListener(`keydown`,t),()=>document.removeEventListener(`keydown`,t)},[e,V,c,D,M,B]);let H=(0,p.useMemo)(()=>{let e=x.trim().toLowerCase();return e?_.filter(t=>[t.name,t.displayName,t.description,t.source,t.compatibility,t.allowedTools,...t.tags??[],...t.triggers??[]].filter(Boolean).join(` `).toLowerCase().includes(e)):_},[x,_]),U=async e=>{N(e),F(null),z(``),L(!0);try{let t=new URLSearchParams({name:e});j&&i?(t.set(`scope`,`project`),t.set(`projectId`,i.id)):t.set(`scope`,`global`),F(await h(await fetch(`/api/skills/content?${t}`)))}catch(e){z(e instanceof Error?e.message:d(`failedToReadSkill`))}finally{L(!1)}};if(!e||j&&!i)return null;let W=e=>{if(D)return;let t=new Set(y);t.has(e)?t.delete(e):t.add(e),b(t),G(t)},G=async e=>{O(!0),A(``);try{let n=await h(await fetch(j?`/api/skills/project`:`/api/skills/global`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify(j?{projectId:i.id,selectedSkills:[...e]}:{selectedSkills:[...e]})}));g({scope:t,project:j?n.projects?.find(e=>e.id===i.id)??{...i,skills:n.selectedSkills}:void 0,projects:n.projects,selectedSkills:n.selectedSkills})}catch(e){b(y),A(e instanceof Error?e.message:d(`failedToSaveSkills`))}finally{O(!1)}},K=d(j?`projectSkills`:`globalSkills`),q=j?d(`projectSkillsDescription`,{project:i.name}):d(`globalSkillsDescription`),J=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`div`,{className:`quickforge-settings-divider p-3`,children:(0,m.jsxs)(`div`,{className:`quickforge-settings-inline-field`,children:[(0,m.jsx)(a,{className:`pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/60`}),(0,m.jsx)(`input`,{value:x,onChange:e=>S(e.target.value),placeholder:d(`searchSkills`),className:`quickforge-settings-input`,disabled:T||D})]})}),k?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:k}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsx)(`span`,{className:`quickforge-settings-row-title`,children:d(`availableSkills`)}),(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-info`,children:d(`selectedSkillsCount`,{count:y.size})})]}),(0,m.jsx)(`div`,{className:c?``:`max-h-[46vh] overflow-y-auto`,children:T?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),d(`loading`)]}):H.length===0?(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:d(`noMatchingSkills`)}):H.map(e=>{let t=y.has(e.name);return(0,m.jsxs)(`div`,{className:u(`quickforge-settings-list-item`,t&&`bg-muted/12`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-main`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-row-title`,children:e.displayName||e.name}),e.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:e.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta`,children:[e.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.source}):null,e.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.compatibility}):null,e.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.allowedTools}):null,e.tags?.slice(0,5).map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]})]}),(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":D?`true`:`false`,children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:t,disabled:D,onChange:()=>W(e.name),"aria-label":t?`Disable ${e.name}`:`Enable ${e.name}`}),(0,m.jsx)(`span`,{"aria-hidden":`true`})]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:()=>void U(e.name),disabled:D||I,title:d(`readSkill`),"aria-label":`${d(`readSkill`)}: ${e.displayName||e.name}`,children:(0,m.jsx)(r,{className:`size-4`})})]})]},e.name)})})]}),Y=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`quickforge-settings-button quickforge-settings-button-secondary`,onClick:B,disabled:I,children:[(0,m.jsx)(o,{className:`mr-2 size-4`}),d(`backToSkillList`)]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-main min-w-0`,children:(0,m.jsx)(`div`,{className:`quickforge-settings-row-title truncate`,children:P?.displayName||P?.name||M})})]}),(0,m.jsx)(`div`,{className:`p-4`,children:I?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),d(`loading`)]}):R?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:R}):P?(0,m.jsxs)(m.Fragment,{children:[P.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description mb-3`,children:P.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta mb-3`,children:[P.version?(0,m.jsxs)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:[`v`,P.version]}):null,P.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.source}):null,P.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.compatibility}):null,P.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.allowedTools}):null,P.license?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.license}):null,P.tags?.map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]}),P.triggers?.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description mb-3`,children:[`Triggers: `,P.triggers.join(`, `)]}):null,P.instructions?(0,m.jsx)(`pre`,{className:`whitespace-pre-wrap break-words rounded-md border border-border bg-muted/30 px-4 py-3 font-mono text-sm leading-6 text-foreground/80`,children:P.instructions}):(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:d(`noSkillContent`)})]}):null})]});return c?(0,m.jsx)(`div`,{className:`quickforge-settings-stack`,children:(0,m.jsx)(`section`,{className:`quickforge-settings-section`,"aria-label":K,children:M?Y:(0,m.jsxs)(m.Fragment,{children:[C.length?(0,m.jsx)(`div`,{className:`px-5 pt-4`,children:(0,m.jsxs)(`span`,{className:`quickforge-settings-row-description break-all`,children:[d(`skillSearchPaths`),`: `,C.join(` · `)]})}):null,J]})})}):(0,m.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4`,onClick:e=>{e.target===e.currentTarget&&!D&&V()},children:(0,m.jsxs)(`div`,{className:`flex max-h-[85vh] w-full max-w-2xl flex-col rounded-lg border border-border bg-background shadow-quickforge`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[K,(0,m.jsx)(l,{label:q})]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:q}),C.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description break-all`,children:[d(`skillSearchPaths`),`: `,C.join(` · `)]}):null]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:V,disabled:D,"aria-label":d(`close`),children:(0,m.jsx)(s,{className:`size-4`})})]}),(0,m.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:M?Y:J})]})})}function _({open:e,scope:t,project:n,onOpenChange:r,onSaved:i}){return(0,m.jsx)(g,{active:e,scope:t,project:n,onClose:()=>r(!1),onSaved:i})}export{f as n,_ as t};
1
+ import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{B as n,Ot as r,Pt as i,_ as a,jt as o,n as s}from"./icons-BP8YOS-Z.js";import{n as c}from"./react-vendor-Dr5xvL-e.js";import{Ot as l,wt as u,xt as d}from"./index-C6k5taeb.js";var f=t({SkillsDialog:()=>_,SkillsManagerPanel:()=>g}),p=e(i(),1),m=c();async function h(e){let t=await e.json().catch(()=>null);if(!e.ok)throw Error(t?.error||`HTTP ${e.status}`);return t}function g({active:e=!0,scope:t,project:i,embedded:c=!1,onClose:f,onSaved:g}){let[_,v]=(0,p.useState)([]),[y,b]=(0,p.useState)(()=>new Set),[x,S]=(0,p.useState)(``),[C,w]=(0,p.useState)([]),[T,E]=(0,p.useState)(!1),[D,O]=(0,p.useState)(!1),[k,A]=(0,p.useState)(``),j=t===`project`,[M,N]=(0,p.useState)(null),[P,F]=(0,p.useState)(null),[I,L]=(0,p.useState)(!1),[R,z]=(0,p.useState)(``),B=(0,p.useCallback)(()=>{N(null),F(null),z(``)},[]),V=(0,p.useCallback)(()=>{B(),f?.()},[f,B]);(0,p.useEffect)(()=>{if(!e||j&&!i)return;let t=!1;return(async()=>{E(!0),A(``);try{let e=j?`/api/skills?projectId=${encodeURIComponent(i.id)}`:`/api/skills?scope=global`,n=await h(await fetch(e));if(t)return;v(Array.isArray(n.skills)?n.skills:[]),b(new Set(Array.isArray(n.selectedSkills)?n.selectedSkills:[])),w(Array.isArray(n.searchPaths)?n.searchPaths:[])}catch(e){t||A(e instanceof Error?e.message:l(`failedToLoadSkills`))}finally{t||E(!1)}})(),()=>{t=!0}},[e,i,j]),(0,p.useEffect)(()=>{if(!e||c)return;let t=e=>{e.key===`Escape`&&(M?B():D||V())};return document.addEventListener(`keydown`,t),()=>document.removeEventListener(`keydown`,t)},[e,V,c,D,M,B]);let H=(0,p.useMemo)(()=>{let e=x.trim().toLowerCase();return e?_.filter(t=>[t.name,t.displayName,t.description,t.source,t.compatibility,t.allowedTools,...t.tags??[],...t.triggers??[]].filter(Boolean).join(` `).toLowerCase().includes(e)):_},[x,_]),U=async e=>{N(e),F(null),z(``),L(!0);try{let t=new URLSearchParams({name:e});j&&i?(t.set(`scope`,`project`),t.set(`projectId`,i.id)):t.set(`scope`,`global`),F(await h(await fetch(`/api/skills/content?${t}`)))}catch(e){z(e instanceof Error?e.message:l(`failedToReadSkill`))}finally{L(!1)}};if(!e||j&&!i)return null;let W=e=>{if(D)return;let t=new Set(y);t.has(e)?t.delete(e):t.add(e),b(t),G(t)},G=async e=>{O(!0),A(``);try{let n=await h(await fetch(j?`/api/skills/project`:`/api/skills/global`,{method:`PUT`,headers:{"content-type":`application/json`},body:JSON.stringify(j?{projectId:i.id,selectedSkills:[...e]}:{selectedSkills:[...e]})}));g({scope:t,project:j?n.projects?.find(e=>e.id===i.id)??{...i,skills:n.selectedSkills}:void 0,projects:n.projects,selectedSkills:n.selectedSkills})}catch(e){b(y),A(e instanceof Error?e.message:l(`failedToSaveSkills`))}finally{O(!1)}},K=l(j?`projectSkills`:`globalSkills`),q=j?l(`projectSkillsDescription`,{project:i.name}):l(`globalSkillsDescription`),J=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`div`,{className:`quickforge-settings-divider p-3`,children:(0,m.jsxs)(`div`,{className:`quickforge-settings-inline-field`,children:[(0,m.jsx)(a,{className:`pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground/60`}),(0,m.jsx)(`input`,{value:x,onChange:e=>S(e.target.value),placeholder:l(`searchSkills`),className:`quickforge-settings-input`,disabled:T||D})]})}),k?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:k}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsx)(`span`,{className:`quickforge-settings-row-title`,children:l(`availableSkills`)}),(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-info`,children:l(`selectedSkillsCount`,{count:y.size})})]}),(0,m.jsx)(`div`,{className:c?``:`max-h-[46vh] overflow-y-auto`,children:T?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),l(`loading`)]}):H.length===0?(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:l(`noMatchingSkills`)}):H.map(e=>{let t=y.has(e.name);return(0,m.jsxs)(`div`,{className:u(`quickforge-settings-list-item`,t&&`bg-muted/12`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-main`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-row-title`,children:e.displayName||e.name}),e.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:e.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta`,children:[e.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.source}):null,e.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.compatibility}):null,e.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e.allowedTools}):null,e.tags?.slice(0,5).map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]})]}),(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":D?`true`:`false`,children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:t,disabled:D,onChange:()=>W(e.name),"aria-label":t?`Disable ${e.name}`:`Enable ${e.name}`}),(0,m.jsx)(`span`,{"aria-hidden":`true`})]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:()=>void U(e.name),disabled:D||I,title:l(`readSkill`),"aria-label":`${l(`readSkill`)}: ${e.displayName||e.name}`,children:(0,m.jsx)(r,{className:`size-4`})})]})]},e.name)})})]}),Y=(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`quickforge-settings-button quickforge-settings-button-secondary`,onClick:B,disabled:I,children:[(0,m.jsx)(o,{className:`mr-2 size-4`}),l(`backToSkillList`)]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-main min-w-0`,children:(0,m.jsx)(`div`,{className:`quickforge-settings-row-title truncate`,children:P?.displayName||P?.name||M})})]}),(0,m.jsx)(`div`,{className:`p-4`,children:I?(0,m.jsxs)(`div`,{className:`quickforge-settings-empty-row inline-flex items-center gap-2`,children:[(0,m.jsx)(n,{className:`size-4 animate-spin`}),l(`loading`)]}):R?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:R}):P?(0,m.jsxs)(m.Fragment,{children:[P.description?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description mb-3`,children:P.description}):null,(0,m.jsxs)(`div`,{className:`quickforge-settings-meta mb-3`,children:[P.version?(0,m.jsxs)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:[`v`,P.version]}):null,P.source?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.source}):null,P.compatibility?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.compatibility}):null,P.allowedTools?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.allowedTools}):null,P.license?(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:P.license}):null,P.tags?.map(e=>(0,m.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:e},e))]}),P.triggers?.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description mb-3`,children:[`Triggers: `,P.triggers.join(`, `)]}):null,P.instructions?(0,m.jsx)(`pre`,{className:`whitespace-pre-wrap break-words rounded-md border border-border bg-muted/30 px-4 py-3 font-mono text-sm leading-6 text-foreground/80`,children:P.instructions}):(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:l(`noSkillContent`)})]}):null})]});return c?(0,m.jsx)(`div`,{className:`quickforge-settings-stack`,children:(0,m.jsx)(`section`,{className:`quickforge-settings-section`,"aria-label":K,children:M?Y:(0,m.jsxs)(m.Fragment,{children:[C.length?(0,m.jsx)(`div`,{className:`px-5 pt-4`,children:(0,m.jsxs)(`span`,{className:`quickforge-settings-row-description break-all`,children:[l(`skillSearchPaths`),`: `,C.join(` · `)]})}):null,J]})})}):(0,m.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4`,onClick:e=>{e.target===e.currentTarget&&!D&&V()},children:(0,m.jsxs)(`div`,{className:`flex max-h-[85vh] w-full max-w-2xl flex-col rounded-lg border border-border bg-background shadow-quickforge`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[K,(0,m.jsx)(d,{label:q})]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:q}),C.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description break-all`,children:[l(`skillSearchPaths`),`: `,C.join(` · `)]}):null]}),(0,m.jsx)(`button`,{type:`button`,className:`quickforge-settings-icon-action`,onClick:V,disabled:D,"aria-label":l(`close`),children:(0,m.jsx)(s,{className:`size-4`})})]}),(0,m.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto`,children:M?Y:J})]})})}function _({open:e,scope:t,project:n,onOpenChange:r,onSaved:i}){return(0,m.jsx)(g,{active:e,scope:t,project:n,onClose:()=>r(!1),onSaved:i})}export{f as n,_ as t};
package/dist/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <meta name="apple-mobile-web-app-title" content="QuickForge" />
12
12
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
13
13
  <title>速构 QuickForge</title>
14
- <script type="module" crossorigin src="/assets/index-yV1wtqTr.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-C6k5taeb.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-DWdDZTNf.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/pi-ai-Cr3rpCXT.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/lit-vendor-Dr3cpBGF.js">
@@ -20,9 +20,9 @@
20
20
  <link rel="modulepreload" crossorigin href="/assets/icons-BP8YOS-Z.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-Dr5xvL-e.js">
22
22
  <link rel="modulepreload" crossorigin href="/assets/logger-B65Akg8A.js">
23
- <link rel="modulepreload" crossorigin href="/assets/index-yV1wtqTr.js">
24
- <link rel="modulepreload" crossorigin href="/assets/skills-dialog-DiJAUfvW.js">
25
- <link rel="stylesheet" crossorigin href="/assets/index-Cu2bBHLv.css">
23
+ <link rel="modulepreload" crossorigin href="/assets/index-C6k5taeb.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/skills-dialog-REBeTFSH.js">
25
+ <link rel="stylesheet" crossorigin href="/assets/index-CzN8NSKC.css">
26
26
  </head>
27
27
  <body>
28
28
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shawnstack/quickforge",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "AI chat application with Agent access modes for local workspace tools. React + Vite + Tailwind CSS frontend, local Node.js storage server.",
5
5
  "keywords": [
6
6
  "ai",
@@ -54,7 +54,10 @@
54
54
  "@earendil-works/pi-agent-core": "^0.80.3",
55
55
  "@earendil-works/pi-ai": "^0.80.3",
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
+ "@vscode/os-proxy-resolver": "^0.3.0",
57
58
  "mermaid": "^11.16.0",
59
+ "socks": "^2.8.9",
60
+ "undici": "^7.29.0",
58
61
  "ws": "^8.20.1"
59
62
  },
60
63
  "optionalDependencies": {
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
  import { AsyncLocalStorage } from 'node:async_hooks'
4
4
  import { randomUUID } from 'node:crypto'
5
5
  import { streamSimple } from '@earendil-works/pi-ai/compat'
6
+ import { withDefaultAiProviderOptions } from './ai-provider-options.mjs'
6
7
  import { logsDir } from './storage.mjs'
7
8
 
8
9
  const PATCH_MARKER = Symbol.for('quickforge.aiHttpLogger.fetchPatched')
@@ -206,18 +207,17 @@ export function installAiHttpLogger() {
206
207
  }
207
208
 
208
209
  export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
209
- if (!aiHttpLogEnabled) return streamSimple(model, context, options)
210
+ const providerOptions = withDefaultAiProviderOptions(options)
211
+ if (!aiHttpLogEnabled) return streamSimple(model, context, providerOptions)
210
212
 
211
213
  const traceContext = {
212
214
  traceId: randomUUID(),
213
- sessionId: options?.sessionId,
214
- purpose: options?.metadata?.quickforgePurpose || 'chat',
215
+ sessionId: providerOptions.sessionId,
216
+ purpose: providerOptions.metadata?.quickforgePurpose || 'chat',
215
217
  provider: model?.provider,
216
218
  api: model?.api,
217
219
  model: model?.id,
218
220
  }
219
221
 
220
- return aiHttpContext.run(traceContext, () => streamSimple(model, context, options))
222
+ return aiHttpContext.run(traceContext, () => streamSimple(model, context, providerOptions))
221
223
  }
222
-
223
- installAiHttpLogger()
@@ -0,0 +1,8 @@
1
+ export const DEFAULT_AI_MAX_RETRIES = 3
2
+
3
+ export function withDefaultAiProviderOptions(options = {}) {
4
+ return {
5
+ maxRetries: DEFAULT_AI_MAX_RETRIES,
6
+ ...options,
7
+ }
8
+ }
package/server/index.mjs CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'
7
7
  import { sendJson, sendError } from './utils/response.mjs'
8
8
  import { openBrowser } from './utils/platform.mjs'
9
9
  import { ensureStorage, dataDir, configDir, storageDir, cacheDir, logsDir } from './storage.mjs'
10
+ import { getNetworkProxyConfig, initializeNetworkProxy, refreshSystemProxy, updateNetworkProxyConfig } from './network-proxy.mjs'
10
11
  import { setDefaultWorkspaceRoot, initializeActiveProject, readProjectConfig, getActiveProject, readTerminalShellSetting, updateTerminalShellSetting, readTerminalShellConfig, updateTerminalShellConfig } from './project-config.mjs'
11
12
  import { getWorkspaceRoot } from './utils/workspace.mjs'
12
13
  import { handleStorageApi } from './routes/storage.mjs'
@@ -65,7 +66,6 @@ let restartInProgress = false
65
66
  let updateInProgress = false
66
67
 
67
68
  setDefaultWorkspaceRoot(process.env.QUICKFORGE_WORKSPACE_DIR || path.join(dataDir, 'workspace'))
68
- installAiHttpLogger()
69
69
 
70
70
  function getRestartSupport() {
71
71
  return { supported: true, reason: null }
@@ -420,7 +420,7 @@ async function handleApi(req, res, url) {
420
420
  }
421
421
 
422
422
  // System routes
423
- if (pathname === '/api/system/status' || pathname === '/api/system/restart' || pathname === '/api/system/network' || pathname === '/api/system/terminal-shell' || pathname === '/api/system/about' || pathname === '/api/system/update/check' || pathname === '/api/system/update/desktop' || pathname === '/api/system/update') {
423
+ if (pathname === '/api/system/status' || pathname === '/api/system/restart' || pathname === '/api/system/network' || pathname === '/api/system/network-proxy' || pathname === '/api/system/network-proxy/refresh' || pathname === '/api/system/terminal-shell' || pathname === '/api/system/about' || pathname === '/api/system/update/check' || pathname === '/api/system/update/desktop' || pathname === '/api/system/update') {
424
424
  await handleSystemApi(req, res, url, {
425
425
  getSystemStatus,
426
426
  requestRestart,
@@ -433,6 +433,9 @@ async function handleApi(req, res, url) {
433
433
  updateTerminalShellSetting,
434
434
  getTerminalShellConfig: readTerminalShellConfig,
435
435
  updateTerminalShellConfig,
436
+ getNetworkProxyConfig,
437
+ updateNetworkProxyConfig,
438
+ refreshSystemProxy,
436
439
  host,
437
440
  port,
438
441
  remoteEnabled: host !== '127.0.0.1' && host !== 'localhost',
@@ -669,6 +672,8 @@ server.on('upgrade', (req, socket, head) => {
669
672
  })
670
673
 
671
674
  await ensureStorage()
675
+ await initializeNetworkProxy()
676
+ installAiHttpLogger()
672
677
  initializeChannels({ projectRoot })
673
678
  await resetStaleTaskStatuses()
674
679
  await initializeActiveProject()
@@ -0,0 +1,384 @@
1
+ import tls from 'node:tls'
2
+ import { Agent, DecoratorHandler, Dispatcher, ProxyAgent } from 'undici'
3
+ import { SocksClient } from 'socks'
4
+ import { atomicUpdate, readStore } from './storage.mjs'
5
+
6
+ const SETTINGS_KEY = 'network-proxy'
7
+ const VALID_MODES = new Set(['direct', 'system', 'manual'])
8
+ const PROXY_ENV_KEYS = [
9
+ 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
10
+ 'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
11
+ ]
12
+ const ORIGINAL_FETCH = globalThis.fetch.bind(globalThis)
13
+ const FETCH_PATCH_MARKER = Symbol.for('quickforge.networkProxy.fetchPatched')
14
+
15
+ let currentConfig = { mode: 'direct', proxyUrl: '' }
16
+ let hostRuntime = null
17
+ let nativeResolver = null
18
+ let nativeResolverError = null
19
+ let networkDispatcher = null
20
+ let initialized = false
21
+ let lastAppliedAt = null
22
+
23
+ export function normalizeNetworkProxyConfig(value) {
24
+ const mode = VALID_MODES.has(value?.mode) ? value.mode : 'direct'
25
+ const proxyUrl = typeof value?.proxyUrl === 'string' ? value.proxyUrl.trim() : ''
26
+ return { mode, proxyUrl }
27
+ }
28
+
29
+ export function validateManualProxyUrl(value) {
30
+ let url
31
+ try {
32
+ url = new URL(String(value || '').trim())
33
+ } catch {
34
+ const error = new Error('Proxy address must be a valid HTTP or HTTPS URL')
35
+ error.statusCode = 400
36
+ throw error
37
+ }
38
+
39
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
40
+ const error = new Error('Only HTTP and HTTPS proxy addresses are supported')
41
+ error.statusCode = 400
42
+ throw error
43
+ }
44
+ if (!url.hostname || !url.port) {
45
+ const error = new Error('Proxy address must include a host and port')
46
+ error.statusCode = 400
47
+ throw error
48
+ }
49
+ if (url.username || url.password) {
50
+ const error = new Error('Proxy credentials are not supported in the proxy address')
51
+ error.statusCode = 400
52
+ throw error
53
+ }
54
+ if ((url.pathname && url.pathname !== '/') || url.search || url.hash) {
55
+ const error = new Error('Proxy address cannot include a path, query, or fragment')
56
+ error.statusCode = 400
57
+ throw error
58
+ }
59
+
60
+ return `${url.protocol}//${url.host}`
61
+ }
62
+
63
+ function isLoopbackUrl(input) {
64
+ let url
65
+ try {
66
+ if (typeof Request !== 'undefined' && input instanceof Request) url = new URL(input.url)
67
+ else url = new URL(input instanceof URL ? input.href : String(input))
68
+ } catch {
69
+ return false
70
+ }
71
+ return ['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(url.hostname)
72
+ }
73
+
74
+ function withoutProxyEnvironment(callback) {
75
+ const previous = new Map()
76
+ for (const key of PROXY_ENV_KEYS) {
77
+ previous.set(key, process.env[key])
78
+ delete process.env[key]
79
+ }
80
+ try {
81
+ return callback()
82
+ } finally {
83
+ for (const [key, value] of previous) {
84
+ if (value === undefined) delete process.env[key]
85
+ else process.env[key] = value
86
+ }
87
+ }
88
+ }
89
+
90
+ async function getNativeResolver() {
91
+ if (nativeResolver) return nativeResolver
92
+ if (nativeResolverError) throw nativeResolverError
93
+
94
+ try {
95
+ const { ProxyResolver } = await import('@vscode/os-proxy-resolver')
96
+ nativeResolver = withoutProxyEnvironment(() => new ProxyResolver())
97
+ return nativeResolver
98
+ } catch (error) {
99
+ nativeResolverError = error instanceof Error ? error : new Error(String(error))
100
+ throw nativeResolverError
101
+ }
102
+ }
103
+
104
+ function parseProxyHost(value) {
105
+ const raw = String(value || '').trim()
106
+ const url = new URL(raw.includes('://') ? raw : `http://${raw}`)
107
+ return {
108
+ host: url.hostname,
109
+ port: Number(url.port || (url.protocol === 'https:' ? 443 : 80)),
110
+ secure: url.protocol === 'https:',
111
+ }
112
+ }
113
+
114
+ function createSocksConnector(proxyHost) {
115
+ const proxy = parseProxyHost(proxyHost)
116
+ return (options, callback) => {
117
+ const destinationHost = options.hostname || options.host
118
+ const destinationPort = Number(options.port || (options.protocol === 'https:' ? 443 : 80))
119
+ let settled = false
120
+
121
+ const complete = (error, socket) => {
122
+ if (settled) return
123
+ settled = true
124
+ callback(error, socket)
125
+ }
126
+
127
+ SocksClient.createConnection({
128
+ proxy: {
129
+ host: proxy.host,
130
+ port: proxy.port,
131
+ type: 5,
132
+ },
133
+ command: 'connect',
134
+ destination: {
135
+ host: destinationHost,
136
+ port: destinationPort,
137
+ },
138
+ timeout: Number(options.timeout || 10000),
139
+ }).then(({ socket }) => {
140
+ if (options.protocol !== 'https:') {
141
+ complete(null, socket)
142
+ return
143
+ }
144
+ const secureSocket = tls.connect({
145
+ socket,
146
+ servername: options.servername || destinationHost,
147
+ ALPNProtocols: ['http/1.1'],
148
+ })
149
+ secureSocket.once('secureConnect', () => complete(null, secureSocket))
150
+ secureSocket.once('error', (error) => complete(error))
151
+ }).catch((error) => complete(error))
152
+
153
+ return () => {
154
+ settled = true
155
+ }
156
+ }
157
+ }
158
+
159
+ class RetryHandler extends DecoratorHandler {
160
+ constructor(dispatcher, options, handler, retry) {
161
+ super(handler)
162
+ this.dispatcher = dispatcher
163
+ this.options = options
164
+ this.retry = retry
165
+ this.receivedResponse = false
166
+ }
167
+
168
+ onResponseStart(controller, statusCode, headers, statusMessage) {
169
+ this.receivedResponse = true
170
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
171
+ }
172
+
173
+ onResponseError(controller, error) {
174
+ if (!this.receivedResponse) {
175
+ void this.retry(error)
176
+ return
177
+ }
178
+ return super.onResponseError(controller, error)
179
+ }
180
+ }
181
+
182
+ class SystemProxyDispatcher extends Dispatcher {
183
+ constructor(resolver) {
184
+ super()
185
+ this.resolver = resolver
186
+ this.directAgent = new Agent()
187
+ this.dispatchers = new Map()
188
+ }
189
+
190
+ dispatcherFor(route) {
191
+ if (!route || route.kind === 'direct') return this.directAgent
192
+ const key = `${route.kind}:${route.host}`
193
+ if (this.dispatchers.has(key)) return this.dispatchers.get(key)
194
+
195
+ let dispatcher
196
+ if (route.kind === 'http') {
197
+ const proxy = parseProxyHost(route.host)
198
+ dispatcher = new ProxyAgent({ uri: `${proxy.secure ? 'https' : 'http'}://${proxy.host}:${proxy.port}` })
199
+ } else if (route.kind === 'socks') {
200
+ dispatcher = new Agent({ connect: createSocksConnector(route.host) })
201
+ } else {
202
+ dispatcher = this.directAgent
203
+ }
204
+ this.dispatchers.set(key, dispatcher)
205
+ return dispatcher
206
+ }
207
+
208
+ dispatch(options, handler) {
209
+ const url = `${options.origin}${options.path}`
210
+ let routes = []
211
+ let index = 0
212
+
213
+ const tryNext = async (lastError) => {
214
+ if (index >= routes.length) {
215
+ handler.onResponseError?.(null, lastError || new Error('No usable system proxy route'))
216
+ return
217
+ }
218
+ const route = routes[index]
219
+ index += 1
220
+ const dispatcher = this.dispatcherFor(route)
221
+ const retryHandler = new RetryHandler(dispatcher, options, handler, async (error) => {
222
+ if (route?.kind !== 'direct') this.resolver.reportProxyFailed(route)
223
+ await tryNext(error)
224
+ })
225
+ try {
226
+ dispatcher.dispatch(options, retryHandler)
227
+ } catch (error) {
228
+ if (route?.kind !== 'direct') this.resolver.reportProxyFailed(route)
229
+ await tryNext(error)
230
+ }
231
+ }
232
+
233
+ void this.resolver.resolve(url).then((resolvedRoutes) => {
234
+ routes = Array.isArray(resolvedRoutes) && resolvedRoutes.length ? resolvedRoutes : [{ kind: 'direct' }]
235
+ return tryNext()
236
+ }).catch((error) => handler.onResponseError?.(null, error))
237
+
238
+ return true
239
+ }
240
+
241
+ async close() {
242
+ const dispatchers = [this.directAgent, ...this.dispatchers.values()]
243
+ await Promise.allSettled(dispatchers.map((dispatcher) => dispatcher.close()))
244
+ this.dispatchers.clear()
245
+ }
246
+
247
+ async destroy(error) {
248
+ const dispatchers = [this.directAgent, ...this.dispatchers.values()]
249
+ await Promise.allSettled(dispatchers.map((dispatcher) => dispatcher.destroy(error)))
250
+ this.dispatchers.clear()
251
+ }
252
+ }
253
+
254
+ async function createNetworkDispatcher(config) {
255
+ if (config.mode === 'manual') return new ProxyAgent(config.proxyUrl)
256
+ if (config.mode === 'system') return new SystemProxyDispatcher(await getNativeResolver())
257
+ return new Agent()
258
+ }
259
+
260
+ async function replaceNetworkDispatcher(config) {
261
+ const previous = networkDispatcher
262
+ networkDispatcher = await createNetworkDispatcher(config)
263
+ if (previous) void previous.close().catch(() => {})
264
+ }
265
+
266
+ function installNetworkFetch() {
267
+ if (globalThis[FETCH_PATCH_MARKER]) return
268
+ globalThis.fetch = async (input, init = {}) => {
269
+ if (isLoopbackUrl(input)) return ORIGINAL_FETCH(input, init)
270
+ if (hostRuntime) {
271
+ if (currentConfig.mode === 'direct') return ORIGINAL_FETCH(input, init)
272
+ return hostRuntime.fetch(input, init)
273
+ }
274
+ if (!networkDispatcher) return ORIGINAL_FETCH(input, init)
275
+ return ORIGINAL_FETCH(input, { ...init, dispatcher: networkDispatcher })
276
+ }
277
+ globalThis[FETCH_PATCH_MARKER] = true
278
+ }
279
+
280
+ export function registerHostNetworkRuntime(runtime) {
281
+ hostRuntime = runtime || null
282
+ }
283
+
284
+ async function persistConfig(config) {
285
+ await atomicUpdate('settings', (settings) => ({
286
+ ...settings,
287
+ [SETTINGS_KEY]: config,
288
+ }))
289
+ }
290
+
291
+ async function applyConfig(config) {
292
+ const normalized = normalizeNetworkProxyConfig(config)
293
+ if (normalized.mode === 'manual') normalized.proxyUrl = validateManualProxyUrl(normalized.proxyUrl)
294
+
295
+ if (hostRuntime) await hostRuntime.apply(normalized)
296
+ else await replaceNetworkDispatcher(normalized)
297
+
298
+ currentConfig = normalized
299
+ lastAppliedAt = new Date().toISOString()
300
+ return currentConfig
301
+ }
302
+
303
+ async function nativeStatus() {
304
+ try {
305
+ const resolver = await getNativeResolver()
306
+ const config = await resolver.readProxyConfig()
307
+ const source = config.platform?.kind || process.platform
308
+ return {
309
+ supported: true,
310
+ source,
311
+ features: {
312
+ pac: config.configuredPac?.state !== 'unsupported',
313
+ wpad: config.wpadDns?.state !== 'unsupported' || config.wpadDhcp?.state !== 'unsupported',
314
+ httpProxy: true,
315
+ httpsProxy: true,
316
+ socks: true,
317
+ },
318
+ }
319
+ } catch (error) {
320
+ return {
321
+ supported: false,
322
+ source: 'none',
323
+ features: { pac: false, wpad: false, httpProxy: false, httpsProxy: false, socks: false },
324
+ error: error instanceof Error ? error.message : String(error),
325
+ }
326
+ }
327
+ }
328
+
329
+ export async function getNetworkProxyStatus() {
330
+ const runtimeStatus = hostRuntime
331
+ ? await hostRuntime.getStatus()
332
+ : await nativeStatus()
333
+ const configuredMode = currentConfig.mode
334
+ const modeSupported = configuredMode !== 'system' || runtimeStatus.supported
335
+ return {
336
+ configuredMode,
337
+ effectiveMode: modeSupported ? configuredMode : 'unsupported',
338
+ proxyUrl: currentConfig.proxyUrl,
339
+ runtimeKind: hostRuntime ? 'electron-inline' : (process.versions.electron ? 'electron-node' : 'node'),
340
+ lastAppliedAt,
341
+ ...runtimeStatus,
342
+ }
343
+ }
344
+
345
+ export async function initializeNetworkProxy() {
346
+ if (initialized) return { config: currentConfig, status: await getNetworkProxyStatus() }
347
+ installNetworkFetch()
348
+ const settings = await readStore('settings')
349
+ await applyConfig(settings?.[SETTINGS_KEY])
350
+ initialized = true
351
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
352
+ }
353
+
354
+ export async function getNetworkProxyConfig() {
355
+ if (!initialized) await initializeNetworkProxy()
356
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
357
+ }
358
+
359
+ export async function updateNetworkProxyConfig(value) {
360
+ if (!initialized) await initializeNetworkProxy()
361
+ const previous = currentConfig
362
+ const next = normalizeNetworkProxyConfig(value)
363
+ if (next.mode === 'manual') next.proxyUrl = validateManualProxyUrl(next.proxyUrl)
364
+
365
+ await applyConfig(next)
366
+ try {
367
+ await persistConfig(currentConfig)
368
+ } catch (error) {
369
+ await applyConfig(previous)
370
+ throw error
371
+ }
372
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
373
+ }
374
+
375
+ export async function refreshSystemProxy() {
376
+ if (!initialized) await initializeNetworkProxy()
377
+ nativeResolver?.close?.()
378
+ nativeResolver = null
379
+ nativeResolverError = null
380
+ if (hostRuntime) await hostRuntime.refresh()
381
+ else await replaceNetworkDispatcher(currentConfig)
382
+ lastAppliedAt = new Date().toISOString()
383
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
384
+ }
@@ -140,6 +140,10 @@ async function waitForQuickForge(options = {}) {
140
140
  }
141
141
 
142
142
  export async function startQuickForge(options = {}) {
143
+ if (options.networkRuntime) {
144
+ const { registerHostNetworkRuntime } = await import('./network-proxy.mjs')
145
+ registerHostNetworkRuntime(options.networkRuntime)
146
+ }
143
147
  const existingHealth = options.reuseExisting === false ? null : await checkQuickForgeHealth(options)
144
148
  const url = getQuickForgeUrl(options)
145
149
  const healthUrl = getQuickForgeHealthUrl(options)
@@ -1,4 +1,5 @@
1
1
  import { streamSimple } from '@earendil-works/pi-ai/compat'
2
+ import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
2
3
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
3
4
  import { readStore } from '../storage.mjs'
4
5
  import { logger } from '../utils/logger.mjs'
@@ -101,6 +102,7 @@ Rules:
101
102
  maxTokens: 1600,
102
103
  temperature: 0,
103
104
  reasoning: thinkingLevel === 'off' ? undefined : thinkingLevel,
105
+ maxRetries: DEFAULT_AI_MAX_RETRIES,
104
106
  maxRetryDelayMs: 60000,
105
107
  },
106
108
  )