@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.
- package/README.md +1 -0
- package/bin/quickforge.mjs +2 -0
- package/dist/assets/{AgentProfilesPage-BFgyFa5e.js → AgentProfilesPage-D7pbg8nm.js} +1 -1
- package/dist/assets/{ChatPanelHost-Cld9HcVz.js → ChatPanelHost-wfGCaCzD.js} +62 -62
- package/dist/assets/{PluginsPage-DEMqwhOA.js → PluginsPage-NAGroBm3.js} +1 -1
- package/dist/assets/{ScheduledTasksPage-DTP_gedp.js → ScheduledTasksPage-Bp0MNfQD.js} +2 -2
- package/dist/assets/{SettingsWorkspacePage-Dr9zT5Bs.js → SettingsWorkspacePage-BtJZMIsZ.js} +350 -269
- package/dist/assets/{SharedConversationPage-op3DRwcw.js → SharedConversationPage-Ce3KoVZt.js} +1 -1
- package/dist/assets/{TerminalDock-BL_UovwU.js → TerminalDock-4eUuXP7X.js} +2 -2
- package/dist/assets/WorkspaceInspector-kM3BRPjY.js +13 -0
- package/dist/assets/{index-yV1wtqTr.js → index-C6k5taeb.js} +8 -8
- package/dist/assets/index-CzN8NSKC.css +3 -0
- package/dist/assets/{mcp-servers-dialog-Bp6kbIup.js → mcp-servers-dialog-BQbFywVL.js} +2 -2
- package/dist/assets/{skills-dialog-DiJAUfvW.js → skills-dialog-REBeTFSH.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +4 -1
- package/server/ai-http-logger.mjs +6 -6
- package/server/ai-provider-options.mjs +8 -0
- package/server/index.mjs +7 -2
- package/server/network-proxy.mjs +384 -0
- package/server/public-api.mjs +4 -0
- package/server/routes/agent-profiles.mjs +2 -0
- package/server/routes/models.mjs +1 -0
- package/server/routes/scheduled-tasks.mjs +2 -0
- package/server/routes/system.mjs +26 -0
- package/server/routes/workspace.mjs +2 -0
- package/server/session-utils.mjs +2 -0
- package/dist/assets/WorkspaceInspector-73dhAime.js +0 -13
- 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{
|
|
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{
|
|
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-
|
|
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-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/skills-dialog-
|
|
25
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
214
|
-
purpose:
|
|
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,
|
|
222
|
+
return aiHttpContext.run(traceContext, () => streamSimple(model, context, providerOptions))
|
|
221
223
|
}
|
|
222
|
-
|
|
223
|
-
installAiHttpLogger()
|
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
|
+
}
|
package/server/public-api.mjs
CHANGED
|
@@ -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
|
)
|