@shawnstack/quickforge 1.7.4 → 1.7.6

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 (258) hide show
  1. package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
  2. package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
  3. package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
  4. package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
  5. package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
  6. package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
  7. package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
  8. package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
  9. package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
  10. package/dist/assets/WorkspaceInspector-BnfDLf9a.js +36 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  12. package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  16. package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  17. package/dist/assets/channel-Duu1CuH9.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  19. package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  20. package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  21. package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  22. package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  23. package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  24. package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  26. package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  27. package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  29. package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  30. package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  31. package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  32. package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  33. package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  34. package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  35. package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  36. package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  37. package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  38. package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  39. package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  41. package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  42. package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  43. package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  44. package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  45. package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  46. package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  47. package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  48. package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  49. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  50. package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  51. package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  52. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  53. package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  54. package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  55. package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  56. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  57. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  58. package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  59. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  60. package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  61. package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
  62. package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  68. package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
  69. package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  70. package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  71. package/dist/assets/{esm-oa69b5u4.js → esm-sdsK6gaB.js} +2 -2
  72. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  73. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  74. package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  75. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  76. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  77. package/dist/assets/icons-2D18YbSI.js +1 -0
  78. package/dist/assets/index-1ei6nhs-.css +3 -0
  79. package/dist/assets/index-BivLuZvX.js +65 -0
  80. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  81. package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  82. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  83. package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  84. package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  85. package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
  86. package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
  87. package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
  88. package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BEV4_TXC.js} +2 -2
  89. package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  90. package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  91. package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
  92. package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  93. package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
  94. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  95. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  96. package/dist/assets/{monaco-BVtCspYZ.js → monaco-DvXErxp2.js} +1 -1
  97. package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
  98. package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
  99. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  100. package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  101. package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
  102. package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  105. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  106. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  107. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  108. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  109. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  110. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  111. package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  112. package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-3UaA-C9W.js} +1 -1
  113. package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  114. package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  115. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  116. package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  117. package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BTJ7mzk_.js} +3 -3
  118. package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
  119. package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  120. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  121. package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  122. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  123. package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  124. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  125. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  126. package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  127. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  128. package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  129. package/dist/assets/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
  130. package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  131. package/dist/index.html +11 -10
  132. package/dist/sw.js +17 -0
  133. package/package.json +4 -1
  134. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  135. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  136. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  137. package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
  138. package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
  139. package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
  140. package/runtime-assets/agent/linux-x64/qf-agent +0 -0
  141. package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
  142. package/server/access-policy.mjs +11 -0
  143. package/server/acp/server.mjs +36 -26
  144. package/server/agent-manager.mjs +474 -71
  145. package/server/agent-profile-files.mjs +8 -4
  146. package/server/agent-profile-schema.mjs +47 -23
  147. package/server/agent-profiles.mjs +28 -9
  148. package/server/ai-http-logger.mjs +98 -12
  149. package/server/auto-compaction.mjs +35 -8
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +99 -0
  152. package/server/cloud/config.mjs +54 -0
  153. package/server/cloud/credential-store.mjs +200 -0
  154. package/server/cloud/identity.mjs +312 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/qf-agent-process.mjs +571 -0
  158. package/server/cloud/runtime.mjs +135 -0
  159. package/server/cloud/service-config.mjs +81 -0
  160. package/server/index.mjs +124 -48
  161. package/server/mcp/registry.mjs +8 -7
  162. package/server/message-converters.mjs +40 -19
  163. package/server/model-catalog.mjs +269 -0
  164. package/server/opencode-acp-agent.mjs +1151 -0
  165. package/server/plugins/registry.mjs +29 -5
  166. package/server/public-api.mjs +11 -5
  167. package/server/routes/agent-profiles.mjs +46 -9
  168. package/server/routes/agent.mjs +60 -11
  169. package/server/routes/backup.mjs +8 -1
  170. package/server/routes/cloud.mjs +246 -0
  171. package/server/routes/models.mjs +7 -1
  172. package/server/routes/scheduled-tasks.mjs +38 -8
  173. package/server/routes/shared-conversation.mjs +62 -58
  174. package/server/routes/shares.mjs +75 -1
  175. package/server/routes/storage.mjs +9 -1
  176. package/server/routes/system.mjs +7 -4
  177. package/server/routes/workspace.mjs +6 -4
  178. package/server/share-store.mjs +168 -13
  179. package/server/skills.mjs +30 -3
  180. package/server/storage.mjs +7 -1
  181. package/server/subagents.mjs +3 -3
  182. package/server/tools/definitions.mjs +1 -1
  183. package/server/tools/index.mjs +2 -2
  184. package/server/utils/network.mjs +9 -0
  185. package/server/utils/process-tree.mjs +78 -0
  186. package/server/utils/response.mjs +8 -1
  187. package/skills/skill-creator/LICENSE.txt +202 -0
  188. package/skills/skill-creator/SKILL.md +485 -0
  189. package/skills/skill-creator/UPSTREAM.md +6 -0
  190. package/skills/skill-creator/agents/analyzer.md +274 -0
  191. package/skills/skill-creator/agents/comparator.md +202 -0
  192. package/skills/skill-creator/agents/grader.md +223 -0
  193. package/skills/skill-creator/assets/eval_review.html +146 -0
  194. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  195. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  196. package/skills/skill-creator/references/schemas.md +430 -0
  197. package/skills/skill-creator/scripts/__init__.py +0 -0
  198. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  199. package/skills/skill-creator/scripts/generate_report.py +326 -0
  200. package/skills/skill-creator/scripts/improve_description.py +247 -0
  201. package/skills/skill-creator/scripts/package_skill.py +136 -0
  202. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  203. package/skills/skill-creator/scripts/run_eval.py +310 -0
  204. package/skills/skill-creator/scripts/run_loop.py +328 -0
  205. package/skills/skill-creator/scripts/utils.py +47 -0
  206. package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
  207. package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
  208. package/dist/assets/PluginsPage-D2862wul.js +0 -1
  209. package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
  210. package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +0 -1628
  211. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
  212. package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
  213. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
  214. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
  215. package/dist/assets/channel-DToA8d-4.js +0 -1
  216. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
  217. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
  218. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
  219. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
  220. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
  221. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
  222. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
  223. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
  224. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
  225. package/dist/assets/icons-cTkqD_Gi.js +0 -1
  226. package/dist/assets/index-B48prgLL.js +0 -65
  227. package/dist/assets/index-DQhwHHer.css +0 -3
  228. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
  229. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
  230. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
  231. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
  232. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
  233. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
  234. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
  235. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
  236. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
  237. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
  238. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
  239. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
  240. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
  241. /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  242. /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  243. /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  244. /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
  245. /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  246. /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
  247. /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
  248. /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  249. /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  250. /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
  251. /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
  252. /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
  253. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  254. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  255. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  256. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  257. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  258. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto'
2
+ import { EventEmitter } from 'node:events'
2
3
  import { promises as fs } from 'node:fs'
3
4
  import path from 'node:path'
4
5
  import { ensureStorage, storageDir } from './storage.mjs'
@@ -10,6 +11,25 @@ const SHARES_DIR = path.join(storageDir, 'shares')
10
11
  const SHARES_FILE = path.join(SHARES_DIR, 'conversation-shares.json')
11
12
  const writeQueueName = 'conversation-shares'
12
13
  const writeQueues = new Map()
14
+ const shareLifecycleEvents = new EventEmitter()
15
+ shareLifecycleEvents.setMaxListeners(0)
16
+
17
+ export function onConversationShareInvalidated(listener) {
18
+ shareLifecycleEvents.on('invalidated', listener)
19
+ return () => shareLifecycleEvents.removeListener('invalidated', listener)
20
+ }
21
+
22
+ function emitConversationShareInvalidated(shareId, reason) {
23
+ shareLifecycleEvents.emit('invalidated', { shareId, reason })
24
+ }
25
+
26
+ function clearShareTokens(record) {
27
+ record.authVersion = (record.authVersion || 1) + 1
28
+ record.tokens = []
29
+ record.tokenHash = undefined
30
+ record.tokenIssuedAt = undefined
31
+ record.tokenExpiresAt = undefined
32
+ }
13
33
 
14
34
  function enqueueWrite(queueName, operation) {
15
35
  const previous = writeQueues.get(queueName) || Promise.resolve()
@@ -64,6 +84,7 @@ function publicShareRecord(record) {
64
84
  accessCount: record.accessCount || 0,
65
85
  lastAccessedAt: record.lastAccessedAt,
66
86
  hasPassword: Boolean(record.passwordHash),
87
+ allowCloudUsage: record.allowCloudUsage === true,
67
88
  }
68
89
  }
69
90
 
@@ -195,6 +216,7 @@ export async function createConversationShare({
195
216
  permission,
196
217
  password,
197
218
  expiresAt,
219
+ allowCloudUsage = false,
198
220
  titleSnapshot,
199
221
  scope,
200
222
  projectId,
@@ -233,14 +255,13 @@ export async function createConversationShare({
233
255
  }
234
256
 
235
257
  for (const stale of existingRecords.filter((record) => record?.id !== existing?.id)) {
258
+ const newlySuperseded = !stale.supersededAt
236
259
  stale.supersededAt = stale.supersededAt || now
237
260
  stale.revokedAt = stale.revokedAt || now
238
261
  stale.updatedAt = now
239
- stale.tokens = []
240
- stale.tokenHash = undefined
241
- stale.tokenIssuedAt = undefined
242
- stale.tokenExpiresAt = undefined
262
+ clearShareTokens(stale)
243
263
  data[stale.id] = stale
264
+ if (newlySuperseded) emitConversationShareInvalidated(stale.id, 'superseded')
244
265
  }
245
266
 
246
267
  if (existing?.id) {
@@ -251,6 +272,7 @@ export async function createConversationShare({
251
272
  updatedAt: now,
252
273
  supersededAt: undefined,
253
274
  expiresAt: expiresAt || undefined,
275
+ allowCloudUsage: permission === 'operate' && allowCloudUsage === true,
254
276
  revokedAt: undefined,
255
277
  titleSnapshot: titleSnapshot || existing.titleSnapshot || 'New chat',
256
278
  scope: scope === 'project' ? 'project' : 'global',
@@ -264,19 +286,20 @@ export async function createConversationShare({
264
286
  tokenExpiresAt: existing.tokenExpiresAt,
265
287
  }
266
288
  if (passwordProvided) {
267
- record.authVersion = (existing.authVersion || 1) + 1
268
- record.tokens = []
269
- record.tokenHash = undefined
270
- record.tokenIssuedAt = undefined
271
- record.tokenExpiresAt = undefined
289
+ clearShareTokens(record)
272
290
  }
273
291
  if (passwordProvided && !passwordInfo.passwordHash) {
274
292
  record.passwordHash = undefined
275
293
  record.passwordSalt = undefined
276
294
  record.passwordVersion = undefined
277
295
  }
296
+ const lifecycleChanged = Boolean(existing.revokedAt)
297
+ || String(existing.expiresAt || '') !== String(record.expiresAt || '')
298
+ || existing.allowCloudUsage === true !== (record.allowCloudUsage === true)
299
+ || (passwordProvided && existing.authVersion !== record.authVersion)
278
300
  data[record.id] = record
279
301
  await writeShareStoreFile(data)
302
+ if (lifecycleChanged) emitConversationShareInvalidated(record.id, 'updated')
280
303
  return publicShareRecord(record)
281
304
  }
282
305
 
@@ -292,6 +315,7 @@ export async function createConversationShare({
292
315
  updatedAt: now,
293
316
  supersededAt: undefined,
294
317
  expiresAt: expiresAt || undefined,
318
+ allowCloudUsage: permission === 'operate' && allowCloudUsage === true,
295
319
  revokedAt: undefined,
296
320
  titleSnapshot: titleSnapshot || 'New chat',
297
321
  scope: scope === 'project' ? 'project' : 'global',
@@ -335,18 +359,149 @@ export async function revokeConversationShare(shareId) {
335
359
  error.statusCode = 404
336
360
  throw error
337
361
  }
362
+ const wasActive = !record.revokedAt
338
363
  record.revokedAt = record.revokedAt || new Date().toISOString()
339
364
  record.updatedAt = record.revokedAt
340
- record.tokens = []
341
- record.tokenHash = undefined
342
- record.tokenIssuedAt = undefined
343
- record.tokenExpiresAt = undefined
365
+ clearShareTokens(record)
366
+ data[shareId] = record
367
+ await writeShareStoreFile(data)
368
+ if (wasActive) emitConversationShareInvalidated(shareId, 'revoked')
369
+ return publicShareRecord(record)
370
+ })
371
+ }
372
+
373
+ export async function restoreConversationShare(shareId, expiresAt) {
374
+ assertSafeShareId(shareId)
375
+ if (expiresAt && (Number.isNaN(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.now())) {
376
+ const error = new Error('Expiration time must be in the future')
377
+ error.statusCode = 400
378
+ throw error
379
+ }
380
+ return enqueueWrite(writeQueueName, async () => {
381
+ const data = await readShareStoreFile()
382
+ const record = data[shareId]
383
+ if (!record) {
384
+ const error = new Error('Share not found')
385
+ error.statusCode = 404
386
+ throw error
387
+ }
388
+ if (record.supersededAt) {
389
+ const error = new Error('Superseded shares cannot be restored')
390
+ error.statusCode = 409
391
+ throw error
392
+ }
393
+ record.revokedAt = undefined
394
+ record.expiresAt = expiresAt || undefined
395
+ record.updatedAt = new Date().toISOString()
396
+ clearShareTokens(record)
344
397
  data[shareId] = record
345
398
  await writeShareStoreFile(data)
346
399
  return publicShareRecord(record)
347
400
  })
348
401
  }
349
402
 
403
+ export async function updateConversationShareExpiration(shareId, expiresAt) {
404
+ assertSafeShareId(shareId)
405
+ if (expiresAt && (Number.isNaN(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.now())) {
406
+ const error = new Error('Expiration time must be in the future')
407
+ error.statusCode = 400
408
+ throw error
409
+ }
410
+ return enqueueWrite(writeQueueName, async () => {
411
+ const data = await readShareStoreFile()
412
+ const record = data[shareId]
413
+ if (!record) {
414
+ const error = new Error('Share not found')
415
+ error.statusCode = 404
416
+ throw error
417
+ }
418
+ if (record.supersededAt) {
419
+ const error = new Error('Superseded shares cannot be updated')
420
+ error.statusCode = 409
421
+ throw error
422
+ }
423
+ assertShareActive(record)
424
+ record.expiresAt = expiresAt || undefined
425
+ record.updatedAt = new Date().toISOString()
426
+ data[shareId] = record
427
+ await writeShareStoreFile(data)
428
+ emitConversationShareInvalidated(shareId, 'updated')
429
+ return publicShareRecord(record)
430
+ })
431
+ }
432
+
433
+ export async function updateConversationShare(shareId, { permission, password, expiresAt, allowCloudUsage } = {}) {
434
+ assertSafeShareId(shareId)
435
+ const passwordProvided = typeof password === 'string'
436
+ const normalizedPassword = passwordProvided ? password.trim() : undefined
437
+ if (expiresAt && (Number.isNaN(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.now())) {
438
+ const error = new Error('Expiration time must be in the future')
439
+ error.statusCode = 400
440
+ throw error
441
+ }
442
+ const passwordInfo = passwordProvided ? await hashSharePassword(normalizedPassword) : {}
443
+ return enqueueWrite(writeQueueName, async () => {
444
+ const data = await readShareStoreFile()
445
+ const record = data[shareId]
446
+ if (!record) {
447
+ const error = new Error('Share not found')
448
+ error.statusCode = 404
449
+ throw error
450
+ }
451
+ if (record.supersededAt) {
452
+ const error = new Error('Superseded shares cannot be updated')
453
+ error.statusCode = 409
454
+ throw error
455
+ }
456
+ assertShareActive(record)
457
+ const nextPermission = permission === undefined ? record.permission : permission
458
+ assertValidPermission(nextPermission)
459
+ const willHavePassword = passwordProvided ? Boolean(normalizedPassword) : Boolean(record.passwordHash)
460
+ if (nextPermission === 'operate' && !willHavePassword) {
461
+ const error = new Error('Editable shares require a non-empty password')
462
+ error.statusCode = 400
463
+ throw error
464
+ }
465
+ const nextAllowCloudUsage = nextPermission === 'operate'
466
+ && (allowCloudUsage === undefined ? record.allowCloudUsage === true : allowCloudUsage === true)
467
+ const lifecycleChanged = nextPermission !== record.permission
468
+ || nextAllowCloudUsage !== (record.allowCloudUsage === true)
469
+ || (expiresAt !== undefined && String(record.expiresAt || '') !== String(expiresAt || ''))
470
+ || passwordProvided
471
+ if (passwordProvided) {
472
+ record.passwordHash = passwordInfo.passwordHash
473
+ record.passwordSalt = passwordInfo.passwordSalt
474
+ record.passwordVersion = passwordInfo.passwordVersion
475
+ clearShareTokens(record)
476
+ }
477
+ record.permission = nextPermission
478
+ record.allowCloudUsage = nextAllowCloudUsage
479
+ if (expiresAt !== undefined) record.expiresAt = expiresAt || undefined
480
+ record.updatedAt = new Date().toISOString()
481
+ data[shareId] = record
482
+ await writeShareStoreFile(data)
483
+ if (lifecycleChanged) emitConversationShareInvalidated(shareId, 'updated')
484
+ return publicShareRecord(record)
485
+ })
486
+ }
487
+
488
+ export async function deleteConversationShare(shareId) {
489
+ assertSafeShareId(shareId)
490
+ return enqueueWrite(writeQueueName, async () => {
491
+ const data = await readShareStoreFile()
492
+ const record = data[shareId]
493
+ if (!record) {
494
+ const error = new Error('Share not found')
495
+ error.statusCode = 404
496
+ throw error
497
+ }
498
+ delete data[shareId]
499
+ await writeShareStoreFile(data)
500
+ emitConversationShareInvalidated(shareId, 'deleted')
501
+ return publicShareRecord(record)
502
+ })
503
+ }
504
+
350
505
  export async function issueConversationShareToken(shareId) {
351
506
  assertSafeShareId(shareId)
352
507
  return enqueueWrite(writeQueueName, async () => {
package/server/skills.mjs CHANGED
@@ -1,16 +1,21 @@
1
1
  import { existsSync, promises as fs } from 'node:fs'
2
2
  import os from 'node:os'
3
3
  import path from 'node:path'
4
- import { dataDir } from './storage.mjs'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { atomicProjectConfigUpdate, configDir, dataDir } from './storage.mjs'
5
6
  import { getEnabledPluginSkillSources } from './plugins/registry.mjs'
6
7
  import { logger } from './utils/logger.mjs'
7
8
 
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+ const bundledSkillsDir = path.resolve(__dirname, '..', 'skills')
8
11
  const userSkillsDir = path.join(dataDir, 'skills')
9
12
  const sharedUserSkillsDir = path.join(os.homedir(), '.agents', 'skills')
10
13
  const claudeUserSkillsDir = path.join(os.homedir(), '.claude', 'skills')
11
14
  const opencodeUserSkillsDir = path.join(os.homedir(), '.opencode', 'skills')
15
+ const defaultSkillsMarkerFile = path.join(configDir, '.default-skills-v1')
16
+ const defaultGlobalSkillNames = ['skill-creator']
12
17
  const defaultEntry = 'SKILL.md'
13
- const resourceDirs = ['scripts', 'references', 'assets']
18
+ const resourceDirs = ['scripts', 'references', 'assets', 'agents', 'eval-viewer']
14
19
  const maxResourceFiles = 200
15
20
 
16
21
  function isValidSkillName(value) {
@@ -421,7 +426,7 @@ export function mergeSkills(...skillLists) {
421
426
  }
422
427
 
423
428
  export const skillSearchPaths = {
424
- global: searchDirsForList([claudeUserSkillsDir, opencodeUserSkillsDir, sharedUserSkillsDir, userSkillsDir]),
429
+ global: searchDirsForList([bundledSkillsDir, claudeUserSkillsDir, opencodeUserSkillsDir, sharedUserSkillsDir, userSkillsDir]),
425
430
  project: ['<project>/.claude/skills', '<project>/.opencode/skills', '<project>/.agents/skills', '<project>/.quickforge/skills'],
426
431
  }
427
432
 
@@ -444,6 +449,7 @@ async function loadPluginSkills(workspaceRoot) {
444
449
 
445
450
  export async function loadGlobalSkills() {
446
451
  return loadSkillsFromSources([
452
+ { dir: bundledSkillsDir, name: 'builtin' },
447
453
  { dir: claudeUserSkillsDir, name: 'user-claude' },
448
454
  { dir: opencodeUserSkillsDir, name: 'user-opencode' },
449
455
  { dir: sharedUserSkillsDir, name: 'user-shared' },
@@ -451,6 +457,27 @@ export async function loadGlobalSkills() {
451
457
  ])
452
458
  }
453
459
 
460
+ export async function ensureDefaultGlobalSkills() {
461
+ if (existsSync(defaultSkillsMarkerFile)) return false
462
+
463
+ const known = new Set((await loadGlobalSkills()).map((skill) => skill.name))
464
+ const defaults = defaultGlobalSkillNames.filter((name) => known.has(name))
465
+ if (defaults.length === 0) return false
466
+
467
+ await atomicProjectConfigUpdate((config) => {
468
+ config.globalSkills = normalizeSkillNames([
469
+ ...(Array.isArray(config.globalSkills) ? config.globalSkills : []),
470
+ ...defaults,
471
+ ])
472
+ return config
473
+ })
474
+ await fs.mkdir(configDir, { recursive: true })
475
+ await fs.writeFile(defaultSkillsMarkerFile, `${new Date().toISOString()}\n`, { flag: 'wx' }).catch((error) => {
476
+ if (error?.code !== 'EEXIST') throw error
477
+ })
478
+ return true
479
+ }
480
+
454
481
  export async function loadProjectSkills(workspaceRoot) {
455
482
  const pluginSkills = await loadPluginSkills(workspaceRoot)
456
483
  const projectSkills = await loadSkillsFromSources([
@@ -618,10 +618,16 @@ export async function atomicSessionValueUpdate(sessionId, updateFn) {
618
618
  export async function deleteSessionValue(sessionId) {
619
619
  return enqueueWrite('sessions', async () => {
620
620
  const bucket = await findSessionBucket(sessionId)
621
- if (!bucket) return
621
+ if (!bucket) {
622
+ const { deleteCloudChatIdempotencySession } = await import('./cloud/chat-idempotency-store.mjs')
623
+ await deleteCloudChatIdempotencySession(sessionId)
624
+ return
625
+ }
622
626
  await fs.rm(sessionDataFile(sessionId, bucket), { force: true })
623
627
  const { deleteSessionAssets } = await import('./session-assets.mjs')
624
628
  await deleteSessionAssets(bucket, sessionId)
629
+ const { deleteCloudChatIdempotencySession } = await import('./cloud/chat-idempotency-store.mjs')
630
+ await deleteCloudChatIdempotencySession(sessionId)
625
631
  sessionBucketIndex.delete(sessionId)
626
632
  })
627
633
  }
@@ -32,7 +32,7 @@ export const subagentDefinitions = [
32
32
  model: { mode: 'inherit' },
33
33
  lifecycle: 'builtin',
34
34
  allowFileMutations: true,
35
- maxRuntimeMs: 30 * 60 * 1000,
35
+ maxRuntimeMs: 60 * 60 * 1000,
36
36
  maxToolCalls: 300,
37
37
  systemPrompt: `You are General, a general-purpose subagent for bounded complex multi-step implementation tasks and broader independent work. You may inspect, edit, write files, and run commands using the built-in workspace tools when needed. You do not have MCP tools or Agent Skills. Prefer Explore for focused read-only repository discovery, source search, call-chain lookup, tests/docs discovery, and impact analysis. Make focused, minimal changes that satisfy the delegated task, and verify your changes when appropriate.`,
38
38
  },
@@ -46,7 +46,7 @@ export const subagentDefinitions = [
46
46
  model: { mode: 'inherit' },
47
47
  lifecycle: 'builtin',
48
48
  allowFileMutations: false,
49
- maxRuntimeMs: 30 * 60 * 1000,
49
+ maxRuntimeMs: 60 * 60 * 1000,
50
50
  maxToolCalls: 300,
51
51
  systemPrompt: `You are Explore, the preferred read-only repository research subagent. Investigate the delegated question using read_file, grep_files, and safe read-only run_command calls, and report only findings grounded in repository evidence. Do not design solutions or propose implementation, architecture, refactoring, or code changes. Cite relevant file paths, symbols, and line ranges when practical. Clearly separate verified facts, reasonable inferences, and unknowns, and never present an inference as a verified fact. Unless the parent Agent explicitly requests them, do not provide an implementation plan, recommendations, or suggested next steps. You cannot modify files.`,
52
52
  },
@@ -82,7 +82,7 @@ capabilityPolicy: ${definition.capabilityPolicy || 'review-only'}
82
82
  tools: ${definition.allowedTools.join(', ')}
83
83
  model:
84
84
  mode: inherit
85
- max-runtime-ms: ${definition.maxRuntimeMs || 30 * 60 * 1000}
85
+ max-runtime-ms: ${definition.maxRuntimeMs || 60 * 60 * 1000}
86
86
  max-tool-calls: ${definition.maxToolCalls || 300}
87
87
  ---
88
88
  ${body}
@@ -111,7 +111,7 @@ export const workspaceTools = [
111
111
  description: 'Run a shell command in the project bound to this chat. Use this for lint, build, tests, git status, and diagnostics.',
112
112
  parameters: Type.Object({
113
113
  command: Type.String({ description: 'Command to execute in the workspace.' }),
114
- timeoutMs: Type.Optional(Type.Number({ description: 'Command timeout in milliseconds. Defaults to 30 minutes and is clamped to the supported range.', default: 1800000 })),
114
+ timeoutMs: Type.Optional(Type.Number({ description: 'Command timeout in milliseconds. Defaults to 1 hour and is clamped to the supported range.', default: 3600000 })),
115
115
  description: Type.Optional(Type.String({ description: 'Short explanation of why this command is being run.' })),
116
116
  }),
117
117
  executionMode: 'sequential',
@@ -709,9 +709,9 @@ export async function toolPresentFiles(params, context) {
709
709
  }
710
710
 
711
711
  // --- run_command ---
712
- const DEFAULT_RUN_COMMAND_TIMEOUT_MS = 30 * 60 * 1000
712
+ const DEFAULT_RUN_COMMAND_TIMEOUT_MS = 60 * 60 * 1000
713
713
  const MIN_RUN_COMMAND_TIMEOUT_MS = 1000
714
- const MAX_RUN_COMMAND_TIMEOUT_MS = 30 * 60 * 1000
714
+ const MAX_RUN_COMMAND_TIMEOUT_MS = 60 * 60 * 1000
715
715
  const COMMAND_PREVIEW_LINES = 200
716
716
  const COMMAND_PREVIEW_TOTAL_CHARS = 10000
717
717
 
@@ -8,6 +8,15 @@ export function isPrivateIpv4(hostname) {
8
8
  return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168)
9
9
  }
10
10
 
11
+ export function isTailscaleAddress(address) {
12
+ if (!address) return false
13
+ const normalized = String(address).replace(/^::ffff:/i, '')
14
+ if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(normalized)) return false
15
+ const parts = normalized.split('.').map((part) => Number(part))
16
+ if (parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false
17
+ return parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127
18
+ }
19
+
11
20
  export function isLoopbackAddress(address) {
12
21
  if (!address) return false
13
22
  const normalized = address.replace(/^::ffff:/, '')
@@ -0,0 +1,78 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { once } from 'node:events'
3
+
4
+ const DEFAULT_GRACE_MS = 1000
5
+ const DEFAULT_FORCE_WAIT_MS = 1000
6
+
7
+ function isRunning(child) {
8
+ return Boolean(child?.pid) && child.exitCode == null && child.signalCode == null
9
+ }
10
+
11
+ function waitForExit(child, timeoutMs, setTimer = setTimeout, clearTimer = clearTimeout) {
12
+ if (!isRunning(child)) return Promise.resolve(true)
13
+ return new Promise((resolve) => {
14
+ let settled = false
15
+ const finish = (exited) => {
16
+ if (settled) return
17
+ settled = true
18
+ clearTimer(timer)
19
+ child.removeListener?.('exit', onExit)
20
+ resolve(exited)
21
+ }
22
+ const onExit = () => finish(true)
23
+ const timer = setTimer(() => finish(!isRunning(child)), Math.max(0, timeoutMs))
24
+ timer?.unref?.()
25
+ child.once?.('exit', onExit)
26
+ })
27
+ }
28
+
29
+ function runTaskkill(pid, force, spawnImpl = spawn) {
30
+ return new Promise((resolve) => {
31
+ const args = ['/PID', String(pid), '/T', ...(force ? ['/F'] : [])]
32
+ let command
33
+ try {
34
+ command = spawnImpl('taskkill.exe', args, { windowsHide: true, stdio: 'ignore' })
35
+ } catch {
36
+ resolve(false)
37
+ return
38
+ }
39
+ command.once?.('error', () => resolve(false))
40
+ command.once?.('exit', (code) => resolve(code === 0))
41
+ })
42
+ }
43
+
44
+ export async function signalProcessTree(child, signal, options = {}) {
45
+ if (!child?.pid) return false
46
+ const platform = options.platform ?? process.platform
47
+ if (platform === 'win32') {
48
+ return runTaskkill(child.pid, signal === 'SIGKILL', options.spawnImpl)
49
+ }
50
+
51
+ const killImpl = options.killImpl ?? process.kill
52
+ try {
53
+ killImpl(-child.pid, signal)
54
+ return true
55
+ } catch {
56
+ try {
57
+ return child.kill?.(signal) !== false
58
+ } catch {
59
+ return false
60
+ }
61
+ }
62
+ }
63
+
64
+ export async function terminateProcessTree(child, options = {}) {
65
+ if (!isRunning(child)) return
66
+ const graceMs = options.graceMs ?? DEFAULT_GRACE_MS
67
+ const forceWaitMs = options.forceWaitMs ?? DEFAULT_FORCE_WAIT_MS
68
+ const timerOptions = { setTimer: options.setTimer, clearTimer: options.clearTimer }
69
+
70
+ await signalProcessTree(child, 'SIGTERM', options)
71
+ if (await waitForExit(child, graceMs, timerOptions.setTimer, timerOptions.clearTimer)) return
72
+
73
+ await signalProcessTree(child, 'SIGKILL', options)
74
+ await Promise.race([
75
+ once(child, 'exit').catch(() => {}),
76
+ waitForExit(child, forceWaitMs, timerOptions.setTimer, timerOptions.clearTimer),
77
+ ])
78
+ }
@@ -15,7 +15,14 @@ export function sendError(res, error) {
15
15
  return
16
16
  }
17
17
  const status = error?.statusCode || 500
18
- sendJson(res, status, { error: error?.message || 'Internal server error' })
18
+ const payload = { error: error?.message || 'Internal server error' }
19
+ const code = typeof error?.errorCode === 'string' && error.errorCode
20
+ ? error.errorCode
21
+ : typeof error?.code === 'string' && error.code
22
+ ? error.code
23
+ : undefined
24
+ if (code) payload.code = code
25
+ sendJson(res, status, payload)
19
26
  }
20
27
 
21
28
  export async function readJsonBody(req, maxBodyBytes = DEFAULT_MAX_BODY_BYTES) {