@shawnstack/quickforge 1.7.3 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +1 -1
  2. package/bin/quickforge.mjs +0 -21
  3. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  4. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  5. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  6. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  7. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  8. package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +1639 -0
  9. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  10. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  11. package/dist/assets/TerminalDock-D4zJBJc4.js +2 -0
  12. package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
  13. package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  14. package/dist/assets/{arc-Bhg4c0ht.js → arc-w21O0TLn.js} +1 -1
  15. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  16. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  17. package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  18. package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  19. package/dist/assets/channel-Duu1CuH9.js +1 -0
  20. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  21. package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  22. package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  23. package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  24. package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  25. package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  26. package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  27. package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  28. package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  29. package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  30. package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  31. package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  32. package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  33. package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  34. package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  35. package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  36. package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  37. package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  38. package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  39. package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  40. package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  41. package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  42. package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  43. package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  44. package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  45. package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  46. package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  47. package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  48. package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  49. package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  50. package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  51. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  52. package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  53. package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  54. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  55. package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  56. package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  57. package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  58. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  59. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  60. package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  61. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  62. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  63. package/dist/assets/{dagre-CY3D84Cl.js → dagre-DmHb3Ctt.js} +1 -1
  64. package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  65. package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  70. package/dist/assets/dist-DLKC_odd.js +1 -0
  71. package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  72. package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  73. package/dist/assets/esm-Df4edDpj.js +2 -0
  74. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  75. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  76. package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  77. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  79. package/dist/assets/icons-CXPP1fFT.js +1 -0
  80. package/dist/assets/index-_ZhK6bvp.js +65 -0
  81. package/dist/assets/index-iVV2bP_f.css +3 -0
  82. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  83. package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  84. package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  85. package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  86. package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  87. package/dist/assets/{line-ikKkQiFp.js → line-B4DxX7tC.js} +1 -1
  88. package/dist/assets/{linear-GqPyxQkN.js → linear-U-nKUhyW.js} +1 -1
  89. package/dist/assets/{map-CJwjCvwr.js → map-BneTM-ri.js} +1 -1
  90. package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  91. package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  92. package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  93. package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DWpShXCs.js} +3 -3
  94. package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  95. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  96. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  97. package/dist/assets/{monaco-BTsVCDWS.js → monaco-D6OhXgjg.js} +1 -1
  98. package/dist/assets/{ordinal-CibZU3PM.js → ordinal-B2r5L1v4.js} +1 -1
  99. package/dist/assets/{otel-BNGM50C-.js → otel-BFnphzjt.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  102. package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-Cr2ee4a0.js} +2 -2
  103. package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-DV5jKjSg.js} +1 -1
  104. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  105. package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  106. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  107. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  108. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  109. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  110. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  111. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  113. package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-1KC5PyRx.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  118. package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-BThnIUBs.js} +3 -1
  119. package/dist/assets/{src-B4Tvg3gT.js → src-PYsDme0X.js} +1 -1
  120. package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  121. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  122. package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  123. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  124. package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  125. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  126. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  127. package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  128. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  129. package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  130. package/dist/assets/web-DoV8MSMn.js +1 -0
  131. package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  132. package/dist/index.html +13 -11
  133. package/dist/mobile-error.html +24 -0
  134. package/package.json +6 -1
  135. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  136. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  137. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  138. package/server/acp/server.mjs +50 -35
  139. package/server/agent-manager.mjs +343 -46
  140. package/server/agent-profile-files.mjs +8 -4
  141. package/server/agent-profile-schema.mjs +47 -23
  142. package/server/agent-profiles.mjs +1 -1
  143. package/server/ai-http-logger.mjs +156 -13
  144. package/server/ai-provider-options.mjs +3 -0
  145. package/server/channels/channel-logs.mjs +13 -0
  146. package/server/channels/event-relay.mjs +33 -0
  147. package/server/channels/process-channel.mjs +29 -0
  148. package/server/channels/providers/wechat.mjs +9 -3
  149. package/server/channels/registry.mjs +5 -1
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +102 -0
  152. package/server/cloud/config.mjs +53 -0
  153. package/server/cloud/credential-store.mjs +193 -0
  154. package/server/cloud/identity.mjs +355 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/runtime.mjs +129 -0
  158. package/server/cloud/service-config.mjs +77 -0
  159. package/server/index.mjs +63 -33
  160. package/server/lan-access-store.mjs +93 -3
  161. package/server/model-catalog.mjs +262 -0
  162. package/server/network-proxy.mjs +68 -3
  163. package/server/plugins/registry.mjs +93 -9
  164. package/server/public-api.mjs +6 -2
  165. package/server/routes/agent-profiles.mjs +43 -9
  166. package/server/routes/agent.mjs +48 -11
  167. package/server/routes/backup.mjs +7 -1
  168. package/server/routes/channels.mjs +47 -0
  169. package/server/routes/cloud.mjs +227 -0
  170. package/server/routes/lan-access.mjs +246 -21
  171. package/server/routes/models.mjs +13 -4
  172. package/server/routes/project.mjs +19 -1
  173. package/server/routes/scheduled-tasks.mjs +40 -10
  174. package/server/routes/shared-conversation.mjs +62 -58
  175. package/server/routes/shares.mjs +76 -1
  176. package/server/routes/static.mjs +26 -1
  177. package/server/routes/storage.mjs +24 -1
  178. package/server/routes/system.mjs +11 -0
  179. package/server/routes/terminal.mjs +7 -2
  180. package/server/routes/workspace.mjs +66 -13
  181. package/server/session-utils.mjs +2 -2
  182. package/server/share-store.mjs +168 -13
  183. package/server/skills.mjs +30 -3
  184. package/server/storage.mjs +7 -1
  185. package/server/subagents.mjs +4 -4
  186. package/server/terminal/terminal-manager.mjs +2 -4
  187. package/server/tools/definitions.mjs +3 -3
  188. package/server/tools/index.mjs +7 -5
  189. package/server/utils/network.mjs +9 -0
  190. package/server/utils/package-update.mjs +70 -47
  191. package/server/utils/process-env.mjs +7 -0
  192. package/server/utils/response.mjs +10 -3
  193. package/skills/skill-creator/LICENSE.txt +202 -0
  194. package/skills/skill-creator/SKILL.md +485 -0
  195. package/skills/skill-creator/UPSTREAM.md +6 -0
  196. package/skills/skill-creator/agents/analyzer.md +274 -0
  197. package/skills/skill-creator/agents/comparator.md +202 -0
  198. package/skills/skill-creator/agents/grader.md +223 -0
  199. package/skills/skill-creator/assets/eval_review.html +146 -0
  200. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  201. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  202. package/skills/skill-creator/references/schemas.md +430 -0
  203. package/skills/skill-creator/scripts/__init__.py +0 -0
  204. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  205. package/skills/skill-creator/scripts/generate_report.py +326 -0
  206. package/skills/skill-creator/scripts/improve_description.py +247 -0
  207. package/skills/skill-creator/scripts/package_skill.py +136 -0
  208. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  209. package/skills/skill-creator/scripts/run_eval.py +310 -0
  210. package/skills/skill-creator/scripts/run_loop.py +328 -0
  211. package/skills/skill-creator/scripts/utils.py +47 -0
  212. package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
  213. package/dist/assets/ChatPanelHost-wfGCaCzD.js +0 -288
  214. package/dist/assets/PluginsPage-NAGroBm3.js +0 -1
  215. package/dist/assets/ScheduledTasksPage-Bp0MNfQD.js +0 -2
  216. package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
  217. package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
  218. package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
  219. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
  220. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
  221. package/dist/assets/channel-Rz20dKTK.js +0 -1
  222. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
  223. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
  224. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
  225. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
  226. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
  227. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
  228. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
  229. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
  230. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
  231. package/dist/assets/icons-BP8YOS-Z.js +0 -1
  232. package/dist/assets/index-C6k5taeb.js +0 -66
  233. package/dist/assets/index-CzN8NSKC.css +0 -3
  234. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
  235. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
  236. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
  237. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
  238. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
  239. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
  240. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
  241. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
  242. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
  243. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
  244. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
  245. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
  246. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
  247. /package/dist/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
  248. /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  249. /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  250. /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
  251. /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-DyfqweF2.js} +0 -0
  252. /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/assets/{dist-DelZBZpK.js → dist-CLg13NtV.js} +0 -0
  254. /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-DJkmx0Cq.js} +0 -0
  255. /package/dist/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
  256. /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
  257. /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
  258. /package/dist/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
  259. /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-Uu2xAB4d.js} +0 -0
  260. /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
  261. /package/dist/assets/{xterm-CGiQzDiR.js → xterm-Bm_IbozX.js} +0 -0
  262. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  263. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  264. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  265. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  266. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  267. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -1,7 +1,8 @@
1
1
  import { promises as fs } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { spawn } from 'node:child_process'
4
- import { streamSimple } from '@earendil-works/pi-ai/compat'
4
+ import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
5
+ import { resolveModelBinding } from '../model-catalog.mjs'
5
6
  import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
6
7
  import { sendJson, readJsonBody } from '../utils/response.mjs'
7
8
  import { projectContextFromId } from '../project-config.mjs'
@@ -17,6 +18,7 @@ import {
17
18
 
18
19
  const MAX_PREVIEW_BYTES = 50 * 1024 * 1024
19
20
  const MAX_STATIC_PREVIEW_BYTES = 50 * 1024 * 1024
21
+ const DEFAULT_GIT_TIMEOUT_MS = 2 * 60 * 1000
20
22
  const MAX_GIT_LINE_COUNT_FILES = 100
21
23
  const MAX_GIT_LINE_COUNT_FILE_BYTES = 1024 * 1024
22
24
  const MAX_GIT_LINE_COUNT_TOTAL_BYTES = 10 * 1024 * 1024
@@ -29,7 +31,7 @@ const extensionLanguageMap = new Map([
29
31
  ['ts', 'typescript'], ['tsx', 'typescript'], ['js', 'javascript'], ['jsx', 'javascript'],
30
32
  ['mjs', 'javascript'], ['cjs', 'javascript'], ['json', 'json'], ['jsonc', 'json'],
31
33
  ['css', 'css'], ['scss', 'scss'], ['less', 'less'], ['html', 'html'], ['htm', 'html'],
32
- ['md', 'markdown'], ['markdown', 'markdown'], ['py', 'python'], ['rb', 'ruby'], ['go', 'go'],
34
+ ['md', 'markdown'], ['mdx', 'markdown'], ['markdown', 'markdown'], ['py', 'python'], ['rb', 'ruby'], ['go', 'go'],
33
35
  ['rs', 'rust'], ['java', 'java'], ['c', 'c'], ['h', 'c'], ['cpp', 'cpp'], ['cc', 'cpp'],
34
36
  ['cxx', 'cpp'], ['hpp', 'cpp'], ['cs', 'csharp'], ['php', 'php'], ['swift', 'swift'],
35
37
  ['kt', 'kotlin'], ['kts', 'kotlin'], ['sh', 'shell'], ['bash', 'shell'], ['zsh', 'shell'],
@@ -76,20 +78,65 @@ async function projectContextFromUrl(url) {
76
78
  return projectContextFromId(projectId)
77
79
  }
78
80
 
79
- function git(args, cwd, options = {}) {
81
+ function killProcessTree(child) {
82
+ if (!child?.pid) return
83
+ if (process.platform === 'win32') {
84
+ const killer = spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], {
85
+ stdio: 'ignore',
86
+ windowsHide: true,
87
+ })
88
+ killer.on('error', () => {
89
+ try { child.kill('SIGKILL') } catch { /* ignore */ }
90
+ })
91
+ return
92
+ }
93
+ try {
94
+ process.kill(-child.pid, 'SIGKILL')
95
+ } catch {
96
+ try { child.kill('SIGKILL') } catch { /* ignore */ }
97
+ }
98
+ }
99
+
100
+ export function git(args, cwd, options = {}) {
80
101
  return new Promise((resolve, reject) => {
102
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1, options.timeoutMs) : DEFAULT_GIT_TIMEOUT_MS
81
103
  const child = spawn('git', args, {
82
104
  cwd,
83
105
  shell: false,
84
106
  windowsHide: true,
85
- env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
107
+ detached: process.platform !== 'win32',
108
+ stdio: ['ignore', 'pipe', 'pipe'],
109
+ env: {
110
+ ...process.env,
111
+ GIT_OPTIONAL_LOCKS: '0',
112
+ GIT_TERMINAL_PROMPT: '0',
113
+ GCM_INTERACTIVE: 'Never',
114
+ },
86
115
  })
87
116
  const stdout = []
88
117
  const stderr = []
118
+ let settled = false
119
+ const finish = (callback) => {
120
+ if (settled) return
121
+ settled = true
122
+ clearTimeout(timeout)
123
+ callback()
124
+ }
125
+ const timeout = setTimeout(() => {
126
+ killProcessTree(child)
127
+ finish(() => {
128
+ const error = new Error(`git ${args.join(' ')} timed out after ${timeoutMs}ms`)
129
+ error.statusCode = 504
130
+ error.code = 'GIT_TIMEOUT'
131
+ reject(error)
132
+ })
133
+ }, timeoutMs)
134
+ timeout.unref?.()
135
+
89
136
  child.stdout.on('data', (chunk) => stdout.push(chunk))
90
137
  child.stderr.on('data', (chunk) => stderr.push(chunk))
91
- child.once('error', reject)
92
- child.once('close', (code) => {
138
+ child.once('error', (error) => finish(() => reject(error)))
139
+ child.once('close', (code) => finish(() => {
93
140
  const out = Buffer.concat(stdout)
94
141
  const err = Buffer.concat(stderr).toString('utf8').trim()
95
142
  if (code === 0 || options.allowFailure) {
@@ -99,7 +146,7 @@ function git(args, cwd, options = {}) {
99
146
  error.statusCode = 400
100
147
  reject(error)
101
148
  }
102
- })
149
+ }))
103
150
  })
104
151
  }
105
152
 
@@ -619,7 +666,7 @@ Unstaged diff:
619
666
  ${trimForPrompt(worktreeDiff)}`
620
667
 
621
668
  try {
622
- const stream = streamSimple(
669
+ const stream = streamSimpleWithAiHttpLogging(
623
670
  model,
624
671
  {
625
672
  systemPrompt,
@@ -1034,9 +1081,10 @@ async function contextFromGitBody(req) {
1034
1081
  return { context: await projectContextFromId(projectId), body }
1035
1082
  }
1036
1083
 
1037
- async function handleGitGenerateCommitMessage(req, res) {
1084
+ async function handleGitGenerateCommitMessage(req, res, requestContext = {}) {
1038
1085
  const { context, body } = await contextFromGitBody(req)
1039
- const message = await generateGitCommitMessage(context, body?.model, body?.thinkingLevel, Boolean(body?.includeUnstaged))
1086
+ const binding = await resolveModelBinding(body, { context: requestContext, legacySnapshot: body?.model })
1087
+ const message = await generateGitCommitMessage(context, binding.model, body?.thinkingLevel, Boolean(body?.includeUnstaged))
1040
1088
  sendJson(res, 200, { message })
1041
1089
  }
1042
1090
 
@@ -1100,7 +1148,7 @@ async function handleGitCommitAndPush(req, res) {
1100
1148
  sendJson(res, 200, await commitAndPushGitChanges(context, body?.message, Boolean(body?.includeUnstaged)))
1101
1149
  }
1102
1150
 
1103
- export async function handleWorkspaceApi(req, res, url) {
1151
+ export async function handleWorkspaceApi(req, res, url, requestContext = {}) {
1104
1152
  if (req.method === 'GET' && url.pathname === '/api/workspace/tree') {
1105
1153
  await handleWorkspaceTree(req, res, url)
1106
1154
  return
@@ -1118,6 +1166,11 @@ export async function handleWorkspaceApi(req, res, url) {
1118
1166
  return
1119
1167
  }
1120
1168
  if (req.method === 'POST' && url.pathname === '/api/workspace/open-external') {
1169
+ if (requestContext.isLocalRequest !== true) {
1170
+ const error = new Error('Opening applications is only allowed from this computer')
1171
+ error.statusCode = 403
1172
+ throw error
1173
+ }
1121
1174
  await handleWorkspaceOpenExternal(req, res)
1122
1175
  return
1123
1176
  }
@@ -1127,7 +1180,7 @@ export async function handleWorkspaceApi(req, res, url) {
1127
1180
  throw error
1128
1181
  }
1129
1182
 
1130
- export async function handleGitApi(req, res, url) {
1183
+ export async function handleGitApi(req, res, url, requestContext = {}) {
1131
1184
  if (req.method === 'GET' && url.pathname === '/api/git/status') {
1132
1185
  await handleGitStatus(req, res, url)
1133
1186
  return
@@ -1153,7 +1206,7 @@ export async function handleGitApi(req, res, url) {
1153
1206
  return
1154
1207
  }
1155
1208
  if (req.method === 'POST' && url.pathname === '/api/git/generate-commit-message') {
1156
- await handleGitGenerateCommitMessage(req, res)
1209
+ await handleGitGenerateCommitMessage(req, res, requestContext)
1157
1210
  return
1158
1211
  }
1159
1212
  if (req.method === 'POST' && url.pathname === '/api/git/stage') {
@@ -1,4 +1,4 @@
1
- import { streamSimple } from '@earendil-works/pi-ai/compat'
1
+ import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
2
2
  import { DEFAULT_AI_MAX_RETRIES } from './ai-provider-options.mjs'
3
3
  import { buildInstructionsPayload, projectContextFromId } from './project-config.mjs'
4
4
  import { composeSystemPrompt } from './system-prompt.mjs'
@@ -88,7 +88,7 @@ export async function generateAiTitle(messages, model, thinkingLevel, getApiKey)
88
88
 
89
89
  try {
90
90
  const apiKey = getApiKey ? await getApiKey(model.provider) : undefined
91
- const stream = streamSimple(
91
+ const stream = streamSimpleWithAiHttpLogging(
92
92
  model,
93
93
  {
94
94
  systemPrompt: '你是对话标题生成器。请用和用户相同的语言,根据对话主题生成 3 到 5 个词的短标题。只输出标题,不要解释,不要标点。',
@@ -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,9 +46,9 @@ 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
- systemPrompt: `You are Explore, the preferred read-only repository exploration subagent. Use read_file, grep_files, and safe read-only run_command calls to locate files, inspect project structure, search source, trace call chains, find related tests/docs/wiki pages, run diagnostics, identify patterns, assess impact, and answer focused questions before non-trivial implementation. You cannot modify files.`,
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
  },
53
53
  ]
54
54
 
@@ -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}
@@ -7,8 +7,7 @@ import { logger } from '../utils/logger.mjs'
7
7
  const require = createRequire(import.meta.url)
8
8
  const MAX_SESSIONS = Math.max(1, Number(process.env.QUICKFORGE_MAX_TERMINALS || 6))
9
9
  const TERMINAL_DISABLED = process.env.QUICKFORGE_TERMINAL === '0'
10
- const RECONNECT_GRACE_MS = Number(process.env.QUICKFORGE_TERMINAL_RECONNECT_MS || 5 * 60 * 1000)
11
- const IDLE_TIMEOUT_MS = Number(process.env.QUICKFORGE_TERMINAL_IDLE_MS || 30 * 60 * 1000)
10
+ const RECONNECT_GRACE_MS = Number(process.env.QUICKFORGE_TERMINAL_RECONNECT_MS || 30 * 60 * 1000)
12
11
 
13
12
  const sessions = new Map()
14
13
  let cleanupTimer = null
@@ -85,8 +84,7 @@ function scheduleCleanup() {
85
84
  const now = Date.now()
86
85
  for (const session of sessions.values()) {
87
86
  const disconnectedTooLong = session.clients.size === 0 && now - session.disconnectedAt > RECONNECT_GRACE_MS
88
- const idleTooLong = now - session.touchedAt > IDLE_TIMEOUT_MS
89
- if (session.exited || disconnectedTooLong || idleTooLong) {
87
+ if (session.exited || disconnectedTooLong) {
90
88
  destroyTerminalSession(session.id)
91
89
  }
92
90
  }
@@ -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',
@@ -129,7 +129,7 @@ export const workspaceTools = [
129
129
  {
130
130
  name: 'present_files',
131
131
  label: 'Present files',
132
- description: 'Show one or more AI-produced artifact files to the user. Use this after creating or editing user-facing files such as HTML pages, SVG/images, Markdown documents, or other deliverables. HTML files will be previewed directly in the artifact preview panel.',
132
+ description: 'Present one or more AI-produced artifact files only when the user is likely to benefit from inspecting the actual deliverable directly. Use it for visual outputs, reports, documents, generated assets, or when the user explicitly asks to view or review a file. Source code and configuration files should only be presented when they are themselves the requested deliverable or the user asks to inspect them. Do not present routine implementation changes, test files, supporting code, or large sets of modified files merely because they were edited. Prefer a small, relevant selection. HTML and supported images open in Browser; Markdown, code, configuration, and text files open in Reader. Unsupported files remain available in the artifact list.',
133
133
  parameters: Type.Object({
134
134
  files: Type.Array(Type.Union([
135
135
  Type.String({ description: 'File path relative to the workspace root.' }),
@@ -138,7 +138,7 @@ export const workspaceTools = [
138
138
  title: Type.Optional(Type.String({ description: 'Optional display title.' })),
139
139
  description: Type.Optional(Type.String({ description: 'Optional short description shown in artifact lists.' })),
140
140
  kind: Type.Optional(Type.String({ description: 'Optional file kind hint, such as html, image, markdown, or code.' })),
141
- preview: Type.Optional(Type.Boolean({ description: 'Whether this file should be opened as the default preview.' })),
141
+ preview: Type.Optional(Type.Boolean({ description: 'Whether this file may be opened automatically. Set false to list it without opening it.' })),
142
142
  }),
143
143
  ]), { description: 'Artifact files to present.' }),
144
144
  defaultPreview: Type.Optional(Type.String({ description: 'File path to open as the default preview when multiple files are presented.' })),
@@ -624,10 +624,12 @@ export async function toolReadSkillResource(params, context) {
624
624
  // --- present_files ---
625
625
  function inferPresentedFileKind(relativePath) {
626
626
  const lower = String(relativePath || '').toLowerCase()
627
+ const fileName = lower.replace(/\\/g, '/').split('/').pop() || lower
627
628
  if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'html'
628
- if (/\.(svg|png|jpe?g|webp|gif|bmp|ico)$/.test(lower)) return 'image'
629
- if (lower.endsWith('.md') || lower.endsWith('.mdx')) return 'markdown'
630
- if (/\.(ts|tsx|js|jsx|mjs|cjs|css|scss|less|json|jsonc|txt|xml|yml|yaml|toml|ini|py|rb|go|rs|java|c|h|cpp|hpp|cs|php|sh|bash|zsh|ps1)$/.test(lower)) return 'code'
629
+ if (/\.(svg|png|jpe?g|webp|gif|ico)$/.test(lower)) return 'image'
630
+ if (/\.(md|mdx|markdown)$/.test(lower)) return 'markdown'
631
+ if (fileName === 'dockerfile' || fileName.endsWith('.dockerfile') || fileName === 'makefile') return 'code'
632
+ if (/\.(ts|tsx|js|jsx|mjs|cjs|css|scss|less|json|jsonc|txt|csv|tsv|log|sql|xml|yml|yaml|toml|ini|py|rb|go|rs|java|swift|kt|kts|c|h|cpp|hpp|cs|php|sh|bash|zsh|ps1)$/.test(lower)) return 'code'
631
633
  return 'unknown'
632
634
  }
633
635
 
@@ -707,9 +709,9 @@ export async function toolPresentFiles(params, context) {
707
709
  }
708
710
 
709
711
  // --- run_command ---
710
- const DEFAULT_RUN_COMMAND_TIMEOUT_MS = 30 * 60 * 1000
712
+ const DEFAULT_RUN_COMMAND_TIMEOUT_MS = 60 * 60 * 1000
711
713
  const MIN_RUN_COMMAND_TIMEOUT_MS = 1000
712
- const MAX_RUN_COMMAND_TIMEOUT_MS = 30 * 60 * 1000
714
+ const MAX_RUN_COMMAND_TIMEOUT_MS = 60 * 60 * 1000
713
715
  const COMMAND_PREVIEW_LINES = 200
714
716
  const COMMAND_PREVIEW_TOTAL_CHARS = 10000
715
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:/, '')