specrails-desktop 2.18.2 → 2.19.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/README.md +1 -1
- package/client/dist/assets/{ActivityFeedPage-mGnv5SOe.js → ActivityFeedPage-C0_k6n5T.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-NHUCEnTh.js +1 -0
- package/client/dist/assets/AgentModeCodePane-jddXrhPY.js +2 -0
- package/client/dist/assets/AgentsPage-BPjf6bId.js +86 -0
- package/client/dist/assets/AnalyticsPage-BaJjj5mO.js +1 -0
- package/client/dist/assets/{BarChart-BqHUOnl9.js → BarChart-HPiao--v.js} +1 -1
- package/client/dist/assets/CodePage-CAD4KfkC.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CR-m97Ui.js → DesktopAnalyticsPage-B2RBxIvj.js} +1 -1
- package/client/dist/assets/DocsDialog-BJDvUEGj.js +11 -0
- package/client/dist/assets/DocsPage-B6H-DBs8.js +11 -0
- package/client/dist/assets/{ExportDropdown-C-o1Q4ef.js → ExportDropdown-CYMcbLTD.js} +1 -1
- package/client/dist/assets/IntegrationsPage-Dx_UpQVk.js +3 -0
- package/client/dist/assets/JobDetailPage-BlTnsRAN.js +16 -0
- package/client/dist/assets/JobsPage-EB0PbXOk.js +1 -0
- package/client/dist/assets/LoopBuilderPage-DfWfhhJY.js +7 -0
- package/client/dist/assets/LoopsPage-DCVATTv5.js +1 -0
- package/client/dist/assets/{addspec-B9dY_F5T.js → addspec-BXV6sx5l.js} +1 -1
- package/client/dist/assets/{addspec-tbxr3sHa.js → addspec-CS895X6A.js} +1 -1
- package/client/dist/assets/{addspec-W0r16BOC2.js → addspec-CXfEOrD42.js} +1 -1
- package/client/dist/assets/{addspec-D43TlMVp2.js → addspec-D28kw9ow2.js} +1 -1
- package/client/dist/assets/{addspec-B9nhMikb.js → addspec-D39EEg02.js} +1 -1
- package/client/dist/assets/{addspec-DfgIZ5lO.js → addspec-DC1tgsFG.js} +1 -1
- package/client/dist/assets/{addspec-BtNaJHu2.js → addspec-DcHALPEp.js} +1 -1
- package/client/dist/assets/{addspec-BloGn_ey.js → addspec-DxW6oPsk.js} +1 -1
- package/client/dist/assets/{agent-Bm2tPrdr.js → agent-B5Ycau5M.js} +1 -1
- package/client/dist/assets/{agent-DMUrz9ZS2.js → agent-BEz08l4_2.js} +1 -1
- package/client/dist/assets/{agent-C4bO1l_v.js → agent-BIX51CEA.js} +1 -1
- package/client/dist/assets/{agent-9WdSxPU5.js → agent-Bc9Kwf65.js} +1 -1
- package/client/dist/assets/{agent-pO8x4jbR.js → agent-BiM7puyn.js} +1 -1
- package/client/dist/assets/{agent-CgKsh88Z2.js → agent-C0kYSv3m2.js} +1 -1
- package/client/dist/assets/agent-CCzWlfqs.js +1 -0
- package/client/dist/assets/{agent-ASIyVcg0.js → agent-DiCSo31C.js} +1 -1
- package/client/dist/assets/{agents-DfGvYtpq2.js → agents-BFrHy5d-2.js} +1 -1
- package/client/dist/assets/{agents-Dli56gr82.js → agents-BFy2Kq_k2.js} +1 -1
- package/client/dist/assets/{agents-C07al8rI.js → agents-BXDCFJQa.js} +1 -1
- package/client/dist/assets/{agents-BAxAwHy-.js → agents-BySH0Gvw.js} +1 -1
- package/client/dist/assets/{agents-Eqw_AQ6H.js → agents-CTdwacTO.js} +1 -1
- package/client/dist/assets/{agents-BjT_xLjv.js → agents-Cev2oFfX.js} +1 -1
- package/client/dist/assets/{agents-BUYBb825.js → agents-CsDEv4Fj.js} +1 -1
- package/client/dist/assets/{agents-52t8NVpS.js → agents-z0kA-y0Q.js} +1 -1
- package/client/dist/assets/dashboard-Bl2_Yjs6.js +1 -0
- package/client/dist/assets/dashboard-C_vS9RaM2.js +1 -0
- package/client/dist/assets/dashboard-CcEpALV_.js +1 -0
- package/client/dist/assets/dashboard-CfltY66K.js +1 -0
- package/client/dist/assets/dashboard-CoED_41r.js +1 -0
- package/client/dist/assets/dashboard-DLeC5ziO.js +1 -0
- package/client/dist/assets/dashboard-DxEPkALo2.js +1 -0
- package/client/dist/assets/dashboard-MgDVmaYS2.js +1 -0
- package/client/dist/assets/{dist-js-CUegfF6k.js → dist-js-ClEfL6Bo.js} +1 -1
- package/client/dist/assets/{dist-js-BHhtSerX.js → dist-js-b2_z7zAi.js} +1 -1
- package/client/dist/assets/index-9yNUBwEi.css +2 -0
- package/client/dist/assets/{index-B7vB20Jw.js → index-BeujshsU.js} +57 -57
- package/client/dist/assets/{lib-PQoHlR5u.js → lib-CfphudAm.js} +1 -1
- package/client/dist/assets/loops-BEUiCzEI.js +1 -0
- package/client/dist/assets/loops-BlrQiK122.js +1 -0
- package/client/dist/assets/loops-Bs0ectZ32.js +1 -0
- package/client/dist/assets/loops-CLZV8HHY2.js +1 -0
- package/client/dist/assets/loops-CQuuMlyZ.js +1 -0
- package/client/dist/assets/loops-DuuWQWTI.js +1 -0
- package/client/dist/assets/loops-KOTryzk5.js +1 -0
- package/client/dist/assets/loops-t1mUZtf0.js +1 -0
- package/client/dist/assets/nav--1ErXZrS.js +1 -0
- package/client/dist/assets/nav-18nqSJ8h.js +1 -0
- package/client/dist/assets/nav-BuonHb17.js +1 -0
- package/client/dist/assets/nav-C-sCoOLY2.js +1 -0
- package/client/dist/assets/nav-ClXz_3Ic2.js +1 -0
- package/client/dist/assets/nav-CtqgSQeE2.js +1 -0
- package/client/dist/assets/nav-S3YvCaVv.js +1 -0
- package/client/dist/assets/nav-czg8NkN-.js +1 -0
- package/client/dist/assets/settings-BOZBX-Vq.js +1 -0
- package/client/dist/assets/settings-CX29QcQW.js +1 -0
- package/client/dist/assets/settings-CfOWHPzc2.js +1 -0
- package/client/dist/assets/settings-DaXhxNug2.js +1 -0
- package/client/dist/assets/settings-DmyDpuzu.js +1 -0
- package/client/dist/assets/settings-DwPRLcxr2.js +1 -0
- package/client/dist/assets/settings-MjYVJvDE.js +1 -0
- package/client/dist/assets/settings-zJFdTzKE.js +1 -0
- package/client/dist/assets/{setup-BYoDYoPY2.js → setup-BmXfxlzA2.js} +1 -1
- package/client/dist/assets/{setup-faMApo0U2.js → setup-BxVydxbl2.js} +1 -1
- package/client/dist/assets/{setup-C5UbM0RP.js → setup-DIRZ1Lf-.js} +1 -1
- package/client/dist/assets/{setup-Bf9eLKxU.js → setup-DwbKSnKQ.js} +1 -1
- package/client/dist/assets/{setup-JFkWWZnR.js → setup-Dx13pESZ.js} +1 -1
- package/client/dist/assets/{setup-CqGrWb6G.js → setup-G4ZvDKxX.js} +1 -1
- package/client/dist/assets/{setup-tloaTobG2.js → setup-SLAi4xxQ2.js} +1 -1
- package/client/dist/assets/{setup-4us-Bz3H.js → setup-Z01j3st4.js} +1 -1
- package/client/dist/assets/{upload-PnJDPHZH.js → upload-Ox9jUq1X.js} +1 -1
- package/client/dist/assets/{value-BTDefcOV.js → value-BeQVX5gN.js} +1 -1
- package/client/dist/index.html +10 -10
- package/docs/codex.md +5 -5
- package/docs/customizing.md +4 -4
- package/docs/gemini.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/guide/de/integrations/1-ai-providers.md +2 -2
- package/docs/guide/de/integrations/5-mcp-server.md +20 -1
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/de/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/en/integrations/1-ai-providers.md +2 -2
- package/docs/guide/en/integrations/5-mcp-server.md +20 -1
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/en/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/es/integrations/1-ai-providers.md +2 -2
- package/docs/guide/es/integrations/5-mcp-server.md +20 -1
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/es/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/fr/integrations/1-ai-providers.md +2 -2
- package/docs/guide/fr/integrations/5-mcp-server.md +20 -1
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/fr/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/it/integrations/1-ai-providers.md +2 -2
- package/docs/guide/it/integrations/5-mcp-server.md +20 -1
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/it/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/ja/integrations/1-ai-providers.md +2 -2
- package/docs/guide/ja/integrations/5-mcp-server.md +20 -1
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/ja/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/pt/integrations/1-ai-providers.md +2 -2
- package/docs/guide/pt/integrations/5-mcp-server.md +20 -1
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +2 -2
- package/docs/guide/zh/integrations/1-ai-providers.md +2 -2
- package/docs/guide/zh/integrations/5-mcp-server.md +20 -1
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +4 -4
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +1 -1
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/zh/pipeline/5-the-loop-builder.md +2 -2
- package/docs/internals/switch-to-agent-mode-design.md +301 -0
- package/docs/internals/visual-design-audit.md +184 -0
- package/docs/mcp.md +26 -1
- package/docs/running-pipelines.md +10 -10
- package/docs/tracking-cost.md +1 -1
- package/package.json +1 -1
- package/server/dist/agent-chat-manager.js +107 -33
- package/server/dist/agent-chat-router.js +170 -8
- package/server/dist/agent-cwd-manager.js +2 -47
- package/server/dist/agent-operator-prompt.js +277 -0
- package/server/dist/agent-store.js +27 -5
- package/server/dist/attachment-manager.js +138 -0
- package/server/dist/desktop-db.js +15 -0
- package/server/dist/loop-factory.js +12 -2
- package/server/dist/mcp/guide.js +186 -51
- package/server/dist/mcp/tools/analytics.js +1 -1
- package/server/dist/mcp/tools/app-settings.js +45 -5
- package/server/dist/mcp/tools/chat.js +9 -2
- package/server/dist/mcp/tools/code.js +3 -2
- package/server/dist/mcp/tools/jobs.js +18 -6
- package/server/dist/mcp/tools/loops.js +57 -5
- package/server/dist/mcp/tools/meta.js +43 -3
- package/server/dist/mcp/tools/rails.js +6 -6
- package/server/dist/mcp/tools/setup.js +2 -1
- package/server/dist/mcp/tools/specs.js +60 -38
- package/server/dist/mcp/tools/types.js +17 -8
- package/server/dist/mcp/tools/watch.js +71 -8
- package/server/dist/project-router-helpers.js +1 -1
- package/server/dist/project-router-tickets.js +43 -4
- package/server/dist/providers/claude-adapter.js +6 -0
- package/server/dist/providers/codex-adapter.js +10 -0
- package/server/dist/providers/gemini-adapter.js +3 -0
- package/server/dist/rails-router.js +8 -17
- package/server/dist/spec-models.js +1 -0
- package/client/dist/assets/AgentsPage-BH1isg5j.js +0 -86
- package/client/dist/assets/AnalyticsPage-ByyshYRI.js +0 -1
- package/client/dist/assets/CodePage-CU7T_HYs.js +0 -2
- package/client/dist/assets/DocsDialog-CEmZp_8V.js +0 -11
- package/client/dist/assets/DocsPage-DYbAQ1AT.js +0 -11
- package/client/dist/assets/IntegrationsPage-C6fKdwVH.js +0 -3
- package/client/dist/assets/JobDetailPage-CKfIfBgE.js +0 -16
- package/client/dist/assets/JobsPage-nBng0riZ.js +0 -1
- package/client/dist/assets/LoopBuilderPage-Co_TYqYk.js +0 -7
- package/client/dist/assets/LoopsPage-a2hd37Ya.js +0 -1
- package/client/dist/assets/agent-Dwkz5mPS.js +0 -1
- package/client/dist/assets/dashboard-BTiH361F.js +0 -1
- package/client/dist/assets/dashboard-COSm_14E.js +0 -1
- package/client/dist/assets/dashboard-CfOs70FN2.js +0 -1
- package/client/dist/assets/dashboard-Cyzp4ox9.js +0 -1
- package/client/dist/assets/dashboard-CzHDp14_2.js +0 -1
- package/client/dist/assets/dashboard-D6Woy2B5.js +0 -1
- package/client/dist/assets/dashboard-DwbH4oVH.js +0 -1
- package/client/dist/assets/dashboard-cWhU3krA2.js +0 -1
- package/client/dist/assets/index-B0FxQALR.css +0 -2
- package/client/dist/assets/loops-BDhEfnx82.js +0 -1
- package/client/dist/assets/loops-BM5QggwH.js +0 -1
- package/client/dist/assets/loops-CPjLAAxj.js +0 -1
- package/client/dist/assets/loops-D8Iu6Jss.js +0 -1
- package/client/dist/assets/loops-DKnk79TB2.js +0 -1
- package/client/dist/assets/loops-DLW0aFMm.js +0 -1
- package/client/dist/assets/loops-Dcv3dFy_2.js +0 -1
- package/client/dist/assets/loops-y7wFRx6M.js +0 -1
- package/client/dist/assets/nav-1hj2izqB2.js +0 -1
- package/client/dist/assets/nav-BBUKMAu-2.js +0 -1
- package/client/dist/assets/nav-CLqkP-26.js +0 -1
- package/client/dist/assets/nav-Cv46L837.js +0 -1
- package/client/dist/assets/nav-DE3yWQw6.js +0 -1
- package/client/dist/assets/nav-DgA-rjLk2.js +0 -1
- package/client/dist/assets/nav-EG2pr6vN.js +0 -1
- package/client/dist/assets/nav-r3mIJVfU.js +0 -1
- package/client/dist/assets/settings-BKmORxlB2.js +0 -1
- package/client/dist/assets/settings-BVv2JgQ8.js +0 -1
- package/client/dist/assets/settings-Bghm3pAV.js +0 -1
- package/client/dist/assets/settings-BuC_aplX.js +0 -1
- package/client/dist/assets/settings-Bw_Qk8E32.js +0 -1
- package/client/dist/assets/settings-CAKgJv7p.js +0 -1
- package/client/dist/assets/settings-CEUgk1Ck.js +0 -1
- package/client/dist/assets/settings-C_PDE20s2.js +0 -1
- /package/client/dist/assets/{activity-Uj576ckT.js → activity-BlvN5rhP.js} +0 -0
- /package/client/dist/assets/{activity-w1qMFwxh.js → activity-C5SeKbom.js} +0 -0
- /package/client/dist/assets/{activity-BySXFT2J2.js → activity-DIJexk6v2.js} +0 -0
- /package/client/dist/assets/{activity-B82z3Pna.js → activity-DLdwUYv7.js} +0 -0
- /package/client/dist/assets/{activity-BVpZrs0J.js → activity-DOKV_Wyf.js} +0 -0
- /package/client/dist/assets/{activity-BId0pwuS2.js → activity-DVuiy8TI2.js} +0 -0
- /package/client/dist/assets/{activity-DIWnV0Pl.js → activity-Dsf3J7iW.js} +0 -0
- /package/client/dist/assets/{agentstudio-C7xxqVWn.js → agentstudio-B8H2H4Db2.js} +0 -0
- /package/client/dist/assets/{agentstudio-D-UoNAIA.js → agentstudio-B9zthaTJ.js} +0 -0
- /package/client/dist/assets/{agentstudio-0o3XKrcy.js → agentstudio-BDMpU01s.js} +0 -0
- /package/client/dist/assets/{agentstudio-Bu8m5jDw.js → agentstudio-CSZo7p8K.js} +0 -0
- /package/client/dist/assets/{agentstudio-ObTpVbBP.js → agentstudio-CdD6J23W.js} +0 -0
- /package/client/dist/assets/{agentstudio-PzFB3vfh2.js → agentstudio-D80fZu_M2.js} +0 -0
- /package/client/dist/assets/{agentstudio-OJjjz6Uf2.js → agentstudio-EuKglkaF2.js} +0 -0
- /package/client/dist/assets/{aiedit-DRrNiaB4.js → aiedit-B_VOTUo42.js} +0 -0
- /package/client/dist/assets/{aiedit-sP_iao2j.js → aiedit-Cxoe_0wv.js} +0 -0
- /package/client/dist/assets/{aiedit-DAM_WRAl2.js → aiedit-D59sPapq2.js} +0 -0
- /package/client/dist/assets/{aiedit-BaLZalLa.js → aiedit-DX3BBpXF.js} +0 -0
- /package/client/dist/assets/{aiedit-BIQqv2XM2.js → aiedit-GYOYWCHJ2.js} +0 -0
- /package/client/dist/assets/{aiedit-BjlUzasF.js → aiedit-TON-4N9S.js} +0 -0
- /package/client/dist/assets/{aiedit-BmDufyGU.js → aiedit-ybQmdsST.js} +0 -0
- /package/client/dist/assets/{analytics-MVJRjcY52.js → analytics-B2jjXLCP2.js} +0 -0
- /package/client/dist/assets/{analytics-C4z_NgJZ.js → analytics-BTkXLTfa.js} +0 -0
- /package/client/dist/assets/{analytics-DL2vJCbq.js → analytics-BotqRU_R.js} +0 -0
- /package/client/dist/assets/{analytics-e1mkNTgy2.js → analytics-Ck3vl2Qo2.js} +0 -0
- /package/client/dist/assets/{analytics-Ds_PNxtO2.js → analytics-CqMJQb7N.js} +0 -0
- /package/client/dist/assets/{analytics-B_rxNC9X.js → analytics-DGOoMkaZ.js} +0 -0
- /package/client/dist/assets/{analytics-BDqqCzN9.js → analytics-DL7uwNAI2.js} +0 -0
- /package/client/dist/assets/{attachments-BP0Ac1yt.js → attachments-7w-BR2V2.js} +0 -0
- /package/client/dist/assets/{attachments-CPBVeHX72.js → attachments-B5QX1Xfa2.js} +0 -0
- /package/client/dist/assets/{attachments-Db7JfA8a.js → attachments-BcE7kZG-2.js} +0 -0
- /package/client/dist/assets/{attachments-CJaO1vCH.js → attachments-BsJJXFzT.js} +0 -0
- /package/client/dist/assets/{attachments-8QCKHCEF2.js → attachments-Cbh4AwjP.js} +0 -0
- /package/client/dist/assets/{attachments-CV2uaTGE.js → attachments-D0fGlFve.js} +0 -0
- /package/client/dist/assets/{attachments-CaOjdN332.js → attachments-D3DJpd3e2.js} +0 -0
- /package/client/dist/assets/{browser-C9rUUxo4.js → browser-9GR11iTi.js} +0 -0
- /package/client/dist/assets/{browser-CEqx11iy.js → browser-BOxg0p3n.js} +0 -0
- /package/client/dist/assets/{browser-543jbxE4.js → browser-BSh8xUWV.js} +0 -0
- /package/client/dist/assets/{browser-CLC0etB_2.js → browser-CaV3BJNY2.js} +0 -0
- /package/client/dist/assets/{browser-0xeW8fUa2.js → browser-CmWGMRGf2.js} +0 -0
- /package/client/dist/assets/{browser-ExmELH702.js → browser-D6wbpBOL2.js} +0 -0
- /package/client/dist/assets/{browser-DyG2l9Wa.js → browser-dsgDU1HB.js} +0 -0
- /package/client/dist/assets/{chat-DagUyoVA.js → chat-B90iLDI4.js} +0 -0
- /package/client/dist/assets/{chat-Cu6F3d6g2.js → chat-CAo0jCnj2.js} +0 -0
- /package/client/dist/assets/{chat-CN8BMAMc.js → chat-CW54IP3u.js} +0 -0
- /package/client/dist/assets/{chat-NQPCBQo72.js → chat-CYHUUW0p2.js} +0 -0
- /package/client/dist/assets/{chat-CCytD3P8.js → chat-D1PXX_p2.js} +0 -0
- /package/client/dist/assets/{chat-IjutwlBg.js → chat-DhpcWjOf.js} +0 -0
- /package/client/dist/assets/{chat-vVpwkOp52.js → chat-cFG-UfRA2.js} +0 -0
- /package/client/dist/assets/{code-Dhb6jbAU.js → code--qUk_yn-.js} +0 -0
- /package/client/dist/assets/{code-Ce-nBIwj.js → code-B1aOMCVV.js} +0 -0
- /package/client/dist/assets/{code-BaG05zK62.js → code-B5UrmjaE2.js} +0 -0
- /package/client/dist/assets/{code-Dap54vOb.js → code-CjBHU0nw.js} +0 -0
- /package/client/dist/assets/{code-_NOCo-Ol2.js → code-Cjv4_HkK2.js} +0 -0
- /package/client/dist/assets/{code-32l6Naxu2.js → code-D7GLAjrz2.js} +0 -0
- /package/client/dist/assets/{code-C5cibF2F.js → code-DZKjfzBN.js} +0 -0
- /package/client/dist/assets/{commands-Cc2WpuXa.js → commands-B9XaopQC.js} +0 -0
- /package/client/dist/assets/{commands-k9wbNYoi.js → commands-BCx1uDIE.js} +0 -0
- /package/client/dist/assets/{commands-CM99Upsz2.js → commands-Bhw9Hqsa2.js} +0 -0
- /package/client/dist/assets/{commands-Bc8y9eNo2.js → commands-D0RnooQF2.js} +0 -0
- /package/client/dist/assets/{commands-Bl3-KVfj.js → commands-DuaQT-it.js} +0 -0
- /package/client/dist/assets/{commands-DASXJD-l.js → commands-MqNuGGO4.js} +0 -0
- /package/client/dist/assets/{commands-C5WQ9blx2.js → commands-oB9q2vy_2.js} +0 -0
- /package/client/dist/assets/{common-CHARW6MZ2.js → common-BXzqeuiC2.js} +0 -0
- /package/client/dist/assets/{common-0aY9HQBP.js → common-BaJ6MF7T.js} +0 -0
- /package/client/dist/assets/{common-CQCJzAQq2.js → common-C-jaOdxz2.js} +0 -0
- /package/client/dist/assets/{common-DNhhqjlJ.js → common-CKjy-R8-.js} +0 -0
- /package/client/dist/assets/{common-YdyZtmaI2.js → common-CRd-Ge5u2.js} +0 -0
- /package/client/dist/assets/{common-5jScBJqt.js → common-DPGR3Tfo.js} +0 -0
- /package/client/dist/assets/{common-BdaRciTu.js → common-wEB9RBdf.js} +0 -0
- /package/client/dist/assets/{explore-ChtxIiSM.js → explore-B_79NLR5.js} +0 -0
- /package/client/dist/assets/{explore-BzdMEpuf.js → explore-D-htKul_.js} +0 -0
- /package/client/dist/assets/{explore-B82VMUJ_.js → explore-D5b2jgvR.js} +0 -0
- /package/client/dist/assets/{explore-DfcUfiPw2.js → explore-Dvp-5qOw2.js} +0 -0
- /package/client/dist/assets/{explore-CbW0381i2.js → explore-FaKfWCr72.js} +0 -0
- /package/client/dist/assets/{explore-BGz1rvQE.js → explore-IS8VyaSZ.js} +0 -0
- /package/client/dist/assets/{explore-yRF7wFSp2.js → explore-OZhWV0Eb2.js} +0 -0
- /package/client/dist/assets/{format-command-D96da4fZ.js → format-command-CHImQv6W.js} +0 -0
- /package/client/dist/assets/{integrations-CnrX7vqw2.js → integrations-B97cGWfo2.js} +0 -0
- /package/client/dist/assets/{integrations-BJvrlcc7.js → integrations-Bp3leAh3.js} +0 -0
- /package/client/dist/assets/{integrations-CPKYkG36.js → integrations-C3Ma3GdM.js} +0 -0
- /package/client/dist/assets/{integrations-BA5pM05t.js → integrations-C5MUA-0-.js} +0 -0
- /package/client/dist/assets/{integrations-B0OBPU9_.js → integrations-CiQbCn_W.js} +0 -0
- /package/client/dist/assets/{integrations-DeJFRyXz2.js → integrations-Du_0rDfi2.js} +0 -0
- /package/client/dist/assets/{integrations-DBou159M2.js → integrations-t4X5UVcx2.js} +0 -0
- /package/client/dist/assets/{jira-mi37H4Kt.js → jira-B1XaR6fj.js} +0 -0
- /package/client/dist/assets/{jira-Srx2P5zS.js → jira-BCHx9zUF.js} +0 -0
- /package/client/dist/assets/{jira-DeotPx6P2.js → jira-BqXyykbt2.js} +0 -0
- /package/client/dist/assets/{jira-BwLBgmfI2.js → jira-BzFvFn7t2.js} +0 -0
- /package/client/dist/assets/{jira-BDgxAFTF2.js → jira-D6IDTIsc2.js} +0 -0
- /package/client/dist/assets/{jira-NwlUCcQD.js → jira-DSZ5xFRl.js} +0 -0
- /package/client/dist/assets/{jira-D3GGzXOI.js → jira-DdEKW9Pu.js} +0 -0
- /package/client/dist/assets/{jobs-D8adm2BX.js → jobs-BX9dP3Qn.js} +0 -0
- /package/client/dist/assets/{jobs-B56vQEkh2.js → jobs-BgHEgCWk2.js} +0 -0
- /package/client/dist/assets/{jobs-K_Y8KAZ2.js → jobs-CU-khJTt.js} +0 -0
- /package/client/dist/assets/{jobs-BYIk6Tg_.js → jobs-Ce0SE81t.js} +0 -0
- /package/client/dist/assets/{jobs-C-lM-iax2.js → jobs-CeOcGHd_2.js} +0 -0
- /package/client/dist/assets/{jobs-CYdevGgT.js → jobs-Ct5v2p6T.js} +0 -0
- /package/client/dist/assets/{jobs-OwM0ZWhI2.js → jobs-m7L0K9uC2.js} +0 -0
- /package/client/dist/assets/{mcp-D3E4_Mmk2.js → mcp-4QbfLPa4.js} +0 -0
- /package/client/dist/assets/{mcp-B6nwJJmR.js → mcp-CXy61khH.js} +0 -0
- /package/client/dist/assets/{mcp-CNgFiTEh.js → mcp-CmOBPDjE.js} +0 -0
- /package/client/dist/assets/{mcp-mupPuPDO2.js → mcp-NlLc9T8u2.js} +0 -0
- /package/client/dist/assets/{mcp-B4irfLJ2.js → mcp-_DUUXTHB2.js} +0 -0
- /package/client/dist/assets/{mcp-BP7PpbDr2.js → mcp-dlOyUQQy2.js} +0 -0
- /package/client/dist/assets/{mcp-BOoEGTSQ.js → mcp-zqSVP7nA.js} +0 -0
- /package/client/dist/assets/{specs-D4z5d8uq.js → specs-57pue7UQ.js} +0 -0
- /package/client/dist/assets/{specs-CWNKAScX2.js → specs-BWQ-0EXt2.js} +0 -0
- /package/client/dist/assets/{specs-B8mwScoM2.js → specs-BYSOlQRw2.js} +0 -0
- /package/client/dist/assets/{specs-B5XOoH-02.js → specs-CkP0B28v2.js} +0 -0
- /package/client/dist/assets/{specs-DgvIRZtM.js → specs-RSYuLgF4.js} +0 -0
- /package/client/dist/assets/{specs-DuRzE5wE.js → specs-d-zU7_3Q.js} +0 -0
- /package/client/dist/assets/{specs-Bj_3fDrQ.js → specs-nM5Cpcqe.js} +0 -0
- /package/client/dist/assets/{terminal-DoMTHedv2.js → terminal-Adi3W-GZ2.js} +0 -0
- /package/client/dist/assets/{terminal-DOwfkxqa2.js → terminal-BA6FCZna2.js} +0 -0
- /package/client/dist/assets/{terminal-CH0fdHvP.js → terminal-BCIUSkrg.js} +0 -0
- /package/client/dist/assets/{terminal-B2sExUis.js → terminal-C8KlD61D.js} +0 -0
- /package/client/dist/assets/{terminal-DjSSa5qP.js → terminal-D3RTuBwy.js} +0 -0
- /package/client/dist/assets/{terminal-w0i-OCp32.js → terminal-DCkOEjSj2.js} +0 -0
- /package/client/dist/assets/{terminal-CPMigUX7.js → terminal-DOuNm2BI.js} +0 -0
- /package/client/dist/assets/{tickets-D00mSt832.js → tickets-B2q36GlQ2.js} +0 -0
- /package/client/dist/assets/{tickets-CQTana1u.js → tickets-Bsyv_znj.js} +0 -0
- /package/client/dist/assets/{tickets-D7CSuWjO.js → tickets-C_UPra_1.js} +0 -0
- /package/client/dist/assets/{tickets-heDAaj8C2.js → tickets-D72LbUtU2.js} +0 -0
- /package/client/dist/assets/{tickets-DvbFg_-c2.js → tickets-DDjU7UfE2.js} +0 -0
- /package/client/dist/assets/{tickets-onKZz6Dd.js → tickets-DR4ZrPMf.js} +0 -0
- /package/client/dist/assets/{tickets-Dq7HDy8n.js → tickets-DiUA3rXq.js} +0 -0
|
@@ -21,7 +21,7 @@ SpecsBoard (left) Rails (right)
|
|
|
21
21
|
|
|
22
22
|
A **rail** is an execution lane. Drag a spec card from the SpecsBoard onto a rail and press **▶ Play** to launch the pipeline. Rails let you organise and queue work into named lanes.
|
|
23
23
|
|
|
24
|
-
Rails run on **Claude** by default. If your project has more than one provider installed, a per-rail engine selector lets you launch a rail on **Codex** or **Gemini** instead — see [Using Codex](codex.md) and [Using Gemini](gemini.md). Only the **
|
|
24
|
+
Rails run on **Claude** by default. If your project has more than one provider installed, a per-rail engine selector lets you launch a rail on **Codex** or **Gemini** instead — see [Using Codex](codex.md) and [Using Gemini](gemini.md). Only the **Freestyle** loop is Claude-only; the standard Implement and Batch loops run on any installed provider.
|
|
25
25
|
|
|
26
26
|
> **One job at a time per project.** Each project has a single queue, so within a project only one rail job runs at a time; the rest queue behind it. Real parallelism is **across projects** — open two projects and their rails run independently. See [Running multiple rails](#running-multiple-rails).
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ Each rail has a header with:
|
|
|
31
31
|
|
|
32
32
|
- **Status pill** — `idle`, `running`, or `failed`. (There's no separate "completed" state — a rail returns to `idle` when its job finishes cleanly.)
|
|
33
33
|
- **Spec list** — the IDs of the specs assigned to this rail. Drag in more, drag out to detach. You can also use the **Move to rail** popover from a spec card; it shows a status dot per rail so you don't push work onto a busy lane.
|
|
34
|
-
- **Loop picker** — the **Loop** this rail runs: a built-in (`Implement`, `Batch`, or `
|
|
34
|
+
- **Loop picker** — the **Loop** this rail runs: a built-in (`Implement`, `Batch`, or `Freestyle`) or one of your published custom loops. See [Loops](#loops).
|
|
35
35
|
- **Profile picker** — which agent profile this rail uses. This only appears once the project has **at least one** profile (create them on the Agents page). When present, `No profile` runs the rail in legacy mode.
|
|
36
36
|
- **Engine selector** — pick which installed provider (Claude, Codex, or Gemini) runs this rail. Only renders on projects with more than one provider installed.
|
|
37
37
|
- **Play / Stop button** — start or cancel.
|
|
@@ -44,7 +44,7 @@ A rail runs a **Loop**, picked in the rail header and persisted per rail. The th
|
|
|
44
44
|
|------|---------|--------------|
|
|
45
45
|
| **Implement** | `/specrails:implement` | One job covering all the specs on the rail. Runs the full Architect → Developer → Reviewer → Ship pipeline. |
|
|
46
46
|
| **Batch** | `/specrails:batch-implement` | One job that works through the rail's specs sequentially, in dependency-aware waves. |
|
|
47
|
-
| **
|
|
47
|
+
| **Freestyle** | (Freestyle) | Claude implements each spec autonomously, **bypassing** the OpenSpec pipeline. One independent job per spec. Claude only. |
|
|
48
48
|
|
|
49
49
|
Beyond the built-ins, the **Loops** section (left sidebar, above the project list) is a global, n8n-style **visual builder** shared across all your projects. A loop is a graph of typed steps:
|
|
50
50
|
|
|
@@ -76,17 +76,17 @@ Each phase is a specialised agent invoked by the rail's engine (Claude, Codex, o
|
|
|
76
76
|
|
|
77
77
|
In plain terms: the project's **agent profile** decides which AI agent handles each phase. The baseline trio (`sr-architect`, `sr-developer`, `sr-reviewer`) is always present; a profile's routing rules can add extra agents or swap which one runs a phase. The phase progress bar only renders when the command defines phases. For the full format, see [internals/profiles.md](internals/profiles.md).
|
|
78
78
|
|
|
79
|
-
###
|
|
79
|
+
### Freestyle
|
|
80
80
|
|
|
81
|
-
`
|
|
81
|
+
`Freestyle` (mode value `ultracode` on the API — its original name) is a Claude-only loop that skips the Architect → Developer → Reviewer → Ship pipeline entirely. Instead of orchestrating the agent chain, it hands Claude a configurable pre-prompt plus the full spec text and lets it work autonomously with its native tools.
|
|
82
82
|
|
|
83
|
-
- **One job per spec.** If the rail has three specs, `
|
|
83
|
+
- **One job per spec.** If the rail has three specs, `Freestyle` launches three independent jobs.
|
|
84
84
|
- **Variable cost.** Because the run is open-ended, pressing Play opens a confirmation dialog before anything spawns.
|
|
85
|
-
- **Model picker.** A per-rail control lets you pick **Haiku / Sonnet / Opus** (default Sonnet) for the
|
|
86
|
-
- **Claude only.** The
|
|
87
|
-
- **Interactive (optional).** An `Interactive` toggle next to the
|
|
85
|
+
- **Model picker.** A per-rail control lets you pick **Haiku / Sonnet / Opus** (default Sonnet) for the Freestyle run.
|
|
86
|
+
- **Claude only.** The Freestyle loop and its model picker are offered only when the rail's engine is Claude. Codex and Gemini rails can't run Freestyle, and agent profiles don't apply to Freestyle rails.
|
|
87
|
+
- **Interactive (optional).** An `Interactive` toggle next to the Freestyle launch turns the run into a back-and-forth session: you can chat with the running job, send follow-up prompts, and click **Finalize** when you're done. Without it, the job runs to completion on its own. (Available when the rail is idle; can be disabled server-side via `SPECRAILS_INTERACTIVE_JOBS=false`.)
|
|
88
88
|
|
|
89
|
-
You can customise the
|
|
89
|
+
You can customise the Freestyle pre-prompt per project on the [Settings page](customizing.md#freestyle-pre-prompt).
|
|
90
90
|
|
|
91
91
|
### Running multiple rails
|
|
92
92
|
|
package/docs/tracking-cost.md
CHANGED
|
@@ -8,7 +8,7 @@ Six surfaces, all per-project:
|
|
|
8
8
|
|
|
9
9
|
| Surface | What it captures |
|
|
10
10
|
|---------|------------------|
|
|
11
|
-
| **`job`** | Every rail run (the Architect → Developer → Reviewer → Ship pipeline, plus
|
|
11
|
+
| **`job`** | Every rail run (the Architect → Developer → Reviewer → Ship pipeline, plus Freestyle and Batch runs) |
|
|
12
12
|
| **`quick-spec`** | One-shot Quick spec generation (including a Quick-mode Contract Refine run) |
|
|
13
13
|
| **`explore-spec`** | Every Explore conversation turn (including the Explore Contract Refine run) |
|
|
14
14
|
| **`ai-edit`** | The **AI Edit / Refine** overlay (refining an agent or a ticket) |
|
package/package.json
CHANGED
|
@@ -4,41 +4,22 @@ exports.AgentChatManager = void 0;
|
|
|
4
4
|
const providers_1 = require("./providers");
|
|
5
5
|
const spawn_lifecycle_1 = require("./spawn-lifecycle");
|
|
6
6
|
const agent_cwd_manager_1 = require("./agent-cwd-manager");
|
|
7
|
+
const agent_operator_prompt_1 = require("./agent-operator-prompt");
|
|
7
8
|
const agent_mcp_config_1 = require("./agent-mcp-config");
|
|
8
9
|
const agent_tier_1 = require("./agent-tier");
|
|
10
|
+
const attachment_manager_1 = require("./attachment-manager");
|
|
9
11
|
const agent_store_1 = require("./agent-store");
|
|
12
|
+
const explore_draft_title_1 = require("./explore-draft-title");
|
|
10
13
|
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
14
|
class AgentChatManager {
|
|
38
15
|
_broadcast;
|
|
39
16
|
_db;
|
|
40
17
|
_port;
|
|
41
18
|
_active = new Map();
|
|
19
|
+
/** Conversations with a turn in-flight but not yet spawned. Closes the TOCTOU
|
|
20
|
+
* window the attachment-extraction await opens between the busy guard and
|
|
21
|
+
* `_active.set` in onSpawn (mirrors ChatManager's reservation pattern). */
|
|
22
|
+
_reserved = new Set();
|
|
42
23
|
constructor(broadcast, db, port) {
|
|
43
24
|
this._broadcast = broadcast;
|
|
44
25
|
this._db = db;
|
|
@@ -59,10 +40,20 @@ class AgentChatManager {
|
|
|
59
40
|
this._emitError(conversationId, 'Unknown conversation');
|
|
60
41
|
return;
|
|
61
42
|
}
|
|
62
|
-
if (this._active.has(conversationId)) {
|
|
43
|
+
if (this._active.has(conversationId) || this._reserved.has(conversationId)) {
|
|
63
44
|
this._emitError(conversationId, 'The agent is busy. Try again in a moment.');
|
|
64
45
|
return;
|
|
65
46
|
}
|
|
47
|
+
this._reserved.add(conversationId);
|
|
48
|
+
try {
|
|
49
|
+
await this._runTurn(conversation, userText, options);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this._reserved.delete(conversationId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async _runTurn(conversation, userText, options) {
|
|
56
|
+
const conversationId = conversation.id;
|
|
66
57
|
const tierLevel = (0, agent_tier_1.normalizeLevel)(options.tierLevel ?? conversation.tier_level);
|
|
67
58
|
const adapter = (0, providers_1.getAdapter)(conversation.provider);
|
|
68
59
|
// Resolve a model that is VALID for this provider. A stale model from another
|
|
@@ -71,15 +62,63 @@ class AgentChatManager {
|
|
|
71
62
|
const catalog = new Set(adapter.modelCatalog().map((m) => m.value));
|
|
72
63
|
const requested = options.model || conversation.model;
|
|
73
64
|
const model = requested && catalog.has(requested) ? requested : adapter.defaultModel();
|
|
65
|
+
// Reasoning effort: stored per conversation; null = the app default
|
|
66
|
+
// ("medium"). Passed only to providers with a per-spawn knob, and only when
|
|
67
|
+
// the value is in the provider's catalog (a provider switch clears it, but
|
|
68
|
+
// belt-and-braces against stale rows).
|
|
69
|
+
const efforts = (adapter.capabilities.reasoningEfforts ?? []);
|
|
70
|
+
const storedEffort = conversation.reasoning_effort;
|
|
71
|
+
const reasoningEffort = efforts.length
|
|
72
|
+
? (storedEffort && efforts.includes(storedEffort) ? storedEffort : 'medium')
|
|
73
|
+
: undefined;
|
|
74
74
|
// Make the pinned project (Cursor-style selector) the MCP active project for
|
|
75
75
|
// this turn, so project-scoped tools resolve without the agent having to call
|
|
76
76
|
// specrails_select_project. Home (null) clears it → app-global mode.
|
|
77
77
|
(0, types_1.setActiveProject)(conversation.pinned_project_id ?? null);
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
// Resolve attachments (conversation-keyed) into extracted text blocks +
|
|
79
|
+
// absolute image paths. Extraction failure degrades to a text-only turn.
|
|
80
|
+
const attachmentIds = options.attachmentIds ?? [];
|
|
81
|
+
let userWithAttachments = userText;
|
|
82
|
+
let imagePaths = [];
|
|
83
|
+
let hasAttachments = false;
|
|
84
|
+
if (attachmentIds.length > 0) {
|
|
85
|
+
try {
|
|
86
|
+
const resolved = await attachment_manager_1.attachmentManager.getClaudeArgsAgent(conversationId, attachmentIds);
|
|
87
|
+
if (resolved.textBlocks.length > 0) {
|
|
88
|
+
userWithAttachments = `${userText}\n\n## Attached Resources\n\n${resolved.textBlocks.join('\n\n')}`;
|
|
89
|
+
hasAttachments = true;
|
|
90
|
+
}
|
|
91
|
+
imagePaths = resolved.imagePaths;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error(`[agent-chat] attachment extraction failed (${conversationId}):`, err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// The conversation may have been deleted while attachments were extracting
|
|
98
|
+
// (DELETE aborts the child and drops the row) — inserting would violate the FK.
|
|
99
|
+
if (!(0, agent_store_1.getAgentConversation)(this._db, conversationId))
|
|
100
|
+
return;
|
|
101
|
+
(0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'user', content: userText, attachmentIds });
|
|
102
|
+
this._autoTitle(conversationId, conversation.title);
|
|
103
|
+
// Providers WITHOUT a --system-prompt flag (codex, gemini) drop opts.systemPrompt
|
|
104
|
+
// for chat turns, so the attachment prompt-injection note would never reach them —
|
|
105
|
+
// fold it into the user turn instead (same capability-gated pattern as ChatManager).
|
|
106
|
+
if (hasAttachments && !adapter.capabilities.systemPromptArg) {
|
|
107
|
+
userWithAttachments = `${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}\n\n${userWithAttachments}`;
|
|
108
|
+
}
|
|
109
|
+
// Per-turn dynamic context (pinned project, permission level, provider) rides
|
|
110
|
+
// the USER turn, never the system prompt — byte-stability contract (see
|
|
111
|
+
// agent-operator-prompt.ts).
|
|
112
|
+
const contextPrefix = conversation.pinned_project_id
|
|
113
|
+
? `[Active project: projectId="${conversation.pinned_project_id}" | Permission level: ${tierLevel} | Provider: ${adapter.id}. Use the project for project-scoped tools unless told otherwise.]`
|
|
114
|
+
: `[No project pinned (Home) | Permission level: ${tierLevel} | Provider: ${adapter.id}]`;
|
|
115
|
+
const prompt = `${contextPrefix}\n\n${userWithAttachments}`;
|
|
116
|
+
const systemPrompt = hasAttachments
|
|
117
|
+
? `${agent_operator_prompt_1.OPERATOR_SYSTEM_PROMPT}\n\n${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}`
|
|
118
|
+
: agent_operator_prompt_1.OPERATOR_SYSTEM_PROMPT;
|
|
119
|
+
// Only pass native image paths to providers that can vision-load them (codex
|
|
120
|
+
// `--image`); claude/gemini already have the `@path` ref folded into prompt.
|
|
121
|
+
const spawnImagePaths = adapter.capabilities.supportsImageInput ? imagePaths : undefined;
|
|
83
122
|
const cwd = (0, agent_cwd_manager_1.ensureAgentCwd)();
|
|
84
123
|
let mcpArgs = [];
|
|
85
124
|
let mcpEnv = {};
|
|
@@ -114,10 +153,12 @@ class AgentChatManager {
|
|
|
114
153
|
env: { ...process.env, ...mcpEnv },
|
|
115
154
|
buildOpts: {
|
|
116
155
|
prompt,
|
|
117
|
-
systemPrompt: adapter.capabilities.systemPromptArg ?
|
|
156
|
+
systemPrompt: adapter.capabilities.systemPromptArg ? systemPrompt : undefined,
|
|
118
157
|
model,
|
|
119
158
|
sessionId: useResume ? conversation.session_id ?? undefined : undefined,
|
|
120
159
|
extraArgs: mcpArgs,
|
|
160
|
+
imagePaths: spawnImagePaths,
|
|
161
|
+
reasoning_effort: reasoningEffort,
|
|
121
162
|
},
|
|
122
163
|
onSpawn: (child) => {
|
|
123
164
|
this._active.set(conversationId, child);
|
|
@@ -160,6 +201,10 @@ class AgentChatManager {
|
|
|
160
201
|
};
|
|
161
202
|
const canResume = !!conversation.session_id && adapter.capabilities.nativeResume;
|
|
162
203
|
let r = await invoke(canResume);
|
|
204
|
+
// Deleted mid-turn (DELETE aborts the child and drops the row): stop here —
|
|
205
|
+
// no auto-heal respawn, no FK-violating assistant INSERT.
|
|
206
|
+
if (!(0, agent_store_1.getAgentConversation)(this._db, conversationId))
|
|
207
|
+
return;
|
|
163
208
|
// Auto-heal a stale/foreign session: a resume that produced no text (e.g. a
|
|
164
209
|
// provider switch leaving another provider's session id, or codex
|
|
165
210
|
// "no rollout found for thread id") retries once as a fresh turn.
|
|
@@ -167,6 +212,8 @@ class AgentChatManager {
|
|
|
167
212
|
console.log(`[agent-chat] resume produced no text — retrying fresh conv=${conversationId}`);
|
|
168
213
|
(0, agent_store_1.updateAgentConversation)(this._db, conversationId, { session_id: null });
|
|
169
214
|
r = await invoke(false);
|
|
215
|
+
if (!(0, agent_store_1.getAgentConversation)(this._db, conversationId))
|
|
216
|
+
return;
|
|
170
217
|
}
|
|
171
218
|
if (r.spawnFailed) {
|
|
172
219
|
this._emitError(conversationId, `Failed to launch ${adapter.binary}. Is it installed and on PATH?`);
|
|
@@ -191,6 +238,33 @@ class AgentChatManager {
|
|
|
191
238
|
(r.code !== 0 ? `${adapter.binary} exited with code ${r.code}` : 'The agent returned no output.');
|
|
192
239
|
this._emitError(conversationId, reason);
|
|
193
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Auto-title a conversation from its first TWO user prompts (deterministic —
|
|
243
|
+
* no AI spend). Turn 1 sets the title when none exists; turn 2 refines it,
|
|
244
|
+
* but ONLY while the current title is still turn 1's auto title (a manual
|
|
245
|
+
* rename is recomputable-detectable and never clobbered). Broadcasts
|
|
246
|
+
* `agent_title` so the sidebar list updates live.
|
|
247
|
+
*/
|
|
248
|
+
_autoTitle(conversationId, currentTitle) {
|
|
249
|
+
try {
|
|
250
|
+
const userMsgs = (0, agent_store_1.listAgentMessages)(this._db, conversationId).filter((m) => m.role === 'user');
|
|
251
|
+
if (userMsgs.length === 0 || userMsgs.length > 2)
|
|
252
|
+
return;
|
|
253
|
+
const autoFromFirst = (0, explore_draft_title_1.generateAutoTitle)([{ role: 'user', content: userMsgs[0].content }]);
|
|
254
|
+
const stillAuto = currentTitle === null || (userMsgs.length === 2 && currentTitle === autoFromFirst);
|
|
255
|
+
if (!stillAuto)
|
|
256
|
+
return;
|
|
257
|
+
const source = userMsgs.map((m) => m.content).join(' — ');
|
|
258
|
+
const title = (0, explore_draft_title_1.generateAutoTitle)([{ role: 'user', content: source }]);
|
|
259
|
+
if (!title || title === currentTitle)
|
|
260
|
+
return;
|
|
261
|
+
(0, agent_store_1.updateAgentConversation)(this._db, conversationId, { title });
|
|
262
|
+
this._broadcast({ type: 'agent_title', conversationId, title, timestamp: new Date().toISOString() });
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
console.error(`[agent-chat] auto-title failed (${conversationId}):`, err);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
194
268
|
/** Aborts the active turn for a conversation, if any. */
|
|
195
269
|
abort(conversationId) {
|
|
196
270
|
const child = this._active.get(conversationId);
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.isAgentChatEnabled = isAgentChatEnabled;
|
|
4
7
|
exports.createAgentChatRouter = createAgentChatRouter;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const multer_1 = __importDefault(require("multer"));
|
|
5
10
|
const express_1 = require("express");
|
|
6
11
|
const providers_1 = require("./providers");
|
|
7
12
|
const agent_tier_1 = require("./agent-tier");
|
|
13
|
+
const attachment_manager_1 = require("./attachment-manager");
|
|
8
14
|
const agent_store_1 = require("./agent-store");
|
|
9
15
|
function isAgentChatEnabled() {
|
|
10
16
|
return process.env.SPECRAILS_AGENT_CHAT !== 'false';
|
|
@@ -20,6 +26,20 @@ function validProvider(provider) {
|
|
|
20
26
|
return null;
|
|
21
27
|
}
|
|
22
28
|
}
|
|
29
|
+
const attachmentUpload = (0, multer_1.default)({
|
|
30
|
+
storage: multer_1.default.memoryStorage(),
|
|
31
|
+
limits: { fileSize: 25 * 1024 * 1024 },
|
|
32
|
+
fileFilter: (req, file, cb) => {
|
|
33
|
+
if ((0, attachment_manager_1.isSupportedUploadedFile)({ mimetype: file.mimetype, originalname: file.originalname })) {
|
|
34
|
+
cb(null, true);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
;
|
|
38
|
+
req.fileRejected = file.mimetype || file.originalname || 'unknown';
|
|
39
|
+
cb(null, false);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
23
43
|
function createAgentChatRouter(deps) {
|
|
24
44
|
const { manager, desktopDb } = deps;
|
|
25
45
|
const router = (0, express_1.Router)();
|
|
@@ -34,8 +54,27 @@ function createAgentChatRouter(deps) {
|
|
|
34
54
|
// Per-provider model catalog for the header model selector.
|
|
35
55
|
router.get('/models', (req, res) => {
|
|
36
56
|
const provider = validProvider(req.query.provider) ?? 'claude';
|
|
37
|
-
|
|
57
|
+
const adapter = (0, providers_1.getAdapter)(provider);
|
|
58
|
+
res.json({
|
|
59
|
+
provider,
|
|
60
|
+
models: adapter.modelCatalog(),
|
|
61
|
+
// The composer gates the image affordance on this (design D22: capability,
|
|
62
|
+
// never provider id — gemini stays false until live-verified).
|
|
63
|
+
supportsImageInput: adapter.capabilities.supportsImageInput === true,
|
|
64
|
+
// Per-provider reasoning-effort tiers (ascending). Empty ⇒ no selector
|
|
65
|
+
// (gemini has no per-spawn knob).
|
|
66
|
+
efforts: adapter.capabilities.reasoningEfforts ?? [],
|
|
67
|
+
});
|
|
38
68
|
});
|
|
69
|
+
/** Validate a requested reasoning effort against a provider's catalog. */
|
|
70
|
+
const validEffort = (provider, value) => {
|
|
71
|
+
if (value === null)
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof value !== 'string')
|
|
74
|
+
return undefined;
|
|
75
|
+
const efforts = (0, providers_1.getAdapter)(provider).capabilities.reasoningEfforts ?? [];
|
|
76
|
+
return efforts.includes(value) ? value : undefined;
|
|
77
|
+
};
|
|
39
78
|
router.get('/conversations', (_req, res) => {
|
|
40
79
|
res.json({ conversations: (0, agent_store_1.listAgentConversations)(desktopDb) });
|
|
41
80
|
});
|
|
@@ -55,6 +94,8 @@ function createAgentChatRouter(deps) {
|
|
|
55
94
|
model: typeof body.model === 'string' ? body.model : null,
|
|
56
95
|
pinnedProjectId: typeof body.pinnedProjectId === 'string' ? body.pinnedProjectId : null,
|
|
57
96
|
tierLevel: body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : 0,
|
|
97
|
+
// Off-catalog values fall back to null (= app default "medium").
|
|
98
|
+
reasoningEffort: validEffort(provider, body.reasoningEffort) ?? null,
|
|
58
99
|
});
|
|
59
100
|
res.status(201).json({ conversation });
|
|
60
101
|
});
|
|
@@ -88,13 +129,15 @@ function createAgentChatRouter(deps) {
|
|
|
88
129
|
return;
|
|
89
130
|
}
|
|
90
131
|
patch.provider = v;
|
|
91
|
-
// Switching provider invalidates the stored session id AND
|
|
92
|
-
// belong to the previous provider) — clear them so the next turn
|
|
93
|
-
// fresh
|
|
94
|
-
// claude's "
|
|
132
|
+
// Switching provider invalidates the stored session id, model AND effort
|
|
133
|
+
// (all belong to the previous provider) — clear them so the next turn
|
|
134
|
+
// starts fresh with the new provider's defaults (codex rejects claude's
|
|
135
|
+
// "sonnet"; claude's "xhigh" is off-catalog for codex; resume of a
|
|
136
|
+
// foreign thread → "no rollout found").
|
|
95
137
|
if (v !== conversation.provider) {
|
|
96
138
|
patch.session_id = null;
|
|
97
139
|
patch.model = null;
|
|
140
|
+
patch.reasoning_effort = null;
|
|
98
141
|
}
|
|
99
142
|
}
|
|
100
143
|
// Explicit model pick — validated against the (new or current) provider's catalog.
|
|
@@ -112,13 +155,127 @@ function createAgentChatRouter(deps) {
|
|
|
112
155
|
patch.model = body.model;
|
|
113
156
|
}
|
|
114
157
|
}
|
|
158
|
+
// Explicit effort pick — validated against the (new or current) provider's
|
|
159
|
+
// effort catalog; null resets to the app default ("medium").
|
|
160
|
+
if (body.reasoningEffort !== undefined && patch.reasoning_effort === undefined) {
|
|
161
|
+
const effectiveProvider = patch.provider ?? conversation.provider;
|
|
162
|
+
const v = validEffort(effectiveProvider, body.reasoningEffort);
|
|
163
|
+
if (v === undefined) {
|
|
164
|
+
res.status(400).json({ error: 'Effort not available for this provider' });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
patch.reasoning_effort = v;
|
|
168
|
+
}
|
|
115
169
|
res.json({ conversation: (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, patch) });
|
|
116
170
|
});
|
|
117
171
|
router.delete('/conversations/:id', (req, res) => {
|
|
118
|
-
|
|
119
|
-
|
|
172
|
+
const id = String(req.params.id);
|
|
173
|
+
manager.abort(id);
|
|
174
|
+
(0, agent_store_1.deleteAgentConversation)(desktopDb, id);
|
|
175
|
+
// Cascade-remove the conversation's attachment directory (best-effort).
|
|
176
|
+
void attachment_manager_1.attachmentManager.deleteAllAgent(id).catch((e) => console.error('[agent-chat] attachment cleanup failed:', e));
|
|
120
177
|
res.json({ ok: true });
|
|
121
178
|
});
|
|
179
|
+
// ── Attachments (conversation-keyed; mirror the ticket attachment routes) ──
|
|
180
|
+
router.post('/conversations/:id/attachments', attachmentUpload.single('file'), (err, _req, res, next) => {
|
|
181
|
+
if (err instanceof multer_1.default.MulterError) {
|
|
182
|
+
const msg = err.code === 'LIMIT_FILE_SIZE' ? 'File too large (max 25 MB)' : `Upload rejected: ${err.code}`;
|
|
183
|
+
res.status(400).json({ error: msg });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (err) {
|
|
187
|
+
next(err);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
next();
|
|
191
|
+
}, async (req, res) => {
|
|
192
|
+
const id = String(req.params.id);
|
|
193
|
+
if (!(0, agent_store_1.getAgentConversation)(desktopDb, id)) {
|
|
194
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const file = req.file;
|
|
198
|
+
if (!file) {
|
|
199
|
+
const rejected = req.fileRejected;
|
|
200
|
+
res.status(400).json({ error: rejected ? `Unsupported file type: ${rejected}` : 'No file uploaded' });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const attachment = await attachment_manager_1.attachmentManager.uploadAgent({
|
|
205
|
+
conversationId: id,
|
|
206
|
+
file: { buffer: file.buffer, originalname: file.originalname, mimetype: file.mimetype, size: file.size },
|
|
207
|
+
});
|
|
208
|
+
res.status(201).json({ attachment });
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
const status = e.status ?? 500;
|
|
212
|
+
console.error('[agent-chat] attachment upload error:', e);
|
|
213
|
+
res.status(status).json({ error: e instanceof Error ? e.message : 'Upload failed' });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
router.get('/conversations/:id/attachments', (req, res) => {
|
|
217
|
+
const id = String(req.params.id);
|
|
218
|
+
if (!(0, agent_store_1.getAgentConversation)(desktopDb, id)) {
|
|
219
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
res.json({ attachments: attachment_manager_1.attachmentManager.listAgent(id) });
|
|
223
|
+
});
|
|
224
|
+
router.get('/conversations/:id/attachments/:attachmentId', (req, res) => {
|
|
225
|
+
const id = String(req.params.id);
|
|
226
|
+
if (!(0, agent_store_1.getAgentConversation)(desktopDb, id)) {
|
|
227
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const attachmentId = String(req.params.attachmentId);
|
|
231
|
+
let meta = null;
|
|
232
|
+
let abs = null;
|
|
233
|
+
try {
|
|
234
|
+
meta = attachment_manager_1.attachmentManager.getAgentMeta(id, attachmentId);
|
|
235
|
+
abs = meta ? attachment_manager_1.attachmentManager.getAgentFilePath(id, attachmentId) : null;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
res.status(400).json({ error: 'Invalid attachment id' });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!meta || !abs) {
|
|
242
|
+
res.status(404).json({ error: 'Attachment not found' });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
res.setHeader('Content-Type', meta.mimeType);
|
|
246
|
+
const asciiName = meta.filename.replace(/[\r\n"]/g, '_');
|
|
247
|
+
res.setHeader('Content-Disposition', `inline; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(meta.filename)}`);
|
|
248
|
+
// The existsSync check above is not atomic with the stream's async open — a
|
|
249
|
+
// concurrent conversation/attachment DELETE can rm the file in between, and an
|
|
250
|
+
// unhandled ReadStream 'error' would take down the sidecar.
|
|
251
|
+
const stream = fs_1.default.createReadStream(abs);
|
|
252
|
+
stream.on('error', () => {
|
|
253
|
+
if (!res.headersSent)
|
|
254
|
+
res.status(404).json({ error: 'Attachment not found' });
|
|
255
|
+
else
|
|
256
|
+
res.destroy();
|
|
257
|
+
});
|
|
258
|
+
stream.pipe(res);
|
|
259
|
+
});
|
|
260
|
+
router.delete('/conversations/:id/attachments/:attachmentId', async (req, res) => {
|
|
261
|
+
const id = String(req.params.id);
|
|
262
|
+
if (!(0, agent_store_1.getAgentConversation)(desktopDb, id)) {
|
|
263
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const ok = await attachment_manager_1.attachmentManager.deleteAgent(id, String(req.params.attachmentId));
|
|
268
|
+
if (!ok) {
|
|
269
|
+
res.status(404).json({ error: 'Attachment not found' });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
res.status(204).end();
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
console.error('[agent-chat] attachment delete error:', e);
|
|
276
|
+
res.status(400).json({ error: 'Delete failed' });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
122
279
|
router.post('/conversations/:id/send', (req, res) => {
|
|
123
280
|
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
124
281
|
if (!conversation) {
|
|
@@ -133,11 +290,16 @@ function createAgentChatRouter(deps) {
|
|
|
133
290
|
}
|
|
134
291
|
const tierLevel = body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : undefined;
|
|
135
292
|
const model = typeof body.model === 'string' ? body.model : undefined;
|
|
293
|
+
let attachmentIds = [];
|
|
294
|
+
const rawAtt = body.attachments;
|
|
295
|
+
if (rawAtt && typeof rawAtt === 'object' && Array.isArray(rawAtt.ids)) {
|
|
296
|
+
attachmentIds = (rawAtt.ids).filter((x) => typeof x === 'string');
|
|
297
|
+
}
|
|
136
298
|
// Fire-and-forget: the turn streams over WS. Persist the chosen tier first so
|
|
137
299
|
// a refresh mid-turn restores the right level.
|
|
138
300
|
if (tierLevel !== undefined)
|
|
139
301
|
(0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, { tier_level: tierLevel });
|
|
140
|
-
void manager.sendMessage(conversation.id, text, { tierLevel, model });
|
|
302
|
+
void manager.sendMessage(conversation.id, text, { tierLevel, model, attachmentIds }).catch((e) => console.error('[agent-chat] send failed:', e));
|
|
141
303
|
res.status(202).json({ accepted: true });
|
|
142
304
|
});
|
|
143
305
|
router.post('/conversations/:id/abort', (req, res) => {
|
|
@@ -9,6 +9,7 @@ exports.removeAgentCwd = removeAgentCwd;
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const agent_operator_prompt_1 = require("./agent-operator-prompt");
|
|
12
13
|
// ─── App-level agent working directory (design D1) ────────────────────────────
|
|
13
14
|
//
|
|
14
15
|
// The agent chat spawns the AI CLI from an app-owned cwd (NOT a project path), so
|
|
@@ -23,52 +24,6 @@ function agentCwdPath() {
|
|
|
23
24
|
return path_1.default.join(homeDir(), '.specrails', 'agent-cwd');
|
|
24
25
|
}
|
|
25
26
|
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
27
|
/**
|
|
73
28
|
* Ensures the app-level agent cwd exists with operator instruction files.
|
|
74
29
|
* Idempotent; safe to call on every turn. Returns the cwd path.
|
|
@@ -79,7 +34,7 @@ function ensureAgentCwd() {
|
|
|
79
34
|
for (const name of INSTRUCTION_FILES) {
|
|
80
35
|
// App-owned files (not user-edited) — always (re)write so operator-prompt
|
|
81
36
|
// 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');
|
|
37
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, name), agent_operator_prompt_1.OPERATOR_INSTRUCTIONS, 'utf-8');
|
|
83
38
|
}
|
|
84
39
|
return dir;
|
|
85
40
|
}
|