@shawnstack/quickforge 1.7.8 → 1.7.10

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 (35) hide show
  1. package/bin/quickforge.mjs +10 -1
  2. package/dist/assets/{AgentProfilesPage-BSM5w3bn.js → AgentProfilesPage-Dw9orPNt.js} +1 -1
  3. package/dist/assets/{ChatPanelHost-cd80S4EX.js → ChatPanelHost-CW3-MQyL.js} +2 -2
  4. package/dist/assets/{CloudAccountSettingsPage-BJMrfgy9.js → CloudAccountSettingsPage-CDkKN1Sz.js} +1 -1
  5. package/dist/assets/{PluginsPage-mJkTmVVV.js → PluginsPage-Dj1bmsEf.js} +1 -1
  6. package/dist/assets/{ScheduledTasksPage-DJj_NLm9.js → ScheduledTasksPage-C9lOTi75.js} +1 -1
  7. package/dist/assets/{SettingsWorkspacePage-7lQ0Wl63.js → SettingsWorkspacePage-CFuvSATi.js} +3 -3
  8. package/dist/assets/{ShareLinksSettingsPage-DjFq_Lnb.js → ShareLinksSettingsPage-CH_t33A3.js} +1 -1
  9. package/dist/assets/{SharedConversationPage-BbuNpcpP.js → SharedConversationPage-DLo05j7c.js} +1 -1
  10. package/dist/assets/{TerminalDock-CczcCJJE.js → TerminalDock-CSOVub-m.js} +1 -1
  11. package/dist/assets/{WorkspaceInspector-0i6x-diB.js → WorkspaceInspector-DPXgLIum.js} +1 -1
  12. package/dist/assets/index-DQBOm9gt.js +66 -0
  13. package/dist/assets/index-ps85guTs.css +3 -0
  14. package/dist/assets/{local-tools-Dn1Y9aPe.js → local-tools-BxfQeIUQ.js} +1 -1
  15. package/dist/assets/{mcp-servers-dialog-BG4b67ea.js → mcp-servers-dialog-CwHp0K4q.js} +1 -1
  16. package/dist/assets/{skills-dialog-DIzZvQqW.js → skills-dialog-k9ycekIU.js} +1 -1
  17. package/dist/index.html +4 -4
  18. package/package.json +1 -1
  19. package/server/agent-manager.mjs +68 -2
  20. package/server/auto-compaction.mjs +0 -2
  21. package/server/context-usage.mjs +9 -7
  22. package/server/maintenance/downgrade-session-state-v1.mjs +4 -2
  23. package/server/maintenance/export-session-state-v1.mjs +4 -2
  24. package/server/public-api.mjs +21 -2
  25. package/server/routes/backup.mjs +7 -0
  26. package/server/routes/storage.mjs +23 -0
  27. package/server/session-persistence-lock.mjs +17 -6
  28. package/server/session-state-backup.mjs +4 -2
  29. package/server/session-state-cutover.mjs +284 -91
  30. package/server/session-state-service.mjs +38 -17
  31. package/server/sqlite/session-state-repository.mjs +145 -25
  32. package/server/storage.mjs +28 -0
  33. package/server/utils/process-tree.mjs +14 -2
  34. package/dist/assets/index-D-J_8Smf.css +0 -3
  35. package/dist/assets/index-DFASc15l.js +0 -66
@@ -1,5 +1,5 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mermaid.core-DWpShXCs.js","assets/pi-ai-Cr2ee4a0.js","assets/rolldown-runtime-DWdDZTNf.js","assets/chunk-HOUHSVGY-tdc-Z9eW.js","assets/src-PYsDme0X.js","assets/chunk-Y2CYZVJY-D6N-CrJv.js","assets/chunk-WYO6CB5R-NF-MsJJR.js","assets/chunk-ICXQ74PX-Cd7igVGt.js","assets/dist-CLg13NtV.js","assets/chunk-52WLFC77-CDbaHw80.js","assets/line-B4DxX7tC.js","assets/path-6uRLdFF7.js","assets/array-B9UHiPd-.js","assets/chunk-7BUUIJ7U-CCcsI9XE.js","assets/chunk-C7G6YPKG-ZMLUvfLy.js","assets/chunk-OGEWGWER-TgvkXkFX.js","assets/chunk-Q4XR5HBZ-fo5-HsqY.js","assets/pi-web-ui-DV5jKjSg.js","assets/lit-vendor-DvaQX_UQ.js","assets/chunk-ZGVPDNZ5-CRrAYi4g.js","assets/rough.esm-Bud4g4fh.js","assets/chunk-FWX5IMBZ-DrnrdrSu.js","assets/chunk-VAUOI2AC-CWYjg8zE.js","assets/chunk-ZIRB5QZD-Dc1s5bLM.js"])))=>i.map(i=>d[i]);
2
- import{d as e,p as t,t as n,u as r}from"./lit-vendor-DvaQX_UQ.js";import{y as i}from"./pi-web-ui-DV5jKjSg.js";import{J as a}from"./pi-ai-Cr2ee4a0.js";import{$t as o,At as s,Dt as c,Et as l,Gt as u,Jt as d,Kt as f,Ot as p,Pn as m,Qt as h,Xt as g,Yt as _,Zt as v,d as y,en as b,in as x,nn as S,ot as C,qt as w,r as T,rn as ee,tn as E}from"./index-DFASc15l.js";function D(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function O(e,t,n){if(!e||e.isError||t||!D(e.details))return``;let r=e.details.action,i=e.details.status,a=e.details.markdown;if(typeof a!=`string`)return``;if(r===`read`){if(a)return a;if(i===`empty`||i===void 0)return n(`memoryContentEmpty`)}return r===`write`&&(i===`saved`||i===void 0)?n(`memoryContentSaved`):``}var te=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.(?:png|jpg|webp|gif)$/i,ne=new Set([`image/png`,`image/jpeg`,`image/webp`,`image/gif`]);function re(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function k(e){if(!re(e)||e.type!==`generated_image_result`||typeof e.sessionId!=`string`||!e.sessionId||typeof e.prompt!=`string`||typeof e.model!=`string`||!Array.isArray(e.assets))return null;let t=e.assets.flatMap(e=>!re(e)||typeof e.assetId!=`string`||!te.test(e.assetId)||typeof e.mimeType!=`string`||!ne.has(e.mimeType)||typeof e.size!=`number`||!Number.isFinite(e.size)||e.size<0?[]:[{assetId:e.assetId,mimeType:e.mimeType,size:e.size}]);return t.length?{type:`generated_image_result`,sessionId:e.sessionId,prompt:e.prompt,model:e.model,assets:t,...typeof e.text==`string`&&e.text?{text:e.text}:{}}:null}function ie(e,t,n=window.location.pathname){let r=n.match(/^\/share\/([^/]+)\/?$/)?.[1];return r?`/api/shared/${encodeURIComponent(decodeURIComponent(r))}/assets/${encodeURIComponent(t.assetId)}`:`/api/session-assets/${encodeURIComponent(e.sessionId)}/${encodeURIComponent(t.assetId)}`}var A=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`,j=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`,M=`<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>`,N=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9"/><path d="M12 12v3"/></svg>`,P=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15.36-5.64L21 9"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15.36 5.64L3 15"/></svg>`,ae=`<svg class="quickforge-plan-mode-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h1"/><path d="M4 12h1"/><path d="M4 18h1"/><path d="M9 6h11"/><path d="M9 12h11"/><path d="M9 18h11"/></svg>`,oe=`<svg class="quickforge-plan-remove-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,se=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12h14"/></svg>`,ce=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`,F=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 4.5h5a1.5 1.5 0 0 1 1.5 1.5v2h1a2.5 2.5 0 0 1 0 5h-1v2a1.5 1.5 0 0 1-1.5 1.5h-2v1a2.5 2.5 0 0 1-5 0v-1h-2A1.5 1.5 0 0 1 2 15.5v-3h1a2 2 0 1 0 0-4H2v-2A1.5 1.5 0 0 1 3.5 5h2v-1a2.5 2.5 0 0 1 5 0v.5Z"/></svg>`,le=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><path d="M5.4 2.8h6.1L15.8 7v10.2H5.4z"/><path d="M11.4 2.9V7h4.1"/><path d="M7.6 10.2h5"/><path d="M7.6 13h4.3"/></svg>`,ue=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><rect x="3.4" y="4" width="13.2" height="12.2" rx="1.5"/><path d="M3.4 8h13.2"/><path d="M7.8 4v12.2"/><path d="M12.2 4v12.2"/><path d="M3.4 12h13.2"/></svg>`,de=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.2h14"/><rect x="4.2" y="4.2" width="11.6" height="8.4" rx="1.2"/><path d="M10 12.6v3.2"/><path d="m7.2 17 2.8-1.2 2.8 1.2"/><path d="M7.1 9.5 9 7.7l1.5 1.3 2.4-2.5"/></svg>`,fe=`<svg class="quickforge-agent-access-trigger-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 19 6v5c0 4.5-2.8 8.4-7 10-4.2-1.6-7-5.5-7-10V6l7-3Z"/><path d="M9 12h6"/></svg>`,pe=`<svg class="quickforge-agent-access-option-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,me=`<svg class="quickforge-agent-access-check" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`,he=`<svg class="quickforge-agent-access-chevron" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>`,ge=`<svg class="quickforge-opencode-config-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20V10"/><path d="M18 20V4"/><path d="M6 20v-4"/><path d="M18 4a2 2 0 0 0-4 0"/><path d="M6 16a2 2 0 0 0-4 0"/><path d="M12 10a2 2 0 0 0-4 0"/></svg>`;function _e(e){return m(e===`full-access`?`agentAccessFullLabel`:`agentAccessDefaultLabel`)}function I(e){let t=document.querySelector(`.quickforge-agent-access-menu`);t?.__quickforgeDismissHandler&&(document.removeEventListener(`pointerdown`,t.__quickforgeDismissHandler,!0),document.removeEventListener(`keydown`,t.__quickforgeDismissHandler,!0),window.removeEventListener(`resize`,t.__quickforgeDismissHandler,!0),window.removeEventListener(`scroll`,t.__quickforgeDismissHandler,!0),t.__quickforgeDismissHandler=void 0),t?.remove(),e.querySelector(`.quickforge-agent-access-inline`)?.setAttribute(`aria-expanded`,`false`)}function ve(e,t,n){let r=e===t,i=document.createElement(`button`);return i.type=`button`,i.className=`quickforge-agent-access-item`,i.setAttribute(`role`,`menuitemradio`),i.setAttribute(`aria-checked`,String(r)),i.dataset.quickforgeAgentAccessMode=e,i.innerHTML=`
2
+ import{d as e,p as t,t as n,u as r}from"./lit-vendor-DvaQX_UQ.js";import{y as i}from"./pi-web-ui-DV5jKjSg.js";import{J as a}from"./pi-ai-Cr2ee4a0.js";import{$t as o,At as s,Dt as c,Et as l,Gt as u,Jt as d,Kt as f,Ot as p,Pn as m,Qt as h,Xt as g,Yt as _,Zt as v,d as y,en as b,in as x,nn as S,ot as C,qt as w,r as T,rn as ee,tn as E}from"./index-DQBOm9gt.js";function D(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function O(e,t,n){if(!e||e.isError||t||!D(e.details))return``;let r=e.details.action,i=e.details.status,a=e.details.markdown;if(typeof a!=`string`)return``;if(r===`read`){if(a)return a;if(i===`empty`||i===void 0)return n(`memoryContentEmpty`)}return r===`write`&&(i===`saved`||i===void 0)?n(`memoryContentSaved`):``}var te=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.(?:png|jpg|webp|gif)$/i,ne=new Set([`image/png`,`image/jpeg`,`image/webp`,`image/gif`]);function re(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function k(e){if(!re(e)||e.type!==`generated_image_result`||typeof e.sessionId!=`string`||!e.sessionId||typeof e.prompt!=`string`||typeof e.model!=`string`||!Array.isArray(e.assets))return null;let t=e.assets.flatMap(e=>!re(e)||typeof e.assetId!=`string`||!te.test(e.assetId)||typeof e.mimeType!=`string`||!ne.has(e.mimeType)||typeof e.size!=`number`||!Number.isFinite(e.size)||e.size<0?[]:[{assetId:e.assetId,mimeType:e.mimeType,size:e.size}]);return t.length?{type:`generated_image_result`,sessionId:e.sessionId,prompt:e.prompt,model:e.model,assets:t,...typeof e.text==`string`&&e.text?{text:e.text}:{}}:null}function ie(e,t,n=window.location.pathname){let r=n.match(/^\/share\/([^/]+)\/?$/)?.[1];return r?`/api/shared/${encodeURIComponent(decodeURIComponent(r))}/assets/${encodeURIComponent(t.assetId)}`:`/api/session-assets/${encodeURIComponent(e.sessionId)}/${encodeURIComponent(t.assetId)}`}var A=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`,j=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`,M=`<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>`,N=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9"/><path d="M12 12v3"/></svg>`,P=`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15.36-5.64L21 9"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15.36 5.64L3 15"/></svg>`,ae=`<svg class="quickforge-plan-mode-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h1"/><path d="M4 12h1"/><path d="M4 18h1"/><path d="M9 6h11"/><path d="M9 12h11"/><path d="M9 18h11"/></svg>`,oe=`<svg class="quickforge-plan-remove-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,se=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12h14"/></svg>`,ce=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`,F=`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 4.5h5a1.5 1.5 0 0 1 1.5 1.5v2h1a2.5 2.5 0 0 1 0 5h-1v2a1.5 1.5 0 0 1-1.5 1.5h-2v1a2.5 2.5 0 0 1-5 0v-1h-2A1.5 1.5 0 0 1 2 15.5v-3h1a2 2 0 1 0 0-4H2v-2A1.5 1.5 0 0 1 3.5 5h2v-1a2.5 2.5 0 0 1 5 0v.5Z"/></svg>`,le=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><path d="M5.4 2.8h6.1L15.8 7v10.2H5.4z"/><path d="M11.4 2.9V7h4.1"/><path d="M7.6 10.2h5"/><path d="M7.6 13h4.3"/></svg>`,ue=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><rect x="3.4" y="4" width="13.2" height="12.2" rx="1.5"/><path d="M3.4 8h13.2"/><path d="M7.8 4v12.2"/><path d="M12.2 4v12.2"/><path d="M3.4 12h13.2"/></svg>`,de=`<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.2h14"/><rect x="4.2" y="4.2" width="11.6" height="8.4" rx="1.2"/><path d="M10 12.6v3.2"/><path d="m7.2 17 2.8-1.2 2.8 1.2"/><path d="M7.1 9.5 9 7.7l1.5 1.3 2.4-2.5"/></svg>`,fe=`<svg class="quickforge-agent-access-trigger-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 19 6v5c0 4.5-2.8 8.4-7 10-4.2-1.6-7-5.5-7-10V6l7-3Z"/><path d="M9 12h6"/></svg>`,pe=`<svg class="quickforge-agent-access-option-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`,me=`<svg class="quickforge-agent-access-check" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`,he=`<svg class="quickforge-agent-access-chevron" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>`,ge=`<svg class="quickforge-opencode-config-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20V10"/><path d="M18 20V4"/><path d="M6 20v-4"/><path d="M18 4a2 2 0 0 0-4 0"/><path d="M6 16a2 2 0 0 0-4 0"/><path d="M12 10a2 2 0 0 0-4 0"/></svg>`;function _e(e){return m(e===`full-access`?`agentAccessFullLabel`:`agentAccessDefaultLabel`)}function I(e){let t=document.querySelector(`.quickforge-agent-access-menu`);t?.__quickforgeDismissHandler&&(document.removeEventListener(`pointerdown`,t.__quickforgeDismissHandler,!0),document.removeEventListener(`keydown`,t.__quickforgeDismissHandler,!0),window.removeEventListener(`resize`,t.__quickforgeDismissHandler,!0),window.removeEventListener(`scroll`,t.__quickforgeDismissHandler,!0),t.__quickforgeDismissHandler=void 0),t?.remove(),e.querySelector(`.quickforge-agent-access-inline`)?.setAttribute(`aria-expanded`,`false`)}function ve(e,t,n){let r=e===t,i=document.createElement(`button`);return i.type=`button`,i.className=`quickforge-agent-access-item`,i.setAttribute(`role`,`menuitemradio`),i.setAttribute(`aria-checked`,String(r)),i.dataset.quickforgeAgentAccessMode=e,i.innerHTML=`
3
3
  <span class="quickforge-agent-access-check-slot">${r?me:``}</span>
4
4
  <span class="quickforge-agent-access-option-icon-wrap">${e===`full-access`?pe:fe}</span>
5
5
  <span class="quickforge-agent-access-item-label"></span>
@@ -1,4 +1,4 @@
1
- import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{D as t,Gt as n,J as r,Jt as i,S as a,ft as o,j as s,s as c,v as l}from"./icons-DM_qJ7VJ.js";import{n as u}from"./react-vendor-BpWW_Nvp.js";import{An as d,Dn as f,H as p,Pn as m,Tt as h,kn as g}from"./index-DFASc15l.js";var _=e(i(),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:t,reconnecting:n,onToggle:i,onEdit:o,onDelete:l,onReconnect:u}){let f=e.tools?.slice(0,b)??[],p=e.toolCount??e.tools?.length??0,h=Math.max(0,p-f.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:d(`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:m(`mcpToolsCount`,{count:p})})]}),(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:t,className:d(`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:()=>i(e),title:e.enabled?m(`pauseTask`):m(`enable`),children:(0,v.jsx)(`span`,{className:d(`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)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>u(e.name),disabled:n,"aria-label":m(`mcpReconnectServer`),title:m(`mcpReconnectServer`),children:n?(0,v.jsx)(r,{className:`size-4 animate-spin`}):(0,v.jsx)(a,{className:`size-4`})}):null,(0,v.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>o(e),"aria-label":m(`editTask`),title:m(`editTask`),children:(0,v.jsx)(s,{className:`size-4`})}),(0,v.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-destructive`,onClick:()=>l(e.name),"aria-label":m(`delete`),title:m(`delete`),children:(0,v.jsx)(c,{className:`size-4`})})]})]}),f.length>0?(0,v.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[f.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)),h>0?(0,v.jsx)(`span`,{className:`rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground/55`,children:m(`mcpMoreTools`,{count:h})}):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{D as t,Gt as n,J as r,Jt as i,S as a,ft as o,j as s,s as c,v as l}from"./icons-DM_qJ7VJ.js";import{n as u}from"./react-vendor-BpWW_Nvp.js";import{An as d,Dn as f,H as p,Pn as m,Tt as h,kn as g}from"./index-DQBOm9gt.js";var _=e(i(),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:t,reconnecting:n,onToggle:i,onEdit:o,onDelete:l,onReconnect:u}){let f=e.tools?.slice(0,b)??[],p=e.toolCount??e.tools?.length??0,h=Math.max(0,p-f.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:d(`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:m(`mcpToolsCount`,{count:p})})]}),(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:t,className:d(`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:()=>i(e),title:e.enabled?m(`pauseTask`):m(`enable`),children:(0,v.jsx)(`span`,{className:d(`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)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>u(e.name),disabled:n,"aria-label":m(`mcpReconnectServer`),title:m(`mcpReconnectServer`),children:n?(0,v.jsx)(r,{className:`size-4 animate-spin`}):(0,v.jsx)(a,{className:`size-4`})}):null,(0,v.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-muted-foreground`,onClick:()=>o(e),"aria-label":m(`editTask`),title:m(`editTask`),children:(0,v.jsx)(s,{className:`size-4`})}),(0,v.jsx)(g,{type:`button`,variant:`ghost`,size:`icon`,className:`size-8 text-destructive`,onClick:()=>l(e.name),"aria-label":m(`delete`),title:m(`delete`),children:(0,v.jsx)(c,{className:`size-4`})})]})]}),f.length>0?(0,v.jsxs)(`div`,{className:`mt-2 flex flex-wrap gap-1.5`,children:[f.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)),h>0?(0,v.jsx)(`span`,{className:`rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground/55`,children:m(`mcpMoreTools`,{count:h})}):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(`
@@ -1,3 +1,3 @@
1
- import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{Bt as n,Gt as r,J as i,Jt as a,b as o,n as s}from"./icons-DM_qJ7VJ.js";import{n as c}from"./react-vendor-BpWW_Nvp.js";import{An as l,Dn as u,Pn as d}from"./index-DFASc15l.js";var f=t({SkillsDialog:()=>_,SkillsManagerPanel:()=>g}),p=e(a(),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:a,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&&!a)return;let t=!1;return(async()=>{E(!0),A(``);try{let e=j?`/api/skills?projectId=${encodeURIComponent(a.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,a,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&&a?(t.set(`scope`,`project`),t.set(`projectId`,a.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&&!a)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:a.id,selectedSkills:[...e]}:{selectedSkills:[...e]})}));g({scope:t,project:j?n.projects?.find(e=>e.id===a.id)??{...a,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:a.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)(o,{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)(i,{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:l(`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)(n,{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)(r,{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)(i,{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 inline-flex items-center`,children:[d(`skillSearchPaths`),(0,m.jsx)(u,{label:C.join(`
1
+ import{i as e,n as t}from"./rolldown-runtime-DWdDZTNf.js";import{Bt as n,Gt as r,J as i,Jt as a,b as o,n as s}from"./icons-DM_qJ7VJ.js";import{n as c}from"./react-vendor-BpWW_Nvp.js";import{An as l,Dn as u,Pn as d}from"./index-DQBOm9gt.js";var f=t({SkillsDialog:()=>_,SkillsManagerPanel:()=>g}),p=e(a(),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:a,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&&!a)return;let t=!1;return(async()=>{E(!0),A(``);try{let e=j?`/api/skills?projectId=${encodeURIComponent(a.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,a,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&&a?(t.set(`scope`,`project`),t.set(`projectId`,a.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&&!a)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:a.id,selectedSkills:[...e]}:{selectedSkills:[...e]})}));g({scope:t,project:j?n.projects?.find(e=>e.id===a.id)??{...a,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:a.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)(o,{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)(i,{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:l(`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)(n,{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)(r,{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)(i,{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 inline-flex items-center`,children:[d(`skillSearchPaths`),(0,m.jsx)(u,{label:C.join(`
2
2
  `)})]})}):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)(u,{label:q})]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:q}),C.length?(0,m.jsxs)(`div`,{className:`quickforge-settings-row-description inline-flex items-center`,children:[d(`skillSearchPaths`),(0,m.jsx)(u,{label:C.join(`
3
3
  `)})]}):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};
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-DFASc15l.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-DQBOm9gt.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-DWdDZTNf.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/pi-ai-Cr2ee4a0.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/dist-yDHbi5ux.js">
@@ -22,9 +22,9 @@
22
22
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-BpWW_Nvp.js">
23
23
  <link rel="modulepreload" crossorigin href="/assets/logger-8REuLPGK.js">
24
24
  <link rel="modulepreload" crossorigin href="/assets/model-display-label-CURwH0xM.js">
25
- <link rel="modulepreload" crossorigin href="/assets/index-DFASc15l.js">
26
- <link rel="modulepreload" crossorigin href="/assets/skills-dialog-DIzZvQqW.js">
27
- <link rel="stylesheet" crossorigin href="/assets/index-D-J_8Smf.css">
25
+ <link rel="modulepreload" crossorigin href="/assets/index-DQBOm9gt.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/skills-dialog-k9ycekIU.js">
27
+ <link rel="stylesheet" crossorigin href="/assets/index-ps85guTs.css">
28
28
  </head>
29
29
  <body>
30
30
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shawnstack/quickforge",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
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",
@@ -2403,8 +2403,28 @@ async function persistSessionUnlocked(session) {
2403
2403
  return persistedMetadata || metadata
2404
2404
  }
2405
2405
 
2406
+ // Persist slower than this (queue wait + synchronous SQLite write) is worth a
2407
+ // warning: large sessions write big synchronous transactions that stall the
2408
+ // event loop and make every in-flight request (including /restore) wait.
2409
+ const SLOW_PERSIST_LOG_MS = 1_000
2410
+
2406
2411
  async function persistSession(session) {
2407
- return withSessionPersistenceLock(() => persistSessionUnlocked(session))
2412
+ const startedAt = performance.now()
2413
+ // Serialize per session once SQLite state is authoritative (per-row revision
2414
+ // CAS guarantees correctness); cross-session persists then run independently
2415
+ // instead of piling onto one global chain that also gates destroyAgent.
2416
+ // JSON-mirror mode keeps the global lock for bucket-level atomicity.
2417
+ const lockKey = isSessionStateAuthoritative() ? `session:${session.sessionId}` : ''
2418
+ const result = await withSessionPersistenceLock(() => persistSessionUnlocked(session), lockKey)
2419
+ const durationMs = performance.now() - startedAt
2420
+ if (durationMs >= SLOW_PERSIST_LOG_MS) {
2421
+ logger.warn(`Session ${session.sessionId} persist took ${Math.round(durationMs)}ms (queue wait + write)`, {
2422
+ sessionId: session.sessionId,
2423
+ durationMs: Math.round(durationMs),
2424
+ messageCount: session.agent?.state?.messages?.length ?? 0,
2425
+ })
2426
+ }
2427
+ return result
2408
2428
  }
2409
2429
 
2410
2430
  export async function persistSessionState(session) {
@@ -3069,14 +3089,33 @@ export async function destroyAgent(sessionId) {
3069
3089
  agentSessions.delete(sessionId)
3070
3090
  }
3071
3091
 
3092
+ // In-flight restores keyed by sessionId. Concurrent route handlers
3093
+ // (POST /restore, GET /state, GET /messages, GET /status, SSE) all fall back
3094
+ // to restoreAgent; without dedupe each raced through createAgent and the last
3095
+ // agentSessions.set overwrote the others, leaking the overwritten sessions
3096
+ // (listeners, idle/persist timers, OpenCode child processes) forever.
3097
+ const pendingRestores = new Map()
3098
+
3072
3099
  /**
3073
3100
  * Try to restore an agent session from persisted storage.
3101
+ * Concurrent calls for the same session share one in-flight restore.
3074
3102
  * Returns the restored session, or null if not found.
3075
3103
  */
3076
- export async function restoreAgent(sessionId) {
3104
+ export function restoreAgent(sessionId) {
3077
3105
  const existing = agentSessions.get(sessionId)
3078
3106
  if (existing) return existing
3079
3107
 
3108
+ const inFlight = pendingRestores.get(sessionId)
3109
+ if (inFlight) return inFlight
3110
+
3111
+ const restorePromise = restoreAgentUnlocked(sessionId).finally(() => {
3112
+ pendingRestores.delete(sessionId)
3113
+ })
3114
+ pendingRestores.set(sessionId, restorePromise)
3115
+ return restorePromise
3116
+ }
3117
+
3118
+ async function restoreAgentUnlocked(sessionId) {
3080
3119
  try {
3081
3120
  const sessionData = await readSessionValue(sessionId)
3082
3121
  if (!sessionData) {
@@ -3223,6 +3262,33 @@ export async function refreshAllSessionTools() {
3223
3262
  return result
3224
3263
  }
3225
3264
 
3265
+ /**
3266
+ * Refresh the model binding of every active QuickForge session after model
3267
+ * configuration changes (custom providers, maxTokens, ...). OpenCode owns its
3268
+ * model selection and streaming sessions are skipped (runPrompt re-resolves
3269
+ * the binding on the next message). Only sessions whose model actually
3270
+ * changed get a state event.
3271
+ */
3272
+ export async function refreshAllSessionModels() {
3273
+ for (const [sessionId, session] of agentSessions) {
3274
+ if (session.harness !== AGENT_HARNESS_QUICKFORGE) continue
3275
+ if (session.agent?.state?.isStreaming) continue
3276
+ try {
3277
+ const before = JSON.stringify(session.model ?? null)
3278
+ await refreshSessionModelBinding(session)
3279
+ const after = JSON.stringify(session.model ?? null)
3280
+ if (before === after) continue
3281
+ const state = getSessionState(sessionId)
3282
+ emitSessionEvent(session, { type: 'state', ...state })
3283
+ } catch (error) {
3284
+ // The model may have been deleted (model_not_configured); runPrompt will
3285
+ // surface the same error on the next message. Log and keep other
3286
+ // sessions refreshing.
3287
+ logger.error(`Failed to refresh model for session ${sessionId}:`, error, { sessionId })
3288
+ }
3289
+ }
3290
+ }
3291
+
3226
3292
  export async function updateSessionTitle(sessionId, title) {
3227
3293
  let session = agentSessions.get(sessionId)
3228
3294
  if (!session) session = await restoreAgent(sessionId)
@@ -295,7 +295,6 @@ export function estimateSessionContextUsage(session, messages = session?.agent?.
295
295
  estimatedInputTokens: 0,
296
296
  knownInputTokens: 0,
297
297
  inputTokenSource: 'estimated',
298
- reservedOutputTokens: 0,
299
298
  totalTokens: 0,
300
299
  contextWindow,
301
300
  percent: 0,
@@ -306,7 +305,6 @@ export function estimateSessionContextUsage(session, messages = session?.agent?.
306
305
  systemPromptTokens: 0,
307
306
  messagesTokens: 0,
308
307
  toolsTokens: 0,
309
- reservedOutputTokens: 0,
310
308
  },
311
309
  }
312
310
  }
@@ -50,7 +50,6 @@ function localMessagesTokens(messages) {
50
50
 
51
51
  export function estimateContextUsage({ systemPrompt, messages, tools, model, minimumProviderUsageIndex = 0 }) {
52
52
  const contextWindow = Number(model?.contextWindow) || 0
53
- const reservedOutputTokens = Math.max(0, Number(model?.maxTokens) || 4096)
54
53
  const normalizedMessages = normalizeMessagesForTokenEstimate(messages)
55
54
  const coreEstimate = estimateContextTokens(normalizedMessages)
56
55
  const systemPromptTokens = textTokens(systemPrompt)
@@ -68,8 +67,11 @@ export function estimateContextUsage({ systemPrompt, messages, tools, model, min
68
67
  const inputTokens = providerBasedContextTokens > 0
69
68
  ? Math.max(estimatedInputTokens, providerBasedContextTokens)
70
69
  : estimatedInputTokens
71
- const totalTokens = inputTokens + reservedOutputTokens
72
- const percent = contextWindow > 0 ? Math.round((totalTokens / contextWindow) * 1000) / 10 : 0
70
+ // 上下文占用按纯输入口径统计:percent = inputTokens / contextWindow。
71
+ // 真实请求的 max_tokens pi-ai `clampMaxTokensToContext` 按窗口收缩,
72
+ // 统计侧不再预留输出 token;totalTokens 字段仅为兼容保留,恒等于 inputTokens。
73
+ const totalTokens = inputTokens
74
+ const percent = contextWindow > 0 ? Math.round((inputTokens / contextWindow) * 1000) / 10 : 0
73
75
  const inputTokenSource = providerBasedContextTokens > 0
74
76
  ? providerBasedContextTokens >= estimatedInputTokens ? 'provider' : 'mixed'
75
77
  : 'estimated'
@@ -80,7 +82,6 @@ export function estimateContextUsage({ systemPrompt, messages, tools, model, min
80
82
  knownInputTokens: providerBasedContextTokens,
81
83
  providerContextTokens: providerBasedContextTokens,
82
84
  inputTokenSource,
83
- reservedOutputTokens,
84
85
  totalTokens,
85
86
  contextWindow,
86
87
  percent,
@@ -88,7 +89,6 @@ export function estimateContextUsage({ systemPrompt, messages, tools, model, min
88
89
  systemPromptTokens,
89
90
  messagesTokens,
90
91
  toolsTokens,
91
- reservedOutputTokens,
92
92
  providerUsageTokens,
93
93
  trailingTokens: providerUsageTokens > 0
94
94
  ? Math.max(0, Number(coreEstimate.trailingTokens) || 0)
@@ -101,13 +101,15 @@ export function estimateContextUsage({ systemPrompt, messages, tools, model, min
101
101
 
102
102
  export function shouldCompactContextByPercent(usage, thresholdPercent) {
103
103
  const contextWindow = Number(usage?.contextWindow) || 0
104
- const totalTokens = Math.max(0, Number(usage?.totalTokens) || 0)
104
+ // 阈值按纯输入占用判断:inputTokens / contextWindow thresholdPercent 即触发,
105
+ // 不包含预留输出 token(真实请求的 max_tokens 由 pi-ai 按窗口收缩)。
106
+ const inputTokens = Math.max(0, Number(usage?.inputTokens) || 0)
105
107
  const threshold = Math.min(100, Math.max(0, Number(thresholdPercent) || 0))
106
108
  if (!contextWindow) return false
107
109
 
108
110
  const thresholdTokens = Math.ceil(contextWindow * threshold / 100)
109
111
  const reserveTokens = Math.min(contextWindow, Math.max(0, contextWindow - thresholdTokens + 1))
110
- return shouldCompact(totalTokens, contextWindow, {
112
+ return shouldCompact(inputTokens, contextWindow, {
111
113
  enabled: true,
112
114
  reserveTokens,
113
115
  keepRecentTokens: 0,
@@ -81,8 +81,10 @@ try {
81
81
  const integrity = repository.verifyIntegrity({ quickCheck: true })
82
82
  if (!integrity.ok) throw new Error('Session state integrity verification failed')
83
83
  const snapshot = repository.exportSnapshot()
84
- if (snapshot.count !== integrity.count || snapshot.digest !== integrity.digest) {
85
- throw new Error('Session state snapshot count/digest verification failed')
84
+ // Lightweight integrity has no digest; the count cross-check plus the
85
+ // snapshot's own digest stay authoritative.
86
+ if (snapshot.count !== integrity.count) {
87
+ throw new Error('Session state snapshot count verification failed')
86
88
  }
87
89
  const report = {
88
90
  ok: true,
@@ -33,8 +33,10 @@ try {
33
33
  const integrity = repository.verifyIntegrity({ quickCheck: true })
34
34
  if (!integrity.ok) throw new Error('Session state integrity verification failed')
35
35
  const snapshot = repository.exportSnapshot()
36
- if (snapshot.count !== integrity.count || snapshot.digest !== integrity.digest) {
37
- throw new Error('Session state backup count/digest verification failed')
36
+ // Lightweight integrity has no digest; the count cross-check plus the
37
+ // snapshot's own digest stay authoritative.
38
+ if (snapshot.count !== integrity.count) {
39
+ throw new Error('Session state backup count verification failed')
38
40
  }
39
41
  const backup = {
40
42
  app: 'quickforge',
@@ -13,6 +13,17 @@ function sleep(ms) {
13
13
  return new Promise((resolve) => setTimeout(resolve, ms))
14
14
  }
15
15
 
16
+ const STARTUP_HEALTH_TIMEOUT_MS = 300000
17
+
18
+ function isProcessRunning(pid) {
19
+ try {
20
+ process.kill(pid, 0)
21
+ return true
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
16
27
  function normalizeHost(host) {
17
28
  return host || '127.0.0.1'
18
29
  }
@@ -135,12 +146,20 @@ export async function checkQuickForgeHealth(options = {}) {
135
146
  }
136
147
 
137
148
  async function waitForQuickForge(options = {}) {
138
- const timeoutMs = options.timeoutMs || 15000
149
+ const timeoutMs = options.timeoutMs || STARTUP_HEALTH_TIMEOUT_MS
139
150
  const deadline = Date.now() + timeoutMs
151
+ const expectedPid = options.expectedPid || null
140
152
 
141
153
  while (Date.now() < deadline) {
142
154
  const health = await checkQuickForgeHealth(options)
143
155
  if (health) return health
156
+ if (expectedPid && !isProcessRunning(expectedPid)) {
157
+ // The spawned server exited. Give the async 'exit' event a moment to
158
+ // dispatch so startQuickForge can report the real exit code/signal
159
+ // instead of a generic timeout.
160
+ await sleep(300)
161
+ return null
162
+ }
144
163
  await sleep(options.pollIntervalMs || 300)
145
164
  }
146
165
 
@@ -213,7 +232,7 @@ export async function startQuickForge(options = {}) {
213
232
  child.once('error', reject)
214
233
  })
215
234
 
216
- const health = await waitForQuickForge(options)
235
+ const health = await waitForQuickForge({ ...options, expectedPid: child.pid })
217
236
  if (!health) {
218
237
  if (!child.killed && exitInfo === null) {
219
238
  try {
@@ -15,6 +15,8 @@ import { isLanAccessStorageAuthoritative } from '../lan-access-service.mjs'
15
15
  import { isLanAccessMaintenanceActive } from '../lan-access-cutover.mjs'
16
16
  import { exportLanAccessStateForBackup, restoreLanAccessStateSnapshot } from '../lan-access-backup.mjs'
17
17
  import { readLanAccessJsonFile, writeLanAccessJsonFile } from '../lan-access-json-file.mjs'
18
+ import { refreshAllSessionModels } from '../agent-manager.mjs'
19
+ import { logger } from '../utils/logger.mjs'
18
20
  import { sendJson, readJsonBody } from '../utils/response.mjs'
19
21
  import {
20
22
  ensureStorage,
@@ -641,6 +643,11 @@ async function restoreValidatedBackup(backup, mode = 'replace') {
641
643
  if (sections.customProviders !== undefined) {
642
644
  const value = merge ? mergeRecordStore(await readStore('custom-providers'), sections.customProviders) : sections.customProviders
643
645
  await writeStore('custom-providers', value)
646
+ try {
647
+ await refreshAllSessionModels()
648
+ } catch (error) {
649
+ logger.error('Failed to refresh session models after restoring custom providers:', error)
650
+ }
644
651
  summary.customProviders = countKeys(value)
645
652
  }
646
653
 
@@ -1,6 +1,8 @@
1
1
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
2
2
  import { readStore, writeStore, atomicUpdate, getComparable, getStoreRevision, readSessionStoreScoped, readSessionValue, writeSessionValueWithMetadata, deleteSessionWithMetadata, applySessionBatch, ensureStorage, dataDir, configDir, storageDir, cacheDir, logsDir } from '../storage.mjs'
3
3
  import { AUTO_ARCHIVE_SETTINGS_KEY, archiveInactiveSessions, normalizeAutoArchiveSettings } from '../auto-archive.mjs'
4
+ import { refreshAllSessionModels } from '../agent-manager.mjs'
5
+ import { logger } from '../utils/logger.mjs'
4
6
  import { directorySize } from '../utils/workspace.mjs'
5
7
  import { isAuthenticatedAppClient } from '../access-policy.mjs'
6
8
  import { getSessionIndexDiagnostics, markSessionIndexQueryFailure, querySessionIndexPage } from '../session-index-service.mjs'
@@ -293,6 +295,13 @@ export async function handleStorageApi(req, res, url, context = { isLocalRequest
293
295
 
294
296
  if (req.method === 'DELETE' && parts.length === 3) {
295
297
  await writeStore(store, {})
298
+ if (store === 'custom-providers') {
299
+ try {
300
+ await refreshAllSessionModels()
301
+ } catch (error) {
302
+ logger.error('Failed to refresh session models after clearing custom-providers:', error)
303
+ }
304
+ }
296
305
  sendJson(res, 200, { ok: true })
297
306
  return
298
307
  }
@@ -340,6 +349,13 @@ export async function handleStorageApi(req, res, url, context = { isLocalRequest
340
349
  if (store === 'settings' && key === AUTO_ARCHIVE_SETTINGS_KEY && normalizeAutoArchiveSettings(body?.value).enabled) {
341
350
  await archiveInactiveSessions()
342
351
  }
352
+ if (store === 'custom-providers') {
353
+ try {
354
+ await refreshAllSessionModels()
355
+ } catch (error) {
356
+ logger.error('Failed to refresh session models after custom-providers update:', error)
357
+ }
358
+ }
343
359
  sendJson(res, 200, { ok: true })
344
360
  return
345
361
  }
@@ -355,6 +371,13 @@ export async function handleStorageApi(req, res, url, context = { isLocalRequest
355
371
  delete data[key]
356
372
  return data
357
373
  })
374
+ if (store === 'custom-providers') {
375
+ try {
376
+ await refreshAllSessionModels()
377
+ } catch (error) {
378
+ logger.error('Failed to refresh session models after custom-providers key deletion:', error)
379
+ }
380
+ }
358
381
  sendJson(res, 200, { ok: true })
359
382
  return
360
383
  }
@@ -1,9 +1,20 @@
1
- let persistenceQueue = Promise.resolve()
1
+ // Keyed FIFO persistence queues. Each key owns one promise chain, so
2
+ // operations under the same key run strictly in order while operations under
3
+ // different keys run independently. The default (global) key serializes
4
+ // everything; per-session keys are used once SQLite session state is
5
+ // authoritative, where correctness relies on per-row revision CAS instead of
6
+ // global mutual exclusion. JSON-mirror mode and auto-archive keep the global
7
+ // key for their bucket-wide read-modify-write atomicity.
8
+ const persistenceQueues = new Map()
2
9
 
3
- export function withSessionPersistenceLock(operation) {
4
- const result = persistenceQueue
5
- .catch(() => undefined)
6
- .then(operation)
7
- persistenceQueue = result.then(() => undefined, () => undefined)
10
+ export function withSessionPersistenceLock(operation, key = '') {
11
+ const previous = persistenceQueues.get(key) ?? Promise.resolve()
12
+ const result = previous.catch(() => undefined).then(operation)
13
+ const tail = result.then(() => undefined, () => undefined)
14
+ persistenceQueues.set(key, tail)
15
+ tail.then(() => {
16
+ // Drop the queue entry once it drains so idle keys do not linger.
17
+ if (persistenceQueues.get(key) === tail) persistenceQueues.delete(key)
18
+ })
8
19
  return result
9
20
  }
@@ -86,8 +86,10 @@ export async function exportSessionStateForBackup(options = {}) {
86
86
  const integrity = repository.verifyIntegrity({ quickCheck: true })
87
87
  if (!integrity.ok) throw new Error('Session state integrity verification failed during backup export')
88
88
  const snapshot = repository.exportSnapshot()
89
- if (snapshot.count !== integrity.count || snapshot.digest !== integrity.digest) {
90
- throw new Error('Session state backup count/digest verification failed')
89
+ // The lightweight integrity result has no digest (null); the count
90
+ // cross-check plus exportSnapshot's own digest computation stay.
91
+ if (snapshot.count !== integrity.count) {
92
+ throw new Error('Session state backup count verification failed')
91
93
  }
92
94
  return {
93
95
  ...snapshotValues(snapshot),