@shawnstack/quickforge 1.7.3 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/quickforge.mjs +0 -21
- package/dist/assets/AgentProfilesPage-B84w-r9S.js +1 -0
- package/dist/assets/{ChatPanelHost-wfGCaCzD.js → ChatPanelHost-CyO8fEoj.js} +91 -91
- package/dist/assets/{PluginsPage-NAGroBm3.js → PluginsPage-D2862wul.js} +1 -1
- package/dist/assets/{ScheduledTasksPage-Bp0MNfQD.js → ScheduledTasksPage-GqbiJDPX.js} +2 -2
- package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +1628 -0
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +1 -0
- package/dist/assets/TerminalDock-Dgg-_ZmU.js +2 -0
- package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +13 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-Dy7veffk.js} +1 -1
- package/dist/assets/{arc-Bhg4c0ht.js → arc-Bo3p2lGy.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-BiuaAOFa.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-BTcGfhK2.js} +1 -1
- package/dist/assets/channel-DToA8d-4.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-B9PuF_RF.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-3zGItfIM.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-DQ66qlmt.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-CLqTO-VF.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-Csxv1jr6.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-BCQttpBr.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-DEWJGSTb.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-DNorm9MM.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-DgvY_BOT.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-DMvn9IPs.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-Dq2duUgr.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-BovYyc4Y.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BMDOpuYN.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-6eP2UPOf.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-D4q-wTSy.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-XjIcuzH7.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-YBXbanq9.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-CcN3uNXF.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-Cwg6AUAt.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-NNspcGIU.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-pMJ_jMos.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-CJy-3Ldv.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-D7cLHHOA.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-D5YBi1Pi.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-DYmqQdIf.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Bfcx00X0.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-oXzadpZY.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-Dt3-8yUh.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-B55eSvpx.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-Btk4JwCt.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-DgaSiVOY.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-rRDZ_Tta.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-BnFxj5o4.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CrnlPlmJ.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-BYTiATIH.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-BORKJRwF.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-BXKeaSl8.js} +1 -1
- package/dist/assets/{dagre-CY3D84Cl.js → dagre-CrA0XVwJ.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-ouq_lA1a.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-BxR5kUiQ.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-DWonk1sL.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-fmV_wV5e.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DvOSRRiV.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-Bp-xb3uS.js} +1 -1
- package/dist/assets/dist-DLKC_odd.js +1 -0
- package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-BTlnwneA.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-C3fFWGCm.js} +1 -1
- package/dist/assets/esm-oa69b5u4.js +2 -0
- package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-DzAiojNi.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-Cv57oNEA.js} +1 -1
- package/dist/assets/icons-cTkqD_Gi.js +1 -0
- package/dist/assets/index-B48prgLL.js +65 -0
- package/dist/assets/index-DQhwHHer.css +3 -0
- package/dist/assets/info-DKCQHKI2-BsLOivgq.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B5Z_O3Ro.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKTK_TKV.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-Cm9k4ywW.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-DzNteLwx.js} +1 -1
- package/dist/assets/{line-ikKkQiFp.js → line-BG78c_WJ.js} +1 -1
- package/dist/assets/{linear-GqPyxQkN.js → linear-1X20_EGu.js} +1 -1
- package/dist/assets/{map-CJwjCvwr.js → map-CCI8WZkS.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-GXXD9K0U.js} +2 -2
- package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-Bb8HI_OS.js} +2 -2
- package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-PIaNiXAz.js} +2 -2
- package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DUHOIwL5.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-Tnbszo7b.js} +1 -1
- package/dist/assets/{monaco-BTsVCDWS.js → monaco-BVtCspYZ.js} +1 -1
- package/dist/assets/{ordinal-CibZU3PM.js → ordinal-v4prQcuq.js} +1 -1
- package/dist/assets/{otel-BNGM50C-.js → otel-D1LswXpM.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CX824Kd0.js} +1 -1
- package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-DAJlO9ZB.js} +2 -2
- package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-B-RJSxjR.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-BT04VWPQ.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-BvOZxkBr.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-DnTU--BV.js} +1 -1
- package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-eWH0QhNU.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE--MZX_yEv.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB-SEZkHhYn.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-BhC2OHiz.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-Ci5LeToV.js} +1 -1
- package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-DDh1h8eH.js} +3 -1
- package/dist/assets/{src-B4Tvg3gT.js → src-CovulcRC.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-DKK5tFT4.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-DxxGCk67.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-BPXB3Sue.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CxcBCKye.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-DEL6Yes5.js} +1 -1
- package/dist/assets/web-RgX7H-OH.js +1 -0
- package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-N8cPbcjC.js} +1 -1
- package/dist/index.html +12 -11
- package/dist/mobile-error.html +24 -0
- package/package.json +4 -1
- package/server/acp/server.mjs +14 -9
- package/server/agent-manager.mjs +215 -20
- package/server/ai-http-logger.mjs +68 -11
- package/server/ai-provider-options.mjs +3 -0
- package/server/channels/channel-logs.mjs +13 -0
- package/server/channels/event-relay.mjs +33 -0
- package/server/channels/process-channel.mjs +29 -0
- package/server/channels/providers/wechat.mjs +9 -3
- package/server/channels/registry.mjs +5 -1
- package/server/index.mjs +36 -23
- package/server/lan-access-store.mjs +93 -3
- package/server/network-proxy.mjs +68 -3
- package/server/plugins/registry.mjs +64 -4
- package/server/public-api.mjs +6 -2
- package/server/routes/agent-profiles.mjs +2 -2
- package/server/routes/agent.mjs +28 -0
- package/server/routes/channels.mjs +47 -0
- package/server/routes/lan-access.mjs +246 -21
- package/server/routes/models.mjs +6 -3
- package/server/routes/project.mjs +19 -1
- package/server/routes/scheduled-tasks.mjs +2 -2
- package/server/routes/static.mjs +26 -1
- package/server/routes/system.mjs +11 -0
- package/server/routes/terminal.mjs +7 -2
- package/server/routes/workspace.mjs +60 -9
- package/server/session-utils.mjs +2 -2
- package/server/subagents.mjs +1 -1
- package/server/terminal/terminal-manager.mjs +2 -4
- package/server/tools/definitions.mjs +2 -2
- package/server/tools/index.mjs +5 -3
- package/server/utils/package-update.mjs +70 -47
- package/server/utils/process-env.mjs +7 -0
- package/server/utils/response.mjs +2 -2
- package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
- package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
- package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
- package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
- package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
- package/dist/assets/channel-Rz20dKTK.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
- package/dist/assets/icons-BP8YOS-Z.js +0 -1
- package/dist/assets/index-C6k5taeb.js +0 -66
- package/dist/assets/index-CzN8NSKC.css +0 -3
- package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
- package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
- package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
- package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
- /package/dist/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-s7Qq-R3i.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-BgISNSiw.js} +0 -0
- /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
- /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-iekjSZ2n.js} +0 -0
- /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
- /package/dist/assets/{dist-DelZBZpK.js → dist-zzfqk5hx.js} +0 -0
- /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-CIz_CfxW.js} +0 -0
- /package/dist/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
- /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
- /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
- /package/dist/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
- /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-C8hMT5W6.js} +0 -0
- /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-BMOtDaaS.js} +0 -0
- /package/dist/assets/{xterm-CGiQzDiR.js → xterm-BFzZ1FH4.js} +0 -0
package/server/routes/agent.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
|
+
import { logger } from '../utils/logger.mjs'
|
|
2
3
|
import {
|
|
3
4
|
createAgent,
|
|
4
5
|
runPrompt,
|
|
@@ -96,6 +97,30 @@ export async function handleAgentApi(req, res, url) {
|
|
|
96
97
|
return
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// POST /api/agents/:sessionId/restore — restore once and return the authoritative state
|
|
101
|
+
if (req.method === 'POST' && subPath === 'restore') {
|
|
102
|
+
const startedAt = performance.now()
|
|
103
|
+
const session = await restoreAgent(sessionId)
|
|
104
|
+
if (!session) {
|
|
105
|
+
const error = new Error('Session not found')
|
|
106
|
+
error.statusCode = 404
|
|
107
|
+
throw error
|
|
108
|
+
}
|
|
109
|
+
const state = getSessionState(sessionId)
|
|
110
|
+
if (!state) {
|
|
111
|
+
const error = new Error('Session not found')
|
|
112
|
+
error.statusCode = 404
|
|
113
|
+
throw error
|
|
114
|
+
}
|
|
115
|
+
logger.debug(`Restored session ${sessionId} for client`, {
|
|
116
|
+
sessionId,
|
|
117
|
+
messageCount: state.messages?.length ?? 0,
|
|
118
|
+
durationMs: Math.round((performance.now() - startedAt) * 10) / 10,
|
|
119
|
+
})
|
|
120
|
+
sendJson(res, 200, state)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
99
124
|
// GET /api/agents/:sessionId/state — get session state
|
|
100
125
|
if (req.method === 'GET' && subPath === 'state') {
|
|
101
126
|
let state = getSessionState(sessionId)
|
|
@@ -141,6 +166,9 @@ export async function handleAgentApi(req, res, url) {
|
|
|
141
166
|
status: session.status,
|
|
142
167
|
scope: session.scope,
|
|
143
168
|
title: session.title,
|
|
169
|
+
source: session.source || undefined,
|
|
170
|
+
channelId: session.channelId || undefined,
|
|
171
|
+
channelName: session.channelName || undefined,
|
|
144
172
|
accessMode: session.accessMode,
|
|
145
173
|
yoloMode: session.yoloMode,
|
|
146
174
|
})
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs'
|
|
2
|
+
import { logger } from '../utils/logger.mjs'
|
|
1
3
|
import { sendJson, decodeSegment, readJsonBody } from '../utils/response.mjs'
|
|
4
|
+
import { channelLogsDirectory } from '../channels/channel-logs.mjs'
|
|
5
|
+
import { openPathInFileManager } from '../utils/platform.mjs'
|
|
2
6
|
import {
|
|
3
7
|
channelEvents,
|
|
4
8
|
getChannelStatus,
|
|
@@ -25,6 +29,24 @@ function assertActionHeader(req) {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
function assertEventHeader(req) {
|
|
33
|
+
if (req.headers['x-quickforge-action'] !== 'channel-event') {
|
|
34
|
+
const error = new Error('Forbidden event')
|
|
35
|
+
error.statusCode = 403
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readChannelEvent(req) {
|
|
41
|
+
const event = await readJsonBody(req, 256 * 1024)
|
|
42
|
+
if (!event || event.type !== 'sessions-changed' || typeof event.sessionId !== 'string' || !event.sessionId.trim()) {
|
|
43
|
+
const error = new Error('Invalid channel event')
|
|
44
|
+
error.statusCode = 400
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
47
|
+
return event
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
async function readActionOptions(req) {
|
|
29
51
|
if (!['POST', 'PUT', 'PATCH'].includes(req.method || '')) return {}
|
|
30
52
|
const contentType = String(req.headers['content-type'] || '')
|
|
@@ -46,6 +68,15 @@ export async function handleChannelsApi(req, res, url, context = {}) {
|
|
|
46
68
|
return
|
|
47
69
|
}
|
|
48
70
|
|
|
71
|
+
if (req.method === 'POST' && pathname === '/api/channels/events') {
|
|
72
|
+
assertLocal(context)
|
|
73
|
+
assertEventHeader(req)
|
|
74
|
+
const event = await readChannelEvent(req)
|
|
75
|
+
channelEvents.emit('channel_event', event)
|
|
76
|
+
sendJson(res, 202, { ok: true })
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
49
80
|
if (parts.length < 3 || parts[0] !== 'api' || parts[1] !== 'channels') {
|
|
50
81
|
const error = new Error('Not found')
|
|
51
82
|
error.statusCode = 404
|
|
@@ -67,6 +98,22 @@ export async function handleChannelsApi(req, res, url, context = {}) {
|
|
|
67
98
|
|
|
68
99
|
assertLocal(context)
|
|
69
100
|
|
|
101
|
+
if (req.method === 'POST' && subPath.length === 1 && subPath[0] === 'open-logs') {
|
|
102
|
+
assertActionHeader(req)
|
|
103
|
+
getChannelStatus(channelId)
|
|
104
|
+
if (!context.logsDir) {
|
|
105
|
+
const error = new Error('Channel log directory is unavailable')
|
|
106
|
+
error.statusCode = 500
|
|
107
|
+
throw error
|
|
108
|
+
}
|
|
109
|
+
const directory = channelLogsDirectory(context.logsDir, channelId)
|
|
110
|
+
await fs.mkdir(directory, { recursive: true })
|
|
111
|
+
await (context.openPathInFileManager ?? openPathInFileManager)(directory)
|
|
112
|
+
logger.info('Opened channel log directory.', { channelId })
|
|
113
|
+
sendJson(res, 200, { ok: true })
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
70
117
|
if (req.method === 'POST' && subPath[0] === 'start') {
|
|
71
118
|
assertActionHeader(req)
|
|
72
119
|
sendJson(res, 202, await startChannel(channelId, await readActionOptions(req)))
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
2
2
|
import { getLanUrls } from '../utils/network.mjs'
|
|
3
3
|
import { logger } from '../utils/logger.mjs'
|
|
4
|
+
import { parseCookies } from '../share-store.mjs'
|
|
4
5
|
import {
|
|
5
6
|
issueLanAccessToken,
|
|
6
7
|
lanAccessCookieName,
|
|
7
8
|
readLanAccessStatus,
|
|
9
|
+
revokeLanAccessToken,
|
|
10
|
+
revokeLanAccessTokenById,
|
|
8
11
|
revokeLanAccessTokens,
|
|
9
12
|
updateLanAccessSettings,
|
|
10
13
|
} from '../lan-access-store.mjs'
|
|
@@ -102,36 +105,242 @@ export function renderLanUnlockPage(res) {
|
|
|
102
105
|
<head>
|
|
103
106
|
<meta charset="utf-8" />
|
|
104
107
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
108
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
|
|
109
|
+
<meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" />
|
|
110
|
+
<link rel="icon" href="/favicon.svg" />
|
|
105
111
|
<title>QuickForge 局域网访问</title>
|
|
106
112
|
<style>
|
|
107
|
-
:root {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
:root {
|
|
114
|
+
color-scheme: light dark;
|
|
115
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
116
|
+
"Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
117
|
+
--background: #ffffff;
|
|
118
|
+
--foreground: #171717;
|
|
119
|
+
--muted-foreground: #737373;
|
|
120
|
+
--border: #e5e5e5;
|
|
121
|
+
--input-border: #dedede;
|
|
122
|
+
--surface: #ffffff;
|
|
123
|
+
--surface-muted: #fafafa;
|
|
124
|
+
--primary: #171717;
|
|
125
|
+
--primary-hover: #292929;
|
|
126
|
+
--primary-foreground: #fafafa;
|
|
127
|
+
--danger: #dc2626;
|
|
128
|
+
--shadow: 0 10px 26px -18px rgba(15, 23, 42, .48);
|
|
129
|
+
}
|
|
130
|
+
* { box-sizing: border-box; }
|
|
131
|
+
html, body { margin: 0; min-width: 0; min-height: 100%; }
|
|
132
|
+
body {
|
|
133
|
+
min-height: 100vh;
|
|
134
|
+
min-height: 100dvh;
|
|
135
|
+
display: grid;
|
|
136
|
+
place-items: center;
|
|
137
|
+
padding: 32px 20px;
|
|
138
|
+
background: var(--background);
|
|
139
|
+
color: var(--foreground);
|
|
140
|
+
-webkit-font-smoothing: antialiased;
|
|
141
|
+
}
|
|
142
|
+
main { width: min(100%, 440px); }
|
|
143
|
+
.intro { display: flex; align-items: center; gap: 13px; margin-bottom: 30px; }
|
|
144
|
+
.brand-mark {
|
|
145
|
+
display: grid;
|
|
146
|
+
width: 46px;
|
|
147
|
+
height: 46px;
|
|
148
|
+
flex: 0 0 auto;
|
|
149
|
+
place-items: center;
|
|
150
|
+
border: 1px solid var(--border);
|
|
151
|
+
border-radius: 13px;
|
|
152
|
+
background: var(--surface);
|
|
153
|
+
box-shadow: var(--shadow);
|
|
154
|
+
}
|
|
155
|
+
.brand-mark img { width: 29px; height: 29px; }
|
|
156
|
+
.eyebrow {
|
|
157
|
+
margin: 0 0 2px;
|
|
158
|
+
color: var(--muted-foreground);
|
|
159
|
+
font-size: 11px;
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
letter-spacing: .1em;
|
|
162
|
+
}
|
|
163
|
+
h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: -.02em; line-height: 1.35; }
|
|
164
|
+
.subtitle { margin: 3px 0 0; color: var(--muted-foreground); font-size: 14px; line-height: 1.5; }
|
|
165
|
+
form {
|
|
166
|
+
padding: 22px;
|
|
167
|
+
border: 1px solid var(--border);
|
|
168
|
+
border-radius: 16px;
|
|
169
|
+
background: var(--surface);
|
|
170
|
+
box-shadow: var(--shadow);
|
|
171
|
+
}
|
|
172
|
+
label { display: block; margin-bottom: 9px; font-size: 14px; font-weight: 500; }
|
|
173
|
+
.input-wrap { position: relative; }
|
|
174
|
+
.input-icon {
|
|
175
|
+
position: absolute;
|
|
176
|
+
top: 50%;
|
|
177
|
+
left: 13px;
|
|
178
|
+
width: 17px;
|
|
179
|
+
height: 17px;
|
|
180
|
+
color: var(--muted-foreground);
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
transform: translateY(-50%);
|
|
183
|
+
}
|
|
184
|
+
input {
|
|
185
|
+
width: 100%;
|
|
186
|
+
height: 44px;
|
|
187
|
+
border: 1px solid var(--input-border);
|
|
188
|
+
border-radius: 12px;
|
|
189
|
+
outline: none;
|
|
190
|
+
background: var(--surface);
|
|
191
|
+
color: var(--foreground);
|
|
192
|
+
padding: 0 13px 0 40px;
|
|
193
|
+
font: inherit;
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
transition: border-color 150ms ease, box-shadow 150ms ease;
|
|
196
|
+
}
|
|
197
|
+
input::placeholder { color: var(--muted-foreground); opacity: .7; }
|
|
198
|
+
input:hover { border-color: color-mix(in srgb, var(--foreground) 24%, var(--border)); }
|
|
199
|
+
input:focus {
|
|
200
|
+
border-color: color-mix(in srgb, var(--foreground) 30%, var(--border));
|
|
201
|
+
box-shadow: var(--shadow);
|
|
202
|
+
}
|
|
203
|
+
input[aria-invalid="true"] { border-color: color-mix(in srgb, var(--danger) 60%, var(--border)); }
|
|
204
|
+
button {
|
|
205
|
+
width: 100%;
|
|
206
|
+
height: 44px;
|
|
207
|
+
margin-top: 18px;
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
gap: 8px;
|
|
212
|
+
border: 0;
|
|
213
|
+
border-radius: 12px;
|
|
214
|
+
background: var(--primary);
|
|
215
|
+
color: var(--primary-foreground);
|
|
216
|
+
font: inherit;
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
font-weight: 500;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
transition: background-color 150ms ease, opacity 150ms ease;
|
|
221
|
+
}
|
|
222
|
+
button:hover:not(:disabled) { background: var(--primary-hover); }
|
|
223
|
+
button:active:not(:disabled) { opacity: .88; }
|
|
224
|
+
button:focus-visible { outline: 2px solid color-mix(in srgb, var(--foreground) 28%, transparent); outline-offset: 3px; }
|
|
225
|
+
button:disabled { cursor: default; opacity: .62; }
|
|
226
|
+
.button-icon { width: 16px; height: 16px; }
|
|
227
|
+
.spinner { display: none; width: 15px; height: 15px; animation: spin .8s linear infinite; }
|
|
228
|
+
button.is-loading .button-icon { display: none; }
|
|
229
|
+
button.is-loading .spinner { display: block; }
|
|
230
|
+
.error {
|
|
231
|
+
min-height: 20px;
|
|
232
|
+
margin: 10px 0 -2px;
|
|
233
|
+
color: var(--danger);
|
|
234
|
+
font-size: 13px;
|
|
235
|
+
line-height: 20px;
|
|
236
|
+
}
|
|
237
|
+
.note {
|
|
238
|
+
display: flex;
|
|
239
|
+
gap: 9px;
|
|
240
|
+
margin: 17px 3px 0;
|
|
241
|
+
color: var(--muted-foreground);
|
|
242
|
+
font-size: 12px;
|
|
243
|
+
line-height: 1.65;
|
|
244
|
+
}
|
|
245
|
+
.note svg { width: 15px; height: 15px; flex: 0 0 auto; margin-top: 2px; }
|
|
246
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
247
|
+
@media (prefers-color-scheme: dark) {
|
|
248
|
+
:root {
|
|
249
|
+
--background: #171717;
|
|
250
|
+
--foreground: #fafafa;
|
|
251
|
+
--muted-foreground: #a3a3a3;
|
|
252
|
+
--border: #353535;
|
|
253
|
+
--input-border: #404040;
|
|
254
|
+
--surface: #1d1d1d;
|
|
255
|
+
--surface-muted: #262626;
|
|
256
|
+
--primary: #e5e5e5;
|
|
257
|
+
--primary-hover: #fafafa;
|
|
258
|
+
--primary-foreground: #171717;
|
|
259
|
+
--danger: #f87171;
|
|
260
|
+
--shadow: 0 10px 26px -18px rgba(0, 0, 0, .9);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
@media (max-width: 480px) {
|
|
264
|
+
body { place-items: start center; padding-top: max(28px, 8vh); }
|
|
265
|
+
.intro { margin-bottom: 24px; }
|
|
266
|
+
form { padding: 20px; }
|
|
267
|
+
}
|
|
268
|
+
@media (prefers-reduced-motion: reduce) {
|
|
269
|
+
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
|
|
270
|
+
}
|
|
117
271
|
</style>
|
|
118
272
|
</head>
|
|
119
273
|
<body>
|
|
120
274
|
<main>
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
275
|
+
<header class="intro">
|
|
276
|
+
<div class="brand-mark" aria-hidden="true"><img src="/favicon.svg" alt="" /></div>
|
|
277
|
+
<div>
|
|
278
|
+
<p class="eyebrow">QUICKFORGE</p>
|
|
279
|
+
<h1>验证局域网访问</h1>
|
|
280
|
+
<p class="subtitle">输入主机端设置的访问密码,继续进入工作区。</p>
|
|
281
|
+
</div>
|
|
282
|
+
</header>
|
|
283
|
+
|
|
284
|
+
<form id="unlock-form">
|
|
285
|
+
<label for="password">访问密码</label>
|
|
286
|
+
<div class="input-wrap">
|
|
287
|
+
<svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
288
|
+
<rect x="4.5" y="10" width="15" height="10" rx="2.5"></rect>
|
|
289
|
+
<path d="M8 10V7.5a4 4 0 0 1 8 0V10"></path>
|
|
290
|
+
</svg>
|
|
291
|
+
<input
|
|
292
|
+
id="password"
|
|
293
|
+
type="password"
|
|
294
|
+
autocomplete="current-password"
|
|
295
|
+
placeholder="请输入访问密码"
|
|
296
|
+
aria-describedby="error"
|
|
297
|
+
aria-invalid="false"
|
|
298
|
+
autofocus
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
<button id="submit" type="submit">
|
|
302
|
+
<svg class="spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
303
|
+
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-opacity=".25" stroke-width="3"></circle>
|
|
304
|
+
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path>
|
|
305
|
+
</svg>
|
|
306
|
+
<span id="button-label">进入 QuickForge</span>
|
|
307
|
+
<svg class="button-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
308
|
+
<path d="M5 12h14M13 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
309
|
+
</svg>
|
|
310
|
+
</button>
|
|
311
|
+
<p id="error" class="error" role="alert" aria-live="polite"></p>
|
|
312
|
+
</form>
|
|
313
|
+
|
|
314
|
+
<p class="note">
|
|
315
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
316
|
+
<path d="M12 3 5 6v5c0 4.6 2.9 8.4 7 10 4.1-1.6 7-5.4 7-10V6l-7-3Z" stroke-linejoin="round"></path>
|
|
317
|
+
<path d="m9.5 12 1.7 1.7 3.6-3.7" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
318
|
+
</svg>
|
|
319
|
+
<span>密码仅用于验证本次局域网访问,不会保存在此设备。</span>
|
|
320
|
+
</p>
|
|
127
321
|
</main>
|
|
128
322
|
<script>
|
|
323
|
+
const form = document.getElementById('unlock-form')
|
|
129
324
|
const password = document.getElementById('password')
|
|
130
325
|
const button = document.getElementById('submit')
|
|
326
|
+
const buttonLabel = document.getElementById('button-label')
|
|
131
327
|
const error = document.getElementById('error')
|
|
132
|
-
|
|
328
|
+
|
|
329
|
+
password.addEventListener('input', () => {
|
|
330
|
+
error.textContent = ''
|
|
331
|
+
password.setAttribute('aria-invalid', 'false')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
form.addEventListener('submit', async (event) => {
|
|
335
|
+
event.preventDefault()
|
|
336
|
+
if (button.disabled) return
|
|
337
|
+
|
|
133
338
|
error.textContent = ''
|
|
339
|
+
password.setAttribute('aria-invalid', 'false')
|
|
134
340
|
button.disabled = true
|
|
341
|
+
button.classList.add('is-loading')
|
|
342
|
+
buttonLabel.textContent = '正在验证…'
|
|
343
|
+
|
|
135
344
|
try {
|
|
136
345
|
const response = await fetch('/api/lan-access/unlock', {
|
|
137
346
|
method: 'POST',
|
|
@@ -143,12 +352,14 @@ export function renderLanUnlockPage(res) {
|
|
|
143
352
|
window.location.reload()
|
|
144
353
|
} catch (err) {
|
|
145
354
|
error.textContent = err instanceof Error ? err.message : '密码错误'
|
|
355
|
+
password.setAttribute('aria-invalid', 'true')
|
|
356
|
+
password.focus()
|
|
146
357
|
} finally {
|
|
147
358
|
button.disabled = false
|
|
359
|
+
button.classList.remove('is-loading')
|
|
360
|
+
buttonLabel.textContent = '进入 QuickForge'
|
|
148
361
|
}
|
|
149
|
-
}
|
|
150
|
-
button.addEventListener('click', unlock)
|
|
151
|
-
password.addEventListener('keydown', (event) => { if (event.key === 'Enter') unlock() })
|
|
362
|
+
})
|
|
152
363
|
</script>
|
|
153
364
|
</body>
|
|
154
365
|
</html>`
|
|
@@ -186,7 +397,10 @@ export async function handleLanAccessApi(req, res, url, context = {}) {
|
|
|
186
397
|
assertNotLocked(req)
|
|
187
398
|
const body = await readJsonBody(req, 1024)
|
|
188
399
|
try {
|
|
189
|
-
const result = await issueLanAccessToken(body?.password
|
|
400
|
+
const result = await issueLanAccessToken(body?.password, {
|
|
401
|
+
remoteAddress: req.socket.remoteAddress,
|
|
402
|
+
userAgent: req.headers['user-agent'],
|
|
403
|
+
})
|
|
190
404
|
setLanCookie(res, result.token, result.maxAge)
|
|
191
405
|
clearFailures(req)
|
|
192
406
|
logger.info('LAN access unlock succeeded.', { remoteAddress: req.socket.remoteAddress })
|
|
@@ -202,11 +416,22 @@ export async function handleLanAccessApi(req, res, url, context = {}) {
|
|
|
202
416
|
}
|
|
203
417
|
|
|
204
418
|
if (req.method === 'POST' && pathname === '/api/lan-access/logout') {
|
|
419
|
+
const token = parseCookies(req.headers.cookie).get(lanAccessCookieName())
|
|
420
|
+
await revokeLanAccessToken(token)
|
|
205
421
|
clearLanCookie(res)
|
|
206
422
|
sendJson(res, 200, { ok: true })
|
|
207
423
|
return
|
|
208
424
|
}
|
|
209
425
|
|
|
426
|
+
if (req.method === 'POST' && pathname === '/api/lan-access/revoke') {
|
|
427
|
+
requireLocal(context)
|
|
428
|
+
const body = await readJsonBody(req, 1024)
|
|
429
|
+
const status = await revokeLanAccessTokenById(body?.id)
|
|
430
|
+
logger.info('LAN access session revoked.', { sessionId: body?.id })
|
|
431
|
+
sendJson(res, 200, { ok: true, ...status })
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
210
435
|
if (req.method === 'POST' && pathname === '/api/lan-access/revoke-all') {
|
|
211
436
|
requireLocal(context)
|
|
212
437
|
const status = await revokeLanAccessTokens()
|
package/server/routes/models.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
|
|
2
2
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
3
3
|
import { readStore } from '../storage.mjs'
|
|
4
4
|
import { logger } from '../utils/logger.mjs'
|
|
@@ -22,7 +22,7 @@ async function getApiKey(provider) {
|
|
|
22
22
|
// API key is valid. Returns { ok: true } on success; throws on failure.
|
|
23
23
|
async function probeModelConnection(model, apiKeyOverride) {
|
|
24
24
|
const apiKey = apiKeyOverride || (await getApiKey(model?.provider))
|
|
25
|
-
const stream =
|
|
25
|
+
const stream = streamSimpleWithAiHttpLogging(
|
|
26
26
|
model,
|
|
27
27
|
{
|
|
28
28
|
systemPrompt: 'You are a connectivity test. Reply with a single word.',
|
|
@@ -39,7 +39,10 @@ async function probeModelConnection(model, apiKeyOverride) {
|
|
|
39
39
|
maxRetryDelayMs: 30000,
|
|
40
40
|
},
|
|
41
41
|
)
|
|
42
|
-
await stream.result()
|
|
42
|
+
const message = await stream.result()
|
|
43
|
+
if (message?.stopReason === 'error' || message?.stopReason === 'aborted') {
|
|
44
|
+
throw new Error(message.errorMessage || `Model request ${message.stopReason}`)
|
|
45
|
+
}
|
|
43
46
|
return { ok: true }
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -8,7 +8,25 @@ import { getWorkspaceRoot, setWorkspaceRoot } from '../utils/workspace.mjs'
|
|
|
8
8
|
import { selectDirectoryDialog, openPathInFileManager, openPathInIDEA, openPathInVSCode } from '../utils/platform.mjs'
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function assertLocalExternalAction(context) {
|
|
12
|
+
if (context.isLocalRequest === true) return
|
|
13
|
+
const error = new Error('Opening applications is only allowed from this computer')
|
|
14
|
+
error.statusCode = 403
|
|
15
|
+
throw error
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleProjectApi(req, res, url, context = {}) {
|
|
19
|
+
const externalAction = req.method === 'POST' && (
|
|
20
|
+
url.pathname === '/api/project/select-directory'
|
|
21
|
+
|| (url.pathname.startsWith('/api/project/') && (
|
|
22
|
+
url.pathname.endsWith('/open-in-explorer')
|
|
23
|
+
|| url.pathname.endsWith('/open-in-vscode')
|
|
24
|
+
|| url.pathname.endsWith('/open-in-idea')
|
|
25
|
+
))
|
|
26
|
+
|| url.pathname === '/api/project/open-path'
|
|
27
|
+
)
|
|
28
|
+
if (externalAction) assertLocalExternalAction(context)
|
|
29
|
+
|
|
12
30
|
const config = await readProjectConfig()
|
|
13
31
|
|
|
14
32
|
if (req.method === 'GET' && url.pathname === '/api/project') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
|
|
2
2
|
import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
|
|
3
3
|
import { readJsonBody, sendJson, decodeSegment } from '../utils/response.mjs'
|
|
4
4
|
import { readStore, atomicUpdate } from '../storage.mjs'
|
|
@@ -154,7 +154,7 @@ async function parseScheduledTaskInstructionWithAi(instruction, model, thinkingL
|
|
|
154
154
|
示例输出:{"title":"生成日报","instruction":"生成销售日报","cronExpression":"0 9 * * *","scheduleRule":"每天 09:00","question":""}`
|
|
155
155
|
|
|
156
156
|
try {
|
|
157
|
-
const stream =
|
|
157
|
+
const stream = streamSimpleWithAiHttpLogging(
|
|
158
158
|
model,
|
|
159
159
|
{
|
|
160
160
|
systemPrompt,
|
package/server/routes/static.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
4
4
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url)
|
|
6
6
|
const projectRoot = path.resolve(path.dirname(__filename), '../..')
|
|
7
|
+
const androidApkPath = path.join(projectRoot, 'android', 'app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk')
|
|
7
8
|
|
|
8
9
|
function getContentType(filePath) {
|
|
9
10
|
const extension = path.extname(filePath).toLowerCase()
|
|
@@ -45,7 +46,31 @@ function shouldFallbackToIndex(url, pathname) {
|
|
|
45
46
|
return !path.extname(pathname)
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function cacheControlFor(filePath) {
|
|
50
|
+
if (filePath.endsWith('index.html')) return 'no-cache'
|
|
51
|
+
if (path.extname(filePath).toLowerCase() === '.apk') return 'no-cache'
|
|
52
|
+
return 'public, max-age=31536000, immutable'
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
export async function serveStatic(req, res, url) {
|
|
56
|
+
if (url.pathname === '/downloads/quickforge-android.apk') {
|
|
57
|
+
try {
|
|
58
|
+
const data = await fs.readFile(androidApkPath)
|
|
59
|
+
res.writeHead(200, {
|
|
60
|
+
'content-type': 'application/vnd.android.package-archive',
|
|
61
|
+
'content-length': data.length,
|
|
62
|
+
'content-disposition': 'attachment; filename="quickforge-android.apk"',
|
|
63
|
+
'cache-control': 'no-cache',
|
|
64
|
+
'x-content-type-options': 'nosniff',
|
|
65
|
+
})
|
|
66
|
+
res.end(data)
|
|
67
|
+
} catch {
|
|
68
|
+
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' })
|
|
69
|
+
res.end('Android APK not found. Run npm run android:build first.')
|
|
70
|
+
}
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
const distDir = path.join(projectRoot, 'dist')
|
|
50
75
|
const requested = decodeURIComponent(requestPathname(url))
|
|
51
76
|
const normalized = path.normalize(requested).replace(/^([.][.][/])+/, '').replace(/^[/\\]+/, '')
|
|
@@ -74,7 +99,7 @@ export async function serveStatic(req, res, url) {
|
|
|
74
99
|
const data = await fs.readFile(filePath)
|
|
75
100
|
res.writeHead(200, {
|
|
76
101
|
'content-type': getContentType(filePath),
|
|
77
|
-
'cache-control': filePath
|
|
102
|
+
'cache-control': cacheControlFor(filePath),
|
|
78
103
|
})
|
|
79
104
|
res.end(data)
|
|
80
105
|
} catch {
|
package/server/routes/system.mjs
CHANGED
|
@@ -36,6 +36,12 @@ export async function handleSystemApi(req, res, url, context) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (req.method === 'POST' && url.pathname === '/api/system/restart') {
|
|
39
|
+
if (!context.isLocalRequest) {
|
|
40
|
+
const error = new Error('Restart is only allowed from this computer')
|
|
41
|
+
error.statusCode = 403
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
if (req.headers['x-quickforge-action'] !== 'restart') {
|
|
40
46
|
const error = new Error('Forbidden action')
|
|
41
47
|
error.statusCode = 403
|
|
@@ -59,6 +65,11 @@ export async function handleSystemApi(req, res, url, context) {
|
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
if (req.method === 'PUT') {
|
|
68
|
+
if (!context.isLocalRequest) {
|
|
69
|
+
const error = new Error('Terminal shell settings can only be changed from this computer')
|
|
70
|
+
error.statusCode = 403
|
|
71
|
+
throw error
|
|
72
|
+
}
|
|
62
73
|
const body = await readJsonBody(req, 64 * 1024) || {}
|
|
63
74
|
if (Array.isArray(body.profiles) || typeof body.defaultProfileId === 'string') {
|
|
64
75
|
sendJson(res, 200, await context.updateTerminalShellConfig(body))
|
|
@@ -59,7 +59,7 @@ export async function handleTerminalApi(req, res, url, options = {}) {
|
|
|
59
59
|
await assertLocalTerminalRequest(req, options.isLocalRequest)
|
|
60
60
|
|
|
61
61
|
if (req.method === 'GET' && url.pathname === '/api/terminal/capabilities') {
|
|
62
|
-
sendJson(res, 200, { ...await terminalCapabilities(), ...await platformInfo() })
|
|
62
|
+
sendJson(res, 200, { ...await terminalCapabilities(), ...await platformInfo() }, 'private, max-age=300')
|
|
63
63
|
return
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -137,7 +137,12 @@ async function handleTerminalUpgradeAsync(req, socket, head, url, options = {})
|
|
|
137
137
|
attachTerminalClient(sessionId, ws)
|
|
138
138
|
} catch (err) {
|
|
139
139
|
try {
|
|
140
|
-
ws.send(JSON.stringify({
|
|
140
|
+
ws.send(JSON.stringify({
|
|
141
|
+
type: 'error',
|
|
142
|
+
code: err?.statusCode === 404 ? 'SESSION_NOT_FOUND' : 'TERMINAL_CONNECTION_FAILED',
|
|
143
|
+
message: err?.message || 'Terminal connection failed',
|
|
144
|
+
retryable: err?.statusCode !== 404,
|
|
145
|
+
}), () => ws.close())
|
|
141
146
|
} catch {
|
|
142
147
|
ws.close()
|
|
143
148
|
}
|