farming-code 2.2.27 → 2.2.29
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 +15 -30
- package/README.zh_cn.md +15 -30
- package/THIRD_PARTY_NOTICES.md +28 -6
- package/backend/acp/packaged-claude-acp.js +13 -0
- package/backend/acp/packaged-codex-acp.js +2 -2
- package/backend/acp-runtime.js +52 -3
- package/backend/acp-transcript.js +174 -9
- package/backend/agent-manager.js +1081 -96
- package/backend/atomic-json-store.js +37 -0
- package/backend/cli-agents.js +9 -7
- package/backend/config-manager.js +211 -58
- package/backend/control-api.js +2 -1
- package/backend/data/LICENSE.agent-browser +201 -0
- package/backend/data/runtime-dependency-manifest.json +199 -0
- package/backend/data/runtime-dependency-sources.json +5 -0
- package/backend/executable-discovery.js +7 -0
- package/backend/farming-agent-bootstrap.js +49 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +7 -0
- package/backend/farming-app-cli.js +168 -65
- package/backend/farming-cli.js +57 -0
- package/backend/farming-session-store.js +92 -89
- package/backend/index-html.js +13 -0
- package/backend/main-agent-skills.js +13 -0
- package/backend/npm-update-helper.js +43 -6
- package/backend/preview-session-manager.js +68 -0
- package/backend/provider-adapters.js +54 -5
- package/backend/review-session-store.js +32 -25
- package/backend/review-state-store.js +13 -14
- package/backend/run-history-store.js +13 -16
- package/backend/runtime-dependency-manager.js +759 -0
- package/backend/runtime-executable-invocation.js +20 -0
- package/backend/server-process-identity.js +47 -0
- package/backend/server.js +231 -223
- package/backend/slash-command-discovery.js +138 -1
- package/backend/storage-layout.js +35 -0
- package/backend/update-service.js +27 -1
- package/backend/workspace-file-router.js +141 -0
- package/backend/workspace-file-service.js +19 -0
- package/backend/workspace-root-registry.js +6 -4
- package/config/farming.install.env.example +4 -0
- package/dist/acp/LICENSE.claude-agent-acp +191 -0
- package/dist/acp/LICENSE.claude-agent-sdk +1 -0
- package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
- package/dist/assets/App-BKNUILuL.css +1 -0
- package/dist/assets/App-Ex2oVVXO.js +693 -0
- package/dist/assets/{FileEditorMarkdownPreview-Da5jguUs.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +5 -5
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
- package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
- package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +12 -0
- package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-nnglrXFi.js → abnfDiagram-VRR7QNED-BY1qwq8A.js} +1 -1
- package/dist/assets/api-w2x5QfxB.js +1 -0
- package/dist/assets/{arc-CWoVlxJw.js → arc-DgZRAlwj.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-BEXMaFfY.js → architectureDiagram-ZJ3FMSHR-jFn144od.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-1mCezSBk.js → blockDiagram-677ZJIJ3-BmRjlV0H.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-DhTiI6Uv.js → c4Diagram-LMCZKHZV-DUKul6tT.js} +1 -1
- package/dist/assets/{workspace-editor-monaco-DLlws5Ig.js → channel-CI9j3QFD.js} +1 -1
- package/dist/assets/chunk-2Q5K7J3B-87UnWaoh.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-CCdsKd73.js → chunk-32BRIVSS-rGVu23o5.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-Gncjk4LF.js → chunk-52WLFC77-CvFDKgbB.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-Dlw7AXWp.js → chunk-5VM5RSS4-CFgnRW1C.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-ZBRhjLOh.js → chunk-7BUUIJ7U-Cp7bqW6s.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-CiDVUP82.js → chunk-C7G6YPKG-BIS1gjZO.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-z6gXP2_N.js → chunk-EX3LRPZG-BKG-cR9j.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DMZBccQa.js → chunk-FWX5IMBZ-B8PsTr8a.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-D8tIeoPk.js → chunk-HOUHSVGY-BHFOAiXT.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-b6oQ2Q17.js → chunk-ICXQ74PX-CvLE1J9X.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DSyBRaRG.js → chunk-JWPE2WC7-D8misks7.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-DAVH_p2a.js → chunk-MOJQB5TN-D-oNgSNH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-DxoiMCI-.js → chunk-OGEWGWER-C2N-la9e.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-NsOWh7HM.js → chunk-PUDLZKDR-BwI3Jz8q.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-Di4H7L21.js → chunk-Q4XR5HBZ-BuqGfsXA.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CZA9pvFX.js → chunk-RYQCIY6F-Bq4XZcGt.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DttpZg9Y.js → chunk-V7JOEXUC-QZLzdTTJ.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-Cb0-kQ9g.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-D-Fmtd4Z.js → chunk-VR4S4FIN-DcZTpML_.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-CUxMtfK2.js → chunk-WYO6CB5R-DCU8wuny.js} +2 -2
- package/dist/assets/chunk-XXDRQBXY-BZAC1Krv.js +1 -0
- package/dist/assets/{chunk-ZGVPDNZ5-CoV2QEl0.js → chunk-ZGVPDNZ5-B7r85QbY.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-DncwlQQc.js → chunk-ZIRB5QZD-BgEnDWhQ.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DcvGOuvi.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DDYUSofQ.js +1 -0
- package/dist/assets/code-dark-BEZTQwYZ.css +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BNIAz0nN.js → cose-bilkent-JH36ORCC-CThXS2qG.js} +1 -1
- package/dist/assets/{cssMode-DDWxTPm_.js → cssMode-Bdg5RpcJ.js} +1 -1
- package/dist/assets/{cynefinDiagram-TSTJHNR4-0xPI92Lg.js → cynefinDiagram-TSTJHNR4-DxBgzw25.js} +1 -1
- package/dist/assets/{dagre-DXlcHgr3.js → dagre-BM-GvGUZ.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-DfuMG4gU.js → dagre-VKFMJZFB-B7UCRmxR.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CT00bLZ6.js → diagram-FQU43EPY-CeL2DwKx.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-Xbh7zXUx.js → diagram-G47NLZAW-BEirglCC.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-SRDVPfn7.js → diagram-NH7WQ7WH-C6wcI-Rl.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-CQRli4Np.js → diagram-OA4YK3LP-DpM-XXtJ.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-DMf17WQi.js → diagram-WEI45ONY-BQyaut9k.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-DhpFkgJ5.js → ebnfDiagram-CCIWWBDH-UmAXsUhr.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-1IwxWmJo.js → erDiagram-Q63AITRT-CdYLEOch.js} +1 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-CO-z9-tq.js +1 -0
- package/dist/assets/{freemarker2-QBsTERH-.js → freemarker2-DfMDJfnE.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-BNCdfyr1.js → ganttDiagram-NO4QXBWP-DgSUNdUB.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-pbyWZuiX.js → gitGraphDiagram-IHSO6WYX-DjwjtRtF.js} +1 -1
- package/dist/assets/{handlebars-CuoxPo5A.js → handlebars-BEdPtzMT.js} +1 -1
- package/dist/assets/{html-gX5PEu0Q.js → html-B1fPb1QD.js} +1 -1
- package/dist/assets/html2canvas-DsczWxBa.js +5 -0
- package/dist/assets/{htmlMode-DIWUbUxd.js → htmlMode-CY2ZjHxH.js} +1 -1
- package/dist/assets/{index-CtwKYTiC.js → index-CQNaZc3l.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-BcTOgDbe.js → infoDiagram-FWYZ7A6U-Bi9076l2.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-DCdvGKrn.js → ishikawaDiagram-FXEZZL3T-DaMaNZN7.js} +1 -1
- package/dist/assets/{javascript-BYKGIUGs.js → javascript-BV2OsWA0.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-BZ-1q_IT.js → journeyDiagram-5HDEW3XC-CfDy69EZ.js} +1 -1
- package/dist/assets/{jsonMode-AFmQQP6u.js → jsonMode-zd07aeuX.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-BIgejCjj.js → kanban-definition-HUTT4EX6-XFvZ722B.js} +1 -1
- package/dist/assets/{line-CfptFMmX.js → line-BXK9PKLn.js} +1 -1
- package/dist/assets/{linear-Dld3_ERu.js → linear-CULvmq2Z.js} +1 -1
- package/dist/assets/{liquid-DBIl-f0z.js → liquid-BParI24X.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DK6tqicV.js → lspLanguageFeatures-BzredkoV.js} +1 -1
- package/dist/assets/main-DK9Sy7-c.css +1 -0
- package/dist/assets/{map-LZWWoeWa.js → map-C9Qw9eWh.js} +1 -1
- package/dist/assets/{mdx-DS-bVtZN.js → mdx-vGzUJN98.js} +1 -1
- package/dist/assets/{mermaid.core-gSpX30pV.js → mermaid.core-CzazaIKr.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-BKBaI4LW.js → mindmap-definition-LN4V7U3C-Uoff7hZ3.js} +1 -1
- package/dist/assets/{monaco.contribution-BXz-lfFB.js → monaco.contribution-BmaeBQEc.js} +2 -2
- package/dist/assets/{ordinal-BLrOss5K.js → ordinal-r6pIwRdZ.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-sBvDY3VX.js → pegDiagram-2B236MQR-CCBiHQjk.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-DWlWcu08.js → pieDiagram-ENE6RG2P-D61s7l75.js} +1 -1
- package/dist/assets/{python-C93q3xXz.js → python-CUwdLgsI.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-DZutUd3J.js → quadrantDiagram-ABIIQ3AL-D_CWk2DQ.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-DQb3lXGM.js → railroadDiagram-RFXS5EU6-CdlCHtxN.js} +1 -1
- package/dist/assets/{razor-CBifNGXO.js → razor-BScebUxU.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-DqFGXu2e.js → requirementDiagram-TGXJPOKE-BLpHtkoC.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-d7ECahnO.js → sankeyDiagram-HTMAVEWB-CkhYZN2j.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-DdsbKqct.js → sequenceDiagram-DBY2YBRQ-8bploKJl.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-daTB6OcF.js → sizeCapture-X5ZJPWSS-DtSdkS0d.js} +1 -1
- package/dist/assets/{src-WBW1QxJt.js → src-6yeq6WBA.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-P6iSrgwR.js → stateDiagram-2N3HPSRC-DfmVGpfA.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-B2c9CQ-J.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BGnZJx1M.js → swimlanes-5IMT3BWC-DRLk2zRF.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B7_APh_h.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-rA-r3aUR.js → timeline-definition-FHXFAJF6-Cngfy588.js} +1 -1
- package/dist/assets/{tsMode-B0oUTcXQ.js → tsMode-DzUAibXK.js} +1 -1
- package/dist/assets/{typescript-KJKfYO7K.js → typescript-D9EOeM7N.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-DiVKUjA9.js → vennDiagram-L72KCM5P-DeJn63Lq.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-Cfxh7UOn.js → wardleyDiagram-EHGQE667-BH1nzy77.js} +1 -1
- package/dist/assets/{workers-BSQRIkqa.js → workers-DAiomYZM.js} +1 -1
- package/dist/assets/workspace-editor-model-CSGCl21x.js +1 -0
- package/dist/assets/{workspace-editor-monaco-BIpqcWf1.js → workspace-editor-monaco-CJNwbpCy.js} +3 -3
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
- package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
- package/dist/assets/{xml-C8FAcN6j.js → xml-wWl1dhkQ.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG--hmryQeL.js → xychartDiagram-FW5EYKEG-SkO2prCU.js} +1 -1
- package/dist/assets/{yaml-BQ4NrRhG.js → yaml-Dc2WDaOZ.js} +1 -1
- package/dist/crt-markdown-renderer.js +46 -46
- package/dist/index.html +32 -3
- package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
- package/extensions/browser/backend/agent-capability.js +55 -0
- package/extensions/browser/backend/browser-mcp-server.js +621 -0
- package/extensions/browser/backend/browser-resource-manager.js +1309 -0
- package/extensions/browser/backend/browser-resource-store.js +190 -0
- package/extensions/browser/backend/browser-router.js +133 -0
- package/extensions/browser/backend/executable-discovery.js +224 -0
- package/extensions/browser/backend/farming-browser-client.js +83 -0
- package/extensions/browser/backend/index.js +7 -0
- package/extensions/browser/bin/farming-browser +471 -0
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +360 -0
- package/extensions/browser/frontend/BrowserViewer.tsx +500 -0
- package/extensions/browser/frontend/browser-resource-state.ts +60 -0
- package/extensions/browser/frontend/browser.css +567 -0
- package/extensions/browser/frontend/types.ts +31 -0
- package/extensions/browser/frontend/useBrowserResources.ts +158 -0
- package/frontend/session-bridge.js +7 -6
- package/frontend/skins/crt/app.js +64 -4
- package/index.html +31 -2
- package/package.json +27 -18
- package/shared/backend-connection-status.d.ts +12 -0
- package/shared/backend-connection-status.js +35 -4
- package/shared/browser-protocol.js +1 -1
- package/backend/agent-activity.js +0 -35
- package/dist/assets/App-BrP-ENHg.css +0 -1
- package/dist/assets/App-Cm8qTjsc.js +0 -210
- package/dist/assets/FileEditorPane-fFqHA4wW.js +0 -2
- package/dist/assets/IconGlyphs-C2WFcoJS.js +0 -1
- package/dist/assets/ProjectFilesSection-Cyy3Vcn8.js +0 -12
- package/dist/assets/ReviewPage-L1vh1Ggs.js +0 -3
- package/dist/assets/api-D8nyOEbz.js +0 -1
- package/dist/assets/channel-BVWNatUB.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-UHNr_yhX.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +0 -1
- package/dist/assets/code-dark-BKtvf0Xc.css +0 -1
- package/dist/assets/file-icons-BqiH0rDI.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +0 -1
- package/dist/assets/main-XMXgyJJt.css +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +0 -8
- package/dist/assets/workspace-editor-model-D0Im34S5.js +0 -1
- package/dist/assets/workspace-view-state-BiuEbmRr.js +0 -7
- /package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +0 -0
- /package/dist/assets/{abap-Cc5D5h5i.js → abap-CtX5nWyX.js} +0 -0
- /package/dist/assets/{apex-BKopwLmx.js → apex-DQJRAHwc.js} +0 -0
- /package/dist/assets/{array-HeX70jSN.js → array-BTwzYU0x.js} +0 -0
- /package/dist/assets/{azcli-DI-C34WA.js → azcli-9SMrOQ4q.js} +0 -0
- /package/dist/assets/{bat-Cbp6u42z.js → bat-CBGLUsw1.js} +0 -0
- /package/dist/assets/{bicep-4QA1jTCM.js → bicep-pDx8da1v.js} +0 -0
- /package/dist/assets/{cameligo-Dda_adPs.js → cameligo-hZV3vQ6_.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-Di0sYzi1.js → chunk-Y2CYZVJY-BYi0W5ea.js} +0 -0
- /package/dist/assets/{clojure-CvY8A83L.js → clojure-BhCwU3gq.js} +0 -0
- /package/dist/assets/{coffee-4LX2Sv8A.js → coffee-BVB1PsgH.js} +0 -0
- /package/dist/assets/{core-B9MwRjDE.js → core-vPkV8P_m.js} +0 -0
- /package/dist/assets/{cpp-D4AzSV28.js → cpp-eTjuiNPq.js} +0 -0
- /package/dist/assets/{csharp-C4nWN4Yd.js → csharp-1kP-kKZT.js} +0 -0
- /package/dist/assets/{csp-CiEaaS5Z.js → csp-C_iQqIes.js} +0 -0
- /package/dist/assets/{css-DXxBRsbk.js → css-CxOs7IfP.js} +0 -0
- /package/dist/assets/{cypher-DOMzqyFA.js → cypher-Cd-a9Y82.js} +0 -0
- /package/dist/assets/{cytoscape.esm-Cl8iC3D7.js → cytoscape.esm-OLojuicg.js} +0 -0
- /package/dist/assets/{dart-BWS7MXt_.js → dart-whhXpexn.js} +0 -0
- /package/dist/assets/{defaultLocale-GJwWH1Jr.js → defaultLocale-CPimVhfE.js} +0 -0
- /package/dist/assets/{dist-ClAL-Tv5.js → dist-CLPYXg1u.js} +0 -0
- /package/dist/assets/{dockerfile-Baf4U3xT.js → dockerfile-DibBi8b6.js} +0 -0
- /package/dist/assets/{ecl-CT7udlkQ.js → ecl-DlF1KdoW.js} +0 -0
- /package/dist/assets/{editor.api2-B9h6iFPK.js → editor.api2-D74ufkbr.js} +0 -0
- /package/dist/assets/{elixir-CIbAZw5l.js → elixir-CHQ8uFT3.js} +0 -0
- /package/dist/assets/{flow9-Dqiw45WV.js → flow9-8nUnVKk1.js} +0 -0
- /package/dist/assets/{fsharp-E8E2L9jU.js → fsharp-DNZNZyEE.js} +0 -0
- /package/dist/assets/{go-CouVSN4D.js → go-_kPJjNc_.js} +0 -0
- /package/dist/assets/{graphlib-CbSvZcZU.js → graphlib-CBGF9gA9.js} +0 -0
- /package/dist/assets/{graphql-a6h6h5-t.js → graphql-4HCNVt5g.js} +0 -0
- /package/dist/assets/{hcl-vso3e6N-.js → hcl-C87ymY0o.js} +0 -0
- /package/dist/assets/{ini-SwZZkotR.js → ini-BYrrGNTc.js} +0 -0
- /package/dist/assets/{init-BPLPMQ3Y.js → init-8_PCa3LO.js} +0 -0
- /package/dist/assets/{java-CtRLC4_3.js → java-B211rJbk.js} +0 -0
- /package/dist/assets/{julia-CuJWFKQH.js → julia-DVPsRtBm.js} +0 -0
- /package/dist/assets/{katex-C8DPyI6s.js → katex-C2iYad0m.js} +0 -0
- /package/dist/assets/{kotlin-B_kBhsre.js → kotlin-BYyNerjp.js} +0 -0
- /package/dist/assets/{less-CtrHIfz3.js → less-EC9UblGE.js} +0 -0
- /package/dist/assets/{lexon-yVt1pJLu.js → lexon-B1yuUdqW.js} +0 -0
- /package/dist/assets/{lua-prOwgJ4U.js → lua-Bo6GFzxT.js} +0 -0
- /package/dist/assets/{m3-DhrdTQ84.js → m3-C-Y9cBVI.js} +0 -0
- /package/dist/assets/{markdown-DoI5HwSM.js → markdown-BHu-wLbe.js} +0 -0
- /package/dist/assets/{mermaid-parser.core-BQtepoLd.js → mermaid-parser.core-IxcGtNYG.js} +0 -0
- /package/dist/assets/{mips-Dye5jsBU.js → mips-Dkds26rX.js} +0 -0
- /package/dist/assets/{msdax-TkNOyIL8.js → msdax-PU4g-8_g.js} +0 -0
- /package/dist/assets/{mysql-CEc-RKZF.js → mysql-fPpuGpS7.js} +0 -0
- /package/dist/assets/{objective-c-CqqCsQz0.js → objective-c-Wy1e5JUf.js} +0 -0
- /package/dist/assets/{pascal-DO_pj8yg.js → pascal-BIXwvrqU.js} +0 -0
- /package/dist/assets/{pascaligo-1cbdZdF3.js → pascaligo-DwR0AtL_.js} +0 -0
- /package/dist/assets/{path-YdFzr2W6.js → path-BxQuywGl.js} +0 -0
- /package/dist/assets/{perl-B_N3PYfC.js → perl-CGHAl8my.js} +0 -0
- /package/dist/assets/{pgsql-9iYk1FQ7.js → pgsql-B91M73E2.js} +0 -0
- /package/dist/assets/{php-rO2sgoUM.js → php-C70ttrCx.js} +0 -0
- /package/dist/assets/{pla-Cbgv60kq.js → pla-BNrXaRh0.js} +0 -0
- /package/dist/assets/{postiats-y6xrhy_M.js → postiats-Dv2IAZR9.js} +0 -0
- /package/dist/assets/{powerquery-BaD8gWNE.js → powerquery-CkY5Wq7J.js} +0 -0
- /package/dist/assets/{powershell-BMhG4JVe.js → powershell-uGIYLU3C.js} +0 -0
- /package/dist/assets/{protobuf-CdOP5kN3.js → protobuf-CavfmJu-.js} +0 -0
- /package/dist/assets/{pug-Ck60hNF9.js → pug-Drqnyitx.js} +0 -0
- /package/dist/assets/{qrcode-DV-_rjU8.js → qrcode-7uGsTEmi.js} +0 -0
- /package/dist/assets/{qsharp-BcaKFfP-.js → qsharp-C7yF-Dti.js} +0 -0
- /package/dist/assets/{r-rFNes0iK.js → r-HXLGP0_-.js} +0 -0
- /package/dist/assets/{redis-DkpQ3K6G.js → redis-C4y6mf2p.js} +0 -0
- /package/dist/assets/{redshift-Di9N5TI3.js → redshift-DdQrFPrv.js} +0 -0
- /package/dist/assets/{restructuredtext-By5ZzLt6.js → restructuredtext-CBrf2PNj.js} +0 -0
- /package/dist/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
- /package/dist/assets/{ruby-fF5zGiLp.js → ruby-CVtMoX2B.js} +0 -0
- /package/dist/assets/{rust-C5b3AYY6.js → rust-Ckq4sbMP.js} +0 -0
- /package/dist/assets/{sb-CD07UNRs.js → sb-er66OpE3.js} +0 -0
- /package/dist/assets/{scala-jtn1ip9G.js → scala-BdWZz-hH.js} +0 -0
- /package/dist/assets/{scheme-C4vqMkn_.js → scheme-kTV_s2kP.js} +0 -0
- /package/dist/assets/{scss-I4Glxjbz.js → scss-Bymy2VJQ.js} +0 -0
- /package/dist/assets/{shell-DubUcuns.js → shell-bwEP1LQ1.js} +0 -0
- /package/dist/assets/{solidity-CNy2KSl2.js → solidity-DCddeBgL.js} +0 -0
- /package/dist/assets/{sophia-D9_URt84.js → sophia-BMN3AQig.js} +0 -0
- /package/dist/assets/{sparql-DviXpm4C.js → sparql-CSXOLjzi.js} +0 -0
- /package/dist/assets/{sql-vDRbAZgK.js → sql-COg8ewmK.js} +0 -0
- /package/dist/assets/{st-CnpnRqA7.js → st-CIaZtBS_.js} +0 -0
- /package/dist/assets/{swift-D_g8Ganz.js → swift-DXiOhUHu.js} +0 -0
- /package/dist/assets/{systemverilog-OuZa5h1M.js → systemverilog-BTDE6Dr2.js} +0 -0
- /package/dist/assets/{tcl-DBm9ucsV.js → tcl-B0zy9NvV.js} +0 -0
- /package/dist/assets/{twig-BRzEqX5Y.js → twig-D594l4Z1.js} +0 -0
- /package/dist/assets/{typespec-D4ZgGeX8.js → typespec-XKVjaPqo.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-Brws6Ar9.js → useWorkspaceMenuKeyboard-Bpy_s69Z.js} +0 -0
- /package/dist/assets/{vb-DcP4s6WS.js → vb-B2wYoKRV.js} +0 -0
- /package/dist/assets/{wgsl-CnRJ_QUf.js → wgsl-D0Yf2LbK.js} +0 -0
- /package/dist/assets/{workspace-working-copy-D8-s_Sgh.js → workspace-working-copy-C3FSR3N6.js} +0 -0
package/backend/agent-manager.js
CHANGED
|
@@ -16,7 +16,12 @@ const { archiveCodexSession, unarchiveCodexSession } = require('./codex-session-
|
|
|
16
16
|
const { buildAgentProviderSessionPlan, sessionFromExactResumeSource } = require('./agent-provider-session');
|
|
17
17
|
const { resolveAgentExecutable, resolveCompatibleCodexExecutable } = require('./executable-discovery');
|
|
18
18
|
const { ensureMainAgentSkillFiles, renderMainAgentBootstrap } = require('./main-agent-skills');
|
|
19
|
+
const {
|
|
20
|
+
appendOpenCodeBootstrap,
|
|
21
|
+
renderFarmingAgentBootstrap,
|
|
22
|
+
} = require('./farming-agent-bootstrap');
|
|
19
23
|
const { mainPageAgentSessionKey, resumedAgentSource } = require('./main-page-session');
|
|
24
|
+
const storageLayout = require('./storage-layout');
|
|
20
25
|
const { isSafeProviderSessionId, isTemporaryProviderSessionId } = require('./provider-session-id');
|
|
21
26
|
const { ProviderSessionService } = require('./provider-session-service');
|
|
22
27
|
const {
|
|
@@ -45,7 +50,13 @@ const {
|
|
|
45
50
|
stopPersistedAcpProcessGroup,
|
|
46
51
|
} = require('./acp-runtime');
|
|
47
52
|
const { chatRuntimeForProvider, isChatMode } = require('./chat-runtime');
|
|
48
|
-
const {
|
|
53
|
+
const {
|
|
54
|
+
acpTranscriptEntries,
|
|
55
|
+
acpTranscriptMedia,
|
|
56
|
+
acpToolChanges,
|
|
57
|
+
acpToolDetail,
|
|
58
|
+
acpToolReviewChanges,
|
|
59
|
+
} = require('./acp-transcript');
|
|
49
60
|
const {
|
|
50
61
|
applyCodexTerminalProfile,
|
|
51
62
|
codexTerminalProfileFromOutput,
|
|
@@ -67,6 +78,7 @@ const { inspectGitWorktree } = require('./git-worktree-info');
|
|
|
67
78
|
const { deserializeTerminalState } = require('./terminal-state-serialization');
|
|
68
79
|
const { compareNativePtyRuntimeEpochs } = require('./native-pty-controller-generation');
|
|
69
80
|
const { canonicalWorkspacePath } = require('./workspace-root-registry');
|
|
81
|
+
const { mergeBrowserMcpServer } = require('../extensions/browser/backend/agent-capability');
|
|
70
82
|
const {
|
|
71
83
|
TERMINAL_OPERATION_STATES,
|
|
72
84
|
activeLifecycleOperation,
|
|
@@ -83,6 +95,7 @@ const AGENT_USAGE_RATE_REFRESH_MS = 5 * 1000;
|
|
|
83
95
|
const ACTIVITY_UPDATE_INTERVAL_MS = 1000;
|
|
84
96
|
const ACTIVITY_HOT_SEC = 30 * 60;
|
|
85
97
|
const ACTIVITY_WARM_SEC = 3 * 60 * 60;
|
|
98
|
+
const MAX_COMPOSER_COMMANDS = 64;
|
|
86
99
|
const ACTIVITY_COOL_SEC = 12 * 60 * 60;
|
|
87
100
|
const ZOMBIE_IDLE_MS = 72 * 60 * 60 * 1000;
|
|
88
101
|
const ZOMBIE_SWEEP_INTERVAL_MS = 60 * 1000;
|
|
@@ -369,6 +382,41 @@ function normalizedComposerPrompt(message) {
|
|
|
369
382
|
return prompt;
|
|
370
383
|
}
|
|
371
384
|
|
|
385
|
+
function composerCommandHash(prompt) {
|
|
386
|
+
return crypto.createHash('sha256')
|
|
387
|
+
.update(JSON.stringify(stableJsonValue(prompt)))
|
|
388
|
+
.digest('hex');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function composerAdmissionError(message, uncertain = false) {
|
|
392
|
+
const error = new Error(message);
|
|
393
|
+
if (uncertain) error.uncertain = true;
|
|
394
|
+
return error;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function normalizedComposerCommands(commands) {
|
|
398
|
+
return (Array.isArray(commands) ? commands : [])
|
|
399
|
+
.filter(command => (
|
|
400
|
+
command
|
|
401
|
+
&& typeof command === 'object'
|
|
402
|
+
&& /^[A-Za-z0-9._:-]{1,160}$/.test(command.requestId)
|
|
403
|
+
&& typeof command.contentHash === 'string'
|
|
404
|
+
&& ['intent', 'accepted', 'unknown', 'failed'].includes(command.state)
|
|
405
|
+
))
|
|
406
|
+
.slice(-MAX_COMPOSER_COMMANDS)
|
|
407
|
+
.map(command => ({
|
|
408
|
+
requestId: command.requestId,
|
|
409
|
+
contentHash: command.contentHash,
|
|
410
|
+
state: command.state,
|
|
411
|
+
result: command.result && typeof command.result === 'object'
|
|
412
|
+
? JSON.parse(JSON.stringify(command.result))
|
|
413
|
+
: null,
|
|
414
|
+
error: typeof command.error === 'string' ? command.error.slice(0, 2000) : '',
|
|
415
|
+
createdAt: Number(command.createdAt) || 0,
|
|
416
|
+
updatedAt: Number(command.updatedAt) || 0,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
|
|
372
420
|
function isShellProgram(command) {
|
|
373
421
|
return ['bash', 'zsh', 'sh', 'fish'].includes(agentProgramName(command).toLowerCase());
|
|
374
422
|
}
|
|
@@ -738,6 +786,27 @@ function timestampSlug(now = new Date()) {
|
|
|
738
786
|
].join('');
|
|
739
787
|
}
|
|
740
788
|
|
|
789
|
+
function projectOperationSignature(value) {
|
|
790
|
+
return crypto.createHash('sha256')
|
|
791
|
+
.update(JSON.stringify(stableJsonValue(value)))
|
|
792
|
+
.digest('hex');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function worktreesFromPorcelain(output) {
|
|
796
|
+
const worktrees = [];
|
|
797
|
+
let current = null;
|
|
798
|
+
for (const line of String(output || '').split(/\r?\n/)) {
|
|
799
|
+
if (line.startsWith('worktree ')) {
|
|
800
|
+
if (current) worktrees.push(current);
|
|
801
|
+
current = { workspace: path.resolve(line.slice('worktree '.length)), branch: '' };
|
|
802
|
+
} else if (current && line.startsWith('branch refs/heads/')) {
|
|
803
|
+
current.branch = line.slice('branch refs/heads/'.length);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (current) worktrees.push(current);
|
|
807
|
+
return worktrees;
|
|
808
|
+
}
|
|
809
|
+
|
|
741
810
|
function isFarmingForkWorktreePath(workspace) {
|
|
742
811
|
const basename = path.basename(String(workspace || '').replace(/[\\/]+$/, ''));
|
|
743
812
|
return /-farming-fork-\d{8}-\d{6}(?:-\d+)?$/.test(basename);
|
|
@@ -757,6 +826,7 @@ class AgentManager extends EventEmitter {
|
|
|
757
826
|
this.controlUrl = options.controlUrl || '';
|
|
758
827
|
this.tokenFile = options.tokenFile || '';
|
|
759
828
|
this.authDisabled = options.authDisabled === true;
|
|
829
|
+
this.browserMcpEnabled = options.browserMcpEnabled === true;
|
|
760
830
|
this.skipExecutablePreflight = options.skipExecutablePreflight === true;
|
|
761
831
|
this.cliBinDir = options.cliBinDir || path.join(__dirname, '..', 'bin');
|
|
762
832
|
this.agentShellEnvProvider = typeof options.agentShellEnvProvider === 'function'
|
|
@@ -780,6 +850,7 @@ class AgentManager extends EventEmitter {
|
|
|
780
850
|
this.pendingResizeByAgent = new Map();
|
|
781
851
|
this.resizeDrains = new Map();
|
|
782
852
|
this.inputQueues = new Map();
|
|
853
|
+
this.composerAdmissions = new Map();
|
|
783
854
|
this.codexTerminalProfileQueues = new Map();
|
|
784
855
|
this.codexTerminalStartQueues = new Map();
|
|
785
856
|
this.codexTerminalStartOutput = new Map();
|
|
@@ -787,6 +858,7 @@ class AgentManager extends EventEmitter {
|
|
|
787
858
|
this.agentLifecycleOperations = new Map();
|
|
788
859
|
this.agentStartAdmissions = new Map();
|
|
789
860
|
this.createRequestAdmissions = new Map();
|
|
861
|
+
this.projectOperationAdmissions = new Map();
|
|
790
862
|
this.projectWorkspaceDeleteAdmissions = new Map();
|
|
791
863
|
this.activeInputOperations = new Set();
|
|
792
864
|
this.verifiedStoppedAgentIds = new Set();
|
|
@@ -1427,9 +1499,12 @@ class AgentManager extends EventEmitter {
|
|
|
1427
1499
|
const recoveredLifecycleOperation = activeLifecycleOperation(agentRecord);
|
|
1428
1500
|
if (
|
|
1429
1501
|
recoveredLifecycleOperation?.type === 'create'
|
|
1430
|
-
&&
|
|
1502
|
+
&& ['pending', 'membership-pending'].includes(recoveredLifecycleOperation.state)
|
|
1431
1503
|
) {
|
|
1432
1504
|
try {
|
|
1505
|
+
if (recoveredLifecycleOperation.state === 'membership-pending') {
|
|
1506
|
+
this.rememberMainPageProviderSession(agentRecord);
|
|
1507
|
+
}
|
|
1433
1508
|
this.transitionPersistentAgentOperation(
|
|
1434
1509
|
agentRecord,
|
|
1435
1510
|
recoveredLifecycleOperation.id,
|
|
@@ -1482,7 +1557,7 @@ class AgentManager extends EventEmitter {
|
|
|
1482
1557
|
changed = true;
|
|
1483
1558
|
}
|
|
1484
1559
|
|
|
1485
|
-
if (this.reconcileMissingTerminalLifecycleOperations(
|
|
1560
|
+
if (await this.reconcileMissingTerminalLifecycleOperations(
|
|
1486
1561
|
persistedRecords,
|
|
1487
1562
|
recoveredRuntimeAgentIds,
|
|
1488
1563
|
)) {
|
|
@@ -1519,7 +1594,7 @@ class AgentManager extends EventEmitter {
|
|
|
1519
1594
|
}
|
|
1520
1595
|
}
|
|
1521
1596
|
|
|
1522
|
-
reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
|
|
1597
|
+
async reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
|
|
1523
1598
|
let changed = false;
|
|
1524
1599
|
for (const record of Array.isArray(records) ? records : []) {
|
|
1525
1600
|
const agentId = String(record?.runtimeAgentId || '').trim();
|
|
@@ -1553,7 +1628,7 @@ class AgentManager extends EventEmitter {
|
|
|
1553
1628
|
operation.request?.previousRuntimeAgentId,
|
|
1554
1629
|
),
|
|
1555
1630
|
);
|
|
1556
|
-
} else {
|
|
1631
|
+
} else if (operation.type === 'delete') {
|
|
1557
1632
|
this.transitionPersistentAgentOperation(
|
|
1558
1633
|
recoveredAgent,
|
|
1559
1634
|
operation.id,
|
|
@@ -1567,7 +1642,27 @@ class AgentManager extends EventEmitter {
|
|
|
1567
1642
|
},
|
|
1568
1643
|
);
|
|
1569
1644
|
this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
|
|
1570
|
-
|
|
1645
|
+
} else {
|
|
1646
|
+
this.transitionPersistentAgentOperation(
|
|
1647
|
+
recoveredAgent,
|
|
1648
|
+
operation.id,
|
|
1649
|
+
'provider-archive-pending',
|
|
1650
|
+
'',
|
|
1651
|
+
{
|
|
1652
|
+
visibleOnMainPage: false,
|
|
1653
|
+
archived: true,
|
|
1654
|
+
archivedAt: Date.now(),
|
|
1655
|
+
runtimeAgentId: '',
|
|
1656
|
+
},
|
|
1657
|
+
);
|
|
1658
|
+
this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
|
|
1659
|
+
const providerArchive = await this.archiveCodexProviderSession(recoveredAgent);
|
|
1660
|
+
this.transitionPersistentAgentOperation(
|
|
1661
|
+
recoveredAgent,
|
|
1662
|
+
operation.id,
|
|
1663
|
+
providerArchive?.error ? 'blocked' : 'succeeded',
|
|
1664
|
+
providerArchive?.error || '',
|
|
1665
|
+
);
|
|
1571
1666
|
}
|
|
1572
1667
|
changed = true;
|
|
1573
1668
|
} catch (error) {
|
|
@@ -1936,11 +2031,16 @@ class AgentManager extends EventEmitter {
|
|
|
1936
2031
|
? this.configManager.getCodexApprovalMode()
|
|
1937
2032
|
: 'approve'
|
|
1938
2033
|
);
|
|
2034
|
+
const recoveryEnv = this.buildAgentEnv(agentId, agent);
|
|
2035
|
+
const recoveryMcpServers = this.projectAcpMcpServers(
|
|
2036
|
+
Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
|
|
2037
|
+
recoveryEnv,
|
|
2038
|
+
);
|
|
1939
2039
|
const prepared = await this.acpRuntime.prepareAgent({
|
|
1940
2040
|
agentId,
|
|
1941
2041
|
provider,
|
|
1942
2042
|
executable,
|
|
1943
|
-
env:
|
|
2043
|
+
env: recoveryEnv,
|
|
1944
2044
|
cwd: agent.cwd,
|
|
1945
2045
|
sessionId,
|
|
1946
2046
|
historyMode: 'checkpoint',
|
|
@@ -1951,8 +2051,9 @@ class AgentManager extends EventEmitter {
|
|
|
1951
2051
|
model: 'config',
|
|
1952
2052
|
reasoningEffort: 'config',
|
|
1953
2053
|
serviceTier: 'config',
|
|
2054
|
+
farmingSystemPrompt: renderFarmingAgentBootstrap(),
|
|
1954
2055
|
additionalDirectories: Array.isArray(record.acpAdditionalDirectories) ? record.acpAdditionalDirectories : [],
|
|
1955
|
-
mcpServers:
|
|
2056
|
+
mcpServers: recoveryMcpServers,
|
|
1956
2057
|
onProcessStarted: async processIdentity => {
|
|
1957
2058
|
agent.structuredRuntimeProcess = {
|
|
1958
2059
|
kind: 'acp-process-group',
|
|
@@ -1967,13 +2068,21 @@ class AgentManager extends EventEmitter {
|
|
|
1967
2068
|
prepared.sessionId,
|
|
1968
2069
|
agent.providerHomeId || record.providerHomeId || 'default'
|
|
1969
2070
|
);
|
|
1970
|
-
|
|
2071
|
+
let recoveredSessionOptions = {
|
|
1971
2072
|
additionalDirectories: Array.isArray(record.acpAdditionalDirectories)
|
|
1972
|
-
?
|
|
1973
|
-
: [],
|
|
1974
|
-
mcpServers: Array.isArray(record.acpMcpServers)
|
|
1975
|
-
? JSON.parse(JSON.stringify(record.acpMcpServers))
|
|
2073
|
+
? record.acpAdditionalDirectories
|
|
1976
2074
|
: [],
|
|
2075
|
+
mcpServers: recoveryMcpServers,
|
|
2076
|
+
};
|
|
2077
|
+
try {
|
|
2078
|
+
recoveredSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
|
|
2079
|
+
} catch {
|
|
2080
|
+
// The live binding already validated these options. Retain the
|
|
2081
|
+
// projected copy for custom runtimes that do not expose it.
|
|
2082
|
+
}
|
|
2083
|
+
this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
|
|
2084
|
+
additionalDirectories: [...recoveredSessionOptions.additionalDirectories],
|
|
2085
|
+
mcpServers: JSON.parse(JSON.stringify(recoveredSessionOptions.mcpServers)),
|
|
1977
2086
|
});
|
|
1978
2087
|
agent.providerSessionTemporary = false;
|
|
1979
2088
|
agent.providerSessionSource = `acp-${prepared.historyMode}`;
|
|
@@ -2053,7 +2162,16 @@ class AgentManager extends EventEmitter {
|
|
|
2053
2162
|
setAgentRecordId(staged, record.id || '');
|
|
2054
2163
|
staged.structuredRuntimeProcess = null;
|
|
2055
2164
|
try {
|
|
2056
|
-
if (operation.type === 'create') {
|
|
2165
|
+
if (operation.type === 'create' && operation.state === 'membership-pending') {
|
|
2166
|
+
this.rememberMainPageProviderSession(staged);
|
|
2167
|
+
this.transitionPersistentAgentOperation(
|
|
2168
|
+
staged,
|
|
2169
|
+
operation.id,
|
|
2170
|
+
'succeeded',
|
|
2171
|
+
'',
|
|
2172
|
+
{ archived: false },
|
|
2173
|
+
);
|
|
2174
|
+
} else if (operation.type === 'create') {
|
|
2057
2175
|
this.transitionPersistentAgentOperation(
|
|
2058
2176
|
staged,
|
|
2059
2177
|
operation.id,
|
|
@@ -2061,7 +2179,7 @@ class AgentManager extends EventEmitter {
|
|
|
2061
2179
|
'Create ACP process was stopped during restart recovery',
|
|
2062
2180
|
createFailurePatch(operation, operation.request?.previousRuntimeAgentId),
|
|
2063
2181
|
);
|
|
2064
|
-
} else {
|
|
2182
|
+
} else if (operation.type === 'delete') {
|
|
2065
2183
|
this.transitionPersistentAgentOperation(
|
|
2066
2184
|
staged,
|
|
2067
2185
|
operation.id,
|
|
@@ -2076,7 +2194,28 @@ class AgentManager extends EventEmitter {
|
|
|
2076
2194
|
},
|
|
2077
2195
|
);
|
|
2078
2196
|
this.removeMainPageProviderSessionsForAgents([staged]);
|
|
2079
|
-
|
|
2197
|
+
} else {
|
|
2198
|
+
this.transitionPersistentAgentOperation(
|
|
2199
|
+
staged,
|
|
2200
|
+
operation.id,
|
|
2201
|
+
'provider-archive-pending',
|
|
2202
|
+
'',
|
|
2203
|
+
{
|
|
2204
|
+
visibleOnMainPage: false,
|
|
2205
|
+
archived: true,
|
|
2206
|
+
archivedAt: Date.now(),
|
|
2207
|
+
runtimeAgentId: '',
|
|
2208
|
+
structuredRuntimeProcess: null,
|
|
2209
|
+
},
|
|
2210
|
+
);
|
|
2211
|
+
this.removeMainPageProviderSessionsForAgents([staged]);
|
|
2212
|
+
const providerArchive = await this.archiveCodexProviderSession(staged);
|
|
2213
|
+
this.transitionPersistentAgentOperation(
|
|
2214
|
+
staged,
|
|
2215
|
+
operation.id,
|
|
2216
|
+
providerArchive?.error ? 'blocked' : 'succeeded',
|
|
2217
|
+
providerArchive?.error || '',
|
|
2218
|
+
);
|
|
2080
2219
|
}
|
|
2081
2220
|
} catch (error) {
|
|
2082
2221
|
console.warn(
|
|
@@ -2201,6 +2340,7 @@ class AgentManager extends EventEmitter {
|
|
|
2201
2340
|
category: metadata.category || 'coding',
|
|
2202
2341
|
launchPermissionMode: metadata.launchPermissionMode || '',
|
|
2203
2342
|
parentAgentId: metadata.parentAgentId || '',
|
|
2343
|
+
forkRequestId: metadata.forkRequestId || '',
|
|
2204
2344
|
task: metadata.task || '',
|
|
2205
2345
|
workflowTemplate: metadata.workflowTemplate || '',
|
|
2206
2346
|
source: metadata.source || 'recovered',
|
|
@@ -2241,6 +2381,7 @@ class AgentManager extends EventEmitter {
|
|
|
2241
2381
|
agentRecordId,
|
|
2242
2382
|
persistentSessionId: agentRecordId,
|
|
2243
2383
|
lifecycleJournal: lifecycleJournal(metadata),
|
|
2384
|
+
composerCommands: normalizedComposerCommands(metadata.composerCommands),
|
|
2244
2385
|
customTitle: metadata.customTitle || '',
|
|
2245
2386
|
terminalBusy: typeof state.terminalBusy === 'boolean' ? state.terminalBusy : null,
|
|
2246
2387
|
shellCwd: state.shellCwd || metadata.cwd || '',
|
|
@@ -2469,6 +2610,26 @@ class AgentManager extends EventEmitter {
|
|
|
2469
2610
|
return operation;
|
|
2470
2611
|
}
|
|
2471
2612
|
|
|
2613
|
+
completePersistentAgentOperation(agent, operationId, result, patch = {}) {
|
|
2614
|
+
const staged = {
|
|
2615
|
+
...agent,
|
|
2616
|
+
lifecycleJournal: lifecycleJournal(agent),
|
|
2617
|
+
};
|
|
2618
|
+
const operation = setLifecycleOperationResult(staged, operationId, result);
|
|
2619
|
+
if (!operation) throw new Error(`Agent operation ${operationId} was not found`);
|
|
2620
|
+
transitionLifecycleOperation(staged, operationId, 'succeeded');
|
|
2621
|
+
const persistentSessionId = this.ensurePersistentAgentSession(staged, patch);
|
|
2622
|
+
if (
|
|
2623
|
+
typeof this.configManager?.ensureAgentSessionRecord === 'function'
|
|
2624
|
+
&& !persistentSessionId
|
|
2625
|
+
) {
|
|
2626
|
+
throw new Error('Agent session store did not return a persistent id');
|
|
2627
|
+
}
|
|
2628
|
+
agent.lifecycleJournal = staged.lifecycleJournal;
|
|
2629
|
+
setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
|
|
2630
|
+
return operation;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2472
2633
|
beginPersistentAgentUpdate(agent, requestKey, request) {
|
|
2473
2634
|
const latest = latestLifecycleOperation(agent);
|
|
2474
2635
|
if (
|
|
@@ -2975,6 +3136,7 @@ class AgentManager extends EventEmitter {
|
|
|
2975
3136
|
env.FARMING_AGENT_ID = agentId;
|
|
2976
3137
|
env.FARMING_IS_MAIN_AGENT = agent.wantsMain ? '1' : '0';
|
|
2977
3138
|
env.FARMING_SKILLS_COMMAND = 'farming skills';
|
|
3139
|
+
env.FARMING_CAPABILITIES_COMMAND = 'farming capabilities';
|
|
2978
3140
|
env.FARMING_MAIN_WORKSPACE = agent.mainWorkspace || '';
|
|
2979
3141
|
env.FARMING_PROJECT_WORKSPACE = effectiveAgentWorkspaceRoot(agent);
|
|
2980
3142
|
|
|
@@ -2992,6 +3154,7 @@ class AgentManager extends EventEmitter {
|
|
|
2992
3154
|
}
|
|
2993
3155
|
if (this.configManager && this.configManager.farmingDir) {
|
|
2994
3156
|
env.FARMING_CONFIG_DIR = this.configManager.farmingDir;
|
|
3157
|
+
env.FARMING_STARTUP_PROMPT_FILE = storageLayout.farmingAgentBootstrapFile(this.configManager.farmingDir);
|
|
2995
3158
|
}
|
|
2996
3159
|
if (agent.mainWorkspace) {
|
|
2997
3160
|
env.FARMING_SKILLS_FILE = path.join(agent.mainWorkspace, 'FARMING_MAIN_AGENT_SKILLS.md');
|
|
@@ -3004,10 +3167,22 @@ class AgentManager extends EventEmitter {
|
|
|
3004
3167
|
agent.providerHomePath
|
|
3005
3168
|
);
|
|
3006
3169
|
}
|
|
3170
|
+
const provider = agent.providerSessionProvider || agentHomeProviderForProgram(agent.forkCommand || agent.command);
|
|
3171
|
+
if (provider === 'opencode' && env.FARMING_STARTUP_PROMPT_FILE) {
|
|
3172
|
+
Object.assign(env, appendOpenCodeBootstrap(env, env.FARMING_STARTUP_PROMPT_FILE));
|
|
3173
|
+
}
|
|
3007
3174
|
|
|
3008
3175
|
return env;
|
|
3009
3176
|
}
|
|
3010
3177
|
|
|
3178
|
+
projectAcpMcpServers(mcpServers, agentEnv) {
|
|
3179
|
+
if (!this.browserMcpEnabled) return Array.isArray(mcpServers) ? mcpServers : [];
|
|
3180
|
+
return mergeBrowserMcpServer(mcpServers, {
|
|
3181
|
+
cliBinDir: this.cliBinDir,
|
|
3182
|
+
agentEnv,
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3011
3186
|
expandWorkspacePath(workspace) {
|
|
3012
3187
|
if (typeof workspace !== 'string') return '';
|
|
3013
3188
|
const value = workspace.trim();
|
|
@@ -3253,6 +3428,7 @@ class AgentManager extends EventEmitter {
|
|
|
3253
3428
|
category: agent.category,
|
|
3254
3429
|
launchPermissionMode: agent.launchPermissionMode,
|
|
3255
3430
|
parentAgentId: agent.parentAgentId || '',
|
|
3431
|
+
forkRequestId: agent.forkRequestId || '',
|
|
3256
3432
|
task: agent.task,
|
|
3257
3433
|
workflowTemplate: agent.workflowTemplate,
|
|
3258
3434
|
source: agent.source,
|
|
@@ -3615,6 +3791,7 @@ class AgentManager extends EventEmitter {
|
|
|
3615
3791
|
codexModel,
|
|
3616
3792
|
codexReasoningEffort,
|
|
3617
3793
|
codexServiceTier,
|
|
3794
|
+
farmingSystemPrompt: renderFarmingAgentBootstrap(),
|
|
3618
3795
|
mainAgentSystemPrompt: wantsMain ? renderMainAgentBootstrap() : '',
|
|
3619
3796
|
});
|
|
3620
3797
|
const program = launch.program;
|
|
@@ -3863,6 +4040,7 @@ class AgentManager extends EventEmitter {
|
|
|
3863
4040
|
category: resolution.spec ? resolution.spec.category : 'other',
|
|
3864
4041
|
launchPermissionMode: launch.permissionMode || '',
|
|
3865
4042
|
parentAgentId,
|
|
4043
|
+
forkRequestId: typeof options.forkRequestId === 'string' ? options.forkRequestId : '',
|
|
3866
4044
|
task: typeof options.task === 'string' ? options.task : '',
|
|
3867
4045
|
workflowTemplate: typeof options.workflowTemplate === 'string' ? options.workflowTemplate : '',
|
|
3868
4046
|
source: typeof options.source === 'string' ? options.source : 'ui',
|
|
@@ -4076,7 +4254,10 @@ class AgentManager extends EventEmitter {
|
|
|
4076
4254
|
const requestedAdditionalDirectories = Array.isArray(options.additionalDirectories)
|
|
4077
4255
|
? options.additionalDirectories
|
|
4078
4256
|
: [];
|
|
4079
|
-
const requestedMcpServers =
|
|
4257
|
+
const requestedMcpServers = this.projectAcpMcpServers(
|
|
4258
|
+
Array.isArray(options.mcpServers) ? options.mcpServers : [],
|
|
4259
|
+
identityEnv,
|
|
4260
|
+
);
|
|
4080
4261
|
const created = await this.createProviderSessionIdentity({
|
|
4081
4262
|
provider: providerSessionPlan.provider,
|
|
4082
4263
|
executable: spawnProgram,
|
|
@@ -4087,6 +4268,7 @@ class AgentManager extends EventEmitter {
|
|
|
4087
4268
|
model: codexModel,
|
|
4088
4269
|
reasoningEffort: codexReasoningEffort,
|
|
4089
4270
|
serviceTier: codexServiceTier,
|
|
4271
|
+
farmingSystemPrompt: renderFarmingAgentBootstrap(),
|
|
4090
4272
|
additionalDirectories: requestedAdditionalDirectories,
|
|
4091
4273
|
mcpServers: requestedMcpServers,
|
|
4092
4274
|
});
|
|
@@ -4230,14 +4412,16 @@ class AgentManager extends EventEmitter {
|
|
|
4230
4412
|
const additionalDirectories = Array.isArray(options.additionalDirectories)
|
|
4231
4413
|
? options.additionalDirectories
|
|
4232
4414
|
: rememberedSessionOptions.additionalDirectories || [];
|
|
4233
|
-
const
|
|
4415
|
+
const requestedMcpServers = Array.isArray(options.mcpServers)
|
|
4234
4416
|
? options.mcpServers
|
|
4235
4417
|
: rememberedSessionOptions.mcpServers || [];
|
|
4418
|
+
const acpEnv = this.buildAgentEnv(agentId, agentRecord);
|
|
4419
|
+
const mcpServers = this.projectAcpMcpServers(requestedMcpServers, acpEnv);
|
|
4236
4420
|
const prepared = await this.acpRuntime.prepareAgent({
|
|
4237
4421
|
agentId,
|
|
4238
4422
|
provider: structuredRuntimeProvider,
|
|
4239
4423
|
executable: spawnProgram,
|
|
4240
|
-
env:
|
|
4424
|
+
env: acpEnv,
|
|
4241
4425
|
cwd: workspace,
|
|
4242
4426
|
sessionId: options.acpStartFresh === true || agentRecord.providerSessionTemporary || acpGeneratedFreshSession
|
|
4243
4427
|
? ''
|
|
@@ -4250,6 +4434,7 @@ class AgentManager extends EventEmitter {
|
|
|
4250
4434
|
model: codexModel,
|
|
4251
4435
|
reasoningEffort: codexReasoningEffort,
|
|
4252
4436
|
serviceTier: codexServiceTier,
|
|
4437
|
+
farmingSystemPrompt: renderFarmingAgentBootstrap(),
|
|
4253
4438
|
additionalDirectories,
|
|
4254
4439
|
mcpServers,
|
|
4255
4440
|
forkSourceSessionId: options.acpForkSourceSessionId || '',
|
|
@@ -4350,13 +4535,18 @@ class AgentManager extends EventEmitter {
|
|
|
4350
4535
|
await startTerminal();
|
|
4351
4536
|
}
|
|
4352
4537
|
}
|
|
4353
|
-
this.transitionPersistentAgentOperation(agentRecord, createOperationId, '
|
|
4538
|
+
this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'membership-pending', '', {
|
|
4354
4539
|
visibleOnMainPage: true,
|
|
4355
4540
|
archived: false,
|
|
4356
4541
|
...(options.customTitleExplicit === true
|
|
4357
4542
|
? { customTitle: agentRecord.customTitle }
|
|
4358
4543
|
: {}),
|
|
4359
4544
|
});
|
|
4545
|
+
this.rememberMainPageProviderSession(agentRecord);
|
|
4546
|
+
this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'succeeded', '', {
|
|
4547
|
+
visibleOnMainPage: true,
|
|
4548
|
+
archived: false,
|
|
4549
|
+
});
|
|
4360
4550
|
if (
|
|
4361
4551
|
previousPersistentRuntimeAgentId
|
|
4362
4552
|
&& previousPersistentRuntimeAgentId !== agentId
|
|
@@ -4576,7 +4766,171 @@ class AgentManager extends EventEmitter {
|
|
|
4576
4766
|
return this.enqueueInputOperation(agentId, () => this.sendInputNow(agentId, input, options));
|
|
4577
4767
|
}
|
|
4578
4768
|
|
|
4579
|
-
|
|
4769
|
+
commitComposerCommand(agent, command) {
|
|
4770
|
+
const commands = normalizedComposerCommands(agent.composerCommands)
|
|
4771
|
+
.filter(candidate => candidate.requestId !== command.requestId);
|
|
4772
|
+
commands.push(command);
|
|
4773
|
+
const staged = {
|
|
4774
|
+
...agent,
|
|
4775
|
+
composerCommands: normalizedComposerCommands(commands),
|
|
4776
|
+
};
|
|
4777
|
+
const persistentSessionId = this.ensurePersistentAgentSession(staged);
|
|
4778
|
+
if (
|
|
4779
|
+
typeof this.configManager?.ensureAgentSessionRecord === 'function'
|
|
4780
|
+
&& !persistentSessionId
|
|
4781
|
+
) {
|
|
4782
|
+
throw new Error('Agent session store did not return a persistent id');
|
|
4783
|
+
}
|
|
4784
|
+
agent.composerCommands = staged.composerCommands;
|
|
4785
|
+
setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
|
|
4786
|
+
return command;
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
setComposerCommandInMemory(agent, command) {
|
|
4790
|
+
agent.composerCommands = normalizedComposerCommands([
|
|
4791
|
+
...normalizedComposerCommands(agent.composerCommands)
|
|
4792
|
+
.filter(candidate => candidate.requestId !== command.requestId),
|
|
4793
|
+
command,
|
|
4794
|
+
]);
|
|
4795
|
+
}
|
|
4796
|
+
|
|
4797
|
+
sendPersistentComposerMessage(agentId, message, requestId) {
|
|
4798
|
+
const agent = this.agents.get(agentId);
|
|
4799
|
+
if (!agent) return Promise.reject(new Error('Agent not found'));
|
|
4800
|
+
if (runtimeKind(agent) === 'terminal') {
|
|
4801
|
+
return Promise.reject(new Error('Persistent Composer admission requires a structured runtime'));
|
|
4802
|
+
}
|
|
4803
|
+
const prompt = normalizedComposerPrompt(message);
|
|
4804
|
+
const contentHash = composerCommandHash(prompt);
|
|
4805
|
+
const commands = normalizedComposerCommands(agent.composerCommands);
|
|
4806
|
+
const existing = commands.find(command => command.requestId === requestId);
|
|
4807
|
+
const admissionKey = `${agentId}:${requestId}`;
|
|
4808
|
+
const inFlight = this.composerAdmissions.get(admissionKey);
|
|
4809
|
+
if (existing?.contentHash && existing.contentHash !== contentHash) {
|
|
4810
|
+
return Promise.reject(new Error(`Composer request ${requestId} was already used for different content`));
|
|
4811
|
+
}
|
|
4812
|
+
if (existing?.state === 'accepted') {
|
|
4813
|
+
return Promise.resolve({ ...(existing.result || {}), accepted: true, deduplicated: true });
|
|
4814
|
+
}
|
|
4815
|
+
if (inFlight) return inFlight.promise;
|
|
4816
|
+
if (existing?.state === 'unknown' || existing?.state === 'intent') {
|
|
4817
|
+
const detail = existing.error || `Composer request ${requestId} has an uncertain outcome and will not be replayed automatically`;
|
|
4818
|
+
if (existing.state === 'intent') {
|
|
4819
|
+
const unknown = { ...existing, state: 'unknown', error: detail, updatedAt: Date.now() };
|
|
4820
|
+
try {
|
|
4821
|
+
this.commitComposerCommand(agent, unknown);
|
|
4822
|
+
} catch {
|
|
4823
|
+
this.setComposerCommandInMemory(agent, unknown);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return Promise.reject(composerAdmissionError(detail, true));
|
|
4827
|
+
}
|
|
4828
|
+
if (existing?.state === 'failed') {
|
|
4829
|
+
return Promise.reject(new Error(existing.error || `Composer request ${requestId} was not accepted`));
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
const intent = {
|
|
4833
|
+
requestId,
|
|
4834
|
+
contentHash,
|
|
4835
|
+
state: 'intent',
|
|
4836
|
+
result: null,
|
|
4837
|
+
error: '',
|
|
4838
|
+
createdAt: Date.now(),
|
|
4839
|
+
updatedAt: Date.now(),
|
|
4840
|
+
};
|
|
4841
|
+
try {
|
|
4842
|
+
this.commitComposerCommand(agent, intent);
|
|
4843
|
+
} catch (error) {
|
|
4844
|
+
return Promise.reject(new Error(`Failed to persist Composer intent: ${error.message || error}`));
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4847
|
+
let resolveAdmission;
|
|
4848
|
+
let rejectAdmission;
|
|
4849
|
+
const admissionPromise = new Promise((resolve, reject) => {
|
|
4850
|
+
resolveAdmission = resolve;
|
|
4851
|
+
rejectAdmission = reject;
|
|
4852
|
+
});
|
|
4853
|
+
const entry = { contentHash, promise: admissionPromise };
|
|
4854
|
+
this.composerAdmissions.set(admissionKey, entry);
|
|
4855
|
+
let submitted = false;
|
|
4856
|
+
const onSubmitted = (result = { kind: runtimeKind(agent) }) => {
|
|
4857
|
+
if (submitted) return;
|
|
4858
|
+
submitted = true;
|
|
4859
|
+
const accepted = {
|
|
4860
|
+
...intent,
|
|
4861
|
+
state: 'accepted',
|
|
4862
|
+
result,
|
|
4863
|
+
updatedAt: Date.now(),
|
|
4864
|
+
};
|
|
4865
|
+
try {
|
|
4866
|
+
this.commitComposerCommand(agent, accepted);
|
|
4867
|
+
resolveAdmission({ ...result, accepted: true });
|
|
4868
|
+
} catch (error) {
|
|
4869
|
+
const unknown = {
|
|
4870
|
+
...intent,
|
|
4871
|
+
state: 'unknown',
|
|
4872
|
+
error: `Provider accepted Composer request, but admission could not be saved: ${error.message || error}`,
|
|
4873
|
+
updatedAt: Date.now(),
|
|
4874
|
+
};
|
|
4875
|
+
this.setComposerCommandInMemory(agent, unknown);
|
|
4876
|
+
rejectAdmission(composerAdmissionError(unknown.error, true));
|
|
4877
|
+
}
|
|
4878
|
+
};
|
|
4879
|
+
|
|
4880
|
+
const completion = isAcpAgent(agent)
|
|
4881
|
+
? this.enqueueInputOperationUntilReleased(
|
|
4882
|
+
agentId,
|
|
4883
|
+
releaseInput => this.sendComposerMessageNow(agentId, prompt, {
|
|
4884
|
+
onSubmitted: () => {
|
|
4885
|
+
try {
|
|
4886
|
+
onSubmitted({ kind: 'acp' });
|
|
4887
|
+
} finally {
|
|
4888
|
+
releaseInput();
|
|
4889
|
+
}
|
|
4890
|
+
},
|
|
4891
|
+
}),
|
|
4892
|
+
)
|
|
4893
|
+
: this.enqueueInputOperation(
|
|
4894
|
+
agentId,
|
|
4895
|
+
() => this.sendComposerMessageNow(agentId, prompt, {
|
|
4896
|
+
onSubmitted: result => onSubmitted(result),
|
|
4897
|
+
}),
|
|
4898
|
+
);
|
|
4899
|
+
void Promise.resolve(completion).then(result => {
|
|
4900
|
+
if (!submitted) onSubmitted(result);
|
|
4901
|
+
}).catch(error => {
|
|
4902
|
+
if (submitted) return;
|
|
4903
|
+
const failed = {
|
|
4904
|
+
...intent,
|
|
4905
|
+
state: 'failed',
|
|
4906
|
+
error: error.message || String(error),
|
|
4907
|
+
updatedAt: Date.now(),
|
|
4908
|
+
};
|
|
4909
|
+
let uncertain = false;
|
|
4910
|
+
try {
|
|
4911
|
+
this.commitComposerCommand(agent, failed);
|
|
4912
|
+
} catch (persistError) {
|
|
4913
|
+
failed.state = 'unknown';
|
|
4914
|
+
failed.error = `${failed.error}; failed to persist rejection: ${persistError.message || persistError}`;
|
|
4915
|
+
this.setComposerCommandInMemory(agent, failed);
|
|
4916
|
+
uncertain = true;
|
|
4917
|
+
}
|
|
4918
|
+
rejectAdmission(composerAdmissionError(failed.error, uncertain));
|
|
4919
|
+
});
|
|
4920
|
+
void admissionPromise.finally(() => {
|
|
4921
|
+
if (this.composerAdmissions.get(admissionKey) === entry) {
|
|
4922
|
+
this.composerAdmissions.delete(admissionKey);
|
|
4923
|
+
}
|
|
4924
|
+
}).catch(() => {});
|
|
4925
|
+
return admissionPromise;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
async sendComposerMessage(agentId, message, options = {}) {
|
|
4929
|
+
const requestId = String(options.requestId || '').trim();
|
|
4930
|
+
if (requestId) {
|
|
4931
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) throw new Error('Composer requestId is invalid');
|
|
4932
|
+
return this.sendPersistentComposerMessage(agentId, message, requestId);
|
|
4933
|
+
}
|
|
4580
4934
|
const agent = this.agents.get(agentId);
|
|
4581
4935
|
if (agent && isAcpAgent(agent)) {
|
|
4582
4936
|
return this.enqueueInputOperationUntilReleased(
|
|
@@ -4695,13 +5049,15 @@ class AgentManager extends EventEmitter {
|
|
|
4695
5049
|
agent.providerSessionId = result.sessionId || agent.providerSessionId;
|
|
4696
5050
|
agent.providerSessionTemporary = !agent.providerSessionId;
|
|
4697
5051
|
this.ensurePersistentAgentSession(agent);
|
|
4698
|
-
|
|
5052
|
+
const submitted = { kind: 'json', sessionId: agent.providerSessionId };
|
|
5053
|
+
options.onSubmitted?.(submitted);
|
|
5054
|
+
return submitted;
|
|
4699
5055
|
}
|
|
4700
5056
|
|
|
4701
5057
|
if (isAcpAgent(agent)) {
|
|
4702
5058
|
this.requireLiveAcpAgent(agentId);
|
|
4703
5059
|
const result = await this.acpRuntime.submitMessage(agentId, prompt, {
|
|
4704
|
-
onSubmitted: options.releaseInput,
|
|
5060
|
+
onSubmitted: options.onSubmitted || options.releaseInput,
|
|
4705
5061
|
});
|
|
4706
5062
|
if (result.steered !== true) {
|
|
4707
5063
|
const runtime = runtimeBindingOf(agent, 'acp');
|
|
@@ -4713,7 +5069,9 @@ class AgentManager extends EventEmitter {
|
|
|
4713
5069
|
}
|
|
4714
5070
|
|
|
4715
5071
|
await this.sendInputNow(agentId, [{ type: 'paste', text }, '\r']);
|
|
4716
|
-
|
|
5072
|
+
const submitted = { kind: 'terminal' };
|
|
5073
|
+
options.onSubmitted?.(submitted);
|
|
5074
|
+
return submitted;
|
|
4717
5075
|
}
|
|
4718
5076
|
|
|
4719
5077
|
getJsonCliTranscript(agentId, options = {}) {
|
|
@@ -4733,10 +5091,21 @@ class AgentManager extends EventEmitter {
|
|
|
4733
5091
|
const transcript = this.acpRuntime.getTranscriptSession(agentId, options);
|
|
4734
5092
|
return {
|
|
4735
5093
|
...transcript,
|
|
4736
|
-
entries: transcript.entries
|
|
5094
|
+
entries: acpTranscriptEntries(transcript.entries, {
|
|
5095
|
+
mediaPathPrefix: options.mediaPathPrefix,
|
|
5096
|
+
}),
|
|
4737
5097
|
};
|
|
4738
5098
|
}
|
|
4739
5099
|
|
|
5100
|
+
getAcpTranscriptMedia(agentId, entryId, mediaId) {
|
|
5101
|
+
this.requireLiveAcpAgent(agentId);
|
|
5102
|
+
const entry = this.acpRuntime.getTranscriptEntry(agentId, entryId);
|
|
5103
|
+
if (!entry) throw new Error('ACP transcript entry not found');
|
|
5104
|
+
const media = acpTranscriptMedia(entry, mediaId);
|
|
5105
|
+
if (!media) throw new Error('ACP transcript media not found');
|
|
5106
|
+
return media;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
4740
5109
|
getAcpToolDetail(agentId, toolCallId) {
|
|
4741
5110
|
this.requireLiveAcpAgent(agentId);
|
|
4742
5111
|
const entry = this.acpRuntime.getToolEntry(agentId, toolCallId);
|
|
@@ -5309,6 +5678,14 @@ class AgentManager extends EventEmitter {
|
|
|
5309
5678
|
if (admission) {
|
|
5310
5679
|
transitionLifecycleOperation(staged, admission.operation.id, 'succeeded');
|
|
5311
5680
|
}
|
|
5681
|
+
if (!admission && !structuralUpdateChanged && !readUpdateChanged) {
|
|
5682
|
+
return {
|
|
5683
|
+
agentId,
|
|
5684
|
+
...updates,
|
|
5685
|
+
changed: false,
|
|
5686
|
+
requiresState: false,
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5312
5689
|
try {
|
|
5313
5690
|
this.ensurePersistentAgentSession(staged);
|
|
5314
5691
|
} catch (error) {
|
|
@@ -5942,6 +6319,104 @@ class AgentManager extends EventEmitter {
|
|
|
5942
6319
|
: this.markAgentReadCursor(agentId);
|
|
5943
6320
|
}
|
|
5944
6321
|
|
|
6322
|
+
persistentProjectOperation(requestId, type, signature, request) {
|
|
6323
|
+
if (
|
|
6324
|
+
!requestId
|
|
6325
|
+
|| typeof this.configManager?.getProjectOperation !== 'function'
|
|
6326
|
+
|| typeof this.configManager?.commitProjectOperation !== 'function'
|
|
6327
|
+
) {
|
|
6328
|
+
return { operation: null, created: true };
|
|
6329
|
+
}
|
|
6330
|
+
const existing = this.configManager.getProjectOperation(requestId);
|
|
6331
|
+
if (existing) {
|
|
6332
|
+
if (existing.type !== type || existing.signature !== signature) {
|
|
6333
|
+
return { error: `Project operation request ${requestId} was already used for different parameters` };
|
|
6334
|
+
}
|
|
6335
|
+
return { operation: existing, created: false };
|
|
6336
|
+
}
|
|
6337
|
+
const operation = {
|
|
6338
|
+
id: requestId,
|
|
6339
|
+
type,
|
|
6340
|
+
state: 'pending',
|
|
6341
|
+
signature,
|
|
6342
|
+
request,
|
|
6343
|
+
result: null,
|
|
6344
|
+
error: '',
|
|
6345
|
+
startedAt: Date.now(),
|
|
6346
|
+
updatedAt: Date.now(),
|
|
6347
|
+
finishedAt: null,
|
|
6348
|
+
};
|
|
6349
|
+
try {
|
|
6350
|
+
this.configManager.commitProjectOperation(operation);
|
|
6351
|
+
return { operation, created: true };
|
|
6352
|
+
} catch (error) {
|
|
6353
|
+
return { error: `Failed to persist Project operation intent: ${error.message || error}` };
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
|
|
6357
|
+
commitPersistentProjectOperation(operation, state, result = null, error = '', membership = {}) {
|
|
6358
|
+
if (!operation || typeof this.configManager?.commitProjectOperation !== 'function') {
|
|
6359
|
+
return { operation: null, projectWorkspaces: null, pinnedProjectWorkspaces: null };
|
|
6360
|
+
}
|
|
6361
|
+
const terminal = ['succeeded', 'failed', 'blocked'].includes(state);
|
|
6362
|
+
const nextOperation = {
|
|
6363
|
+
...operation,
|
|
6364
|
+
state,
|
|
6365
|
+
result,
|
|
6366
|
+
error,
|
|
6367
|
+
updatedAt: Date.now(),
|
|
6368
|
+
finishedAt: terminal ? Date.now() : null,
|
|
6369
|
+
};
|
|
6370
|
+
return this.configManager.commitProjectOperation(nextOperation, membership);
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6373
|
+
async listGitWorktrees(sourceWorkspace) {
|
|
6374
|
+
const { stdout } = await execFileAsync('git', ['-C', sourceWorkspace, 'worktree', 'list', '--porcelain'], {
|
|
6375
|
+
timeout: 15000,
|
|
6376
|
+
maxBuffer: 1024 * 1024 * 4,
|
|
6377
|
+
});
|
|
6378
|
+
return worktreesFromPorcelain(stdout);
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
async inspectGitWorktreePostcondition(sourceWorkspace, workspace, branch = '') {
|
|
6382
|
+
const target = path.resolve(workspace);
|
|
6383
|
+
let worktrees;
|
|
6384
|
+
try {
|
|
6385
|
+
worktrees = await this.listGitWorktrees(sourceWorkspace);
|
|
6386
|
+
} catch (error) {
|
|
6387
|
+
return { proven: false, error: error.message || String(error) };
|
|
6388
|
+
}
|
|
6389
|
+
const registered = worktrees.find(entry => entry.workspace === target) || null;
|
|
6390
|
+
let exists = false;
|
|
6391
|
+
try {
|
|
6392
|
+
exists = fs.statSync(target).isDirectory();
|
|
6393
|
+
} catch {
|
|
6394
|
+
exists = false;
|
|
6395
|
+
}
|
|
6396
|
+
let branchExists = false;
|
|
6397
|
+
if (branch) {
|
|
6398
|
+
try {
|
|
6399
|
+
await execFileAsync('git', ['-C', sourceWorkspace, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`], {
|
|
6400
|
+
timeout: 15000,
|
|
6401
|
+
maxBuffer: 1024 * 1024,
|
|
6402
|
+
});
|
|
6403
|
+
branchExists = true;
|
|
6404
|
+
} catch (error) {
|
|
6405
|
+
if (error?.code !== 1) {
|
|
6406
|
+
return { proven: false, error: error.message || String(error) };
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
return {
|
|
6411
|
+
proven: true,
|
|
6412
|
+
exists,
|
|
6413
|
+
registered: Boolean(registered),
|
|
6414
|
+
branchMatches: !branch || registered?.branch === branch,
|
|
6415
|
+
branchExists,
|
|
6416
|
+
worktree: registered,
|
|
6417
|
+
};
|
|
6418
|
+
}
|
|
6419
|
+
|
|
5945
6420
|
async resolveGitWorktreeSourceRoot(workspace) {
|
|
5946
6421
|
const sourceWorkspace = this.expandWorkspacePath(workspace);
|
|
5947
6422
|
if (!sourceWorkspace) {
|
|
@@ -5986,47 +6461,182 @@ class AgentManager extends EventEmitter {
|
|
|
5986
6461
|
return target;
|
|
5987
6462
|
}
|
|
5988
6463
|
|
|
5989
|
-
|
|
6464
|
+
createPermanentWorktree(workspace, options = {}) {
|
|
6465
|
+
const requestId = String(options.requestId || '').trim().slice(0, 160);
|
|
6466
|
+
if (!requestId) return this.createPermanentWorktreeAdmitted(workspace, options);
|
|
6467
|
+
const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
|
|
6468
|
+
const current = this.projectOperationAdmissions.get(requestId);
|
|
6469
|
+
if (current) {
|
|
6470
|
+
if (current.workspaceKey === workspaceKey) return current.promise;
|
|
6471
|
+
return Promise.reject(new Error(`Project operation request ${requestId} was already used for different parameters`));
|
|
6472
|
+
}
|
|
6473
|
+
const promise = this.createPermanentWorktreeAdmitted(workspace, options);
|
|
6474
|
+
const admission = { workspaceKey, promise };
|
|
6475
|
+
this.projectOperationAdmissions.set(requestId, admission);
|
|
6476
|
+
void promise.finally(() => {
|
|
6477
|
+
if (this.projectOperationAdmissions.get(requestId) === admission) {
|
|
6478
|
+
this.projectOperationAdmissions.delete(requestId);
|
|
6479
|
+
}
|
|
6480
|
+
}).catch(() => {});
|
|
6481
|
+
return promise;
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
async createPermanentWorktreeAdmitted(workspace, options = {}) {
|
|
5990
6485
|
const root = await this.resolveGitWorktreeSourceRoot(workspace);
|
|
6486
|
+
const requestId = String(options.requestId || '').trim().slice(0, 160);
|
|
6487
|
+
const signature = projectOperationSignature({ sourceWorkspace: root, type: 'create-worktree' });
|
|
6488
|
+
const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
|
|
6489
|
+
? this.configManager.getProjectOperation(requestId)
|
|
6490
|
+
: null;
|
|
6491
|
+
const operationWasExisting = Boolean(existingOperation);
|
|
6492
|
+
if (
|
|
6493
|
+
existingOperation
|
|
6494
|
+
&& (
|
|
6495
|
+
existingOperation.type !== 'create-worktree'
|
|
6496
|
+
|| existingOperation.signature !== signature
|
|
6497
|
+
)
|
|
6498
|
+
) {
|
|
6499
|
+
throw new Error(`Project operation request ${requestId} was already used for different parameters`);
|
|
6500
|
+
}
|
|
6501
|
+
if (existingOperation?.state === 'succeeded' && existingOperation.result) {
|
|
6502
|
+
const settings = this.configManager.getSettings?.() || {};
|
|
6503
|
+
return {
|
|
6504
|
+
...existingOperation.result,
|
|
6505
|
+
deduplicated: true,
|
|
6506
|
+
projectWorkspaces: settings.projectWorkspaces || [],
|
|
6507
|
+
pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
|
|
6508
|
+
};
|
|
6509
|
+
}
|
|
6510
|
+
|
|
5991
6511
|
const parentDir = path.dirname(root);
|
|
5992
6512
|
const baseName = path.basename(root);
|
|
5993
|
-
|
|
5994
|
-
let
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
const
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
maxBuffer: 1024 * 1024,
|
|
6005
|
-
});
|
|
6006
|
-
branchExists = true;
|
|
6007
|
-
} catch (error) {
|
|
6008
|
-
if (error?.code !== 1) {
|
|
6009
|
-
const message = error && error.stderr ? String(error.stderr).trim() : '';
|
|
6010
|
-
throw new Error(message || 'Failed to inspect git branches', { cause: error });
|
|
6011
|
-
}
|
|
6012
|
-
}
|
|
6013
|
-
|
|
6014
|
-
if (!fs.existsSync(target) && !branchExists) {
|
|
6513
|
+
let target = String(existingOperation?.request?.workspace || '');
|
|
6514
|
+
let branch = String(existingOperation?.request?.branch || '');
|
|
6515
|
+
let operation = existingOperation;
|
|
6516
|
+
if (!operation) {
|
|
6517
|
+
const slug = timestampSlug();
|
|
6518
|
+
let suffix = 1;
|
|
6519
|
+
while (suffix < 1000) {
|
|
6520
|
+
const suffixText = suffix === 1 ? '' : `-${suffix}`;
|
|
6521
|
+
const candidateTarget = path.join(parentDir, `${baseName}-farming-worktree-${slug}${suffixText}`);
|
|
6522
|
+
const candidateBranch = `farming/worktree-${slug}${suffixText}`;
|
|
6523
|
+
let branchExists = false;
|
|
6015
6524
|
try {
|
|
6016
|
-
await execFileAsync('git', ['-C', root, '
|
|
6017
|
-
timeout:
|
|
6018
|
-
maxBuffer: 1024 * 1024
|
|
6525
|
+
await execFileAsync('git', ['-C', root, 'show-ref', '--verify', '--quiet', `refs/heads/${candidateBranch}`], {
|
|
6526
|
+
timeout: 15000,
|
|
6527
|
+
maxBuffer: 1024 * 1024,
|
|
6019
6528
|
});
|
|
6529
|
+
branchExists = true;
|
|
6020
6530
|
} catch (error) {
|
|
6021
|
-
|
|
6022
|
-
|
|
6531
|
+
if (error?.code !== 1) {
|
|
6532
|
+
const message = error && error.stderr ? String(error.stderr).trim() : '';
|
|
6533
|
+
throw new Error(message || 'Failed to inspect git branches', { cause: error });
|
|
6534
|
+
}
|
|
6023
6535
|
}
|
|
6024
|
-
|
|
6536
|
+
|
|
6537
|
+
if (!fs.existsSync(candidateTarget) && !branchExists) {
|
|
6538
|
+
target = candidateTarget;
|
|
6539
|
+
branch = candidateBranch;
|
|
6540
|
+
break;
|
|
6541
|
+
}
|
|
6542
|
+
suffix += 1;
|
|
6543
|
+
}
|
|
6544
|
+
if (!target || !branch) throw new Error('Unable to allocate a permanent worktree name');
|
|
6545
|
+
const admission = this.persistentProjectOperation(requestId, 'create-worktree', signature, {
|
|
6546
|
+
sourceWorkspace: root,
|
|
6547
|
+
workspace: target,
|
|
6548
|
+
branch,
|
|
6549
|
+
});
|
|
6550
|
+
if (admission.error) throw new Error(admission.error);
|
|
6551
|
+
operation = admission.operation;
|
|
6552
|
+
}
|
|
6553
|
+
|
|
6554
|
+
const commitSuccess = () => {
|
|
6555
|
+
const result = { workspace: target, branch, sourceWorkspace: root, ...(requestId ? { requestId } : {}) };
|
|
6556
|
+
if (!operation) return result;
|
|
6557
|
+
const committed = this.commitPersistentProjectOperation(
|
|
6558
|
+
operation,
|
|
6559
|
+
'succeeded',
|
|
6560
|
+
result,
|
|
6561
|
+
'',
|
|
6562
|
+
{ mountWorkspace: target },
|
|
6563
|
+
);
|
|
6564
|
+
return {
|
|
6565
|
+
...result,
|
|
6566
|
+
projectWorkspaces: committed.projectWorkspaces,
|
|
6567
|
+
pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
|
|
6568
|
+
};
|
|
6569
|
+
};
|
|
6570
|
+
|
|
6571
|
+
if (operation && !['pending', 'unknown'].includes(operation.state)) {
|
|
6572
|
+
throw new Error(operation.error || `Project operation ${requestId} already finished with state ${operation.state}`);
|
|
6573
|
+
}
|
|
6574
|
+
if (operation && operationWasExisting) {
|
|
6575
|
+
const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
|
|
6576
|
+
if (
|
|
6577
|
+
postcondition.proven
|
|
6578
|
+
&& postcondition.exists
|
|
6579
|
+
&& postcondition.registered
|
|
6580
|
+
&& postcondition.branchMatches
|
|
6581
|
+
) {
|
|
6582
|
+
return commitSuccess();
|
|
6583
|
+
}
|
|
6584
|
+
if (operation.state === 'unknown') {
|
|
6585
|
+
throw new Error(operation.error || 'Permanent worktree creation has an uncertain outcome and will not be replayed automatically');
|
|
6586
|
+
}
|
|
6587
|
+
if (
|
|
6588
|
+
!postcondition.proven
|
|
6589
|
+
|| postcondition.exists
|
|
6590
|
+
|| postcondition.registered
|
|
6591
|
+
|| postcondition.branchExists
|
|
6592
|
+
) {
|
|
6593
|
+
const detail = postcondition.error || 'Permanent worktree creation has a partial or unverifiable result';
|
|
6594
|
+
this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
|
|
6595
|
+
throw new Error(`${detail}; the operation will not be replayed automatically`);
|
|
6025
6596
|
}
|
|
6026
|
-
suffix += 1;
|
|
6027
6597
|
}
|
|
6028
6598
|
|
|
6029
|
-
|
|
6599
|
+
try {
|
|
6600
|
+
await execFileAsync('git', ['-C', root, 'worktree', 'add', '-b', branch, target, 'HEAD'], {
|
|
6601
|
+
timeout: 60000,
|
|
6602
|
+
maxBuffer: 1024 * 1024 * 4,
|
|
6603
|
+
});
|
|
6604
|
+
} catch (error) {
|
|
6605
|
+
const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
|
|
6606
|
+
if (
|
|
6607
|
+
postcondition.proven
|
|
6608
|
+
&& postcondition.exists
|
|
6609
|
+
&& postcondition.registered
|
|
6610
|
+
&& postcondition.branchMatches
|
|
6611
|
+
) {
|
|
6612
|
+
return commitSuccess();
|
|
6613
|
+
}
|
|
6614
|
+
const message = error && error.stderr ? String(error.stderr).trim() : '';
|
|
6615
|
+
const detail = message || 'Failed to create permanent git worktree';
|
|
6616
|
+
if (operation) {
|
|
6617
|
+
const state = postcondition.proven
|
|
6618
|
+
&& !postcondition.exists
|
|
6619
|
+
&& !postcondition.registered
|
|
6620
|
+
&& !postcondition.branchExists
|
|
6621
|
+
? 'failed'
|
|
6622
|
+
: 'unknown';
|
|
6623
|
+
this.commitPersistentProjectOperation(operation, state, null, detail);
|
|
6624
|
+
}
|
|
6625
|
+
throw new Error(detail, { cause: error });
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
|
|
6629
|
+
if (
|
|
6630
|
+
!postcondition.proven
|
|
6631
|
+
|| !postcondition.exists
|
|
6632
|
+
|| !postcondition.registered
|
|
6633
|
+
|| !postcondition.branchMatches
|
|
6634
|
+
) {
|
|
6635
|
+
const detail = postcondition.error || 'Permanent worktree creation could not be proven';
|
|
6636
|
+
if (operation) this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
|
|
6637
|
+
throw new Error(`${detail}; the operation will not be replayed automatically`);
|
|
6638
|
+
}
|
|
6639
|
+
return commitSuccess();
|
|
6030
6640
|
}
|
|
6031
6641
|
|
|
6032
6642
|
async rollbackPermanentWorktree(created) {
|
|
@@ -6093,6 +6703,8 @@ class AgentManager extends EventEmitter {
|
|
|
6093
6703
|
}
|
|
6094
6704
|
|
|
6095
6705
|
try {
|
|
6706
|
+
const worktrees = await this.listGitWorktrees(resolvedWorkspace);
|
|
6707
|
+
const sourceWorkspace = worktrees[0]?.workspace || resolvedWorkspace;
|
|
6096
6708
|
const { stdout } = await execFileAsync('git', ['-C', resolvedWorkspace, 'status', '--porcelain', '--untracked-files=all'], {
|
|
6097
6709
|
timeout: 30000,
|
|
6098
6710
|
maxBuffer: 1024 * 1024 * 4,
|
|
@@ -6100,6 +6712,7 @@ class AgentManager extends EventEmitter {
|
|
|
6100
6712
|
const dirtyEntries = statusEntriesFromPorcelain(stdout);
|
|
6101
6713
|
return {
|
|
6102
6714
|
workspace: resolvedWorkspace,
|
|
6715
|
+
sourceWorkspace,
|
|
6103
6716
|
dirtyEntries,
|
|
6104
6717
|
requiresForce: dirtyEntries.length > 0,
|
|
6105
6718
|
};
|
|
@@ -6151,21 +6764,118 @@ class AgentManager extends EventEmitter {
|
|
|
6151
6764
|
const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
|
|
6152
6765
|
if (!workspaceKey) return this.deleteForkWorktreeProjectAdmitted(workspace, options);
|
|
6153
6766
|
const inFlight = this.projectWorkspaceDeleteAdmissions.get(workspaceKey);
|
|
6154
|
-
|
|
6767
|
+
const requestId = String(options.requestId || '').trim();
|
|
6768
|
+
if (inFlight) {
|
|
6769
|
+
if (requestId && inFlight.requestId === requestId) return inFlight.promise;
|
|
6770
|
+
return inFlight.promise
|
|
6771
|
+
.catch(() => {})
|
|
6772
|
+
.then(() => this.deleteForkWorktreeProject(workspace, options));
|
|
6773
|
+
}
|
|
6155
6774
|
|
|
6156
|
-
const
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6775
|
+
const promise = this.deleteForkWorktreeProjectAdmitted(workspace, options);
|
|
6776
|
+
const admission = { requestId, promise };
|
|
6777
|
+
this.projectWorkspaceDeleteAdmissions.set(workspaceKey, admission);
|
|
6778
|
+
void promise.finally(() => {
|
|
6779
|
+
if (this.projectWorkspaceDeleteAdmissions.get(workspaceKey) === admission) {
|
|
6160
6780
|
this.projectWorkspaceDeleteAdmissions.delete(workspaceKey);
|
|
6161
6781
|
}
|
|
6162
6782
|
}).catch(() => {});
|
|
6163
|
-
return
|
|
6783
|
+
return promise;
|
|
6164
6784
|
}
|
|
6165
6785
|
|
|
6166
6786
|
async deleteForkWorktreeProjectAdmitted(workspace, options = {}) {
|
|
6167
6787
|
await this.whenRecovered();
|
|
6168
6788
|
const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
|
|
6789
|
+
const requestId = String(options.requestId || '').trim().slice(0, 160);
|
|
6790
|
+
const signature = projectOperationSignature({
|
|
6791
|
+
force: options.force === true,
|
|
6792
|
+
type: 'delete-worktree',
|
|
6793
|
+
workspace: workspaceKey,
|
|
6794
|
+
});
|
|
6795
|
+
const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
|
|
6796
|
+
? this.configManager.getProjectOperation(requestId)
|
|
6797
|
+
: null;
|
|
6798
|
+
if (
|
|
6799
|
+
existingOperation
|
|
6800
|
+
&& (
|
|
6801
|
+
existingOperation.type !== 'delete-worktree'
|
|
6802
|
+
|| existingOperation.signature !== signature
|
|
6803
|
+
)
|
|
6804
|
+
) {
|
|
6805
|
+
return { workspace: workspaceKey, error: `Project operation request ${requestId} was already used for different parameters` };
|
|
6806
|
+
}
|
|
6807
|
+
if (existingOperation?.state === 'succeeded' && existingOperation.result) {
|
|
6808
|
+
const settings = this.configManager.getSettings?.() || {};
|
|
6809
|
+
return {
|
|
6810
|
+
...existingOperation.result,
|
|
6811
|
+
deduplicated: true,
|
|
6812
|
+
projectWorkspaces: settings.projectWorkspaces || [],
|
|
6813
|
+
pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
let operation = existingOperation;
|
|
6817
|
+
const storedSourceWorkspace = String(operation?.request?.sourceWorkspace || '');
|
|
6818
|
+
const commitDeleted = (baseResult) => {
|
|
6819
|
+
const result = { ...baseResult, ...(requestId ? { requestId } : {}) };
|
|
6820
|
+
if (!operation) return result;
|
|
6821
|
+
const committed = this.commitPersistentProjectOperation(
|
|
6822
|
+
operation,
|
|
6823
|
+
'succeeded',
|
|
6824
|
+
result,
|
|
6825
|
+
'',
|
|
6826
|
+
{ removeWorkspace: workspaceKey },
|
|
6827
|
+
);
|
|
6828
|
+
return {
|
|
6829
|
+
...result,
|
|
6830
|
+
projectWorkspaces: committed.projectWorkspaces,
|
|
6831
|
+
pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
|
|
6832
|
+
};
|
|
6833
|
+
};
|
|
6834
|
+
if (operation && ['pending', 'unknown'].includes(operation.state) && storedSourceWorkspace) {
|
|
6835
|
+
const postcondition = await this.inspectGitWorktreePostcondition(
|
|
6836
|
+
storedSourceWorkspace,
|
|
6837
|
+
operation.request.workspace || workspaceKey,
|
|
6838
|
+
);
|
|
6839
|
+
if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
|
|
6840
|
+
try {
|
|
6841
|
+
return commitDeleted({
|
|
6842
|
+
workspace: workspaceKey,
|
|
6843
|
+
deleted: true,
|
|
6844
|
+
forced: operation.request.force === true,
|
|
6845
|
+
archivedAgentIds: [],
|
|
6846
|
+
removedMainPageSessionKeys: [],
|
|
6847
|
+
});
|
|
6848
|
+
} catch (error) {
|
|
6849
|
+
return {
|
|
6850
|
+
workspace: workspaceKey,
|
|
6851
|
+
deleted: true,
|
|
6852
|
+
retryable: true,
|
|
6853
|
+
error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
|
|
6854
|
+
};
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
if (operation.state === 'unknown') {
|
|
6858
|
+
return {
|
|
6859
|
+
workspace: workspaceKey,
|
|
6860
|
+
error: operation.error || 'Worktree deletion has an uncertain outcome and will not be replayed automatically',
|
|
6861
|
+
uncertain: true,
|
|
6862
|
+
};
|
|
6863
|
+
}
|
|
6864
|
+
if (!postcondition.proven || postcondition.exists !== postcondition.registered) {
|
|
6865
|
+
const detail = postcondition.error || 'Worktree deletion has a partial or unverifiable result';
|
|
6866
|
+
try {
|
|
6867
|
+
this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
|
|
6868
|
+
} catch {
|
|
6869
|
+
// The previously persisted pending intent still prevents blind replay.
|
|
6870
|
+
}
|
|
6871
|
+
return { workspace: workspaceKey, error: `${detail}; the operation will not be replayed automatically`, uncertain: true };
|
|
6872
|
+
}
|
|
6873
|
+
} else if (operation && !['pending', 'unknown'].includes(operation.state)) {
|
|
6874
|
+
return {
|
|
6875
|
+
workspace: workspaceKey,
|
|
6876
|
+
error: operation.error || `Project operation ${requestId} already finished with state ${operation.state}`,
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6169
6879
|
const relatedStarts = workspaceKey
|
|
6170
6880
|
? [...this.agentStartAdmissions.values()].filter(admission => (
|
|
6171
6881
|
!admission.workspaceKey
|
|
@@ -6193,6 +6903,16 @@ class AgentManager extends EventEmitter {
|
|
|
6193
6903
|
};
|
|
6194
6904
|
}
|
|
6195
6905
|
|
|
6906
|
+
if (!operation) {
|
|
6907
|
+
const admission = this.persistentProjectOperation(requestId, 'delete-worktree', signature, {
|
|
6908
|
+
workspace: inspected.workspace,
|
|
6909
|
+
sourceWorkspace: inspected.sourceWorkspace,
|
|
6910
|
+
force: options.force === true,
|
|
6911
|
+
});
|
|
6912
|
+
if (admission.error) return { ...inspected, error: admission.error };
|
|
6913
|
+
operation = admission.operation;
|
|
6914
|
+
}
|
|
6915
|
+
|
|
6196
6916
|
const archivedAgentIds = [];
|
|
6197
6917
|
const removedMainPageSessionKeys = [];
|
|
6198
6918
|
const projectAgents = this.agentsForProjectWorkspace(inspected.workspace);
|
|
@@ -6220,25 +6940,227 @@ class AgentManager extends EventEmitter {
|
|
|
6220
6940
|
maxBuffer: 1024 * 1024 * 4,
|
|
6221
6941
|
});
|
|
6222
6942
|
} catch (error) {
|
|
6943
|
+
const postcondition = await this.inspectGitWorktreePostcondition(
|
|
6944
|
+
inspected.sourceWorkspace,
|
|
6945
|
+
inspected.workspace,
|
|
6946
|
+
);
|
|
6947
|
+
if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
|
|
6948
|
+
try {
|
|
6949
|
+
return commitDeleted({
|
|
6950
|
+
workspace: inspected.workspace,
|
|
6951
|
+
deleted: true,
|
|
6952
|
+
forced: options.force === true,
|
|
6953
|
+
archivedAgentIds,
|
|
6954
|
+
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
6955
|
+
});
|
|
6956
|
+
} catch (commitError) {
|
|
6957
|
+
return {
|
|
6958
|
+
...inspected,
|
|
6959
|
+
deleted: true,
|
|
6960
|
+
retryable: true,
|
|
6961
|
+
error: `Worktree was deleted, but Project operation commit failed: ${commitError.message || commitError}`,
|
|
6962
|
+
archivedAgentIds,
|
|
6963
|
+
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
6964
|
+
};
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6223
6967
|
const message = error && error.stderr ? String(error.stderr).trim() : '';
|
|
6968
|
+
const detail = message || 'Failed to delete git worktree';
|
|
6969
|
+
if (operation) {
|
|
6970
|
+
const state = postcondition.proven && postcondition.exists && postcondition.registered
|
|
6971
|
+
? 'failed'
|
|
6972
|
+
: 'unknown';
|
|
6973
|
+
try {
|
|
6974
|
+
this.commitPersistentProjectOperation(operation, state, null, detail);
|
|
6975
|
+
} catch {
|
|
6976
|
+
// Preserve the earlier durable pending intent when the result write fails.
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6224
6979
|
return {
|
|
6225
6980
|
...inspected,
|
|
6226
|
-
error:
|
|
6981
|
+
error: detail,
|
|
6982
|
+
...(postcondition.proven && postcondition.exists && postcondition.registered
|
|
6983
|
+
? {}
|
|
6984
|
+
: { uncertain: true }),
|
|
6227
6985
|
archivedAgentIds,
|
|
6228
6986
|
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
6229
6987
|
};
|
|
6230
6988
|
}
|
|
6231
6989
|
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6990
|
+
const postcondition = await this.inspectGitWorktreePostcondition(
|
|
6991
|
+
inspected.sourceWorkspace,
|
|
6992
|
+
inspected.workspace,
|
|
6993
|
+
);
|
|
6994
|
+
if (!postcondition.proven || postcondition.exists || postcondition.registered) {
|
|
6995
|
+
const detail = postcondition.error || 'Worktree deletion could not be proven';
|
|
6996
|
+
if (operation) {
|
|
6997
|
+
try {
|
|
6998
|
+
this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
|
|
6999
|
+
} catch {
|
|
7000
|
+
// Preserve the earlier durable pending intent when the result write fails.
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
return {
|
|
7004
|
+
...inspected,
|
|
7005
|
+
error: `${detail}; the operation will not be replayed automatically`,
|
|
7006
|
+
uncertain: true,
|
|
7007
|
+
archivedAgentIds,
|
|
7008
|
+
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
7009
|
+
};
|
|
7010
|
+
}
|
|
7011
|
+
try {
|
|
7012
|
+
return commitDeleted({
|
|
7013
|
+
workspace: inspected.workspace,
|
|
7014
|
+
deleted: true,
|
|
7015
|
+
forced: options.force === true,
|
|
7016
|
+
archivedAgentIds,
|
|
7017
|
+
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
7018
|
+
});
|
|
7019
|
+
} catch (error) {
|
|
7020
|
+
return {
|
|
7021
|
+
...inspected,
|
|
7022
|
+
deleted: true,
|
|
7023
|
+
retryable: true,
|
|
7024
|
+
error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
|
|
7025
|
+
archivedAgentIds,
|
|
7026
|
+
removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
|
|
7027
|
+
};
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
|
|
7031
|
+
async replayPersistentForkRequest(agent, requestId, signature) {
|
|
7032
|
+
const requestKey = `fork-request:${requestId}`;
|
|
7033
|
+
const operation = lifecycleJournal(agent).entries.find(candidate => (
|
|
7034
|
+
candidate.type === 'fork' && candidate.requestKey === requestKey
|
|
7035
|
+
));
|
|
7036
|
+
if (!operation) return null;
|
|
7037
|
+
if (operation.request?.signature && operation.request.signature !== signature) {
|
|
7038
|
+
return { error: `Fork request ${requestId} was already used for different parameters` };
|
|
7039
|
+
}
|
|
7040
|
+
if (operation.state === 'succeeded' && operation.result) {
|
|
7041
|
+
return { ...operation.result, deduplicated: true };
|
|
7042
|
+
}
|
|
7043
|
+
if (TERMINAL_OPERATION_STATES.has(operation.state)) {
|
|
7044
|
+
return { error: operation.error || `Fork request ${requestId} finished with state ${operation.state}` };
|
|
7045
|
+
}
|
|
7046
|
+
|
|
7047
|
+
const children = typeof this.configManager?.listAgentSessionRecords === 'function'
|
|
7048
|
+
? this.configManager.listAgentSessionRecords().filter(record => (
|
|
7049
|
+
record?.parentAgentId === agent.id
|
|
7050
|
+
&& record?.forkRequestId === requestId
|
|
7051
|
+
&& record?.archived !== true
|
|
7052
|
+
))
|
|
7053
|
+
: [];
|
|
7054
|
+
if (children.length === 1) {
|
|
7055
|
+
const child = children[0];
|
|
7056
|
+
const request = operation.request || {};
|
|
7057
|
+
const result = {
|
|
7058
|
+
agentId: child.runtimeAgentId,
|
|
7059
|
+
workspace: child.projectWorkspace || child.cwd || '',
|
|
7060
|
+
mode: request.mode || 'same-worktree',
|
|
7061
|
+
...(request.targetRuntime ? { targetRuntime: request.targetRuntime } : {}),
|
|
7062
|
+
...(child.providerSessionId ? { providerSessionId: child.providerSessionId } : {}),
|
|
7063
|
+
requestId,
|
|
7064
|
+
};
|
|
7065
|
+
try {
|
|
7066
|
+
this.completePersistentAgentOperation(agent, operation.id, result);
|
|
7067
|
+
return { ...result, deduplicated: true, reconciled: true };
|
|
7068
|
+
} catch (error) {
|
|
7069
|
+
return {
|
|
7070
|
+
...result,
|
|
7071
|
+
error: `Fork exists, but its result could not be committed: ${error.message || error}`,
|
|
7072
|
+
retryable: true,
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
|
|
7077
|
+
const detail = children.length > 1
|
|
7078
|
+
? `Fork request ${requestId} has multiple child Agent records and cannot be reconciled safely`
|
|
7079
|
+
: `Fork request ${requestId} has an uncertain outcome and will not be replayed automatically`;
|
|
7080
|
+
if (operation.state !== 'blocked') {
|
|
7081
|
+
try {
|
|
7082
|
+
this.transitionPersistentAgentOperation(agent, operation.id, 'blocked', detail);
|
|
7083
|
+
} catch (error) {
|
|
7084
|
+
return { error: `${detail}; failed to persist blocked state: ${error.message || error}`, uncertain: true };
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
return { error: detail, uncertain: true };
|
|
6239
7088
|
}
|
|
6240
7089
|
|
|
6241
7090
|
async forkAgent(agentId, mode = 'same-worktree', options = {}) {
|
|
7091
|
+
const requestId = String(options.requestId || '').trim().slice(0, 160);
|
|
7092
|
+
if (!requestId) return this.forkAgentUntracked(agentId, mode, options);
|
|
7093
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
|
|
7094
|
+
return { error: 'Fork requires a valid requestId' };
|
|
7095
|
+
}
|
|
7096
|
+
await this.whenRecovered();
|
|
7097
|
+
const agent = this.agents.get(agentId);
|
|
7098
|
+
if (!agent) return { error: 'Agent not found' };
|
|
7099
|
+
const signature = projectOperationSignature({
|
|
7100
|
+
agentRecordId: agent.agentRecordId || agent.persistentSessionId || '',
|
|
7101
|
+
expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
|
|
7102
|
+
mode,
|
|
7103
|
+
targetRuntime: options.targetRuntime || '',
|
|
7104
|
+
});
|
|
7105
|
+
const key = `fork:${requestId}:${signature}`;
|
|
7106
|
+
const inFlight = this.agentLifecycleOperations.get(agentId);
|
|
7107
|
+
if (inFlight?.key === key) return inFlight.promise;
|
|
7108
|
+
const replay = await this.replayPersistentForkRequest(agent, requestId, signature);
|
|
7109
|
+
if (replay) return replay;
|
|
7110
|
+
return this.runAgentLifecycleOperation(
|
|
7111
|
+
agentId,
|
|
7112
|
+
key,
|
|
7113
|
+
'fork',
|
|
7114
|
+
'fork',
|
|
7115
|
+
async lifecycleToken => {
|
|
7116
|
+
const admission = this.beginPersistentAgentOperation(
|
|
7117
|
+
agent,
|
|
7118
|
+
'fork',
|
|
7119
|
+
`fork-request:${requestId}`,
|
|
7120
|
+
{
|
|
7121
|
+
signature,
|
|
7122
|
+
mode,
|
|
7123
|
+
targetRuntime: options.targetRuntime || '',
|
|
7124
|
+
expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
|
|
7125
|
+
},
|
|
7126
|
+
);
|
|
7127
|
+
if (admission.error) return { error: admission.error };
|
|
7128
|
+
const result = await this.forkAgentUntracked(agentId, mode, {
|
|
7129
|
+
...options,
|
|
7130
|
+
requestId: '',
|
|
7131
|
+
forkRequestId: requestId,
|
|
7132
|
+
lifecycleToken,
|
|
7133
|
+
});
|
|
7134
|
+
if (result?.error) {
|
|
7135
|
+
try {
|
|
7136
|
+
this.transitionPersistentAgentOperation(
|
|
7137
|
+
agent,
|
|
7138
|
+
admission.operation.id,
|
|
7139
|
+
'blocked',
|
|
7140
|
+
result.error,
|
|
7141
|
+
);
|
|
7142
|
+
} catch (error) {
|
|
7143
|
+
return { ...result, error: `${result.error}; failed to persist Fork outcome: ${error.message || error}` };
|
|
7144
|
+
}
|
|
7145
|
+
return { ...result, requestId, uncertain: true };
|
|
7146
|
+
}
|
|
7147
|
+
const committedResult = { ...result, requestId };
|
|
7148
|
+
try {
|
|
7149
|
+
this.completePersistentAgentOperation(agent, admission.operation.id, committedResult);
|
|
7150
|
+
return committedResult;
|
|
7151
|
+
} catch (error) {
|
|
7152
|
+
return {
|
|
7153
|
+
...committedResult,
|
|
7154
|
+
error: `Fork was created, but its result could not be committed: ${error.message || error}`,
|
|
7155
|
+
retainedAgentId: result.agentId,
|
|
7156
|
+
retryable: true,
|
|
7157
|
+
};
|
|
7158
|
+
}
|
|
7159
|
+
},
|
|
7160
|
+
);
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
async forkAgentUntracked(agentId, mode = 'same-worktree', options = {}) {
|
|
6242
7164
|
await this.whenRecovered();
|
|
6243
7165
|
if (this.disposing) {
|
|
6244
7166
|
return { error: 'Farming is shutting down; Agent lifecycle changes are not accepted' };
|
|
@@ -6258,12 +7180,20 @@ class AgentManager extends EventEmitter {
|
|
|
6258
7180
|
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
|
|
6259
7181
|
return { error: 'Conversation Fork requires an exact transcript revision' };
|
|
6260
7182
|
}
|
|
7183
|
+
if (options.lifecycleToken) {
|
|
7184
|
+
return this.performAcpConversationFork(
|
|
7185
|
+
agentId,
|
|
7186
|
+
expectedRevision,
|
|
7187
|
+
options.lifecycleToken,
|
|
7188
|
+
options.forkRequestId || '',
|
|
7189
|
+
);
|
|
7190
|
+
}
|
|
6261
7191
|
return this.runAgentLifecycleOperation(
|
|
6262
7192
|
agentId,
|
|
6263
7193
|
`conversation-fork:${expectedRevision}`,
|
|
6264
7194
|
'conversation-fork',
|
|
6265
7195
|
'conversation fork',
|
|
6266
|
-
lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken),
|
|
7196
|
+
lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken, ''),
|
|
6267
7197
|
);
|
|
6268
7198
|
}
|
|
6269
7199
|
if (agent.providerSessionProvider === 'codex' && agent.providerSessionTemporary === true) {
|
|
@@ -6319,6 +7249,7 @@ class AgentManager extends EventEmitter {
|
|
|
6319
7249
|
}, {
|
|
6320
7250
|
wantsMain: false,
|
|
6321
7251
|
parentAgentId: agent.id,
|
|
7252
|
+
forkRequestId: options.forkRequestId || '',
|
|
6322
7253
|
task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
|
|
6323
7254
|
workflowTemplate: agent.workflowTemplate || '',
|
|
6324
7255
|
source: mode === 'new-worktree' ? 'ui-fork-new-worktree' : 'ui-fork-same-worktree',
|
|
@@ -6331,7 +7262,7 @@ class AgentManager extends EventEmitter {
|
|
|
6331
7262
|
});
|
|
6332
7263
|
}
|
|
6333
7264
|
|
|
6334
|
-
async performAcpConversationFork(agentId, expectedRevision, lifecycleToken) {
|
|
7265
|
+
async performAcpConversationFork(agentId, expectedRevision, lifecycleToken, forkRequestId = '') {
|
|
6335
7266
|
const agent = this.agents.get(agentId);
|
|
6336
7267
|
if (!agent) return { error: 'Agent not found' };
|
|
6337
7268
|
if (runtimeKind(agent) !== 'acp') {
|
|
@@ -6360,6 +7291,7 @@ class AgentManager extends EventEmitter {
|
|
|
6360
7291
|
expectedRevision,
|
|
6361
7292
|
lifecycleToken,
|
|
6362
7293
|
acpSessionOptions,
|
|
7294
|
+
forkRequestId,
|
|
6363
7295
|
});
|
|
6364
7296
|
}
|
|
6365
7297
|
|
|
@@ -6423,6 +7355,7 @@ class AgentManager extends EventEmitter {
|
|
|
6423
7355
|
const started = this.startAgent(command, workspace, finish, {
|
|
6424
7356
|
wantsMain: false,
|
|
6425
7357
|
parentAgentId: agent.id,
|
|
7358
|
+
forkRequestId,
|
|
6426
7359
|
task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
|
|
6427
7360
|
workflowTemplate: agent.workflowTemplate || '',
|
|
6428
7361
|
source: 'ui-fork-acp-chat',
|
|
@@ -6467,6 +7400,7 @@ class AgentManager extends EventEmitter {
|
|
|
6467
7400
|
expectedRevision,
|
|
6468
7401
|
lifecycleToken,
|
|
6469
7402
|
acpSessionOptions,
|
|
7403
|
+
forkRequestId,
|
|
6470
7404
|
} = options;
|
|
6471
7405
|
const command = getProviderAdapter(provider)?.executable || provider;
|
|
6472
7406
|
let preparedSessionId = '';
|
|
@@ -6507,6 +7441,7 @@ class AgentManager extends EventEmitter {
|
|
|
6507
7441
|
const started = this.startAgent(command, workspace, finish, {
|
|
6508
7442
|
wantsMain: false,
|
|
6509
7443
|
parentAgentId: agent.id,
|
|
7444
|
+
forkRequestId,
|
|
6510
7445
|
task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
|
|
6511
7446
|
workflowTemplate: agent.workflowTemplate || '',
|
|
6512
7447
|
source: 'ui-fork-acp-chat',
|
|
@@ -6732,7 +7667,7 @@ class AgentManager extends EventEmitter {
|
|
|
6732
7667
|
}
|
|
6733
7668
|
let removedMainPageSessionKeys = [];
|
|
6734
7669
|
try {
|
|
6735
|
-
this.transitionPersistentAgentOperation(agent, operationId, '
|
|
7670
|
+
this.transitionPersistentAgentOperation(agent, operationId, 'provider-archive-pending', '', {
|
|
6736
7671
|
visibleOnMainPage: false,
|
|
6737
7672
|
archived: true,
|
|
6738
7673
|
archivedAt: Date.now(),
|
|
@@ -6750,6 +7685,8 @@ class AgentManager extends EventEmitter {
|
|
|
6750
7685
|
removedMainPageSessionKeys,
|
|
6751
7686
|
};
|
|
6752
7687
|
}
|
|
7688
|
+
agent.archived = true;
|
|
7689
|
+
agent.archivedAt = Date.now();
|
|
6753
7690
|
let metadataWarning = '';
|
|
6754
7691
|
try {
|
|
6755
7692
|
removedMainPageSessionKeys = this.removeMainPageProviderSessionsForAgents([agent]);
|
|
@@ -6759,7 +7696,7 @@ class AgentManager extends EventEmitter {
|
|
|
6759
7696
|
}
|
|
6760
7697
|
|
|
6761
7698
|
let historyWarning = '';
|
|
6762
|
-
if (options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
|
|
7699
|
+
if (!admission.joined && options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
|
|
6763
7700
|
const previousTaskHistory = this.taskHistory;
|
|
6764
7701
|
try {
|
|
6765
7702
|
this.recordTaskHistory(agent, {
|
|
@@ -6772,8 +7709,59 @@ class AgentManager extends EventEmitter {
|
|
|
6772
7709
|
console.error(historyWarning);
|
|
6773
7710
|
}
|
|
6774
7711
|
}
|
|
7712
|
+
if (options.scheduleProviderArchive !== false) {
|
|
7713
|
+
const providerArchive = await this.archiveCodexProviderSession(agent);
|
|
7714
|
+
if (providerArchive?.error) {
|
|
7715
|
+
try {
|
|
7716
|
+
this.transitionPersistentAgentOperation(agent, operationId, 'blocked', providerArchive.error, {
|
|
7717
|
+
visibleOnMainPage: false,
|
|
7718
|
+
archived: true,
|
|
7719
|
+
runtimeAgentId: '',
|
|
7720
|
+
});
|
|
7721
|
+
} catch (error) {
|
|
7722
|
+
return {
|
|
7723
|
+
agentId,
|
|
7724
|
+
archived: true,
|
|
7725
|
+
stopped: true,
|
|
7726
|
+
retryable: true,
|
|
7727
|
+
operationId,
|
|
7728
|
+
removedMainPageSessionKeys,
|
|
7729
|
+
error: `Provider archive failed: ${providerArchive.error}; failed to persist blocked Archive: ${error.message || error}`,
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7732
|
+
this.emit('update');
|
|
7733
|
+
return {
|
|
7734
|
+
agentId,
|
|
7735
|
+
archived: true,
|
|
7736
|
+
stopped: true,
|
|
7737
|
+
providerArchived: false,
|
|
7738
|
+
retryable: true,
|
|
7739
|
+
operationId,
|
|
7740
|
+
removedMainPageSessionKeys,
|
|
7741
|
+
error: `Provider archive failed: ${providerArchive.error}`,
|
|
7742
|
+
};
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7745
|
+
try {
|
|
7746
|
+
this.transitionPersistentAgentOperation(agent, operationId, 'succeeded', '', {
|
|
7747
|
+
visibleOnMainPage: false,
|
|
7748
|
+
archived: true,
|
|
7749
|
+
runtimeAgentId: '',
|
|
7750
|
+
});
|
|
7751
|
+
} catch (error) {
|
|
7752
|
+
this.emit('update');
|
|
7753
|
+
return {
|
|
7754
|
+
agentId,
|
|
7755
|
+
archived: true,
|
|
7756
|
+
stopped: true,
|
|
7757
|
+
providerArchived: true,
|
|
7758
|
+
retryable: true,
|
|
7759
|
+
operationId,
|
|
7760
|
+
removedMainPageSessionKeys,
|
|
7761
|
+
error: `Provider archive succeeded, but terminal Archive result could not be saved: ${error.message || error}`,
|
|
7762
|
+
};
|
|
7763
|
+
}
|
|
6775
7764
|
this.forgetStoppedAgentRecord(agentId);
|
|
6776
|
-
if (options.scheduleProviderArchive !== false) this.scheduleCodexSessionArchive(agent);
|
|
6777
7765
|
return {
|
|
6778
7766
|
agentId,
|
|
6779
7767
|
archived: true,
|
|
@@ -6788,14 +7776,14 @@ class AgentManager extends EventEmitter {
|
|
|
6788
7776
|
};
|
|
6789
7777
|
}
|
|
6790
7778
|
|
|
6791
|
-
|
|
7779
|
+
async archiveCodexProviderSession(agent) {
|
|
6792
7780
|
if (
|
|
6793
7781
|
!agent
|
|
6794
7782
|
|| agent.providerSessionProvider !== 'codex'
|
|
6795
7783
|
|| !agent.providerSessionId
|
|
6796
7784
|
|| agent.providerSessionTemporary === true
|
|
6797
7785
|
) {
|
|
6798
|
-
return;
|
|
7786
|
+
return null;
|
|
6799
7787
|
}
|
|
6800
7788
|
|
|
6801
7789
|
const sessionId = agent.providerSessionId;
|
|
@@ -6805,26 +7793,23 @@ class AgentManager extends EventEmitter {
|
|
|
6805
7793
|
workspace: agent.projectWorkspace || '',
|
|
6806
7794
|
providerHomePath: agent.providerHomePath || '',
|
|
6807
7795
|
};
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
.catch(error => {
|
|
6824
|
-
console.error(`Failed to archive Codex session ${sessionId}:`, error);
|
|
6825
|
-
});
|
|
7796
|
+
try {
|
|
7797
|
+
const result = await this.enqueueCodexSessionMutation(
|
|
7798
|
+
sessionId,
|
|
7799
|
+
{
|
|
7800
|
+
providerHomeId: agent.providerHomeId || 'default',
|
|
7801
|
+
providerHomePath: agent.providerHomePath || '',
|
|
7802
|
+
},
|
|
7803
|
+
'archive',
|
|
7804
|
+
() => this.archiveCodexSession(sessionId, session),
|
|
7805
|
+
true,
|
|
7806
|
+
);
|
|
7807
|
+
return result?.error ? { error: result.error } : { archived: true };
|
|
7808
|
+
} catch (error) {
|
|
7809
|
+
return { error: error.message || String(error) };
|
|
7810
|
+
}
|
|
6826
7811
|
}
|
|
6827
|
-
|
|
7812
|
+
|
|
6828
7813
|
admitPersistentDelete(agent, options = {}) {
|
|
6829
7814
|
const activeOperation = activeLifecycleOperation(agent);
|
|
6830
7815
|
if (
|