specrails-desktop 2.16.0 → 2.18.0
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/client/dist/assets/{ActivityFeedPage-C_clKk8d.js → ActivityFeedPage-mGnv5SOe.js} +1 -1
- package/client/dist/assets/AgentsPage-BH1isg5j.js +86 -0
- package/client/dist/assets/AnalyticsPage-ByyshYRI.js +1 -0
- package/client/dist/assets/{BarChart-Dq1h8eYR.js → BarChart-BqHUOnl9.js} +3 -3
- package/client/dist/assets/CodePage-CU7T_HYs.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CYhXhJoQ.js → DesktopAnalyticsPage-CR-m97Ui.js} +1 -1
- package/client/dist/assets/DocsDialog-CEmZp_8V.js +11 -0
- package/client/dist/assets/DocsPage-DYbAQ1AT.js +11 -0
- package/client/dist/assets/{ExportDropdown-DdB2ncYh.js → ExportDropdown-C-o1Q4ef.js} +1 -1
- package/client/dist/assets/IntegrationsPage-C6fKdwVH.js +3 -0
- package/client/dist/assets/JobDetailPage-CKfIfBgE.js +16 -0
- package/client/dist/assets/JobsPage-nBng0riZ.js +1 -0
- package/client/dist/assets/LoopBuilderPage-Co_TYqYk.js +7 -0
- package/client/dist/assets/LoopsPage-a2hd37Ya.js +1 -0
- package/client/dist/assets/{activity-Ca2XoBi-.js → activity-D8vQJrpW.js} +1 -1
- package/client/dist/assets/{addon-image-biWX2UUm.js → addon-image-Ba-TY3wr.js} +1 -1
- package/client/dist/assets/{addon-ligatures-D4EiDb5r.js → addon-ligatures-T4ARLiuu.js} +1 -1
- package/client/dist/assets/{addon-webgl-CDCIZQ03.js → addon-webgl-Cd73n8eh.js} +1 -1
- package/client/dist/assets/{addspec-DE_VfIyP.js → addspec-BloGn_ey.js} +1 -1
- package/client/dist/assets/agent-9WdSxPU5.js +1 -0
- package/client/dist/assets/agent-ASIyVcg0.js +1 -0
- package/client/dist/assets/agent-Bm2tPrdr.js +1 -0
- package/client/dist/assets/agent-C4bO1l_v.js +1 -0
- package/client/dist/assets/agent-CgKsh88Z2.js +1 -0
- package/client/dist/assets/agent-DMUrz9ZS2.js +1 -0
- package/client/dist/assets/agent-Dwkz5mPS.js +1 -0
- package/client/dist/assets/agent-pO8x4jbR.js +1 -0
- package/client/dist/assets/{agents-DcoUUFZl.js → agents-52t8NVpS.js} +1 -1
- package/client/dist/assets/{agents-DCX9D3-Q.js → agents-BAxAwHy-.js} +1 -1
- package/client/dist/assets/{agents-Jc-CjBDB.js → agents-BUYBb825.js} +1 -1
- package/client/dist/assets/{agents-CIhRSOna.js → agents-BjT_xLjv.js} +1 -1
- package/client/dist/assets/{agents-BYjUOrLD.js → agents-C07al8rI.js} +1 -1
- package/client/dist/assets/{agents-1lhqEcXH.js → agents-DfGvYtpq2.js} +1 -1
- package/client/dist/assets/{agents-CQlu1nOm.js → agents-Dli56gr82.js} +1 -1
- package/client/dist/assets/{agents-DVYpVGur.js → agents-Eqw_AQ6H.js} +1 -1
- package/client/dist/assets/{agentstudio-DCTpIqOx.js → agentstudio-CZm2rOzt.js} +1 -1
- package/client/dist/assets/{aiedit-Dirqf3Bm.js → aiedit-DYtnZU9H.js} +1 -1
- package/client/dist/assets/{analytics-BE49LbUk.js → analytics-B4FJ_pry.js} +1 -1
- package/client/dist/assets/{attachments-C2JPppoP.js → attachments-Ci6_42Kh.js} +1 -1
- package/client/dist/assets/{browser-Dpo_ug4c.js → browser-ZB1afzjZ.js} +1 -1
- package/client/dist/assets/{chat-mq1x9cxZ.js → chat-VMflhzxn.js} +1 -1
- package/client/dist/assets/chunk-BNv3lrIs.js +1 -0
- package/client/dist/assets/{code-DtIatVES.js → code-wtcs0ivb.js} +1 -1
- package/client/dist/assets/commands-Bc8y9eNo2.js +1 -0
- package/client/dist/assets/commands-Bl3-KVfj.js +1 -0
- package/client/dist/assets/commands-C5WQ9blx2.js +1 -0
- package/client/dist/assets/commands-CM99Upsz2.js +1 -0
- package/client/dist/assets/commands-Cc2WpuXa.js +1 -0
- package/client/dist/assets/commands-CckaBWdV.js +1 -0
- package/client/dist/assets/commands-DASXJD-l.js +1 -0
- package/client/dist/assets/commands-k9wbNYoi.js +1 -0
- package/client/dist/assets/{common-kftoE0Q-.js → common-BPQQkgJZ.js} +1 -1
- package/client/dist/assets/{cssMode-DSHXamUH.js → cssMode-DsArsSCT.js} +1 -1
- package/client/dist/assets/{dashboard-BQ3Oa8CE.js → dashboard-Cyzp4ox9.js} +1 -1
- package/client/dist/assets/{dist-js-Ele_sHO7.js → dist-js-BHhtSerX.js} +1 -1
- package/client/dist/assets/{dist-js-D27I-bfi.js → dist-js-CUegfF6k.js} +1 -1
- package/client/dist/assets/{editor.api2-Cwla1nvV.js → editor.api2-T7u8kQWG.js} +1 -1
- package/client/dist/assets/{editor.main-Cd8qBRAT.js → editor.main-G3Sl9C67.js} +2 -2
- package/client/dist/assets/{explore-CBYU76JD.js → explore-BKVV5JSu.js} +1 -1
- package/client/dist/assets/{freemarker2-CkEKTBm2.js → freemarker2-pBzQNgFi.js} +1 -1
- package/client/dist/assets/{handlebars-BRGx0JpL.js → handlebars-CtBTCnBI.js} +1 -1
- package/client/dist/assets/{html-BfRIaGgN.js → html-Dx3DYBzY.js} +1 -1
- package/client/dist/assets/{htmlMode-BTpSnsDE.js → htmlMode-In7frFOo.js} +1 -1
- package/client/dist/assets/index-B0FxQALR.css +2 -0
- package/client/dist/assets/index-B7vB20Jw.js +152 -0
- package/client/dist/assets/{integrations-jJGaKwff.js → integrations-CuWnMR5L.js} +1 -1
- package/client/dist/assets/{javascript-DMmE2kKN.js → javascript-BKcn-YkN.js} +1 -1
- package/client/dist/assets/{jira-BwACz875.js → jira-DXMoAKWD.js} +1 -1
- package/client/dist/assets/{jobs-DRInW0AE.js → jobs---dwa4Nf.js} +1 -1
- package/client/dist/assets/{jsonMode-hYuaUleY.js → jsonMode-jNREoQ3C.js} +1 -1
- package/client/dist/assets/{lib-qdRfWwE1.js → lib-PQoHlR5u.js} +2 -2
- package/client/dist/assets/{liquid-C-VbuTr7.js → liquid-CtRkGqBu.js} +1 -1
- package/client/dist/assets/{loops-DfGadne0.js → loops-D8Iu6Jss.js} +1 -1
- package/client/dist/assets/{lspLanguageFeatures-BALx-vjn.js → lspLanguageFeatures-Cl3gvD_W.js} +1 -1
- package/client/dist/assets/mcp-B4irfLJ2.js +1 -0
- package/client/dist/assets/mcp-B6nwJJmR.js +1 -0
- package/client/dist/assets/mcp-BOoEGTSQ.js +1 -0
- package/client/dist/assets/mcp-BP7PpbDr2.js +1 -0
- package/client/dist/assets/mcp-CIH0pH1K.js +1 -0
- package/client/dist/assets/mcp-CNgFiTEh.js +1 -0
- package/client/dist/assets/mcp-D3E4_Mmk2.js +1 -0
- package/client/dist/assets/mcp-mupPuPDO2.js +1 -0
- package/client/dist/assets/{mdx-Cs8MNCzn.js → mdx-B_nJldHz.js} +1 -1
- package/client/dist/assets/{monaco.contribution-CIvCAJs4.js → monaco.contribution-PUeaZk8T.js} +2 -2
- package/client/dist/assets/{nav-CCz4krPP.js → nav-DE3yWQw6.js} +1 -1
- package/client/dist/assets/{python-CGShLxuI.js → python-DS1Ce-A6.js} +1 -1
- package/client/dist/assets/{razor-CvOBQGd5.js → razor-L41Qg9OK.js} +1 -1
- package/client/dist/assets/settings-BKmORxlB2.js +1 -0
- package/client/dist/assets/settings-BVv2JgQ8.js +1 -0
- package/client/dist/assets/settings-Bghm3pAV.js +1 -0
- package/client/dist/assets/settings-BuC_aplX.js +1 -0
- package/client/dist/assets/settings-Bw_Qk8E32.js +1 -0
- package/client/dist/assets/settings-CAKgJv7p.js +1 -0
- package/client/dist/assets/settings-CEUgk1Ck.js +1 -0
- package/client/dist/assets/settings-C_PDE20s2.js +1 -0
- package/client/dist/assets/setup-4us-Bz3H.js +1 -0
- package/client/dist/assets/setup-BYoDYoPY2.js +1 -0
- package/client/dist/assets/setup-Bf9eLKxU.js +1 -0
- package/client/dist/assets/setup-C5UbM0RP.js +1 -0
- package/client/dist/assets/setup-CqGrWb6G.js +1 -0
- package/client/dist/assets/setup-JFkWWZnR.js +1 -0
- package/client/dist/assets/setup-faMApo0U2.js +1 -0
- package/client/dist/assets/setup-tloaTobG2.js +1 -0
- package/client/dist/assets/{specs-C_8j4ELZ.js → specs-BYeAbIEy.js} +1 -1
- package/client/dist/assets/{terminal-Cj4HzP3p.js → terminal-DFPNRLKp.js} +1 -1
- package/client/dist/assets/{tickets-DBQXMYYA.js → tickets-CkfbISuK.js} +1 -1
- package/client/dist/assets/{tsMode-04uOpiiP.js → tsMode-BC1g186U.js} +1 -1
- package/client/dist/assets/{typescript-CX0CwyIZ.js → typescript-BGlRGvJW.js} +1 -1
- package/client/dist/assets/upload-PnJDPHZH.js +1 -0
- package/client/dist/assets/{value-BRVOAvhY.js → value-BTDefcOV.js} +1 -1
- package/client/dist/assets/{workers-yr2u4c3C.js → workers-CXcXtO3b.js} +1 -1
- package/client/dist/assets/{xml-C0OFlt5X.js → xml-JdZZaIhJ.js} +1 -1
- package/client/dist/assets/{yaml-DgabaPs5.js → yaml-Cm82BVBt.js} +1 -1
- package/client/dist/index.html +30 -28
- package/docs/README.md +2 -0
- package/docs/guide/de/integrations/5-mcp-server.md +74 -0
- package/docs/guide/de/integrations/6-agent-chat.md +64 -0
- package/docs/guide/en/integrations/5-mcp-server.md +74 -0
- package/docs/guide/en/integrations/6-agent-chat.md +64 -0
- package/docs/guide/es/integrations/5-mcp-server.md +74 -0
- package/docs/guide/es/integrations/6-agent-chat.md +64 -0
- package/docs/guide/fr/integrations/5-mcp-server.md +74 -0
- package/docs/guide/fr/integrations/6-agent-chat.md +64 -0
- package/docs/guide/it/integrations/5-mcp-server.md +74 -0
- package/docs/guide/it/integrations/6-agent-chat.md +64 -0
- package/docs/guide/ja/integrations/5-mcp-server.md +74 -0
- package/docs/guide/ja/integrations/6-agent-chat.md +64 -0
- package/docs/guide/pt/integrations/5-mcp-server.md +74 -0
- package/docs/guide/pt/integrations/6-agent-chat.md +64 -0
- package/docs/guide/zh/integrations/5-mcp-server.md +74 -0
- package/docs/guide/zh/integrations/6-agent-chat.md +64 -0
- package/docs/mcp.md +249 -0
- package/package.json +8 -5
- package/server/dist/agent-chat-manager.js +224 -0
- package/server/dist/agent-chat-router.js +148 -0
- package/server/dist/agent-cwd-manager.js +94 -0
- package/server/dist/agent-mcp-config.js +208 -0
- package/server/dist/agent-store.js +59 -0
- package/server/dist/agent-tier.js +86 -0
- package/server/dist/desktop-db.js +27 -0
- package/server/dist/index.js +38 -0
- package/server/dist/mcp/guide.js +82 -0
- package/server/dist/mcp/index.js +21 -0
- package/server/dist/mcp/mcp-admin-router.js +71 -0
- package/server/dist/mcp/mcp-server.js +143 -0
- package/server/dist/mcp/mcp-tiers.js +48 -0
- package/server/dist/mcp/mcp-token.js +119 -0
- package/server/dist/mcp/resources.js +24 -0
- package/server/dist/mcp/tools/agents.js +319 -0
- package/server/dist/mcp/tools/analytics.js +168 -0
- package/server/dist/mcp/tools/app-settings.js +81 -0
- package/server/dist/mcp/tools/catalog.js +45 -0
- package/server/dist/mcp/tools/chat.js +147 -0
- package/server/dist/mcp/tools/code.js +145 -0
- package/server/dist/mcp/tools/jira.js +222 -0
- package/server/dist/mcp/tools/jobs.js +258 -0
- package/server/dist/mcp/tools/loops.js +229 -0
- package/server/dist/mcp/tools/meta.js +96 -0
- package/server/dist/mcp/tools/plugins.js +83 -0
- package/server/dist/mcp/tools/projects.js +62 -0
- package/server/dist/mcp/tools/rails.js +181 -0
- package/server/dist/mcp/tools/setup.js +167 -0
- package/server/dist/mcp/tools/specs.js +327 -0
- package/server/dist/mcp/tools/types.js +156 -0
- package/server/dist/mcp/tools/watch.js +93 -0
- package/server/dist/mobile/mobile-event-bus.js +2 -0
- package/server/dist/project-router-setup.js +15 -1
- package/server/dist/project-router-tickets.js +2 -2
- package/server/dist/providers/codex-adapter.js +19 -3
- package/server/dist/rails-router.js +5 -0
- package/server/dist/setup-manager.js +28 -0
- package/server/dist/spawn-lifecycle.js +21 -13
- package/server/dist/workspace-manager.js +11 -0
- package/client/dist/assets/AgentsPage-jcPUtPhP.js +0 -86
- package/client/dist/assets/AnalyticsPage-DZkVU3yd.js +0 -1
- package/client/dist/assets/CodePage-BcbZT88K.js +0 -2
- package/client/dist/assets/DocsDialog-CS3Syf61.js +0 -11
- package/client/dist/assets/DocsPage-l3ZcTO2E.js +0 -11
- package/client/dist/assets/IntegrationsPage-CJ_Om1zu.js +0 -3
- package/client/dist/assets/JobDetailPage-D5Cxw0md.js +0 -16
- package/client/dist/assets/JobsPage-3v_iX83r.js +0 -1
- package/client/dist/assets/LoopBuilderPage-4s_adZy5.js +0 -7
- package/client/dist/assets/LoopsPage-DA94-o0j.js +0 -1
- package/client/dist/assets/chunk-CilyBKbf.js +0 -1
- package/client/dist/assets/commands-B9XEyMj-.js +0 -1
- package/client/dist/assets/commands-BI1Ta-E7.js +0 -1
- package/client/dist/assets/commands-BdI0CtOL.js +0 -1
- package/client/dist/assets/commands-C2ewlyXo.js +0 -1
- package/client/dist/assets/commands-CwypztRT.js +0 -1
- package/client/dist/assets/commands-DSX3yJyr.js +0 -1
- package/client/dist/assets/commands-Ic1DJDxm.js +0 -1
- package/client/dist/assets/commands-yNn4IA4C.js +0 -1
- package/client/dist/assets/index-BRMyOS1B.css +0 -2
- package/client/dist/assets/index-meTWPBzY.js +0 -144
- package/client/dist/assets/settings-0m2iuTzd.js +0 -1
- package/client/dist/assets/settings-BA1V6KdK.js +0 -1
- package/client/dist/assets/settings-BN7OusER.js +0 -1
- package/client/dist/assets/settings-Bi5FFkIw.js +0 -1
- package/client/dist/assets/settings-CRJxyTNN.js +0 -1
- package/client/dist/assets/settings-DyBxunCh.js +0 -1
- package/client/dist/assets/settings-fk-WlnUt.js +0 -1
- package/client/dist/assets/settings-iQe5Dim-.js +0 -1
- package/client/dist/assets/setup-BOmQ4vh2.js +0 -1
- package/client/dist/assets/setup-BwO2-kuK.js +0 -1
- package/client/dist/assets/setup-CUTNWz-0.js +0 -1
- package/client/dist/assets/setup-Cf3hjChU.js +0 -1
- package/client/dist/assets/setup-CsuXrbJ2.js +0 -1
- package/client/dist/assets/setup-Df4cJOqu.js +0 -1
- package/client/dist/assets/setup-Do0fWx6W.js +0 -1
- package/client/dist/assets/setup-GlkDhBlJ.js +0 -1
- package/client/dist/assets/upload-DPG87J4t.js +0 -1
- /package/client/dist/assets/{abap-CK68cr9-.js → abap-CpHUwCVK.js} +0 -0
- /package/client/dist/assets/{activity-Di2f50_Z.js → activity-B82z3Pna.js} +0 -0
- /package/client/dist/assets/{activity-B2MvEH54.js → activity-BId0pwuS2.js} +0 -0
- /package/client/dist/assets/{activity-D4dhNEwO.js → activity-BVpZrs0J.js} +0 -0
- /package/client/dist/assets/{activity-DHjfmAyT.js → activity-BySXFT2J2.js} +0 -0
- /package/client/dist/assets/{activity-usnNa7FX.js → activity-DIWnV0Pl.js} +0 -0
- /package/client/dist/assets/{activity-C7VdmeGb.js → activity-Uj576ckT.js} +0 -0
- /package/client/dist/assets/{activity-BbNkUmW4.js → activity-w1qMFwxh.js} +0 -0
- /package/client/dist/assets/{addspec-CO1xv6BL.js → addspec-B9dY_F5T.js} +0 -0
- /package/client/dist/assets/{addspec-CNyAUGz_.js → addspec-B9nhMikb.js} +0 -0
- /package/client/dist/assets/{addspec-IozoiiGs.js → addspec-BtNaJHu2.js} +0 -0
- /package/client/dist/assets/{addspec-BxMn39wn.js → addspec-D43TlMVp2.js} +0 -0
- /package/client/dist/assets/{addspec-1HEtmLv1.js → addspec-DfgIZ5lO.js} +0 -0
- /package/client/dist/assets/{addspec-DfuR6EAl.js → addspec-W0r16BOC2.js} +0 -0
- /package/client/dist/assets/{addspec-DAveBiCE.js → addspec-tbxr3sHa.js} +0 -0
- /package/client/dist/assets/{agentstudio-DCyDW8sy.js → agentstudio-0o3XKrcy.js} +0 -0
- /package/client/dist/assets/{agentstudio-9NbsoGXk.js → agentstudio-Bu8m5jDw.js} +0 -0
- /package/client/dist/assets/{agentstudio-B1y0JWY-.js → agentstudio-C7xxqVWn.js} +0 -0
- /package/client/dist/assets/{agentstudio-1E-bag77.js → agentstudio-D-UoNAIA.js} +0 -0
- /package/client/dist/assets/{agentstudio-B-jQgYV-.js → agentstudio-OJjjz6Uf2.js} +0 -0
- /package/client/dist/assets/{agentstudio-Q0Sq_jTg.js → agentstudio-ObTpVbBP.js} +0 -0
- /package/client/dist/assets/{agentstudio-Bwa72b1S.js → agentstudio-PzFB3vfh2.js} +0 -0
- /package/client/dist/assets/{aiedit-DUpe7NxI.js → aiedit-BIQqv2XM2.js} +0 -0
- /package/client/dist/assets/{aiedit-kAU7KADg.js → aiedit-BaLZalLa.js} +0 -0
- /package/client/dist/assets/{aiedit-CvSspQmE.js → aiedit-BjlUzasF.js} +0 -0
- /package/client/dist/assets/{aiedit-DSLtsYIu.js → aiedit-BmDufyGU.js} +0 -0
- /package/client/dist/assets/{aiedit-qTGI_PGs.js → aiedit-DAM_WRAl2.js} +0 -0
- /package/client/dist/assets/{aiedit-CfBNanHE.js → aiedit-DRrNiaB4.js} +0 -0
- /package/client/dist/assets/{aiedit-CGxeml8D.js → aiedit-sP_iao2j.js} +0 -0
- /package/client/dist/assets/{analytics-CgEkAUiP.js → analytics-BDqqCzN9.js} +0 -0
- /package/client/dist/assets/{analytics-DEsEAJZL.js → analytics-B_rxNC9X.js} +0 -0
- /package/client/dist/assets/{analytics-D1ElChhs.js → analytics-C4z_NgJZ.js} +0 -0
- /package/client/dist/assets/{analytics-DDqve9Ma.js → analytics-DL2vJCbq.js} +0 -0
- /package/client/dist/assets/{analytics-CQqu1yE5.js → analytics-Ds_PNxtO2.js} +0 -0
- /package/client/dist/assets/{analytics-BDlELxc7.js → analytics-MVJRjcY52.js} +0 -0
- /package/client/dist/assets/{analytics-DBGO8B6-.js → analytics-e1mkNTgy2.js} +0 -0
- /package/client/dist/assets/{apex-BHHyAVNb.js → apex-DlD7oJX6.js} +0 -0
- /package/client/dist/assets/{atom-one-dark-DrWoEEbZ.js → atom-one-dark-BrRN8wBy.js} +0 -0
- /package/client/dist/assets/{attachments-BNgH6Tcu.js → attachments-8QCKHCEF2.js} +0 -0
- /package/client/dist/assets/{attachments-BLqqPGyy.js → attachments-BP0Ac1yt.js} +0 -0
- /package/client/dist/assets/{attachments-UJtEq892.js → attachments-CJaO1vCH.js} +0 -0
- /package/client/dist/assets/{attachments-D5fVsMgb.js → attachments-CPBVeHX72.js} +0 -0
- /package/client/dist/assets/{attachments-3CuBBRFu.js → attachments-CV2uaTGE.js} +0 -0
- /package/client/dist/assets/{attachments-CppM7de8.js → attachments-CaOjdN332.js} +0 -0
- /package/client/dist/assets/{attachments-pW3zm9nu.js → attachments-Db7JfA8a.js} +0 -0
- /package/client/dist/assets/{azcli-C56lfIEP.js → azcli-CmXFAbc-.js} +0 -0
- /package/client/dist/assets/{bat-BLamSaf-.js → bat-DdNJq4hH.js} +0 -0
- /package/client/dist/assets/{bicep-lrOcEItS.js → bicep-Bppz7WOA.js} +0 -0
- /package/client/dist/assets/{browser-C__Vz7KR.js → browser-0xeW8fUa2.js} +0 -0
- /package/client/dist/assets/{browser-EFmPpkvO.js → browser-543jbxE4.js} +0 -0
- /package/client/dist/assets/{browser-DhwTc0ry.js → browser-C9rUUxo4.js} +0 -0
- /package/client/dist/assets/{browser-C91MQBxx.js → browser-CEqx11iy.js} +0 -0
- /package/client/dist/assets/{browser-B7i7Ysc8.js → browser-CLC0etB_2.js} +0 -0
- /package/client/dist/assets/{browser-DwE289IS.js → browser-DyG2l9Wa.js} +0 -0
- /package/client/dist/assets/{browser-BVQgd1_1.js → browser-ExmELH702.js} +0 -0
- /package/client/dist/assets/{cameligo-BK_p0bgU.js → cameligo-0MRMdSmg.js} +0 -0
- /package/client/dist/assets/{chat-C5nJF6L0.js → chat-CCytD3P8.js} +0 -0
- /package/client/dist/assets/{chat-UMl6H55K.js → chat-CN8BMAMc.js} +0 -0
- /package/client/dist/assets/{chat-BXVx6rb5.js → chat-Cu6F3d6g2.js} +0 -0
- /package/client/dist/assets/{chat-CE8bx6Rz.js → chat-DagUyoVA.js} +0 -0
- /package/client/dist/assets/{chat-PVakYp92.js → chat-IjutwlBg.js} +0 -0
- /package/client/dist/assets/{chat-9RcD5Lcn.js → chat-NQPCBQo72.js} +0 -0
- /package/client/dist/assets/{chat-BnJPyeUK.js → chat-vVpwkOp52.js} +0 -0
- /package/client/dist/assets/{clojure-BQ7JOykv.js → clojure-B5dKj863.js} +0 -0
- /package/client/dist/assets/{clsx-CjLeUJfG.js → clsx-1dnHRn8W.js} +0 -0
- /package/client/dist/assets/{code-OoLqhjoI.js → code-32l6Naxu2.js} +0 -0
- /package/client/dist/assets/{code-Lg4awa35.js → code-BaG05zK62.js} +0 -0
- /package/client/dist/assets/{code-BPMR8_Vp.js → code-C5cibF2F.js} +0 -0
- /package/client/dist/assets/{code-aCUqkVN_.js → code-Ce-nBIwj.js} +0 -0
- /package/client/dist/assets/{code-BsOBm1Fb.js → code-Dap54vOb.js} +0 -0
- /package/client/dist/assets/{code-u9Sg9ohM.js → code-Dhb6jbAU.js} +0 -0
- /package/client/dist/assets/{code-eh4vO2-_.js → code-_NOCo-Ol2.js} +0 -0
- /package/client/dist/assets/{coffee-BD3DiL5s.js → coffee-DSbWjzk7.js} +0 -0
- /package/client/dist/assets/{common-C9VyfY7g.js → common-0aY9HQBP.js} +0 -0
- /package/client/dist/assets/{common-LDwESeCX.js → common-5jScBJqt.js} +0 -0
- /package/client/dist/assets/{common-ULlk84G4.js → common-BdaRciTu.js} +0 -0
- /package/client/dist/assets/{common-CzCQ0rpW.js → common-CHARW6MZ2.js} +0 -0
- /package/client/dist/assets/{common-CNoRVpnA.js → common-CQCJzAQq2.js} +0 -0
- /package/client/dist/assets/{common-Dmpyh323.js → common-DNhhqjlJ.js} +0 -0
- /package/client/dist/assets/{common-CcHFVATT.js → common-YdyZtmaI2.js} +0 -0
- /package/client/dist/assets/{cpp-splnCwCh.js → cpp-SBF8SAh9.js} +0 -0
- /package/client/dist/assets/{csharp-BCpsl-o3.js → csharp-Lzuq_1t-.js} +0 -0
- /package/client/dist/assets/{csp-tkCWrZmb.js → csp-BEzxZqi1.js} +0 -0
- /package/client/dist/assets/{css-B_xdnReq.js → css-C3D3PKln.js} +0 -0
- /package/client/dist/assets/{cypher-OL1zq-y3.js → cypher-CQ3dEPmv.js} +0 -0
- /package/client/dist/assets/{dart-CtTlOkrm.js → dart-DCPmxolI.js} +0 -0
- /package/client/dist/assets/{dashboard-C52PElPV.js → dashboard-BTiH361F.js} +0 -0
- /package/client/dist/assets/{dashboard-C-dXxut-.js → dashboard-COSm_14E.js} +0 -0
- /package/client/dist/assets/{dashboard-CUjyhohl.js → dashboard-CfOs70FN2.js} +0 -0
- /package/client/dist/assets/{dashboard-zH9quXks.js → dashboard-CzHDp14_2.js} +0 -0
- /package/client/dist/assets/{dashboard-03EpOoMM.js → dashboard-D6Woy2B5.js} +0 -0
- /package/client/dist/assets/{dashboard-C2owt3eN.js → dashboard-DwbH4oVH.js} +0 -0
- /package/client/dist/assets/{dashboard-Ch6H-457.js → dashboard-cWhU3krA2.js} +0 -0
- /package/client/dist/assets/{dockerfile-DcQ5CNiW.js → dockerfile-B_2_aaTO.js} +0 -0
- /package/client/dist/assets/{ecl-DZpiDRt6.js → ecl-rADUeXsV.js} +0 -0
- /package/client/dist/assets/{elixir-Dzj2lqtK.js → elixir-DOd-TuJO.js} +0 -0
- /package/client/dist/assets/{explore-CCcPXrHg.js → explore-B82VMUJ_.js} +0 -0
- /package/client/dist/assets/{explore-DQ0XEjed.js → explore-BGz1rvQE.js} +0 -0
- /package/client/dist/assets/{explore-xQoaak6e.js → explore-BzdMEpuf.js} +0 -0
- /package/client/dist/assets/{explore-B2n8r9Ye.js → explore-CbW0381i2.js} +0 -0
- /package/client/dist/assets/{explore-4SD1DxR0.js → explore-ChtxIiSM.js} +0 -0
- /package/client/dist/assets/{explore-BxR9q8Hq.js → explore-DfcUfiPw2.js} +0 -0
- /package/client/dist/assets/{explore-CZt4Ncpa.js → explore-yRF7wFSp2.js} +0 -0
- /package/client/dist/assets/{flow9-CFENCHIZ.js → flow9-Bf7zg_j0.js} +0 -0
- /package/client/dist/assets/{format-command-CMsBTOPN.js → format-command-D96da4fZ.js} +0 -0
- /package/client/dist/assets/{fsharp-D-4bv7FB.js → fsharp-BqROZjbA.js} +0 -0
- /package/client/dist/assets/{go-djxbTJq7.js → go-oQTP5uLe.js} +0 -0
- /package/client/dist/assets/{graphql-DHIFrkZ6.js → graphql-CSHfTU3q.js} +0 -0
- /package/client/dist/assets/{hcl-CXVoowNb.js → hcl-CeG-RoFJ.js} +0 -0
- /package/client/dist/assets/{ini-Crzc5kly.js → ini-B2Dv4ohc.js} +0 -0
- /package/client/dist/assets/{integrations-C7u7SInG.js → integrations-B0OBPU9_.js} +0 -0
- /package/client/dist/assets/{integrations-B17KyA-i.js → integrations-BA5pM05t.js} +0 -0
- /package/client/dist/assets/{integrations-DkBi-Gsh.js → integrations-BJvrlcc7.js} +0 -0
- /package/client/dist/assets/{integrations-DwSVdCpf.js → integrations-CPKYkG36.js} +0 -0
- /package/client/dist/assets/{integrations-A9f08E0o.js → integrations-CnrX7vqw2.js} +0 -0
- /package/client/dist/assets/{integrations-9qxUNhPD.js → integrations-DBou159M2.js} +0 -0
- /package/client/dist/assets/{integrations-B46ac3sf.js → integrations-DeJFRyXz2.js} +0 -0
- /package/client/dist/assets/{java-CYLaJrdE.js → java-aUDDeufb.js} +0 -0
- /package/client/dist/assets/{jira-Dht8qNck.js → jira-BDgxAFTF2.js} +0 -0
- /package/client/dist/assets/{jira-4Fin3fu7.js → jira-BwLBgmfI2.js} +0 -0
- /package/client/dist/assets/{jira-0ldfMByG.js → jira-D3GGzXOI.js} +0 -0
- /package/client/dist/assets/{jira-BpnpUFlb.js → jira-DeotPx6P2.js} +0 -0
- /package/client/dist/assets/{jira-DzIVvNmy.js → jira-NwlUCcQD.js} +0 -0
- /package/client/dist/assets/{jira-CBU0200d.js → jira-Srx2P5zS.js} +0 -0
- /package/client/dist/assets/{jira-01H1RfQj.js → jira-mi37H4Kt.js} +0 -0
- /package/client/dist/assets/{jobs-jEQidSIb.js → jobs-B56vQEkh2.js} +0 -0
- /package/client/dist/assets/{jobs-DM9owF1j.js → jobs-BYIk6Tg_.js} +0 -0
- /package/client/dist/assets/{jobs-Bw4hqEro.js → jobs-C-lM-iax2.js} +0 -0
- /package/client/dist/assets/{jobs-BF9raUkL.js → jobs-CYdevGgT.js} +0 -0
- /package/client/dist/assets/{jobs-ym72SIR9.js → jobs-D8adm2BX.js} +0 -0
- /package/client/dist/assets/{jobs-DcCONARH.js → jobs-K_Y8KAZ2.js} +0 -0
- /package/client/dist/assets/{jobs-Oz3UtLQn.js → jobs-OwM0ZWhI2.js} +0 -0
- /package/client/dist/assets/{julia-BJ7tUU-T.js → julia-C5LbruO5.js} +0 -0
- /package/client/dist/assets/{kotlin-CeWcU3lC.js → kotlin-C6mOuSKk.js} +0 -0
- /package/client/dist/assets/{less-CviwWNG4.js → less-D5qC3caW.js} +0 -0
- /package/client/dist/assets/{lexon-Dt28jqDS.js → lexon-Dr2La-fj.js} +0 -0
- /package/client/dist/assets/{loops-DE-2cmLw.js → loops-BDhEfnx82.js} +0 -0
- /package/client/dist/assets/{loops-mcoeFMat.js → loops-BM5QggwH.js} +0 -0
- /package/client/dist/assets/{loops-D_ZxkD4l.js → loops-CPjLAAxj.js} +0 -0
- /package/client/dist/assets/{loops-BYNUJVyD.js → loops-DKnk79TB2.js} +0 -0
- /package/client/dist/assets/{loops-B_gS4Pqu.js → loops-DLW0aFMm.js} +0 -0
- /package/client/dist/assets/{loops-Fv4Bs-9A.js → loops-Dcv3dFy_2.js} +0 -0
- /package/client/dist/assets/{loops-B_w7STrw.js → loops-y7wFRx6M.js} +0 -0
- /package/client/dist/assets/{lua-B6AOaJSi.js → lua-Zy6u5z6Z.js} +0 -0
- /package/client/dist/assets/{m3-VnMkHJcy.js → m3-C7XHeDz_.js} +0 -0
- /package/client/dist/assets/{markdown-8vKNdvIM.js → markdown-PiF7r-2h.js} +0 -0
- /package/client/dist/assets/{mips-DoEcuzan.js → mips-nn07GiyE.js} +0 -0
- /package/client/dist/assets/{msdax-CNzvHyEp.js → msdax-DaKFVMST.js} +0 -0
- /package/client/dist/assets/{mysql-Buk3vUtL.js → mysql-C6d6xrbU.js} +0 -0
- /package/client/dist/assets/{nav-Cw3_40fA.js → nav-1hj2izqB2.js} +0 -0
- /package/client/dist/assets/{nav-DNDJ3H-_.js → nav-BBUKMAu-2.js} +0 -0
- /package/client/dist/assets/{nav-CrG_mYRr.js → nav-CLqkP-26.js} +0 -0
- /package/client/dist/assets/{nav-DVp1nEsi.js → nav-Cv46L837.js} +0 -0
- /package/client/dist/assets/{nav-DbAbG5_k.js → nav-DgA-rjLk2.js} +0 -0
- /package/client/dist/assets/{nav-B5W9IMnf.js → nav-EG2pr6vN.js} +0 -0
- /package/client/dist/assets/{nav-_0vAgZ_k.js → nav-r3mIJVfU.js} +0 -0
- /package/client/dist/assets/{objective-c-DfOmda-5.js → objective-c-B6KMtI-k.js} +0 -0
- /package/client/dist/assets/{pascal-DoAA2yjy.js → pascal-DYr8MPE7.js} +0 -0
- /package/client/dist/assets/{pascaligo-C65Vn1fu.js → pascaligo-B3pd-jJ_.js} +0 -0
- /package/client/dist/assets/{perl-INlfQ_ro.js → perl-z2xUzoaL.js} +0 -0
- /package/client/dist/assets/{pgsql-DtwRzglx.js → pgsql-DUvT-09-.js} +0 -0
- /package/client/dist/assets/{php-DfHqVNet.js → php-CGyOmJfy.js} +0 -0
- /package/client/dist/assets/{pla-D3dNVRYk.js → pla-CMqgvyD0.js} +0 -0
- /package/client/dist/assets/{postiats-UKXD1rM4.js → postiats-Dx5LUn4Z.js} +0 -0
- /package/client/dist/assets/{powerquery-zE3E3O7s.js → powerquery-DazYxvJ6.js} +0 -0
- /package/client/dist/assets/{powershell-Df9b3zQl.js → powershell-B3nwlDka.js} +0 -0
- /package/client/dist/assets/{protobuf-BF0nUVrW.js → protobuf-BGuu2u2s.js} +0 -0
- /package/client/dist/assets/{pug-Bj6Yikv_.js → pug-Dbte1MXA.js} +0 -0
- /package/client/dist/assets/{qsharp-BI4h-raZ.js → qsharp-DJLwBOvG.js} +0 -0
- /package/client/dist/assets/{r-DhpnmaXx.js → r-DmGBRSHj.js} +0 -0
- /package/client/dist/assets/{redis-DwYYPr4d.js → redis-VvUYn0a4.js} +0 -0
- /package/client/dist/assets/{redshift-D2f3Bc-4.js → redshift-6GdDrxg3.js} +0 -0
- /package/client/dist/assets/{restructuredtext-Dkiif6fe.js → restructuredtext-CfAB8aRU.js} +0 -0
- /package/client/dist/assets/{ruby-C6hLmHy0.js → ruby-CIYK6MXY.js} +0 -0
- /package/client/dist/assets/{rust-X8WAGtxB.js → rust-CTHZfjj9.js} +0 -0
- /package/client/dist/assets/{sb-4M2la0Kr.js → sb-CkKTdkz_.js} +0 -0
- /package/client/dist/assets/{scala-CaCXz58E.js → scala-Bx-Wr9TM.js} +0 -0
- /package/client/dist/assets/{scheme-CMdjpmP7.js → scheme-Dcfi6f83.js} +0 -0
- /package/client/dist/assets/{scss-QopmvPyp.js → scss-ByL-7PFQ.js} +0 -0
- /package/client/dist/assets/{shell-CDBchS00.js → shell-Csnb9Yqd.js} +0 -0
- /package/client/dist/assets/{solidity-DvBfi5mC.js → solidity-Dbw5-0QP.js} +0 -0
- /package/client/dist/assets/{sophia-HSnXR17u.js → sophia-ZQ3saZQx.js} +0 -0
- /package/client/dist/assets/{sparql-DhG_ppup.js → sparql-BEtOel8u.js} +0 -0
- /package/client/dist/assets/{specs-CUE3xSFM.js → specs-B5XOoH-02.js} +0 -0
- /package/client/dist/assets/{specs-C2wW5bq8.js → specs-B8mwScoM2.js} +0 -0
- /package/client/dist/assets/{specs-Bku8SypT.js → specs-Bj_3fDrQ.js} +0 -0
- /package/client/dist/assets/{specs-CnH7dOiF.js → specs-CWNKAScX2.js} +0 -0
- /package/client/dist/assets/{specs-BvsFNKTs.js → specs-D4z5d8uq.js} +0 -0
- /package/client/dist/assets/{specs-D4ORogcT.js → specs-DgvIRZtM.js} +0 -0
- /package/client/dist/assets/{specs-D6Y76Bgi.js → specs-DuRzE5wE.js} +0 -0
- /package/client/dist/assets/{sql-DHeIS-tf.js → sql-DGPDbzB7.js} +0 -0
- /package/client/dist/assets/{st-CMWchiRU.js → st-GeW2MOgh.js} +0 -0
- /package/client/dist/assets/{swift-CDXvBefj.js → swift-BVYTN8jq.js} +0 -0
- /package/client/dist/assets/{systemverilog-0RTukYUE.js → systemverilog-Deg2a8Lr.js} +0 -0
- /package/client/dist/assets/{tcl-CbBF_Ttl.js → tcl-6DnLmROF.js} +0 -0
- /package/client/dist/assets/{terminal-BLh00ouM.js → terminal-B2sExUis.js} +0 -0
- /package/client/dist/assets/{terminal-Cz_Puz0q.js → terminal-CH0fdHvP.js} +0 -0
- /package/client/dist/assets/{terminal-DFEH9XTQ.js → terminal-CPMigUX7.js} +0 -0
- /package/client/dist/assets/{terminal-CB33IsO1.js → terminal-DOwfkxqa2.js} +0 -0
- /package/client/dist/assets/{terminal-CR6t-Oey.js → terminal-DjSSa5qP.js} +0 -0
- /package/client/dist/assets/{terminal-CY_sPfdD.js → terminal-DoMTHedv2.js} +0 -0
- /package/client/dist/assets/{terminal-BTanJIt_.js → terminal-w0i-OCp32.js} +0 -0
- /package/client/dist/assets/{tickets-D6f3GSYn.js → tickets-CQTana1u.js} +0 -0
- /package/client/dist/assets/{tickets-blQnNA4N.js → tickets-D00mSt832.js} +0 -0
- /package/client/dist/assets/{tickets-68LH1xp6.js → tickets-D7CSuWjO.js} +0 -0
- /package/client/dist/assets/{tickets-OrJwUxJR.js → tickets-Dq7HDy8n.js} +0 -0
- /package/client/dist/assets/{tickets-PtHNxQo5.js → tickets-DvbFg_-c2.js} +0 -0
- /package/client/dist/assets/{tickets-REr-EQx9.js → tickets-heDAaj8C2.js} +0 -0
- /package/client/dist/assets/{tickets-jxsTrApR.js → tickets-onKZz6Dd.js} +0 -0
- /package/client/dist/assets/{twig-DCcGd8FE.js → twig-XbbXW9Yz.js} +0 -0
- /package/client/dist/assets/{typespec-HTphp-Uh.js → typespec-CRlV8GhI.js} +0 -0
- /package/client/dist/assets/{vb-Cb_yUcVd.js → vb-BHE2izPH.js} +0 -0
- /package/client/dist/assets/{wgsl-DrKU3YXj.js → wgsl-Bf5vkfmY.js} +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentChatManager = void 0;
|
|
4
|
+
const providers_1 = require("./providers");
|
|
5
|
+
const spawn_lifecycle_1 = require("./spawn-lifecycle");
|
|
6
|
+
const agent_cwd_manager_1 = require("./agent-cwd-manager");
|
|
7
|
+
const agent_mcp_config_1 = require("./agent-mcp-config");
|
|
8
|
+
const agent_tier_1 = require("./agent-tier");
|
|
9
|
+
const agent_store_1 = require("./agent-store");
|
|
10
|
+
const types_1 = require("./mcp/tools/types");
|
|
11
|
+
// ─── AgentChatManager (design D1) ─────────────────────────────────────────────
|
|
12
|
+
//
|
|
13
|
+
// App-level sibling of ChatManager: spawns an AI CLI from the app-owned agent cwd
|
|
14
|
+
// with the Specrails MCP bridge as its tool source, streams the turn over the
|
|
15
|
+
// app-global `agent_*` WS events (no projectId — fans to all subscribers), and
|
|
16
|
+
// persists to the app registry DB. It reuses the shared spawn→stream→settle core
|
|
17
|
+
// (runAiCliInvocation) rather than re-implementing ChatManager's loop.
|
|
18
|
+
const OPERATOR_SYSTEM_PROMPT = 'You are the Specrails operator agent. Drive the Specrails Desktop app on the ' +
|
|
19
|
+
"user's behalf using the specrails_* MCP tools. Target a project with " +
|
|
20
|
+
'specrails_select_project (or the projectId argument); if none is selected and ' +
|
|
21
|
+
'the request is project-specific, ask whether to create a project or search all. ' +
|
|
22
|
+
'Follow HTTP-202 actions to completion with specrails_watch. Respect the ' +
|
|
23
|
+
'permission ladder: if a tool is refused for the current level, tell the user ' +
|
|
24
|
+
'which level it needs rather than working around it. Be concise; report tool ' +
|
|
25
|
+
'outputs faithfully, including failures. Format replies for easy reading: ' +
|
|
26
|
+
'separate distinct ideas into short paragraphs with a blank line between them ' +
|
|
27
|
+
'(not one dense block), and use bullet lists for enumerations. ' +
|
|
28
|
+
'When the user has no projects yet, or wants to add one, first explain the UI ' +
|
|
29
|
+
'steps (click "Add Project" / the + in the left sidebar, enter the repo\'s ' +
|
|
30
|
+
'folder path, pick an AI provider, then run setup), THEN offer to do it for ' +
|
|
31
|
+
'them from here: ask for the repo folder path and which AI providers to set up, ' +
|
|
32
|
+
'then with their go-ahead call specrails_setup add_project (Edit level) and a ' +
|
|
33
|
+
'single specrails_setup install (Operate level) — one install provisions ALL the ' +
|
|
34
|
+
'chosen providers in one shot (no need to install them one by one). Setup is ' +
|
|
35
|
+
'QUICK-only (offline); do NOT offer or mention a "full" or "enrich" install. Use ' +
|
|
36
|
+
'specrails_setup prerequisites / available_providers first to check the machine.';
|
|
37
|
+
class AgentChatManager {
|
|
38
|
+
_broadcast;
|
|
39
|
+
_db;
|
|
40
|
+
_port;
|
|
41
|
+
_active = new Map();
|
|
42
|
+
constructor(broadcast, db, port) {
|
|
43
|
+
this._broadcast = broadcast;
|
|
44
|
+
this._db = db;
|
|
45
|
+
this._port = port;
|
|
46
|
+
}
|
|
47
|
+
/** True while a turn is streaming for this conversation. */
|
|
48
|
+
isStreaming(conversationId) {
|
|
49
|
+
return this._active.has(conversationId);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Runs one agent turn: persists the user message, spawns the AI CLI pointed at
|
|
53
|
+
* the Specrails MCP, streams deltas + tool-use as `agent_*` events, then
|
|
54
|
+
* persists the assistant reply and the session id. Settles once.
|
|
55
|
+
*/
|
|
56
|
+
async sendMessage(conversationId, userText, options = {}) {
|
|
57
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
58
|
+
if (!conversation) {
|
|
59
|
+
this._emitError(conversationId, 'Unknown conversation');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (this._active.has(conversationId)) {
|
|
63
|
+
this._emitError(conversationId, 'The agent is busy. Try again in a moment.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const tierLevel = (0, agent_tier_1.normalizeLevel)(options.tierLevel ?? conversation.tier_level);
|
|
67
|
+
const adapter = (0, providers_1.getAdapter)(conversation.provider);
|
|
68
|
+
// Resolve a model that is VALID for this provider. A stale model from another
|
|
69
|
+
// provider (e.g. claude's "sonnet" after switching to codex) is rejected by
|
|
70
|
+
// the provider — fall back to the provider's default instead of failing.
|
|
71
|
+
const catalog = new Set(adapter.modelCatalog().map((m) => m.value));
|
|
72
|
+
const requested = options.model || conversation.model;
|
|
73
|
+
const model = requested && catalog.has(requested) ? requested : adapter.defaultModel();
|
|
74
|
+
// Make the pinned project (Cursor-style selector) the MCP active project for
|
|
75
|
+
// this turn, so project-scoped tools resolve without the agent having to call
|
|
76
|
+
// specrails_select_project. Home (null) clears it → app-global mode.
|
|
77
|
+
(0, types_1.setActiveProject)(conversation.pinned_project_id ?? null);
|
|
78
|
+
(0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'user', content: userText });
|
|
79
|
+
// Tell the agent which project is pinned (so it phrases + passes projectId).
|
|
80
|
+
const prompt = conversation.pinned_project_id
|
|
81
|
+
? `[Active project: projectId="${conversation.pinned_project_id}". Use it for project-scoped tools unless told otherwise.]\n\n${userText}`
|
|
82
|
+
: userText;
|
|
83
|
+
const cwd = (0, agent_cwd_manager_1.ensureAgentCwd)();
|
|
84
|
+
let mcpArgs = [];
|
|
85
|
+
let mcpEnv = {};
|
|
86
|
+
try {
|
|
87
|
+
const wiring = (0, agent_mcp_config_1.prepareAgentMcp)({
|
|
88
|
+
adapterId: adapter.id,
|
|
89
|
+
conversationId,
|
|
90
|
+
cwd,
|
|
91
|
+
port: this._port,
|
|
92
|
+
tierLevel,
|
|
93
|
+
activeProjectId: conversation.pinned_project_id,
|
|
94
|
+
});
|
|
95
|
+
mcpArgs = wiring.extraArgs;
|
|
96
|
+
mcpEnv = wiring.env;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// A bad conversation id or fs failure shouldn't poison the chat — run tool-less.
|
|
100
|
+
console.error('[agent-chat] failed to prepare mcp:', err);
|
|
101
|
+
}
|
|
102
|
+
const timestamp = () => new Date().toISOString();
|
|
103
|
+
const invoke = async (useResume) => {
|
|
104
|
+
const action = useResume ? 'chat-resume' : 'chat-turn';
|
|
105
|
+
let streamed = '';
|
|
106
|
+
let capturedSessionId = useResume ? conversation.session_id ?? null : null;
|
|
107
|
+
let capturedError = null;
|
|
108
|
+
console.log(`[agent-chat] turn start conv=${conversationId} provider=${adapter.id} binary=${adapter.binary} ` +
|
|
109
|
+
`action=${action} model=${model} cwd=${cwd} mcp=${mcpArgs.length ? 'mcp-config' : Object.keys(mcpEnv).join(',') || 'none'}`);
|
|
110
|
+
const result = await (0, spawn_lifecycle_1.runAiCliInvocation)({
|
|
111
|
+
adapter,
|
|
112
|
+
action,
|
|
113
|
+
cwd,
|
|
114
|
+
env: { ...process.env, ...mcpEnv },
|
|
115
|
+
buildOpts: {
|
|
116
|
+
prompt,
|
|
117
|
+
systemPrompt: adapter.capabilities.systemPromptArg ? OPERATOR_SYSTEM_PROMPT : undefined,
|
|
118
|
+
model,
|
|
119
|
+
sessionId: useResume ? conversation.session_id ?? undefined : undefined,
|
|
120
|
+
extraArgs: mcpArgs,
|
|
121
|
+
},
|
|
122
|
+
onSpawn: (child) => {
|
|
123
|
+
this._active.set(conversationId, child);
|
|
124
|
+
},
|
|
125
|
+
onEvent: (ev) => {
|
|
126
|
+
switch (ev.kind) {
|
|
127
|
+
case 'text-delta':
|
|
128
|
+
streamed += ev.text;
|
|
129
|
+
this._broadcast({ type: 'agent_stream', conversationId, delta: ev.text, timestamp: timestamp() });
|
|
130
|
+
break;
|
|
131
|
+
case 'tool-use':
|
|
132
|
+
this._broadcast({ type: 'agent_tool', conversationId, tool: ev.name, timestamp: timestamp() });
|
|
133
|
+
break;
|
|
134
|
+
case 'session-started':
|
|
135
|
+
if (ev.sessionId)
|
|
136
|
+
capturedSessionId = ev.sessionId;
|
|
137
|
+
break;
|
|
138
|
+
case 'result': {
|
|
139
|
+
const sid = ev.payload.session_id;
|
|
140
|
+
if (sid)
|
|
141
|
+
capturedSessionId = sid;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'error':
|
|
145
|
+
capturedError = ev.message;
|
|
146
|
+
break;
|
|
147
|
+
case 'other':
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
this._active.delete(conversationId);
|
|
153
|
+
const text = streamed.trim();
|
|
154
|
+
console.log(`[agent-chat] turn end conv=${conversationId} provider=${adapter.id} code=${result.code} ` +
|
|
155
|
+
`spawnFailed=${result.spawnFailed} chars=${text.length} err=${capturedError ?? ''}`);
|
|
156
|
+
if (result.stderrTail && (result.spawnFailed || (result.code ?? 0) !== 0 || !text)) {
|
|
157
|
+
console.error(`[agent-chat] ${adapter.id} stderr:\n${result.stderrTail}`);
|
|
158
|
+
}
|
|
159
|
+
return { text, sessionId: capturedSessionId, error: capturedError, code: result.code, spawnFailed: result.spawnFailed, stderrTail: result.stderrTail };
|
|
160
|
+
};
|
|
161
|
+
const canResume = !!conversation.session_id && adapter.capabilities.nativeResume;
|
|
162
|
+
let r = await invoke(canResume);
|
|
163
|
+
// Auto-heal a stale/foreign session: a resume that produced no text (e.g. a
|
|
164
|
+
// provider switch leaving another provider's session id, or codex
|
|
165
|
+
// "no rollout found for thread id") retries once as a fresh turn.
|
|
166
|
+
if (canResume && !r.spawnFailed && !r.text) {
|
|
167
|
+
console.log(`[agent-chat] resume produced no text — retrying fresh conv=${conversationId}`);
|
|
168
|
+
(0, agent_store_1.updateAgentConversation)(this._db, conversationId, { session_id: null });
|
|
169
|
+
r = await invoke(false);
|
|
170
|
+
}
|
|
171
|
+
if (r.spawnFailed) {
|
|
172
|
+
this._emitError(conversationId, `Failed to launch ${adapter.binary}. Is it installed and on PATH?`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (r.text) {
|
|
176
|
+
(0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text });
|
|
177
|
+
(0, agent_store_1.updateAgentConversation)(this._db, conversationId, {
|
|
178
|
+
session_id: r.sessionId,
|
|
179
|
+
provider: conversation.provider,
|
|
180
|
+
model,
|
|
181
|
+
tier_level: tierLevel,
|
|
182
|
+
});
|
|
183
|
+
this._broadcast({ type: 'agent_done', conversationId, fullText: r.text, timestamp: timestamp() });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Failure: reset the session so the NEXT turn starts fresh, and surface the
|
|
187
|
+
// real reason instead of silently doing nothing.
|
|
188
|
+
(0, agent_store_1.updateAgentConversation)(this._db, conversationId, { session_id: null, provider: conversation.provider, model, tier_level: tierLevel });
|
|
189
|
+
const reason = r.error ||
|
|
190
|
+
(r.stderrTail ? r.stderrTail.split('\n').filter(Boolean).slice(-3).join(' ').slice(0, 300) : '') ||
|
|
191
|
+
(r.code !== 0 ? `${adapter.binary} exited with code ${r.code}` : 'The agent returned no output.');
|
|
192
|
+
this._emitError(conversationId, reason);
|
|
193
|
+
}
|
|
194
|
+
/** Aborts the active turn for a conversation, if any. */
|
|
195
|
+
abort(conversationId) {
|
|
196
|
+
const child = this._active.get(conversationId);
|
|
197
|
+
if (!child)
|
|
198
|
+
return false;
|
|
199
|
+
try {
|
|
200
|
+
child.kill('SIGTERM');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* ignore */
|
|
204
|
+
}
|
|
205
|
+
this._active.delete(conversationId);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
/** Kills all active turns (graceful shutdown). */
|
|
209
|
+
async shutdown() {
|
|
210
|
+
for (const [, child] of this._active) {
|
|
211
|
+
try {
|
|
212
|
+
child.kill('SIGTERM');
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* ignore */
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
this._active.clear();
|
|
219
|
+
}
|
|
220
|
+
_emitError(conversationId, error) {
|
|
221
|
+
this._broadcast({ type: 'agent_error', conversationId, error, timestamp: new Date().toISOString() });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.AgentChatManager = AgentChatManager;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAgentChatEnabled = isAgentChatEnabled;
|
|
4
|
+
exports.createAgentChatRouter = createAgentChatRouter;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
const providers_1 = require("./providers");
|
|
7
|
+
const agent_tier_1 = require("./agent-tier");
|
|
8
|
+
const agent_store_1 = require("./agent-store");
|
|
9
|
+
function isAgentChatEnabled() {
|
|
10
|
+
return process.env.SPECRAILS_AGENT_CHAT !== 'false';
|
|
11
|
+
}
|
|
12
|
+
function validProvider(provider) {
|
|
13
|
+
if (typeof provider !== 'string' || !provider)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
(0, providers_1.getAdapter)(provider);
|
|
17
|
+
return provider;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function createAgentChatRouter(deps) {
|
|
24
|
+
const { manager, desktopDb } = deps;
|
|
25
|
+
const router = (0, express_1.Router)();
|
|
26
|
+
// Feature gate: 404 the whole surface when disabled.
|
|
27
|
+
router.use((_req, res, next) => {
|
|
28
|
+
if (!isAgentChatEnabled()) {
|
|
29
|
+
res.status(404).json({ error: 'Agent chat is disabled' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
next();
|
|
33
|
+
});
|
|
34
|
+
// Per-provider model catalog for the header model selector.
|
|
35
|
+
router.get('/models', (req, res) => {
|
|
36
|
+
const provider = validProvider(req.query.provider) ?? 'claude';
|
|
37
|
+
res.json({ provider, models: (0, providers_1.getAdapter)(provider).modelCatalog() });
|
|
38
|
+
});
|
|
39
|
+
router.get('/conversations', (_req, res) => {
|
|
40
|
+
res.json({ conversations: (0, agent_store_1.listAgentConversations)(desktopDb) });
|
|
41
|
+
});
|
|
42
|
+
router.post('/conversations', (req, res) => {
|
|
43
|
+
const body = (req.body ?? {});
|
|
44
|
+
let provider = 'claude';
|
|
45
|
+
if (body.provider !== undefined) {
|
|
46
|
+
const v = validProvider(body.provider);
|
|
47
|
+
if (!v) {
|
|
48
|
+
res.status(400).json({ error: 'Unknown or unavailable provider' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
provider = v;
|
|
52
|
+
}
|
|
53
|
+
const conversation = (0, agent_store_1.createAgentConversation)(desktopDb, {
|
|
54
|
+
provider,
|
|
55
|
+
model: typeof body.model === 'string' ? body.model : null,
|
|
56
|
+
pinnedProjectId: typeof body.pinnedProjectId === 'string' ? body.pinnedProjectId : null,
|
|
57
|
+
tierLevel: body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : 0,
|
|
58
|
+
});
|
|
59
|
+
res.status(201).json({ conversation });
|
|
60
|
+
});
|
|
61
|
+
router.get('/conversations/:id', (req, res) => {
|
|
62
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
63
|
+
if (!conversation) {
|
|
64
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
res.json({ conversation, messages: (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id) });
|
|
68
|
+
});
|
|
69
|
+
router.patch('/conversations/:id', (req, res) => {
|
|
70
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
71
|
+
if (!conversation) {
|
|
72
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const body = (req.body ?? {});
|
|
76
|
+
const patch = {};
|
|
77
|
+
if (body.title !== undefined)
|
|
78
|
+
patch.title = typeof body.title === 'string' ? body.title : null;
|
|
79
|
+
if (body.pinnedProjectId !== undefined) {
|
|
80
|
+
patch.pinned_project_id = typeof body.pinnedProjectId === 'string' ? body.pinnedProjectId : null;
|
|
81
|
+
}
|
|
82
|
+
if (body.tierLevel !== undefined)
|
|
83
|
+
patch.tier_level = (0, agent_tier_1.normalizeLevel)(body.tierLevel);
|
|
84
|
+
if (body.provider !== undefined) {
|
|
85
|
+
const v = validProvider(body.provider);
|
|
86
|
+
if (!v) {
|
|
87
|
+
res.status(400).json({ error: 'Unknown or unavailable provider' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
patch.provider = v;
|
|
91
|
+
// Switching provider invalidates the stored session id AND model (both
|
|
92
|
+
// belong to the previous provider) — clear them so the next turn starts a
|
|
93
|
+
// fresh session with the new provider's default model (codex rejects
|
|
94
|
+
// claude's "sonnet"; resume of a foreign thread → "no rollout found").
|
|
95
|
+
if (v !== conversation.provider) {
|
|
96
|
+
patch.session_id = null;
|
|
97
|
+
patch.model = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Explicit model pick — validated against the (new or current) provider's catalog.
|
|
101
|
+
if (body.model !== undefined && patch.model === undefined) {
|
|
102
|
+
if (body.model === null) {
|
|
103
|
+
patch.model = null;
|
|
104
|
+
}
|
|
105
|
+
else if (typeof body.model === 'string') {
|
|
106
|
+
const effectiveProvider = patch.provider ?? conversation.provider;
|
|
107
|
+
const valid = new Set((0, providers_1.getAdapter)(effectiveProvider).modelCatalog().map((m) => m.value));
|
|
108
|
+
if (!valid.has(body.model)) {
|
|
109
|
+
res.status(400).json({ error: 'Model not available for this provider' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
patch.model = body.model;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
res.json({ conversation: (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, patch) });
|
|
116
|
+
});
|
|
117
|
+
router.delete('/conversations/:id', (req, res) => {
|
|
118
|
+
manager.abort(String(req.params.id));
|
|
119
|
+
(0, agent_store_1.deleteAgentConversation)(desktopDb, String(req.params.id));
|
|
120
|
+
res.json({ ok: true });
|
|
121
|
+
});
|
|
122
|
+
router.post('/conversations/:id/send', (req, res) => {
|
|
123
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
124
|
+
if (!conversation) {
|
|
125
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const body = (req.body ?? {});
|
|
129
|
+
const text = typeof body.text === 'string' ? body.text.trim() : '';
|
|
130
|
+
if (!text) {
|
|
131
|
+
res.status(400).json({ error: 'text is required' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const tierLevel = body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : undefined;
|
|
135
|
+
const model = typeof body.model === 'string' ? body.model : undefined;
|
|
136
|
+
// Fire-and-forget: the turn streams over WS. Persist the chosen tier first so
|
|
137
|
+
// a refresh mid-turn restores the right level.
|
|
138
|
+
if (tierLevel !== undefined)
|
|
139
|
+
(0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, { tier_level: tierLevel });
|
|
140
|
+
void manager.sendMessage(conversation.id, text, { tierLevel, model });
|
|
141
|
+
res.status(202).json({ accepted: true });
|
|
142
|
+
});
|
|
143
|
+
router.post('/conversations/:id/abort', (req, res) => {
|
|
144
|
+
const aborted = manager.abort(String(req.params.id));
|
|
145
|
+
res.json({ aborted });
|
|
146
|
+
});
|
|
147
|
+
return router;
|
|
148
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.agentCwdPath = agentCwdPath;
|
|
7
|
+
exports.ensureAgentCwd = ensureAgentCwd;
|
|
8
|
+
exports.removeAgentCwd = removeAgentCwd;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
// ─── App-level agent working directory (design D1) ────────────────────────────
|
|
13
|
+
//
|
|
14
|
+
// The agent chat spawns the AI CLI from an app-owned cwd (NOT a project path), so
|
|
15
|
+
// no project's CLAUDE.md is auto-loaded and the agent starts with a focused
|
|
16
|
+
// "operator" stance. Mirrors explore-cwd-manager but is app-global (one dir, no
|
|
17
|
+
// project symlink). The operator instructions are written for every provider
|
|
18
|
+
// (CLAUDE.md / AGENTS.md / GEMINI.md) so the chosen CLI picks them up.
|
|
19
|
+
function homeDir() {
|
|
20
|
+
return process.env.SPECRAILS_REGISTRY_HOME || os_1.default.homedir();
|
|
21
|
+
}
|
|
22
|
+
function agentCwdPath() {
|
|
23
|
+
return path_1.default.join(homeDir(), '.specrails', 'agent-cwd');
|
|
24
|
+
}
|
|
25
|
+
const INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
|
|
26
|
+
const OPERATOR_INSTRUCTIONS = `# Specrails Operator Agent
|
|
27
|
+
|
|
28
|
+
You are the in-app Specrails operator. You drive the Specrails Desktop app on the
|
|
29
|
+
user's behalf through the \`specrails_*\` MCP tools (projects, specs, rails, jobs,
|
|
30
|
+
chat, agents, plugins, jira, loops, code, analytics, setup, settings).
|
|
31
|
+
|
|
32
|
+
## How to work
|
|
33
|
+
- Use \`specrails_select_project\` (or the projectId argument) to target a project.
|
|
34
|
+
If no project is selected ("Home") and the request is project-specific, ASK the
|
|
35
|
+
user whether to create a new project or search across all projects — do not guess.
|
|
36
|
+
- For long-running actions that return HTTP 202, use \`specrails_watch\` to follow
|
|
37
|
+
them to completion instead of assuming they finished.
|
|
38
|
+
- Prefer the meta tools \`specrails_guide\` / \`specrails_search\` / \`specrails_describe\`
|
|
39
|
+
to discover the exact action/arguments before calling a domain tool.
|
|
40
|
+
|
|
41
|
+
## Permission ladder
|
|
42
|
+
Your actions are gated by a cumulative level the user sets live:
|
|
43
|
+
observe (read) ▸ edit (write) ▸ operate (launch AI, costs money) ▸ autonomous
|
|
44
|
+
(delete/kill). If a tool is refused for the current level, tell the user which
|
|
45
|
+
level it needs — do not try to work around the refusal.
|
|
46
|
+
|
|
47
|
+
## Stance
|
|
48
|
+
Be concise and action-oriented. Confirm what you did with concrete references
|
|
49
|
+
(ids, names). Never fabricate results — report tool outputs faithfully, including
|
|
50
|
+
failures.
|
|
51
|
+
|
|
52
|
+
## Formatting
|
|
53
|
+
Make replies easy to read: separate distinct ideas into short paragraphs with a
|
|
54
|
+
blank line between them (avoid one dense block of text), and use bullet lists for
|
|
55
|
+
enumerations. Give the conversation some breathing room.
|
|
56
|
+
|
|
57
|
+
## Adding a project
|
|
58
|
+
When the user has no projects yet, or asks to add one:
|
|
59
|
+
1. Explain the UI path: click "Add Project" (the + in the left sidebar), enter the
|
|
60
|
+
repo's folder path, pick an AI provider, then run setup.
|
|
61
|
+
2. Then OFFER to do it for them from here: ask for the repo folder path and which
|
|
62
|
+
AI providers to set up, then with their go-ahead call
|
|
63
|
+
\`specrails_setup(add_project, path, providers)\` (Edit level) and a SINGLE
|
|
64
|
+
\`specrails_setup(install, projectId)\` (Operate level) — one install provisions
|
|
65
|
+
ALL the chosen providers in one shot (do not install them one at a time).
|
|
66
|
+
Use \`specrails_setup(prerequisites)\` / \`available_providers\` first to confirm
|
|
67
|
+
the machine is ready.
|
|
68
|
+
|
|
69
|
+
Setup is QUICK-only (fast, offline). Do NOT offer, mention, or attempt a "full" or
|
|
70
|
+
"enrich" install — that flow is deprecated and not available through you.
|
|
71
|
+
`;
|
|
72
|
+
/**
|
|
73
|
+
* Ensures the app-level agent cwd exists with operator instruction files.
|
|
74
|
+
* Idempotent; safe to call on every turn. Returns the cwd path.
|
|
75
|
+
*/
|
|
76
|
+
function ensureAgentCwd() {
|
|
77
|
+
const dir = agentCwdPath();
|
|
78
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
79
|
+
for (const name of INSTRUCTION_FILES) {
|
|
80
|
+
// App-owned files (not user-edited) — always (re)write so operator-prompt
|
|
81
|
+
// updates take effect instead of being pinned to the first materialization.
|
|
82
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, name), OPERATOR_INSTRUCTIONS, 'utf-8');
|
|
83
|
+
}
|
|
84
|
+
return dir;
|
|
85
|
+
}
|
|
86
|
+
/** Removes the agent cwd (used on full teardown / reset). Best-effort. */
|
|
87
|
+
function removeAgentCwd() {
|
|
88
|
+
try {
|
|
89
|
+
fs_1.default.rmSync(agentCwdPath(), { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* best-effort */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveBridgeScript = resolveBridgeScript;
|
|
7
|
+
exports.resolveNodeCommand = resolveNodeCommand;
|
|
8
|
+
exports.buildSpecrailsMcpEntry = buildSpecrailsMcpEntry;
|
|
9
|
+
exports.mergeSpecrailsIntoWorkspaceMcp = mergeSpecrailsIntoWorkspaceMcp;
|
|
10
|
+
exports.buildAgentMcpArgs = buildAgentMcpArgs;
|
|
11
|
+
exports.prepareAgentMcp = prepareAgentMcp;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const path_resolver_1 = require("./path-resolver");
|
|
16
|
+
const agent_tier_1 = require("./agent-tier");
|
|
17
|
+
// ─── Agent MCP wiring (design D8 / D1) ────────────────────────────────────────
|
|
18
|
+
//
|
|
19
|
+
// The in-app agent drives the app by talking to its OWN embedded MCP server. We
|
|
20
|
+
// spawn the AI CLI with `--mcp-config` pointing at a generated config whose sole
|
|
21
|
+
// server is the bundled `specrails-mcp` stdio bridge, which relays to
|
|
22
|
+
// `127.0.0.1:<port>/api/mcp` and reads the scoped token from disk (no token is
|
|
23
|
+
// ever written into the config file). The current Shift+Tab ladder level is
|
|
24
|
+
// forwarded via env → the bridge sets it as a loopback-only header so the tool
|
|
25
|
+
// guard enforces the agent ladder independently of the external Settings tiers.
|
|
26
|
+
function homeDir() {
|
|
27
|
+
return process.env.SPECRAILS_REGISTRY_HOME || os_1.default.homedir();
|
|
28
|
+
}
|
|
29
|
+
function fileExists(p) {
|
|
30
|
+
try {
|
|
31
|
+
return fs_1.default.existsSync(p);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Absolute path to the `specrails-mcp` bridge JS. Resolution order:
|
|
39
|
+
* 1. SPECRAILS_BUNDLED_MCP_BRIDGE_PATH (set by the Tauri host in the packaged app)
|
|
40
|
+
* 2. the staged copy under src-tauri/binaries/ (dev + bundled layouts)
|
|
41
|
+
* 3. the mcp-bridge build output (mcp-bridge/dist/)
|
|
42
|
+
* Climbs from this module so it works whether running from server/ or server/dist/.
|
|
43
|
+
*/
|
|
44
|
+
function resolveBridgeScript() {
|
|
45
|
+
const fromEnv = process.env.SPECRAILS_BUNDLED_MCP_BRIDGE_PATH;
|
|
46
|
+
if (fromEnv && fileExists(fromEnv))
|
|
47
|
+
return fromEnv;
|
|
48
|
+
const roots = [
|
|
49
|
+
path_1.default.resolve(__dirname, '..'),
|
|
50
|
+
path_1.default.resolve(__dirname, '..', '..'),
|
|
51
|
+
process.cwd(),
|
|
52
|
+
];
|
|
53
|
+
const rels = [
|
|
54
|
+
path_1.default.join('src-tauri', 'binaries', 'specrails-mcp.js'),
|
|
55
|
+
path_1.default.join('mcp-bridge', 'dist', 'specrails-mcp.js'),
|
|
56
|
+
];
|
|
57
|
+
for (const root of roots) {
|
|
58
|
+
for (const rel of rels) {
|
|
59
|
+
const candidate = path_1.default.join(root, rel);
|
|
60
|
+
if (fileExists(candidate))
|
|
61
|
+
return candidate;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** The node executable that should run the bridge (bundled in the packaged app, else PATH `node`). */
|
|
67
|
+
function resolveNodeCommand() {
|
|
68
|
+
return (0, path_resolver_1.resolveBundledNodeExe)() ?? 'node';
|
|
69
|
+
}
|
|
70
|
+
/** Build the `mcpServers.specrails` entry the agent (or a project workspace) uses. */
|
|
71
|
+
function buildSpecrailsMcpEntry(opts) {
|
|
72
|
+
const bridge = resolveBridgeScript();
|
|
73
|
+
if (!bridge)
|
|
74
|
+
return null;
|
|
75
|
+
const env = {
|
|
76
|
+
SPECRAILS_MCP_PORT: String(opts.port),
|
|
77
|
+
};
|
|
78
|
+
// Preserve the test-home override so the bridge reads the right token file.
|
|
79
|
+
if (process.env.SPECRAILS_REGISTRY_HOME)
|
|
80
|
+
env.SPECRAILS_REGISTRY_HOME = process.env.SPECRAILS_REGISTRY_HOME;
|
|
81
|
+
if (opts.tierLevel != null)
|
|
82
|
+
env[agent_tier_1.AGENT_TIER_ENV] = (0, agent_tier_1.tierNameForLevel)(opts.tierLevel);
|
|
83
|
+
if (opts.activeProjectId)
|
|
84
|
+
env[agent_tier_1.AGENT_PROJECT_ENV] = opts.activeProjectId;
|
|
85
|
+
return { command: resolveNodeCommand(), args: [bridge], env };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Part A (design D8): surgically merge the `specrails` MCP server into a
|
|
89
|
+
* project's app-managed WORKSPACE `.mcp.json` so the project's own rails/explore/
|
|
90
|
+
* chat spawns can also call the Specrails MCP. NEVER touches the pristine repo.
|
|
91
|
+
* Additive + idempotent: preserves any existing (e.g. plugin) mcpServers entries,
|
|
92
|
+
* writes atomically (temp + rename), inlines NO token. Returns true when written.
|
|
93
|
+
*/
|
|
94
|
+
function mergeSpecrailsIntoWorkspaceMcp(workspaceDir, port) {
|
|
95
|
+
const entry = buildSpecrailsMcpEntry({ port });
|
|
96
|
+
if (!entry)
|
|
97
|
+
return false;
|
|
98
|
+
const file = path_1.default.join(workspaceDir, '.mcp.json');
|
|
99
|
+
let current = {};
|
|
100
|
+
try {
|
|
101
|
+
if (fs_1.default.existsSync(file)) {
|
|
102
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
103
|
+
if (parsed && typeof parsed === 'object')
|
|
104
|
+
current = parsed;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Corrupt file → start fresh rather than crash assembly.
|
|
109
|
+
current = {};
|
|
110
|
+
}
|
|
111
|
+
const mcpServers = { ...(current.mcpServers ?? {}), specrails: entry };
|
|
112
|
+
const next = { ...current, mcpServers };
|
|
113
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
114
|
+
fs_1.default.mkdirSync(workspaceDir, { recursive: true });
|
|
115
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify(next, null, 2));
|
|
116
|
+
fs_1.default.renameSync(tmp, file);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Writes a per-conversation `--mcp-config` file for the agent and returns the
|
|
121
|
+
* `['--mcp-config', <file>]` argv to inject into SpawnOptions.extraArgs. Uses the
|
|
122
|
+
* bundled `specrails-mcp` stdio bridge (shipped inside the app — the user installs
|
|
123
|
+
* nothing; 100% unattended) which relays to this process's `/api/mcp`. Returns
|
|
124
|
+
* `[]` when the bridge can't be located (agent then runs tool-less / degraded).
|
|
125
|
+
*/
|
|
126
|
+
function buildAgentMcpArgs(opts) {
|
|
127
|
+
const entry = buildSpecrailsMcpEntry({ port: opts.port, tierLevel: opts.tierLevel, activeProjectId: opts.activeProjectId });
|
|
128
|
+
if (!entry)
|
|
129
|
+
return [];
|
|
130
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(opts.conversationId)) {
|
|
131
|
+
throw new Error(`unsafe agent conversation id: ${opts.conversationId}`);
|
|
132
|
+
}
|
|
133
|
+
const dir = path_1.default.join(homeDir(), '.specrails', 'agent', opts.conversationId);
|
|
134
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
135
|
+
const file = path_1.default.join(dir, 'mcp.json');
|
|
136
|
+
fs_1.default.writeFileSync(file, JSON.stringify({ mcpServers: { specrails: entry } }, null, 2), { mode: 0o600 });
|
|
137
|
+
try {
|
|
138
|
+
fs_1.default.chmodSync(file, 0o600);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* best-effort on platforms without chmod */
|
|
142
|
+
}
|
|
143
|
+
return ['--mcp-config', file];
|
|
144
|
+
}
|
|
145
|
+
function agentDir(conversationId) {
|
|
146
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(conversationId)) {
|
|
147
|
+
throw new Error(`unsafe agent conversation id: ${conversationId}`);
|
|
148
|
+
}
|
|
149
|
+
const dir = path_1.default.join(homeDir(), '.specrails', 'agent', conversationId);
|
|
150
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
151
|
+
return dir;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Codex registers the specrails MCP via inline `-c mcp_servers.specrails.*`
|
|
155
|
+
* config overrides. This keeps codex on the USER's real CODEX_HOME (auth + model
|
|
156
|
+
* settings intact — no all-or-nothing 401 from a CODEX_HOME override) and cannot
|
|
157
|
+
* break codex startup the way an unknown `--mcp-config` flag does: `-c` is a known
|
|
158
|
+
* flag and unknown config keys are simply set. TOML scalar values reuse JSON
|
|
159
|
+
* escaping (compatible for our command/path/env inputs).
|
|
160
|
+
*/
|
|
161
|
+
function codexMcpOverrides(entry) {
|
|
162
|
+
const c = (kv) => ['-c', kv];
|
|
163
|
+
const args = [
|
|
164
|
+
...c(`mcp_servers.specrails.command=${JSON.stringify(entry.command)}`),
|
|
165
|
+
...c(`mcp_servers.specrails.args=[${entry.args.map((a) => JSON.stringify(a)).join(', ')}]`),
|
|
166
|
+
];
|
|
167
|
+
for (const [k, v] of Object.entries(entry.env)) {
|
|
168
|
+
args.push(...c(`mcp_servers.specrails.env.${k}=${JSON.stringify(v)}`));
|
|
169
|
+
}
|
|
170
|
+
return args;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Prepares the specrails MCP for the given provider and returns the spawn
|
|
174
|
+
* extraArgs + env to merge. Returns empty wiring when the bridge is unavailable
|
|
175
|
+
* (agent then runs tool-less / degraded).
|
|
176
|
+
*/
|
|
177
|
+
function prepareAgentMcp(opts) {
|
|
178
|
+
const entry = buildSpecrailsMcpEntry({
|
|
179
|
+
port: opts.port,
|
|
180
|
+
tierLevel: opts.tierLevel,
|
|
181
|
+
activeProjectId: opts.activeProjectId,
|
|
182
|
+
});
|
|
183
|
+
if (!entry)
|
|
184
|
+
return { extraArgs: [], env: {} };
|
|
185
|
+
if (opts.adapterId === 'codex') {
|
|
186
|
+
// Inline -c overrides — no CODEX_HOME override, no file mutation; auth intact.
|
|
187
|
+
return { extraArgs: codexMcpOverrides(entry), env: {} };
|
|
188
|
+
}
|
|
189
|
+
if (opts.adapterId === 'claude') {
|
|
190
|
+
return { extraArgs: buildAgentMcpArgs(opts), env: {} };
|
|
191
|
+
}
|
|
192
|
+
// gemini + any other project-json provider: write .mcp.json in the spawn cwd.
|
|
193
|
+
const file = path_1.default.join(opts.cwd, '.mcp.json');
|
|
194
|
+
let current = {};
|
|
195
|
+
try {
|
|
196
|
+
if (fs_1.default.existsSync(file)) {
|
|
197
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
|
|
198
|
+
if (parsed && typeof parsed === 'object')
|
|
199
|
+
current = parsed;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
current = {};
|
|
204
|
+
}
|
|
205
|
+
const next = { ...current, mcpServers: { ...(current.mcpServers ?? {}), specrails: entry } };
|
|
206
|
+
fs_1.default.writeFileSync(file, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
207
|
+
return { extraArgs: [], env: {} };
|
|
208
|
+
}
|