@shawnstack/quickforge 1.7.4 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
- package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
- package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
- package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
- package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
- package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
- package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
- package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
- package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
- package/dist/assets/WorkspaceInspector-BnfDLf9a.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
- package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
- package/dist/assets/channel-Duu1CuH9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
- package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
- package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
- package/dist/assets/{esm-oa69b5u4.js → esm-sdsK6gaB.js} +2 -2
- package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
- package/dist/assets/icons-2D18YbSI.js +1 -0
- package/dist/assets/index-1ei6nhs-.css +3 -0
- package/dist/assets/index-BivLuZvX.js +65 -0
- package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
- package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
- package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
- package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BEV4_TXC.js} +2 -2
- package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
- package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
- package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
- package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
- package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
- package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
- package/dist/assets/{monaco-BVtCspYZ.js → monaco-DvXErxp2.js} +1 -1
- package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
- package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
- package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
- package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
- package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-3UaA-C9W.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
- package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BTJ7mzk_.js} +3 -3
- package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
- package/dist/assets/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +11 -10
- package/dist/sw.js +17 -0
- package/package.json +4 -1
- package/plugins/{openai-documents → documents}/plugin.json +2 -2
- package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
- package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
- package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
- package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
- package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
- package/runtime-assets/agent/linux-x64/qf-agent +0 -0
- package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
- package/server/access-policy.mjs +11 -0
- package/server/acp/server.mjs +36 -26
- package/server/agent-manager.mjs +474 -71
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +28 -9
- package/server/ai-http-logger.mjs +98 -12
- package/server/auto-compaction.mjs +35 -8
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +99 -0
- package/server/cloud/config.mjs +54 -0
- package/server/cloud/credential-store.mjs +200 -0
- package/server/cloud/identity.mjs +312 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/qf-agent-process.mjs +571 -0
- package/server/cloud/runtime.mjs +135 -0
- package/server/cloud/service-config.mjs +81 -0
- package/server/index.mjs +124 -48
- package/server/mcp/registry.mjs +8 -7
- package/server/message-converters.mjs +40 -19
- package/server/model-catalog.mjs +269 -0
- package/server/opencode-acp-agent.mjs +1151 -0
- package/server/plugins/registry.mjs +29 -5
- package/server/public-api.mjs +11 -5
- package/server/routes/agent-profiles.mjs +46 -9
- package/server/routes/agent.mjs +60 -11
- package/server/routes/backup.mjs +8 -1
- package/server/routes/cloud.mjs +246 -0
- package/server/routes/models.mjs +7 -1
- package/server/routes/scheduled-tasks.mjs +38 -8
- package/server/routes/shared-conversation.mjs +62 -58
- package/server/routes/shares.mjs +75 -1
- package/server/routes/storage.mjs +9 -1
- package/server/routes/system.mjs +7 -4
- package/server/routes/workspace.mjs +6 -4
- package/server/share-store.mjs +168 -13
- package/server/skills.mjs +30 -3
- package/server/storage.mjs +7 -1
- package/server/subagents.mjs +3 -3
- package/server/tools/definitions.mjs +1 -1
- package/server/tools/index.mjs +2 -2
- package/server/utils/network.mjs +9 -0
- package/server/utils/process-tree.mjs +78 -0
- package/server/utils/response.mjs +8 -1
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/UPSTREAM.md +6 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
- package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
- package/dist/assets/PluginsPage-D2862wul.js +0 -1
- package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
- package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +0 -1628
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
- package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
- package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
- package/dist/assets/channel-DToA8d-4.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
- package/dist/assets/icons-cTkqD_Gi.js +0 -1
- package/dist/assets/index-B48prgLL.js +0 -65
- package/dist/assets/index-DQhwHHer.css +0 -3
- package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
- package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
- package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
- package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
- /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
- /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
- /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
- /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
- /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
- /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
- /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
- /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
- /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
- /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
package/server/agent-manager.mjs
CHANGED
|
@@ -8,11 +8,13 @@ import { loadSkillToolContext, abortRunningCommand } from './tools/index.mjs'
|
|
|
8
8
|
import { createSkillTools, globalMemoryTool, workspaceTools } from './tools/definitions.mjs'
|
|
9
9
|
import { createMcpToolDefinitions, isMcpToolName } from './mcp/registry.mjs'
|
|
10
10
|
import { createPluginToolDefinitions, isPluginToolName } from './plugins/registry.mjs'
|
|
11
|
+
import { createOpenCodeAcpAgent } from './opencode-acp-agent.mjs'
|
|
11
12
|
import {
|
|
12
13
|
composeSubagentSystemPrompt,
|
|
13
14
|
formatSubagentTask,
|
|
14
15
|
} from './subagents.mjs'
|
|
15
16
|
import { agentProfileSnapshot, getAgentProfile } from './agent-profiles.mjs'
|
|
17
|
+
import { modelBindingFromModel, resolveImplicitModelPreference, resolveModelBinding } from './model-catalog.mjs'
|
|
16
18
|
import { agentProfileFromMarkdown } from './agent-profile-files.mjs'
|
|
17
19
|
import {
|
|
18
20
|
applyCapabilityPolicy,
|
|
@@ -147,6 +149,10 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
async function rebuildSessionTools(session) {
|
|
152
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
153
|
+
session.agent.state.tools = []
|
|
154
|
+
return
|
|
155
|
+
}
|
|
150
156
|
const profileToolNames = Array.isArray(session.agentProfile?.allowedTools) ? session.agentProfile.allowedTools : null
|
|
151
157
|
session.agent.state.tools = await createServerTools(
|
|
152
158
|
session.projectId,
|
|
@@ -179,6 +185,25 @@ const agentSessions = new Map()
|
|
|
179
185
|
|
|
180
186
|
const AGENT_ACCESS_MODE_DEFAULT = 'default'
|
|
181
187
|
const AGENT_ACCESS_MODE_FULL_ACCESS = 'full-access'
|
|
188
|
+
const AGENT_HARNESS_QUICKFORGE = 'quickforge'
|
|
189
|
+
const AGENT_HARNESS_OPENCODE = 'opencode'
|
|
190
|
+
|
|
191
|
+
export function normalizeAgentHarness(value, fallback = AGENT_HARNESS_QUICKFORGE) {
|
|
192
|
+
if (value === AGENT_HARNESS_QUICKFORGE || value === AGENT_HARNESS_OPENCODE) return value
|
|
193
|
+
if (fallback !== value) return normalizeAgentHarness(fallback, AGENT_HARNESS_QUICKFORGE)
|
|
194
|
+
return AGENT_HARNESS_QUICKFORGE
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function validateAgentHarness(value) {
|
|
198
|
+
if (value === undefined || value === null || value === '') return AGENT_HARNESS_QUICKFORGE
|
|
199
|
+
if (value === 'claude-code') {
|
|
200
|
+
throw Object.assign(new Error('Claude Code Harness is not available yet.'), { statusCode: 400 })
|
|
201
|
+
}
|
|
202
|
+
if (value !== AGENT_HARNESS_QUICKFORGE && value !== AGENT_HARNESS_OPENCODE) {
|
|
203
|
+
throw Object.assign(new Error(`Unsupported Harness: ${value}`), { statusCode: 400 })
|
|
204
|
+
}
|
|
205
|
+
return value
|
|
206
|
+
}
|
|
182
207
|
|
|
183
208
|
function normalizeAccessMode(value, fallback = AGENT_ACCESS_MODE_DEFAULT) {
|
|
184
209
|
if (value === AGENT_ACCESS_MODE_DEFAULT || value === AGENT_ACCESS_MODE_FULL_ACCESS) return value
|
|
@@ -200,7 +225,7 @@ function hasFullAccess(session) {
|
|
|
200
225
|
|
|
201
226
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes
|
|
202
227
|
const ABORT_IDLE_WAIT_TIMEOUT_MS = 3000
|
|
203
|
-
const SUBAGENT_DEFAULT_TIMEOUT_MS =
|
|
228
|
+
const SUBAGENT_DEFAULT_TIMEOUT_MS = 60 * 60 * 1000
|
|
204
229
|
const SUBAGENT_TRACE_THROTTLE_MS = 150
|
|
205
230
|
|
|
206
231
|
/**
|
|
@@ -278,6 +303,58 @@ function createApprovalPromise(session, toolCallId, toolName, args, source) {
|
|
|
278
303
|
})
|
|
279
304
|
}
|
|
280
305
|
|
|
306
|
+
function createAcpApprovalPromise(session, request) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
let settled = false
|
|
309
|
+
const requestedAt = Date.now()
|
|
310
|
+
const expiresAt = requestedAt + APPROVAL_TIMEOUT_MS
|
|
311
|
+
const timeout = setTimeout(() => {
|
|
312
|
+
if (settled) return
|
|
313
|
+
settled = true
|
|
314
|
+
pendingApprovals.delete(request.toolCallId)
|
|
315
|
+
resolve({ outcome: { outcome: 'cancelled' } })
|
|
316
|
+
}, APPROVAL_TIMEOUT_MS)
|
|
317
|
+
|
|
318
|
+
const cleanup = () => {
|
|
319
|
+
clearTimeout(timeout)
|
|
320
|
+
if (settled) return
|
|
321
|
+
settled = true
|
|
322
|
+
pendingApprovals.delete(request.toolCallId)
|
|
323
|
+
}
|
|
324
|
+
const allowOption = request.options.find((option) => option.kind === 'allow_once' || option.kind === 'allow_always')
|
|
325
|
+
const rejectOption = request.options.find((option) => option.kind === 'reject_once' || option.kind === 'reject_always')
|
|
326
|
+
|
|
327
|
+
pendingApprovals.set(request.toolCallId, {
|
|
328
|
+
resolve: (approved) => {
|
|
329
|
+
cleanup()
|
|
330
|
+
const option = approved ? allowOption : rejectOption
|
|
331
|
+
resolve(option
|
|
332
|
+
? { outcome: { outcome: 'selected', optionId: option.optionId } }
|
|
333
|
+
: { outcome: { outcome: 'cancelled' } })
|
|
334
|
+
},
|
|
335
|
+
reject: (error) => {
|
|
336
|
+
cleanup()
|
|
337
|
+
reject(error)
|
|
338
|
+
},
|
|
339
|
+
sessionId: session.sessionId,
|
|
340
|
+
toolName: request.toolName,
|
|
341
|
+
args: request.args,
|
|
342
|
+
source: 'opencode',
|
|
343
|
+
requestedAt,
|
|
344
|
+
expiresAt,
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
emitSessionEvent(session, {
|
|
348
|
+
type: 'tool_approval_required',
|
|
349
|
+
sessionId: session.sessionId,
|
|
350
|
+
toolCallId: request.toolCallId,
|
|
351
|
+
toolName: request.toolName,
|
|
352
|
+
args: request.args,
|
|
353
|
+
source: 'opencode',
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
281
358
|
function createAutoCompactApprovalPromise(session, details = {}) {
|
|
282
359
|
if (!session) return Promise.resolve(false)
|
|
283
360
|
const approvalId = randomUUID()
|
|
@@ -376,6 +453,24 @@ function userTextMessage(text, details) {
|
|
|
376
453
|
}
|
|
377
454
|
}
|
|
378
455
|
|
|
456
|
+
function assistantErrorMessage(text, model) {
|
|
457
|
+
return {
|
|
458
|
+
...assistantTextMessage('', model),
|
|
459
|
+
stopReason: 'error',
|
|
460
|
+
errorMessage: text,
|
|
461
|
+
timestamp: Date.now(),
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function appendAssistantErrorMessageOnce(messages, errorMessage, model) {
|
|
466
|
+
const current = Array.isArray(messages) ? messages : []
|
|
467
|
+
const lastMessage = current[current.length - 1]
|
|
468
|
+
const errorAlreadyShown = lastMessage?.role === 'assistant'
|
|
469
|
+
&& lastMessage?.stopReason === 'error'
|
|
470
|
+
&& lastMessage?.errorMessage
|
|
471
|
+
return errorAlreadyShown ? current : [...current, assistantErrorMessage(errorMessage, model)]
|
|
472
|
+
}
|
|
473
|
+
|
|
379
474
|
function compactedSessionTitle(title) {
|
|
380
475
|
const base = typeof title === 'string' && title.trim() ? title.trim() : 'New chat'
|
|
381
476
|
if (base === 'New chat') return 'Compacted chat'
|
|
@@ -515,6 +610,37 @@ function updateSessionMessages(session, messages) {
|
|
|
515
610
|
session.agent.state.messages = messages
|
|
516
611
|
}
|
|
517
612
|
|
|
613
|
+
const CLIENT_MESSAGE_ID_FIELD = 'quickforgeClientMessageId'
|
|
614
|
+
|
|
615
|
+
function isManagedCloudModel(model) {
|
|
616
|
+
return model?.provider === 'quickforge-cloud' && model?.quickforgeModelSource === 'cloud'
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function objectMetadata(message) {
|
|
620
|
+
const metadata = message?.metadata
|
|
621
|
+
return metadata && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : {}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function logicalMessageId(message) {
|
|
625
|
+
const value = objectMetadata(message)[CLIENT_MESSAGE_ID_FIELD]
|
|
626
|
+
return typeof value === 'string' && /^[A-Za-z0-9._:-]{1,128}$/.test(value) ? value : undefined
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function messageWithLogicalId(message) {
|
|
630
|
+
if (!message || typeof message !== 'object' || logicalMessageId(message)) return message
|
|
631
|
+
return {
|
|
632
|
+
...message,
|
|
633
|
+
metadata: {
|
|
634
|
+
...objectMetadata(message),
|
|
635
|
+
[CLIENT_MESSAGE_ID_FIELD]: `qfcm_${randomUUID()}`,
|
|
636
|
+
},
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function prepareCloudUserMessage(session, message) {
|
|
641
|
+
return isManagedCloudModel(session?.model) ? messageWithLogicalId(message) : message
|
|
642
|
+
}
|
|
643
|
+
|
|
518
644
|
function resetSessionCompaction(session) {
|
|
519
645
|
session.contextCompaction = null
|
|
520
646
|
session.lastAutoCompactAt = null
|
|
@@ -532,6 +658,9 @@ function finishManualSessionRun(session, status, errorMessage) {
|
|
|
532
658
|
}
|
|
533
659
|
|
|
534
660
|
async function summarySession(session, initialUserMessage, summaryOptions) {
|
|
661
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
662
|
+
throw Object.assign(new Error('OpenCode Harness does not support QuickForge summary derivation yet.'), { statusCode: 409 })
|
|
663
|
+
}
|
|
535
664
|
if (session.agent.state.isStreaming) {
|
|
536
665
|
session.agent.state.messages = [
|
|
537
666
|
...session.agent.state.messages,
|
|
@@ -625,8 +754,13 @@ async function summarySession(session, initialUserMessage, summaryOptions) {
|
|
|
625
754
|
scope: session.scope,
|
|
626
755
|
projectId: session.projectId,
|
|
627
756
|
accessMode: session.accessMode,
|
|
757
|
+
harness: session.harness,
|
|
758
|
+
sourceHarnessSessionId: session.harness === AGENT_HARNESS_OPENCODE ? session.agent.harnessSessionId : null,
|
|
628
759
|
yoloMode: session.yoloMode,
|
|
629
760
|
model: session.model,
|
|
761
|
+
modelRef: session.modelRef,
|
|
762
|
+
modelAccessContext: session.modelAccessContext,
|
|
763
|
+
resolvePersistedModel: true,
|
|
630
764
|
thinkingLevel: session.thinkingLevel,
|
|
631
765
|
messages: compactedMessages,
|
|
632
766
|
title: compactedTitle,
|
|
@@ -1081,7 +1215,7 @@ model:
|
|
|
1081
1215
|
modelId: ${frontmatterString(spec.model.modelId)}${spec.model.api ? `
|
|
1082
1216
|
api: ${frontmatterString(spec.model.api)}` : ''}${spec.model.baseUrl ? `
|
|
1083
1217
|
baseUrl: ${frontmatterString(spec.model.baseUrl)}` : ''}` : ''}
|
|
1084
|
-
max-runtime-ms: ${Math.max(1000, Math.min(Number(spec.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS),
|
|
1218
|
+
max-runtime-ms: ${Math.max(1000, Math.min(Number(spec.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS), 60 * 60 * 1000))}
|
|
1085
1219
|
max-tool-calls: ${Math.max(1, Math.min(Number(spec.maxToolCalls || 300), 300))}
|
|
1086
1220
|
---
|
|
1087
1221
|
${spec.systemPrompt}
|
|
@@ -1135,10 +1269,15 @@ async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
|
1135
1269
|
throw new Error('No active model is configured for the parent session.')
|
|
1136
1270
|
}
|
|
1137
1271
|
|
|
1138
|
-
const timeoutMs = Math.max(1000, Math.min(Number(definition.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS),
|
|
1272
|
+
const timeoutMs = Math.max(1000, Math.min(Number(definition.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS), 60 * 60 * 1000))
|
|
1139
1273
|
definition.capabilityPolicy ||= inferCapabilityPolicy(definition.allowedTools || [])
|
|
1140
1274
|
definition.allowedTools = applyCapabilityPolicy(definition.allowedTools || [], definition.capabilityPolicy)
|
|
1141
|
-
const { model: subagentModel, info: subagentModelInfo } = await resolveAgentProfileModel(
|
|
1275
|
+
const { model: subagentModel, info: subagentModelInfo } = await resolveAgentProfileModel(
|
|
1276
|
+
definition,
|
|
1277
|
+
parentSession.model,
|
|
1278
|
+
readStore,
|
|
1279
|
+
parentSession.modelAccessContext || {},
|
|
1280
|
+
)
|
|
1142
1281
|
const subagentThinkingLevel = resolveAgentProfileThinkingLevel(definition, parentSession.thinkingLevel, subagentModel)
|
|
1143
1282
|
const subagentSessionId = `${parentSession.sessionId}:subagent:${definition.name}:${randomUUID()}`
|
|
1144
1283
|
const startedAt = Date.now()
|
|
@@ -1511,6 +1650,9 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1511
1650
|
accessMode: rawAccessMode,
|
|
1512
1651
|
yoloMode = false,
|
|
1513
1652
|
model = null,
|
|
1653
|
+
modelRef = null,
|
|
1654
|
+
modelAccessContext = null,
|
|
1655
|
+
resolvePersistedModel = false,
|
|
1514
1656
|
thinkingLevel = 'off',
|
|
1515
1657
|
messages = [],
|
|
1516
1658
|
systemPrompt = null,
|
|
@@ -1519,12 +1661,17 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1519
1661
|
createdAt = new Date().toISOString(),
|
|
1520
1662
|
lastModified = null,
|
|
1521
1663
|
contextCompaction = null,
|
|
1664
|
+
harness: rawHarness,
|
|
1665
|
+
harnessSessionId = null,
|
|
1666
|
+
sourceHarnessSessionId = null,
|
|
1667
|
+
openCodeUsage = null,
|
|
1522
1668
|
agentProfile = null,
|
|
1523
1669
|
idleRetention = null,
|
|
1524
1670
|
stateVersion = 0,
|
|
1525
1671
|
} = config
|
|
1526
1672
|
const accessMode = normalizeAccessMode(rawAccessMode, yoloMode)
|
|
1527
1673
|
const resolvedYoloMode = yoloModeFromAccessMode(accessMode)
|
|
1674
|
+
const harness = normalizeAgentHarness(rawHarness)
|
|
1528
1675
|
|
|
1529
1676
|
// Resolve project context for tool calls. Project conversations resolve to
|
|
1530
1677
|
// their directory; global conversations (no projectId) and any fallback fall
|
|
@@ -1539,8 +1686,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1539
1686
|
}
|
|
1540
1687
|
projectContext ??= defaultGlobalWorkspaceContext()
|
|
1541
1688
|
|
|
1542
|
-
// Build system prompt
|
|
1543
|
-
const projectConfig = await readProjectConfig()
|
|
1689
|
+
// Build system prompt and tools only for the native QuickForge runtime.
|
|
1690
|
+
const projectConfig = harness === AGENT_HARNESS_QUICKFORGE ? await readProjectConfig() : { projects: [], globalSkills: [] }
|
|
1544
1691
|
const configuredProject = projectId
|
|
1545
1692
|
? projectConfig.projects.find((project) => project.id === projectId)
|
|
1546
1693
|
: null
|
|
@@ -1549,61 +1696,82 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1549
1696
|
projectSkillNames: configuredProject?.skills,
|
|
1550
1697
|
}
|
|
1551
1698
|
const profileSystemPrompt = agentProfileSystemPrompt(agentProfile)
|
|
1552
|
-
const resolvedSystemPrompt =
|
|
1699
|
+
const resolvedSystemPrompt = harness === AGENT_HARNESS_QUICKFORGE
|
|
1700
|
+
? systemPrompt ?? `${await buildSystemPrompt(projectId)}${profileSystemPrompt}`
|
|
1701
|
+
: ''
|
|
1702
|
+
if (harness === AGENT_HARNESS_OPENCODE && messages.length > 0 && !harnessSessionId && !sourceHarnessSessionId) {
|
|
1703
|
+
throw Object.assign(new Error('OpenCode history requires a persisted ACP session ID or an ACP fork source.'), { statusCode: 400 })
|
|
1704
|
+
}
|
|
1553
1705
|
|
|
1554
1706
|
let resolvedAgentProfile = agentProfile
|
|
1555
1707
|
|
|
1556
|
-
//
|
|
1708
|
+
// OpenCode owns its login, model, tools and context. QuickForge runtime keeps
|
|
1709
|
+
// the existing model resolution path unchanged.
|
|
1557
1710
|
let resolvedModel = model
|
|
1558
|
-
|
|
1559
|
-
|
|
1711
|
+
let resolvedModelRef = modelRef
|
|
1712
|
+
if (!resolvedModel && harness === AGENT_HARNESS_QUICKFORGE) {
|
|
1713
|
+
// Try to load the active preference from storage. A stale/hidden implicit
|
|
1714
|
+
// preference may fall back, while an explicit session binding never does.
|
|
1560
1715
|
try {
|
|
1561
1716
|
const settings = await readStore('settings')
|
|
1562
1717
|
const raw = settings?.['active-model']
|
|
1563
|
-
|
|
1718
|
+
resolvedModel = await resolveImplicitModelPreference(raw, modelAccessContext || {})
|
|
1564
1719
|
} catch {
|
|
1565
1720
|
// ignore
|
|
1566
1721
|
}
|
|
1567
1722
|
}
|
|
1568
1723
|
if (agentProfile?.model?.mode === 'fixed') {
|
|
1569
|
-
|
|
1724
|
+
const profileBinding = await resolveAgentProfileModel(agentProfile, resolvedModel, readStore, modelAccessContext || {})
|
|
1725
|
+
resolvedModel = profileBinding.model
|
|
1726
|
+
resolvedModelRef = profileBinding.modelRef || null
|
|
1570
1727
|
resolvedAgentProfile = {
|
|
1571
1728
|
...agentProfile,
|
|
1572
1729
|
model: modelReferenceSnapshot(agentProfile.model),
|
|
1573
1730
|
}
|
|
1574
1731
|
}
|
|
1575
1732
|
if (resolvedAgentProfile && !resolvedModel) throw new Error('No active model is configured for the agent session.')
|
|
1733
|
+
const resolvedBinding = harness === AGENT_HARNESS_QUICKFORGE && resolvedModel
|
|
1734
|
+
? (resolvedModelRef
|
|
1735
|
+
? { model: resolvedModel, modelRef: resolvedModelRef }
|
|
1736
|
+
: model
|
|
1737
|
+
? { model: resolvedModel, modelRef: null }
|
|
1738
|
+
: await modelBindingFromModel(resolvedModel))
|
|
1739
|
+
: { model: resolvedModel, modelRef: null }
|
|
1740
|
+
resolvedModel = resolvedBinding.model
|
|
1576
1741
|
const resolvedThinkingLevel = agentProfile
|
|
1577
1742
|
? resolveAgentProfileThinkingLevel(agentProfile, thinkingLevel, resolvedModel)
|
|
1578
1743
|
: thinkingLevel
|
|
1579
1744
|
|
|
1580
|
-
// Build
|
|
1745
|
+
// Build native tools only for QuickForge. OpenCode uses its own tools and
|
|
1746
|
+
// must not receive QuickForge MCP, Skills, Memory or workspace definitions.
|
|
1581
1747
|
const profileToolNames = Array.isArray(agentProfile?.allowedTools) ? agentProfile.allowedTools : null
|
|
1582
|
-
const tools =
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
agentProfile
|
|
1593
|
-
? {
|
|
1594
|
-
allowedToolNames: profileToolNames,
|
|
1595
|
-
includeSubagentTool: false,
|
|
1596
|
-
includeMcpTools: false,
|
|
1597
|
-
parentSessionId: sessionId,
|
|
1598
|
-
sessionId,
|
|
1599
|
-
scope,
|
|
1600
|
-
}
|
|
1601
|
-
: {
|
|
1602
|
-
parentSessionId: sessionId,
|
|
1603
|
-
sessionId,
|
|
1604
|
-
scope,
|
|
1748
|
+
const tools = harness === AGENT_HARNESS_QUICKFORGE
|
|
1749
|
+
? await createServerTools(
|
|
1750
|
+
projectId,
|
|
1751
|
+
projectContext,
|
|
1752
|
+
skillsContext,
|
|
1753
|
+
!!projectContext,
|
|
1754
|
+
(toolName) => {
|
|
1755
|
+
if (profileToolNames && !profileToolNames.includes(toolName)) return `Agent profile ${agentProfile.name} is not allowed to use ${toolName}.`
|
|
1756
|
+
const session = agentSessions.get(sessionId)
|
|
1757
|
+
return session ? createCommandToolPermissions(session)(toolName) : null
|
|
1605
1758
|
},
|
|
1606
|
-
|
|
1759
|
+
agentProfile
|
|
1760
|
+
? {
|
|
1761
|
+
allowedToolNames: profileToolNames,
|
|
1762
|
+
includeSubagentTool: false,
|
|
1763
|
+
includeMcpTools: false,
|
|
1764
|
+
parentSessionId: sessionId,
|
|
1765
|
+
sessionId,
|
|
1766
|
+
scope,
|
|
1767
|
+
}
|
|
1768
|
+
: {
|
|
1769
|
+
parentSessionId: sessionId,
|
|
1770
|
+
sessionId,
|
|
1771
|
+
scope,
|
|
1772
|
+
},
|
|
1773
|
+
)
|
|
1774
|
+
: []
|
|
1607
1775
|
|
|
1608
1776
|
// Resolve API key
|
|
1609
1777
|
const getApiKey = async (provider) => {
|
|
@@ -1615,10 +1783,21 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1615
1783
|
}
|
|
1616
1784
|
}
|
|
1617
1785
|
|
|
1618
|
-
const initialMemoryEnabled = await isGlobalMemoryEnabled()
|
|
1786
|
+
const initialMemoryEnabled = harness === AGENT_HARNESS_QUICKFORGE ? await isGlobalMemoryEnabled() : false
|
|
1619
1787
|
const initialMemoryRevision = initialMemoryEnabled ? await getGlobalMemoryRevision() : null
|
|
1620
1788
|
let session
|
|
1621
|
-
const agent =
|
|
1789
|
+
const agent = harness === AGENT_HARNESS_OPENCODE
|
|
1790
|
+
? await createOpenCodeAcpAgent({
|
|
1791
|
+
sessionId,
|
|
1792
|
+
cwd: projectContext.workspaceRoot,
|
|
1793
|
+
messages,
|
|
1794
|
+
harnessSessionId,
|
|
1795
|
+
sourceHarnessSessionId,
|
|
1796
|
+
restoredUsage: openCodeUsage,
|
|
1797
|
+
logger,
|
|
1798
|
+
requestPermission: (request) => createAcpApprovalPromise(session, request),
|
|
1799
|
+
})
|
|
1800
|
+
: new Agent({
|
|
1622
1801
|
initialState: {
|
|
1623
1802
|
systemPrompt: resolvedSystemPrompt,
|
|
1624
1803
|
model: resolvedModel,
|
|
@@ -1674,6 +1853,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1674
1853
|
session = {
|
|
1675
1854
|
sessionId,
|
|
1676
1855
|
agent,
|
|
1856
|
+
harness,
|
|
1857
|
+
harnessSessionId: agent.harnessSessionId || null,
|
|
1677
1858
|
projectContext,
|
|
1678
1859
|
projectId,
|
|
1679
1860
|
source: typeof source === 'string' && source.trim() ? source.trim() : null,
|
|
@@ -1682,6 +1863,9 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1682
1863
|
accessMode,
|
|
1683
1864
|
yoloMode: resolvedYoloMode,
|
|
1684
1865
|
model: resolvedModel,
|
|
1866
|
+
modelRef: resolvedBinding.modelRef,
|
|
1867
|
+
modelAccessContext: modelAccessContext || null,
|
|
1868
|
+
resolvePersistedModel: resolvePersistedModel === true || Boolean(resolvedBinding.modelRef),
|
|
1685
1869
|
thinkingLevel: resolvedThinkingLevel,
|
|
1686
1870
|
scope,
|
|
1687
1871
|
title,
|
|
@@ -1743,6 +1927,15 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1743
1927
|
: undefined
|
|
1744
1928
|
if (timedEvent.type === 'agent_end') {
|
|
1745
1929
|
markLatestAssistantProcessFinished(agent.state.messages)
|
|
1930
|
+
// Ensure a failed run is visible in the conversation. Most failures emit
|
|
1931
|
+
// an assistant error message through the agent loop (handleRunFailure),
|
|
1932
|
+
// but a few paths (e.g. concurrent-run rejection) only set
|
|
1933
|
+
// `state.errorMessage`. Append an error message so the user sees the
|
|
1934
|
+
// reason at the end of the transcript instead of only a toast.
|
|
1935
|
+
const runError = session.agent.state.errorMessage
|
|
1936
|
+
if (runError) {
|
|
1937
|
+
agent.state.messages = appendAssistantErrorMessageOnce(agent.state.messages, runError, session.model)
|
|
1938
|
+
}
|
|
1746
1939
|
}
|
|
1747
1940
|
const forwardEvent = timedEvent.type === 'agent_end'
|
|
1748
1941
|
? {
|
|
@@ -1758,6 +1951,12 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1758
1951
|
emitSessionEvent(session, forwardEvent)
|
|
1759
1952
|
}
|
|
1760
1953
|
|
|
1954
|
+
// OpenCode runtime usage snapshots are authoritative and lightweight; the
|
|
1955
|
+
// debounced write keeps the latest usage durable without blocking the run.
|
|
1956
|
+
if (event.type === 'acp_session_usage_update') {
|
|
1957
|
+
scheduleSessionPersist(session)
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1761
1960
|
// Track status
|
|
1762
1961
|
if (event.type === 'agent_start') {
|
|
1763
1962
|
session.abortEndEmitted = false
|
|
@@ -1789,22 +1988,23 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1789
1988
|
}
|
|
1790
1989
|
|
|
1791
1990
|
if (event.type === 'message_end') {
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
//
|
|
1991
|
+
const isUserMessage = event.message?.role === 'user' || event.message?.role === 'user-with-attachments'
|
|
1992
|
+
const isInitialUserMessage = isUserMessage && (event.isInitialUserMessage === true || session.agent.state.messages.length === 1)
|
|
1993
|
+
const requiresDurableCloudMessage = isUserMessage && isManagedCloudModel(session.model) && Boolean(logicalMessageId(event.message))
|
|
1994
|
+
if (isInitialUserMessage || requiresDurableCloudMessage) {
|
|
1995
|
+
// Persist every managed Cloud user message before the provider stream starts,
|
|
1996
|
+
// so its logical ID survives a process restart and resolves to the same private key.
|
|
1797
1997
|
try {
|
|
1798
1998
|
const metadata = await flushSessionPersist(session)
|
|
1799
|
-
if (metadata) {
|
|
1999
|
+
if (metadata && isInitialUserMessage) {
|
|
1800
2000
|
if (!session.sessionCreatedEmitted) {
|
|
1801
2001
|
session.sessionCreatedEmitted = true
|
|
1802
2002
|
emitSessionEvent(session, { type: 'session_created', metadata })
|
|
1803
2003
|
}
|
|
1804
|
-
scheduleSessionTitleGeneration(session, event.message)
|
|
2004
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) scheduleSessionTitleGeneration(session, event.message)
|
|
1805
2005
|
}
|
|
1806
2006
|
} catch (err) {
|
|
1807
|
-
logger.error(`Failed to persist
|
|
2007
|
+
logger.error(`Failed to persist user message for session ${sessionId}:`, err, { sessionId })
|
|
1808
2008
|
}
|
|
1809
2009
|
} else {
|
|
1810
2010
|
// Debounced persist for crash recovery; coalesces the many message_end
|
|
@@ -1848,7 +2048,7 @@ function sessionLastModifiedFromMessages(messages, fallback) {
|
|
|
1848
2048
|
* Persist session data to storage.
|
|
1849
2049
|
*/
|
|
1850
2050
|
async function persistSessionUnlocked(session) {
|
|
1851
|
-
const { sessionId, agent, scope, projectId, source, channelId, channelName, title, titleSource, createdAt, lastModified: storedLastModified, status, startedAt, finishedAt, model, thinkingLevel, accessMode, yoloMode, contextCompaction } = session
|
|
2051
|
+
const { sessionId, agent, harness, harnessSessionId, scope, projectId, source, channelId, channelName, title, titleSource, createdAt, lastModified: storedLastModified, status, startedAt, finishedAt, model, modelRef, thinkingLevel, accessMode, yoloMode, contextCompaction } = session
|
|
1852
2052
|
const messages = agent.state.messages
|
|
1853
2053
|
|
|
1854
2054
|
if (messages.length === 0) {
|
|
@@ -1870,7 +2070,11 @@ async function persistSessionUnlocked(session) {
|
|
|
1870
2070
|
id: sessionId,
|
|
1871
2071
|
title,
|
|
1872
2072
|
titleSource,
|
|
2073
|
+
harness,
|
|
2074
|
+
harnessSessionId: agent.harnessSessionId || harnessSessionId || undefined,
|
|
2075
|
+
openCodeUsage: harness === AGENT_HARNESS_OPENCODE && agent.state.acpSession?.usage ? agent.state.acpSession.usage : undefined,
|
|
1873
2076
|
model,
|
|
2077
|
+
modelRef: modelRef || undefined,
|
|
1874
2078
|
thinkingLevel,
|
|
1875
2079
|
accessMode,
|
|
1876
2080
|
yoloMode,
|
|
@@ -1930,6 +2134,8 @@ async function persistSessionUnlocked(session) {
|
|
|
1930
2134
|
messageCount: messages.length,
|
|
1931
2135
|
usage,
|
|
1932
2136
|
thinkingLevel,
|
|
2137
|
+
harness,
|
|
2138
|
+
harnessSessionId: agent.harnessSessionId || harnessSessionId || undefined,
|
|
1933
2139
|
accessMode,
|
|
1934
2140
|
yoloMode,
|
|
1935
2141
|
preview,
|
|
@@ -2063,7 +2269,13 @@ export async function rollbackSessionMessages(sessionId, rollbackMessageIndex) {
|
|
|
2063
2269
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2064
2270
|
}
|
|
2065
2271
|
if (session.agent.state.isStreaming) {
|
|
2066
|
-
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before rolling back.'), {
|
|
2272
|
+
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before rolling back.'), {
|
|
2273
|
+
statusCode: 409,
|
|
2274
|
+
errorCode: 'GENERATION_STILL_RUNNING_BEFORE_ROLLBACK',
|
|
2275
|
+
})
|
|
2276
|
+
}
|
|
2277
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
2278
|
+
throw Object.assign(new Error('OpenCode does not support rollback from a message position. This action is unavailable for OpenCode conversations.'), { statusCode: 409 })
|
|
2067
2279
|
}
|
|
2068
2280
|
|
|
2069
2281
|
const messages = Array.isArray(session.agent.state.messages) ? session.agent.state.messages : []
|
|
@@ -2074,7 +2286,14 @@ export async function rollbackSessionMessages(sessionId, rollbackMessageIndex) {
|
|
|
2074
2286
|
|
|
2075
2287
|
const nextMessages = messages.slice(0, rollbackIndex)
|
|
2076
2288
|
updateSessionMessages(session, nextMessages)
|
|
2077
|
-
|
|
2289
|
+
const compactedUpToIndex = Number(session.contextCompaction?.compactedUpToIndex) || 0
|
|
2290
|
+
if (rollbackIndex < compactedUpToIndex) {
|
|
2291
|
+
// 撤回越过压缩点,摘要覆盖的历史被截断,压缩失效
|
|
2292
|
+
resetSessionCompaction(session)
|
|
2293
|
+
} else {
|
|
2294
|
+
// 撤回发生在压缩点之后,摘要仍然有效,保留压缩上下文
|
|
2295
|
+
session.lastTransformedContextMessages = null
|
|
2296
|
+
}
|
|
2078
2297
|
session.status = 'idle'
|
|
2079
2298
|
session.finishedAt = new Date().toISOString()
|
|
2080
2299
|
await persistSession(session)
|
|
@@ -2127,29 +2346,59 @@ function scheduleSessionTitleGeneration(session, userMessage) {
|
|
|
2127
2346
|
session.titleGenerationPromiseId = generationId
|
|
2128
2347
|
}
|
|
2129
2348
|
|
|
2349
|
+
async function refreshSessionModelBinding(session) {
|
|
2350
|
+
if (!session?.model || (!session.modelRef && session.resolvePersistedModel !== true)) return session
|
|
2351
|
+
const binding = await resolveModelBinding(
|
|
2352
|
+
session.modelRef ? { modelRef: session.modelRef } : { model: session.model },
|
|
2353
|
+
{
|
|
2354
|
+
context: session.modelAccessContext || {},
|
|
2355
|
+
currentModel: session.model,
|
|
2356
|
+
allowCurrentHidden: true,
|
|
2357
|
+
forExecution: true,
|
|
2358
|
+
legacySnapshot: session.model,
|
|
2359
|
+
},
|
|
2360
|
+
)
|
|
2361
|
+
session.model = binding.model
|
|
2362
|
+
session.modelRef = binding.modelRef
|
|
2363
|
+
session.resolvePersistedModel = true
|
|
2364
|
+
session.agent.state.model = binding.model
|
|
2365
|
+
return session
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2130
2368
|
/**
|
|
2131
2369
|
* Send a user message to the agent and start the agent loop.
|
|
2132
2370
|
* Returns immediately; events are streamed via the event bus.
|
|
2133
2371
|
*/
|
|
2134
|
-
export async function runPrompt(sessionId, message, selectedCapabilities = [], promptCommand = null, transientContextPrompt = null) {
|
|
2372
|
+
export async function runPrompt(sessionId, message, selectedCapabilities = [], promptCommand = null, transientContextPrompt = null, modelAccessContext = null) {
|
|
2135
2373
|
let session = await syncSessionFromStorage(sessionId)
|
|
2136
2374
|
if (!session) {
|
|
2137
2375
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2138
2376
|
}
|
|
2377
|
+
if (modelAccessContext) session.modelAccessContext = modelAccessContext
|
|
2378
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) await refreshSessionModelBinding(session)
|
|
2139
2379
|
|
|
2140
2380
|
if (session.agent.state.isStreaming || session.abortPending) {
|
|
2141
|
-
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), {
|
|
2381
|
+
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), {
|
|
2382
|
+
statusCode: 409,
|
|
2383
|
+
errorCode: 'GENERATION_ALREADY_RUNNING',
|
|
2384
|
+
})
|
|
2142
2385
|
}
|
|
2143
2386
|
|
|
2144
|
-
await refreshMemoryState(session)
|
|
2387
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) await refreshMemoryState(session)
|
|
2145
2388
|
resetIdleTimer(session)
|
|
2146
2389
|
|
|
2147
2390
|
// Build user message
|
|
2148
2391
|
const initialUserMessage = typeof message === 'string'
|
|
2149
2392
|
? { role: 'user', content: message, timestamp: new Date().toISOString() }
|
|
2150
2393
|
: message
|
|
2151
|
-
const commandState =
|
|
2152
|
-
|
|
2394
|
+
const commandState = session.harness === AGENT_HARNESS_QUICKFORGE
|
|
2395
|
+
? await resolveCommandState(session, initialUserMessage, promptCommand)
|
|
2396
|
+
: { userMessage: initialUserMessage }
|
|
2397
|
+
const resolvedUserMessage = commandState.userMessage ?? initialUserMessage
|
|
2398
|
+
|
|
2399
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
2400
|
+
session.agent.validatePrompt(resolvedUserMessage)
|
|
2401
|
+
}
|
|
2153
2402
|
|
|
2154
2403
|
if (commandState.textResponse) {
|
|
2155
2404
|
session.agent.state.messages = [
|
|
@@ -2176,6 +2425,10 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2176
2425
|
return compactSession(session, initialUserMessage, commandState.compact)
|
|
2177
2426
|
}
|
|
2178
2427
|
|
|
2428
|
+
const userMessage = session.harness === AGENT_HARNESS_QUICKFORGE
|
|
2429
|
+
? prepareCloudUserMessage(session, resolvedUserMessage)
|
|
2430
|
+
: resolvedUserMessage
|
|
2431
|
+
|
|
2179
2432
|
// Set a meaningful fallback immediately. The AI title request starts only
|
|
2180
2433
|
// after the first user message has been persisted by the message_end handler.
|
|
2181
2434
|
if (session.titleSource === 'default' && session.title === 'New chat') {
|
|
@@ -2197,11 +2450,26 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2197
2450
|
// Fire and forget — events come through eventBus
|
|
2198
2451
|
session.agent.prompt(userMessage).catch((err) => {
|
|
2199
2452
|
logger.error(`Agent prompt error for session ${sessionId}:`, err, { sessionId })
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2453
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) return
|
|
2454
|
+
const errorMessage = err.message || 'Unknown error'
|
|
2455
|
+
// Surface the failure at the end of the conversation itself so the user
|
|
2456
|
+
// sees the reason in the transcript, not only via toast/notification.
|
|
2457
|
+
session.agent.state.messages = appendAssistantErrorMessageOnce(
|
|
2458
|
+
session.agent.state.messages,
|
|
2459
|
+
errorMessage,
|
|
2460
|
+
session.model,
|
|
2461
|
+
)
|
|
2462
|
+
session.agent.state.errorMessage = errorMessage
|
|
2463
|
+
session.agent.state.isStreaming = false
|
|
2464
|
+
session.status = 'error'
|
|
2465
|
+
session.finishedAt = new Date().toISOString()
|
|
2466
|
+
const messages = session.agent.state.messages
|
|
2467
|
+
emitSessionEvent(session, { type: 'message_end', messages })
|
|
2468
|
+
emitSessionEvent(session, { type: 'error', error: errorMessage })
|
|
2469
|
+
emitSessionEvent(session, { type: 'agent_end', messages, errorMessage, status: 'error' })
|
|
2470
|
+
flushSessionPersist(session).catch((persistErr) =>
|
|
2471
|
+
logger.error(`Failed to persist session ${sessionId} after prompt error:`, persistErr, { sessionId }),
|
|
2472
|
+
)
|
|
2205
2473
|
}).finally(() => {
|
|
2206
2474
|
session.activeCommandName = null
|
|
2207
2475
|
session.activeCommandPermissions = null
|
|
@@ -2221,14 +2489,22 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2221
2489
|
* Trims messages to keep up to and including the last user message,
|
|
2222
2490
|
* removing the assistant response that follows it.
|
|
2223
2491
|
*/
|
|
2224
|
-
export async function continueSession(sessionId) {
|
|
2492
|
+
export async function continueSession(sessionId, modelAccessContext = null) {
|
|
2225
2493
|
const session = agentSessions.get(sessionId)
|
|
2226
2494
|
if (!session) {
|
|
2227
2495
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2228
2496
|
}
|
|
2229
2497
|
if (session.agent.state.isStreaming) {
|
|
2230
|
-
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), {
|
|
2498
|
+
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), {
|
|
2499
|
+
statusCode: 409,
|
|
2500
|
+
errorCode: 'GENERATION_ALREADY_RUNNING',
|
|
2501
|
+
})
|
|
2502
|
+
}
|
|
2503
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
2504
|
+
throw Object.assign(new Error('OpenCode does not support retry from a message position. This action is unavailable for OpenCode conversations.'), { statusCode: 409 })
|
|
2231
2505
|
}
|
|
2506
|
+
if (modelAccessContext) session.modelAccessContext = modelAccessContext
|
|
2507
|
+
await refreshSessionModelBinding(session)
|
|
2232
2508
|
|
|
2233
2509
|
const messages = Array.isArray(session.agent.state.messages) ? session.agent.state.messages : []
|
|
2234
2510
|
|
|
@@ -2246,10 +2522,20 @@ export async function continueSession(sessionId) {
|
|
|
2246
2522
|
|
|
2247
2523
|
const lastUserMessage = messages[lastUserIndex]
|
|
2248
2524
|
const commandState = await resolveCommandState(session, lastUserMessage)
|
|
2249
|
-
const continuedUserMessage = commandState.userMessage ?? lastUserMessage
|
|
2525
|
+
const continuedUserMessage = prepareCloudUserMessage(session, commandState.userMessage ?? lastUserMessage)
|
|
2250
2526
|
const trimmedMessages = messages.slice(0, lastUserIndex).concat(continuedUserMessage)
|
|
2251
2527
|
updateSessionMessages(session, trimmedMessages)
|
|
2252
|
-
|
|
2528
|
+
const compactedUpToIndex = Number(session.contextCompaction?.compactedUpToIndex) || 0
|
|
2529
|
+
if (lastUserIndex < compactedUpToIndex) {
|
|
2530
|
+
// 重试点越过压缩点,摘要覆盖的历史被截断,压缩失效
|
|
2531
|
+
resetSessionCompaction(session)
|
|
2532
|
+
} else {
|
|
2533
|
+
// 重试点位于压缩点之后,摘要仍然有效,保留压缩上下文
|
|
2534
|
+
session.lastTransformedContextMessages = null
|
|
2535
|
+
}
|
|
2536
|
+
if (isManagedCloudModel(session.model) && logicalMessageId(continuedUserMessage)) {
|
|
2537
|
+
await flushSessionPersist(session)
|
|
2538
|
+
}
|
|
2253
2539
|
|
|
2254
2540
|
resetIdleTimer(session)
|
|
2255
2541
|
session.activeCommandName = commandState.commandName ?? null
|
|
@@ -2334,9 +2620,9 @@ export function steerAgent(sessionId, message) {
|
|
|
2334
2620
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2335
2621
|
}
|
|
2336
2622
|
|
|
2337
|
-
const agentMessage = typeof message === 'string'
|
|
2623
|
+
const agentMessage = prepareCloudUserMessage(session, typeof message === 'string'
|
|
2338
2624
|
? { role: 'user', content: message, timestamp: Date.now() }
|
|
2339
|
-
: message
|
|
2625
|
+
: message)
|
|
2340
2626
|
|
|
2341
2627
|
session.agent.steer(agentMessage)
|
|
2342
2628
|
return { sessionId, steered: true }
|
|
@@ -2351,9 +2637,9 @@ export function followUpAgent(sessionId, message) {
|
|
|
2351
2637
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2352
2638
|
}
|
|
2353
2639
|
|
|
2354
|
-
const agentMessage = typeof message === 'string'
|
|
2640
|
+
const agentMessage = prepareCloudUserMessage(session, typeof message === 'string'
|
|
2355
2641
|
? { role: 'user', content: message, timestamp: Date.now() }
|
|
2356
|
-
: message
|
|
2642
|
+
: message)
|
|
2357
2643
|
|
|
2358
2644
|
session.agent.followUp(agentMessage)
|
|
2359
2645
|
return { sessionId, followUp: true }
|
|
@@ -2439,10 +2725,13 @@ export function getSessionState(sessionId) {
|
|
|
2439
2725
|
source: session.source || undefined,
|
|
2440
2726
|
channelId: session.channelId || undefined,
|
|
2441
2727
|
channelName: session.channelName || undefined,
|
|
2728
|
+
harness: session.harness,
|
|
2729
|
+
harnessSessionId: session.agent.harnessSessionId || session.harnessSessionId || undefined,
|
|
2442
2730
|
accessMode: session.accessMode,
|
|
2443
2731
|
yoloMode: session.yoloMode,
|
|
2444
2732
|
systemPrompt: session.agent.state.systemPrompt,
|
|
2445
2733
|
model: session.model,
|
|
2734
|
+
modelRef: session.modelRef || undefined,
|
|
2446
2735
|
thinkingLevel: session.thinkingLevel,
|
|
2447
2736
|
title: session.title,
|
|
2448
2737
|
titleSource: session.titleSource,
|
|
@@ -2459,6 +2748,7 @@ export function getSessionState(sessionId) {
|
|
|
2459
2748
|
contextUsage: getSessionContextUsage(session),
|
|
2460
2749
|
pendingToolApproval: getPendingApprovalForSession(session.sessionId),
|
|
2461
2750
|
pendingAutoCompactApproval: getPendingAutoCompactApprovalForSession(session.sessionId),
|
|
2751
|
+
acpSession: session.harness === AGENT_HARNESS_OPENCODE ? session.agent.state.acpSession : undefined,
|
|
2462
2752
|
isStreaming: session.abortPending ? false : session.agent.state.isStreaming,
|
|
2463
2753
|
errorMessage: session.agent.state.errorMessage,
|
|
2464
2754
|
}
|
|
@@ -2477,6 +2767,8 @@ export function getSessionStatus(sessionId) {
|
|
|
2477
2767
|
sessionId: session.sessionId,
|
|
2478
2768
|
scope: session.scope,
|
|
2479
2769
|
projectId: session.projectId,
|
|
2770
|
+
harness: session.harness,
|
|
2771
|
+
harnessSessionId: session.agent.harnessSessionId || session.harnessSessionId || undefined,
|
|
2480
2772
|
source: session.source || undefined,
|
|
2481
2773
|
channelId: session.channelId || undefined,
|
|
2482
2774
|
channelName: session.channelName || undefined,
|
|
@@ -2548,6 +2840,7 @@ export async function destroyAgent(sessionId) {
|
|
|
2548
2840
|
|
|
2549
2841
|
try {
|
|
2550
2842
|
session.agent.abort()
|
|
2843
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) await session.agent.dispose?.()
|
|
2551
2844
|
} catch {
|
|
2552
2845
|
// ignore
|
|
2553
2846
|
}
|
|
@@ -2597,6 +2890,8 @@ export async function restoreAgent(sessionId) {
|
|
|
2597
2890
|
accessMode: normalizeAccessMode(sessionData.accessMode, sessionData.yoloMode),
|
|
2598
2891
|
yoloMode: sessionData.yoloMode || false,
|
|
2599
2892
|
model: sessionData.model,
|
|
2893
|
+
modelRef: sessionData.modelRef || null,
|
|
2894
|
+
resolvePersistedModel: true,
|
|
2600
2895
|
thinkingLevel: sessionData.thinkingLevel || 'off',
|
|
2601
2896
|
messages: sessionData.messages || [],
|
|
2602
2897
|
title: sessionData.title || 'New chat',
|
|
@@ -2604,11 +2899,15 @@ export async function restoreAgent(sessionId) {
|
|
|
2604
2899
|
createdAt: sessionData.createdAt,
|
|
2605
2900
|
lastModified: sessionData.lastModified,
|
|
2606
2901
|
contextCompaction: sessionData.contextCompaction || null,
|
|
2902
|
+
harness: normalizeAgentHarness(sessionData.harness),
|
|
2903
|
+
harnessSessionId: typeof sessionData.harnessSessionId === 'string' ? sessionData.harnessSessionId : null,
|
|
2904
|
+
openCodeUsage: sessionData.openCodeUsage || null,
|
|
2607
2905
|
idleRetention: sessionData.idleRetention || null,
|
|
2608
2906
|
stateVersion: sessionData.stateVersion,
|
|
2609
2907
|
})
|
|
2610
2908
|
} catch (err) {
|
|
2611
2909
|
logger.error(`Failed to restore agent ${sessionId}:`, err, { sessionId })
|
|
2910
|
+
if (err?.statusCode === 503 || err?.errorCode === 'OPENCODE_UNAVAILABLE') throw err
|
|
2612
2911
|
return null
|
|
2613
2912
|
}
|
|
2614
2913
|
}
|
|
@@ -2678,6 +2977,8 @@ export function listSessions() {
|
|
|
2678
2977
|
scope: session.scope,
|
|
2679
2978
|
status: session.status,
|
|
2680
2979
|
title: session.title,
|
|
2980
|
+
harness: session.harness,
|
|
2981
|
+
harnessSessionId: session.agent.harnessSessionId || session.harnessSessionId || undefined,
|
|
2681
2982
|
source: session.source || undefined,
|
|
2682
2983
|
channelId: session.channelId || undefined,
|
|
2683
2984
|
channelName: session.channelName || undefined,
|
|
@@ -2733,7 +3034,7 @@ export async function updateSessionAccessMode(sessionId, accessMode) {
|
|
|
2733
3034
|
|
|
2734
3035
|
session.accessMode = normalizeAccessMode(accessMode, session.accessMode)
|
|
2735
3036
|
session.yoloMode = yoloModeFromAccessMode(session.accessMode)
|
|
2736
|
-
await rebuildSessionTools(session)
|
|
3037
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) await rebuildSessionTools(session)
|
|
2737
3038
|
await persistSession(session)
|
|
2738
3039
|
|
|
2739
3040
|
const state = getSessionState(sessionId)
|
|
@@ -2746,25 +3047,124 @@ export async function updateSessionYoloMode(sessionId, yoloMode) {
|
|
|
2746
3047
|
return updateSessionAccessMode(sessionId, yoloMode ? AGENT_ACCESS_MODE_FULL_ACCESS : AGENT_ACCESS_MODE_DEFAULT)
|
|
2747
3048
|
}
|
|
2748
3049
|
|
|
3050
|
+
function requireOpenCodeHarnessSession(sessionId) {
|
|
3051
|
+
const session = agentSessions.get(sessionId)
|
|
3052
|
+
if (!session) throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
3053
|
+
if (session.harness !== AGENT_HARNESS_OPENCODE) {
|
|
3054
|
+
throw Object.assign(new Error('Harness configuration is only available for OpenCode sessions.'), { statusCode: 409 })
|
|
3055
|
+
}
|
|
3056
|
+
return session
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
export async function updateSessionHarnessConfigOption(sessionId, configId, value) {
|
|
3060
|
+
const session = requireOpenCodeHarnessSession(sessionId)
|
|
3061
|
+
await session.agent.setConfigOption(configId, value)
|
|
3062
|
+
const state = getSessionState(sessionId)
|
|
3063
|
+
emitSessionEvent(session, { type: 'state', ...state })
|
|
3064
|
+
return { sessionId, acpSession: state.acpSession }
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
export async function updateSessionHarnessMode(sessionId, modeId) {
|
|
3068
|
+
const session = requireOpenCodeHarnessSession(sessionId)
|
|
3069
|
+
await session.agent.setMode(modeId)
|
|
3070
|
+
const state = getSessionState(sessionId)
|
|
3071
|
+
emitSessionEvent(session, { type: 'state', ...state })
|
|
3072
|
+
return { sessionId, acpSession: state.acpSession }
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
/**
|
|
3076
|
+
* Fork the entire current OpenCode session into a new QuickForge session.
|
|
3077
|
+
*
|
|
3078
|
+
* OpenCode ACP only supports whole-session `session/fork` (no message-position
|
|
3079
|
+
* fork), so the new agent is created with the full message history and
|
|
3080
|
+
* `sourceHarnessSessionId` pointing at the current ACP session. The new session
|
|
3081
|
+
* is persisted immediately and announced through the existing `session_forked`
|
|
3082
|
+
* event so clients switch to it without a message-level fork semantic.
|
|
3083
|
+
*/
|
|
3084
|
+
export async function forkSession(sessionId) {
|
|
3085
|
+
const session = requireOpenCodeHarnessSession(sessionId)
|
|
3086
|
+
if (session.agent.state.isStreaming) {
|
|
3087
|
+
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before forking the conversation.'), {
|
|
3088
|
+
statusCode: 409,
|
|
3089
|
+
errorCode: 'GENERATION_STILL_RUNNING_BEFORE_FORK',
|
|
3090
|
+
})
|
|
3091
|
+
}
|
|
3092
|
+
const sourceHarnessSessionId = session.agent.harnessSessionId
|
|
3093
|
+
if (!sourceHarnessSessionId) {
|
|
3094
|
+
throw Object.assign(new Error('This OpenCode conversation has no ACP session to fork.'), { statusCode: 409 })
|
|
3095
|
+
}
|
|
3096
|
+
const messages = session.agent.state.messages
|
|
3097
|
+
if (messages.length === 0) {
|
|
3098
|
+
throw Object.assign(new Error('There is no conversation to fork yet.'), { statusCode: 400 })
|
|
3099
|
+
}
|
|
3100
|
+
const forkedSessionId = randomUUID()
|
|
3101
|
+
const forkedSession = await createAgent(forkedSessionId, {
|
|
3102
|
+
scope: session.scope,
|
|
3103
|
+
projectId: session.projectId,
|
|
3104
|
+
accessMode: session.accessMode,
|
|
3105
|
+
yoloMode: session.yoloMode,
|
|
3106
|
+
model: session.model,
|
|
3107
|
+
modelRef: session.modelRef,
|
|
3108
|
+
modelAccessContext: session.modelAccessContext,
|
|
3109
|
+
resolvePersistedModel: true,
|
|
3110
|
+
thinkingLevel: session.thinkingLevel,
|
|
3111
|
+
messages,
|
|
3112
|
+
title: session.title,
|
|
3113
|
+
titleSource: session.titleSource === 'default' ? 'manual' : session.titleSource,
|
|
3114
|
+
createdAt: new Date().toISOString(),
|
|
3115
|
+
harness: session.harness,
|
|
3116
|
+
sourceHarnessSessionId,
|
|
3117
|
+
idleRetention: session.idleRetention || null,
|
|
3118
|
+
})
|
|
3119
|
+
updateSessionMessages(forkedSession, messages)
|
|
3120
|
+
await persistSession(forkedSession)
|
|
3121
|
+
|
|
3122
|
+
emitSessionEvent(session, {
|
|
3123
|
+
type: 'session_forked',
|
|
3124
|
+
sourceSessionId: session.sessionId,
|
|
3125
|
+
targetSessionId: forkedSessionId,
|
|
3126
|
+
title: forkedSession.title,
|
|
3127
|
+
createdAt: forkedSession.createdAt,
|
|
3128
|
+
scope: forkedSession.scope,
|
|
3129
|
+
projectId: forkedSession.projectId,
|
|
3130
|
+
messages: forkedSession.agent.state.messages,
|
|
3131
|
+
})
|
|
3132
|
+
emitSessionEvent(forkedSession, { type: 'state', ...getSessionState(forkedSessionId) })
|
|
3133
|
+
emitSessionEvent(forkedSession, { type: 'message_end', messages: forkedSession.agent.state.messages })
|
|
3134
|
+
emitSessionEvent(forkedSession, { type: 'agent_end', messages: forkedSession.agent.state.messages })
|
|
3135
|
+
return {
|
|
3136
|
+
sessionId: forkedSessionId,
|
|
3137
|
+
title: forkedSession.title,
|
|
3138
|
+
createdAt: forkedSession.createdAt,
|
|
3139
|
+
scope: forkedSession.scope,
|
|
3140
|
+
projectId: forkedSession.projectId,
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
|
|
2749
3144
|
/**
|
|
2750
3145
|
* Update the model for an existing session.
|
|
2751
3146
|
* Syncs the model to both the session record (for persistence) and the agent state (for API calls).
|
|
2752
3147
|
* Does NOT force persistence — normal lifecycle events (message_end, agent_end) will persist
|
|
2753
3148
|
* the updated model.
|
|
2754
3149
|
*/
|
|
2755
|
-
export function updateSessionModel(sessionId, model) {
|
|
3150
|
+
export function updateSessionModel(sessionId, model, modelRef = null) {
|
|
2756
3151
|
const session = agentSessions.get(sessionId)
|
|
2757
3152
|
if (!session) {
|
|
2758
3153
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2759
3154
|
}
|
|
3155
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
3156
|
+
throw Object.assign(new Error('OpenCode manages its model natively.'), { statusCode: 409 })
|
|
3157
|
+
}
|
|
2760
3158
|
if (!model) {
|
|
2761
3159
|
throw Object.assign(new Error('Missing model'), { statusCode: 400 })
|
|
2762
3160
|
}
|
|
2763
3161
|
|
|
2764
3162
|
session.model = model
|
|
3163
|
+
session.modelRef = modelRef
|
|
3164
|
+
session.resolvePersistedModel = true
|
|
2765
3165
|
session.agent.state.model = model
|
|
2766
3166
|
|
|
2767
|
-
return { sessionId, model }
|
|
3167
|
+
return { sessionId, model, modelRef: modelRef || undefined }
|
|
2768
3168
|
}
|
|
2769
3169
|
|
|
2770
3170
|
/**
|
|
@@ -2775,6 +3175,9 @@ export function updateSessionThinkingLevel(sessionId, thinkingLevel) {
|
|
|
2775
3175
|
if (!session) {
|
|
2776
3176
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2777
3177
|
}
|
|
3178
|
+
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
3179
|
+
throw Object.assign(new Error('OpenCode manages its thinking level natively.'), { statusCode: 409 })
|
|
3180
|
+
}
|
|
2778
3181
|
if (!thinkingLevel) {
|
|
2779
3182
|
throw Object.assign(new Error('Missing thinkingLevel'), { statusCode: 400 })
|
|
2780
3183
|
}
|