specrails-desktop 2.18.2 → 2.19.1
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
|
@@ -4,6 +4,45 @@ exports.metaTools = metaTools;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const guide_1 = require("../guide");
|
|
6
6
|
const types_1 = require("./types");
|
|
7
|
+
/**
|
|
8
|
+
* Serialize a raw-shape zod field into `{ name, description, type, enumValues?,
|
|
9
|
+
* optional }`. Unwraps optional/default/nullable wrappers (keeping the OUTERMOST
|
|
10
|
+
* `.describe()` string, the convention across the tool files) down to the base
|
|
11
|
+
* type so the model sees the real type + enum values instead of a bare key name.
|
|
12
|
+
*/
|
|
13
|
+
function describeZodField(name, field) {
|
|
14
|
+
let current = field;
|
|
15
|
+
let optional = false;
|
|
16
|
+
let nullable = false;
|
|
17
|
+
let description;
|
|
18
|
+
for (;;) {
|
|
19
|
+
if (description === undefined && current.description !== undefined)
|
|
20
|
+
description = current.description;
|
|
21
|
+
const def = current._def;
|
|
22
|
+
const typeName = def?.typeName;
|
|
23
|
+
if ((typeName === 'ZodOptional' || typeName === 'ZodDefault') && def?.innerType) {
|
|
24
|
+
optional = true;
|
|
25
|
+
current = def.innerType;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (typeName === 'ZodNullable' && def?.innerType) {
|
|
29
|
+
nullable = true;
|
|
30
|
+
current = def.innerType;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
const baseName = (current._def?.typeName ?? 'ZodUnknown')
|
|
36
|
+
.replace(/^Zod/, '')
|
|
37
|
+
.toLowerCase();
|
|
38
|
+
const type = nullable ? `${baseName} | null` : baseName;
|
|
39
|
+
const out = { name, type, optional };
|
|
40
|
+
if (description !== undefined)
|
|
41
|
+
out.description = description;
|
|
42
|
+
if (current instanceof zod_1.z.ZodEnum)
|
|
43
|
+
out.enumValues = [...current.options];
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
7
46
|
/**
|
|
8
47
|
* Meta tools that teach the platform and aid discovery.
|
|
9
48
|
* `getSpecs` returns the full catalog (passed lazily to avoid a cycle).
|
|
@@ -13,7 +52,8 @@ function metaTools(getSpecs) {
|
|
|
13
52
|
{
|
|
14
53
|
name: 'specrails_guide',
|
|
15
54
|
title: 'Platform guide',
|
|
16
|
-
description: 'Returns a self-contained guide to Specrails: concepts, workflow, invariants, and how to use these tools.
|
|
55
|
+
description: 'Returns a self-contained guide to Specrails: concepts, workflow, invariants, and how to use these tools. ' +
|
|
56
|
+
'Read this first — call once per session before your first domain call.',
|
|
17
57
|
tier: 'read',
|
|
18
58
|
inputSchema: {},
|
|
19
59
|
handler: () => guide_1.SPECRAILS_GUIDE,
|
|
@@ -45,7 +85,7 @@ function metaTools(getSpecs) {
|
|
|
45
85
|
{
|
|
46
86
|
name: 'specrails_describe',
|
|
47
87
|
title: 'Describe a tool',
|
|
48
|
-
description: 'Return the full description and input schema
|
|
88
|
+
description: 'Return the full description and per-field input schema (name, description, type, enum values, optionality) for a named Specrails tool.',
|
|
49
89
|
tier: 'read',
|
|
50
90
|
inputSchema: {
|
|
51
91
|
name: zod_1.z.string().describe('Tool name, e.g. "specrails_rails"'),
|
|
@@ -59,7 +99,7 @@ function metaTools(getSpecs) {
|
|
|
59
99
|
name: spec.name,
|
|
60
100
|
title: spec.title,
|
|
61
101
|
description: spec.description,
|
|
62
|
-
inputFields: Object.
|
|
102
|
+
inputFields: Object.entries(spec.inputSchema).map(([fieldName, field]) => describeZodField(fieldName, field)),
|
|
63
103
|
};
|
|
64
104
|
},
|
|
65
105
|
},
|
|
@@ -72,19 +72,19 @@ function railsTools() {
|
|
|
72
72
|
mode: zod_1.z
|
|
73
73
|
.enum(['implement', 'batch-implement', 'ultracode', 'loop'])
|
|
74
74
|
.optional()
|
|
75
|
-
.describe('Launch mode (launch; default "implement"). ultracode is Claude-only
|
|
75
|
+
.describe('Launch mode (launch; default "implement"). "ultracode" is the wire value for Freestyle mode — hands the spec straight to the model, Claude-only, one job per ticket; loop runs an app-driven loop per ticket.'),
|
|
76
76
|
model: zod_1.z
|
|
77
|
-
.
|
|
77
|
+
.string()
|
|
78
78
|
.optional()
|
|
79
|
-
.describe('
|
|
79
|
+
.describe('Model for launch. Freestyle mode (wire value \'ultracode\') is Claude-only and takes a Claude alias (haiku | sonnet | opus | fable). For loop mode, any model string valid for the rail\'s provider (claude/codex/gemini).'),
|
|
80
80
|
interactive: zod_1.z
|
|
81
81
|
.boolean()
|
|
82
82
|
.optional()
|
|
83
|
-
.describe('
|
|
83
|
+
.describe('DEPRECATED — accepted and ignored: Freestyle jobs are interactive by default whenever the interactive-jobs feature is enabled'),
|
|
84
84
|
loopId: zod_1.z
|
|
85
85
|
.string()
|
|
86
86
|
.optional()
|
|
87
|
-
.describe('Loop id to run (launch). Factory ids (e.g. "factory:implement") map to a legacy mode; a custom published loop id keeps mode="loop".'),
|
|
87
|
+
.describe('Loop id to run (launch). Factory ids (e.g. "factory:implement") map to a legacy mode; a custom published loop id keeps mode="loop". Browse/author loops with specrails_loops; the loop must be Published.'),
|
|
88
88
|
reasoning_effort: zod_1.z
|
|
89
89
|
.enum(['low', 'medium', 'high'])
|
|
90
90
|
.optional()
|
|
@@ -165,7 +165,7 @@ function railsTools() {
|
|
|
165
165
|
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/${railIndex}/launch`, body);
|
|
166
166
|
return {
|
|
167
167
|
...r,
|
|
168
|
-
hint: 'Launch accepted (202). Use specrails_watch with the returned jobId/jobIds (or loopRunIds for loop mode) to await completion. Live output streams over the job WS.',
|
|
168
|
+
hint: 'Launch accepted (202). Use specrails_watch with the returned jobId/jobIds (or loopRunIds for loop mode) to await completion. Live output streams over the job WS. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.',
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
case 'stop': {
|
|
@@ -36,7 +36,8 @@ function setupTools() {
|
|
|
36
36
|
'Per-project actions: install_config (persist install-config.yaml), install (spawn offline framework scaffold — async/202), ' +
|
|
37
37
|
'checkpoints (poll live setup/install state — the canonical completion signal when not streaming WS), ' +
|
|
38
38
|
'abort (SIGTERM/SIGKILL the in-progress install child), integration_contract (read the ticket-provider contract). ' +
|
|
39
|
-
'Install is QUICK-only and provisions every registered provider in one shot; the legacy full/enrich AI flow is not exposed here.'
|
|
39
|
+
'Install is QUICK-only and provisions every registered provider in one shot; the legacy full/enrich AI flow is not exposed here. ' +
|
|
40
|
+
'Lifecycle: 1) prerequisites 2) add_project(path, providers) 3) install_config per provider 4) ONE install (provisions ALL chosen providers; quick-only, offline) 5) poll checkpoints until isInstalling === false.',
|
|
40
41
|
hintTier: 'read',
|
|
41
42
|
tier: (a) => {
|
|
42
43
|
const action = a.action;
|
|
@@ -25,7 +25,12 @@ function resolveSpecsTier(action) {
|
|
|
25
25
|
}
|
|
26
26
|
const WATCH_HINT = 'Returns 202 immediately; the actual result streams over the WebSocket. ' +
|
|
27
27
|
'Use specrails_watch with the returned requestId/scheduled ref to await completion ' +
|
|
28
|
-
'(WS events: spec_gen_*, ticket_ai_edit_*,
|
|
28
|
+
'(WS events: spec_gen_*, ticket_ai_edit_*, smash.*).';
|
|
29
|
+
// Contract Refine settles asymmetrically: only FAILURE emits a watch-terminal
|
|
30
|
+
// event (explore.contract_refine_failed); success lands as a plain
|
|
31
|
+
// ticket_updated broadcast, which watch does not treat as terminal.
|
|
32
|
+
const CONTRACT_REFINE_HINT = 'Returns 202 immediately. Failure settles via specrails_watch (explore.contract_refine_failed); ' +
|
|
33
|
+
"success arrives as ticket_updated — poll specrails_specs(get) for the '## Contract Layer' section.";
|
|
29
34
|
function specsTools() {
|
|
30
35
|
return [
|
|
31
36
|
{
|
|
@@ -35,9 +40,16 @@ function specsTools() {
|
|
|
35
40
|
'Actions: list, get, ' +
|
|
36
41
|
'create (ai-spawn — THE way to add a spec: routes through Quick Add Spec, so the specrails agent generates a structured spec with AI from the user request, exactly matching the app\'s "Add Spec → Quick" flow; pass the request as title/description, it becomes the idea; async), ' +
|
|
37
42
|
'update, delete (destructive), ' +
|
|
38
|
-
'from_prompt (verbatim insert, no AI — use ONLY when you already have a complete finished spec to store as-is
|
|
39
|
-
'
|
|
40
|
-
'
|
|
43
|
+
'from_prompt (verbatim insert, no AI — use ONLY when you already have a complete finished spec to store as-is; ' +
|
|
44
|
+
'cannot set acceptanceCriteria or shortSummary — prefer commit_draft for structured specs), ' +
|
|
45
|
+
'save_draft (persist an Explore conversation as a draft), ' +
|
|
46
|
+
'commit_draft (write, no AI — TWO uses: (a) flip an Explore draft to a real spec, passing conversationId from ' +
|
|
47
|
+
'specrails_chat and/or draftTicketId; (b) with NO conversationId/draftTicketId, insert a COMPLETE spec you ' +
|
|
48
|
+
'authored in ONE call: title (required), description, acceptanceCriteria, priority, labels, shortSummary, ' +
|
|
49
|
+
'assignee, prerequisites, metadata. THE canonical way to persist a spec refined in conversation — do NOT route ' +
|
|
50
|
+
'a finished spec through create/generate, which re-generate the content with AI and lossily rewrite it), ' +
|
|
51
|
+
'generate (same as create but takes a pre-formed idea string; both accept contextScope/attachments/createLocal), ' +
|
|
52
|
+
'ai_edit (ai-spawn, AI-edit a ticket title+description), ' +
|
|
41
53
|
'cancel_ai_edit (abort an in-flight ai-edit), contract_refine (ai-spawn, append a Contract Layer — Claude-only), ' +
|
|
42
54
|
'smash (ai-spawn, decompose a spec into N child sub-specs under an epic — Claude-only), ' +
|
|
43
55
|
'smash_undo (destructive, reverse a prior SMASH), delete_epic_children (destructive, delete all children of an epic), ' +
|
|
@@ -81,7 +93,7 @@ function specsTools() {
|
|
|
81
93
|
status: zod_1.z
|
|
82
94
|
.string()
|
|
83
95
|
.optional()
|
|
84
|
-
.describe('list:
|
|
96
|
+
.describe('list: CSV status filter (draft|todo|in_progress|done|cancelled). update: set ticket status — note job outcomes set in_progress/done automatically; do not fight the pipeline.'),
|
|
85
97
|
label: zod_1.z.string().optional().describe('list: filter by label (CSV)'),
|
|
86
98
|
q: zod_1.z.string().optional().describe('list: free-text search over title + description'),
|
|
87
99
|
// ── create / update / from_prompt / commit_draft fields ──────────
|
|
@@ -92,13 +104,13 @@ function specsTools() {
|
|
|
92
104
|
.describe('Ticket description. For ai_edit this is the REQUIRED current baseline description'),
|
|
93
105
|
priority: zod_1.z
|
|
94
106
|
.enum(['critical', 'high', 'medium', 'low'])
|
|
107
|
+
.nullable()
|
|
95
108
|
.optional()
|
|
96
|
-
.describe('
|
|
97
|
-
labels: zod_1.z.array(zod_1.z.string()).optional().describe('
|
|
98
|
-
assignee: zod_1.z.string().optional().describe('
|
|
99
|
-
prerequisites: zod_1.z.array(zod_1.z.number().int()).optional().describe('
|
|
100
|
-
metadata: zod_1.z.record(zod_1.z.unknown()).optional().describe('
|
|
101
|
-
source: zod_1.z.string().optional().describe('create: source tag'),
|
|
109
|
+
.describe('update/from_prompt/commit_draft: ticket priority. update: null allowed only for drafts'),
|
|
110
|
+
labels: zod_1.z.array(zod_1.z.string()).optional().describe('update/from_prompt/save_draft/commit_draft: labels array'),
|
|
111
|
+
assignee: zod_1.z.string().optional().describe('update/commit_draft: assignee'),
|
|
112
|
+
prerequisites: zod_1.z.array(zod_1.z.number().int()).optional().describe('update/commit_draft: prerequisite ticket ids'),
|
|
113
|
+
metadata: zod_1.z.record(zod_1.z.unknown()).optional().describe('update/commit_draft: free-form metadata object (update merges keys)'),
|
|
102
114
|
acceptanceCriteria: zod_1.z
|
|
103
115
|
.array(zod_1.z.string())
|
|
104
116
|
.optional()
|
|
@@ -106,31 +118,31 @@ function specsTools() {
|
|
|
106
118
|
shortSummary: zod_1.z.string().optional().describe('commit_draft: explicit short summary (camelCase key)'),
|
|
107
119
|
short_summary: zod_1.z.string().optional().describe('update: explicit short summary (snake_case key; null clears)'),
|
|
108
120
|
structured: zod_1.z.boolean().optional().describe('from_prompt: lightly structure the verbatim prompt'),
|
|
109
|
-
createLocal: zod_1.z.boolean().optional().describe('
|
|
121
|
+
createLocal: zod_1.z.boolean().optional().describe('create/generate/commit_draft: keep the spec local (skip Jira promote)'),
|
|
110
122
|
// ── draft linkage ────────────────────────────────────────────────
|
|
111
123
|
conversationId: zod_1.z
|
|
112
124
|
.string()
|
|
113
125
|
.optional()
|
|
114
|
-
.describe(
|
|
126
|
+
.describe("save_draft (required) / commit_draft: origin Explore conversation id — obtained from specrails_chat(create kind:'explore'); NEVER an agent/desktop conversation id"),
|
|
115
127
|
draftTicketId: zod_1.z.number().int().optional().describe('commit_draft: explicit draft ticket id to flip in place'),
|
|
116
128
|
editTicketId: zod_1.z.number().int().optional().describe('save_draft: existing ticket id to demote to a draft in place'),
|
|
117
129
|
pendingSpecId: zod_1.z
|
|
118
130
|
.string()
|
|
119
131
|
.optional()
|
|
120
|
-
.describe('generate/from_prompt/commit_draft: pending-spec UUID owning pre-creation attachments'),
|
|
121
|
-
// ── generate (Quick AI spec)
|
|
132
|
+
.describe('create/generate/from_prompt/commit_draft: pending-spec UUID owning pre-creation attachments'),
|
|
133
|
+
// ── create / generate (Quick AI spec) ────────────────────────────
|
|
122
134
|
idea: zod_1.z.string().optional().describe('generate: the idea/prompt to turn into a spec (required)'),
|
|
123
|
-
model: zod_1.z.string().optional().describe('generate/smash: model override (validated against the provider catalog)'),
|
|
135
|
+
model: zod_1.z.string().optional().describe('create/generate/smash: model override (validated against the provider catalog)'),
|
|
124
136
|
aiEngine: zod_1.z
|
|
125
137
|
.string()
|
|
126
138
|
.optional()
|
|
127
|
-
.describe('generate: provider/engine override (must be an installed provider; defaults to primary)'),
|
|
139
|
+
.describe('create/generate: provider/engine override (must be an installed provider; defaults to primary)'),
|
|
128
140
|
contextScope: zod_1.z
|
|
129
141
|
.record(zod_1.z.unknown())
|
|
130
142
|
.optional()
|
|
131
|
-
.describe('generate: context-awareness flags {specrails,openspec,full,mcp,userMcp,contractRefine}'),
|
|
132
|
-
contractRefine: zod_1.z.boolean().optional().describe('generate: enrich the spec with a Contract Layer (Claude-only)'),
|
|
133
|
-
attachmentIds: zod_1.z.array(zod_1.z.string()).optional().describe('generate: attachment ids (
|
|
143
|
+
.describe('create/generate: context-awareness flags {specrails,openspec,full,mcp,userMcp,contractRefine}'),
|
|
144
|
+
contractRefine: zod_1.z.boolean().optional().describe('create/generate: enrich the spec with a Contract Layer (Claude-only)'),
|
|
145
|
+
attachmentIds: zod_1.z.array(zod_1.z.string()).optional().describe('create/generate/ai_edit: attachment ids (create/generate require pendingSpecId)'),
|
|
134
146
|
// ── ai_edit ──────────────────────────────────────────────────────
|
|
135
147
|
instructions: zod_1.z.string().optional().describe('ai_edit: natural-language editing instructions (required)'),
|
|
136
148
|
priorInstructions: zod_1.z.array(zod_1.z.string()).optional().describe('ai_edit: prior refinement turns for an iterative edit'),
|
|
@@ -156,6 +168,20 @@ function specsTools() {
|
|
|
156
168
|
}
|
|
157
169
|
return id;
|
|
158
170
|
};
|
|
171
|
+
// Shared body builder for the two Quick Add Spec entry points
|
|
172
|
+
// (POST /tickets/generate-spec): `create` derives the idea from
|
|
173
|
+
// title/description, `generate` takes it pre-formed. Both forward the
|
|
174
|
+
// full option set (undefined fields are dropped by JSON.stringify).
|
|
175
|
+
const buildGenerateSpecBody = (idea) => ({
|
|
176
|
+
idea,
|
|
177
|
+
model: args.model,
|
|
178
|
+
aiEngine: args.aiEngine,
|
|
179
|
+
contextScope: args.contextScope,
|
|
180
|
+
contractRefine: args.contractRefine,
|
|
181
|
+
attachmentIds: args.attachmentIds,
|
|
182
|
+
pendingSpecId: args.pendingSpecId,
|
|
183
|
+
createLocal: args.createLocal,
|
|
184
|
+
});
|
|
159
185
|
switch (action) {
|
|
160
186
|
// ── reads ──────────────────────────────────────────────────────
|
|
161
187
|
case 'list': {
|
|
@@ -198,19 +224,21 @@ function specsTools() {
|
|
|
198
224
|
// following the project's conventions, instead of a raw insert of
|
|
199
225
|
// whatever the external LLM wrote. The user's request becomes the
|
|
200
226
|
// `idea`. For a verbatim insert with no AI, use `from_prompt`.
|
|
227
|
+
// generate-spec accepts none of labels/priority/status/assignee —
|
|
228
|
+
// throwing turns the likeliest silent misuse into a self-correcting error.
|
|
229
|
+
if (args.labels !== undefined ||
|
|
230
|
+
args.priority !== undefined ||
|
|
231
|
+
args.status !== undefined ||
|
|
232
|
+
args.assignee !== undefined) {
|
|
233
|
+
throw new Error('create generates the spec with AI and cannot set labels/priority directly — use commit_draft (you control everything) or update after generation.');
|
|
234
|
+
}
|
|
201
235
|
const idea = [args.title, args.description]
|
|
202
236
|
.filter((v) => typeof v === 'string' && v.trim().length > 0)
|
|
203
237
|
.join('\n\n')
|
|
204
238
|
.trim();
|
|
205
239
|
if (!idea)
|
|
206
240
|
throw new Error('create requires a "title" and/or "description" to use as the spec idea.');
|
|
207
|
-
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`,
|
|
208
|
-
idea,
|
|
209
|
-
model: args.model,
|
|
210
|
-
aiEngine: args.aiEngine,
|
|
211
|
-
provider: args.provider,
|
|
212
|
-
contractRefine: args.contractRefine,
|
|
213
|
-
});
|
|
241
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, buildGenerateSpecBody(idea));
|
|
214
242
|
return { ...r, hint: WATCH_HINT };
|
|
215
243
|
}
|
|
216
244
|
case 'update':
|
|
@@ -257,6 +285,9 @@ function specsTools() {
|
|
|
257
285
|
acceptanceCriteria: args.acceptanceCriteria,
|
|
258
286
|
priority: args.priority,
|
|
259
287
|
shortSummary: args.shortSummary,
|
|
288
|
+
assignee: args.assignee,
|
|
289
|
+
prerequisites: args.prerequisites,
|
|
290
|
+
metadata: args.metadata,
|
|
260
291
|
createLocal: args.createLocal,
|
|
261
292
|
});
|
|
262
293
|
case 'cancel_ai_edit': {
|
|
@@ -280,16 +311,7 @@ function specsTools() {
|
|
|
280
311
|
case 'generate': {
|
|
281
312
|
if (!args.idea)
|
|
282
313
|
throw new Error('generate requires an "idea".');
|
|
283
|
-
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`,
|
|
284
|
-
idea: args.idea,
|
|
285
|
-
model: args.model,
|
|
286
|
-
aiEngine: args.aiEngine,
|
|
287
|
-
contextScope: args.contextScope,
|
|
288
|
-
contractRefine: args.contractRefine,
|
|
289
|
-
attachmentIds: args.attachmentIds,
|
|
290
|
-
pendingSpecId: args.pendingSpecId,
|
|
291
|
-
createLocal: args.createLocal,
|
|
292
|
-
});
|
|
314
|
+
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, buildGenerateSpecBody(args.idea));
|
|
293
315
|
return { ...r, hint: WATCH_HINT };
|
|
294
316
|
}
|
|
295
317
|
case 'ai_edit': {
|
|
@@ -309,7 +331,7 @@ function specsTools() {
|
|
|
309
331
|
}
|
|
310
332
|
case 'contract_refine': {
|
|
311
333
|
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/contract-refine`);
|
|
312
|
-
return { ...r, hint:
|
|
334
|
+
return { ...r, hint: CONTRACT_REFINE_HINT };
|
|
313
335
|
}
|
|
314
336
|
case 'smash': {
|
|
315
337
|
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/smash`, {
|
|
@@ -56,13 +56,17 @@ function requireProject(ctx, projectId) {
|
|
|
56
56
|
return pc;
|
|
57
57
|
}
|
|
58
58
|
// ── Active-project stickiness (per server process) ────────────────────────────
|
|
59
|
-
// The active project is a soft default; an explicit projectId always wins
|
|
59
|
+
// The active project is a soft default; an explicit projectId always wins, and
|
|
60
|
+
// a per-request pin (ctx.requestProjectId, from the in-app agent's loopback
|
|
61
|
+
// header) wins over the process-wide selection. `setActiveProject` remains for
|
|
62
|
+
// `specrails_select_project` (and, fallback-only, the agent-chat manager's
|
|
63
|
+
// per-turn pin) — request headers must never mutate this global.
|
|
60
64
|
let _activeProjectId = null;
|
|
61
65
|
function setActiveProject(id) {
|
|
62
66
|
_activeProjectId = id;
|
|
63
67
|
}
|
|
64
|
-
function getActiveProject(
|
|
65
|
-
return _activeProjectId;
|
|
68
|
+
function getActiveProject(ctx) {
|
|
69
|
+
return ctx.requestProjectId ?? _activeProjectId;
|
|
66
70
|
}
|
|
67
71
|
function tierAnnotations(tier) {
|
|
68
72
|
switch (tier) {
|
|
@@ -93,7 +97,9 @@ function errorResult(message) {
|
|
|
93
97
|
*/
|
|
94
98
|
function emitMcpActivity(ctx, spec, args, tier) {
|
|
95
99
|
try {
|
|
96
|
-
const affectedProjectId = (typeof args.projectId === 'string' ? args.projectId : null) ??
|
|
100
|
+
const affectedProjectId = (typeof args.projectId === 'string' ? args.projectId : null) ??
|
|
101
|
+
ctx.requestProjectId ??
|
|
102
|
+
getActiveProject(ctx);
|
|
97
103
|
ctx.broadcast({
|
|
98
104
|
type: 'mcp.activity',
|
|
99
105
|
tool: spec.name,
|
|
@@ -123,12 +129,15 @@ function registerTieredTool(server, ctx, spec) {
|
|
|
123
129
|
}, async (args, extra) => {
|
|
124
130
|
// In-app agent: a per-request active-project header (loopback-only, set by
|
|
125
131
|
// the agent's bridge from the Cursor-style selector) pins the project for
|
|
126
|
-
//
|
|
132
|
+
// THIS CALL ONLY via a per-call ctx copy — it must never mutate the
|
|
133
|
+
// process-wide active project, or a concurrent external MCP client and the
|
|
134
|
+
// in-app agent would clobber each other (wrong-project delete/launch).
|
|
127
135
|
const projectHeader = extra?.requestInfo?.headers?.[agent_tier_1.AGENT_PROJECT_HEADER];
|
|
136
|
+
let callCtx = ctx;
|
|
128
137
|
if (projectHeader != null) {
|
|
129
138
|
const pid = Array.isArray(projectHeader) ? projectHeader[0] : projectHeader;
|
|
130
139
|
if (typeof pid === 'string' && pid.trim())
|
|
131
|
-
|
|
140
|
+
callCtx = { ...ctx, requestProjectId: pid.trim() };
|
|
132
141
|
}
|
|
133
142
|
const tier = typeof spec.tier === 'function' ? spec.tier(args ?? {}) : spec.tier;
|
|
134
143
|
// In-app agent: when the request carries the loopback-only agent-tier header,
|
|
@@ -144,9 +153,9 @@ function registerTieredTool(server, ctx, spec) {
|
|
|
144
153
|
return errorResult((0, mcp_tiers_1.tierRefusalMessage)(tier));
|
|
145
154
|
}
|
|
146
155
|
try {
|
|
147
|
-
const data = await spec.handler(
|
|
156
|
+
const data = await spec.handler(callCtx, args ?? {});
|
|
148
157
|
if (tier !== 'read')
|
|
149
|
-
emitMcpActivity(
|
|
158
|
+
emitMcpActivity(callCtx, spec, args ?? {}, tier);
|
|
150
159
|
return toResult(data);
|
|
151
160
|
}
|
|
152
161
|
catch (err) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.watchTool = watchTool;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
const types_1 = require("./types");
|
|
5
6
|
// Event `type` suffixes/strings that mean an async operation has settled.
|
|
6
7
|
const TERMINAL_PATTERNS = [
|
|
7
8
|
'job.finalized',
|
|
@@ -17,12 +18,25 @@ const TERMINAL_PATTERNS = [
|
|
|
17
18
|
'agent_refine_error',
|
|
18
19
|
'smash.completed',
|
|
19
20
|
'smash.failed',
|
|
21
|
+
'smash.undone',
|
|
20
22
|
'loop.run_completed',
|
|
21
23
|
'loop.run_stopped',
|
|
22
24
|
'explore.contract_refine_failed',
|
|
23
25
|
'file.summary_updated',
|
|
24
26
|
'file.summary_failed',
|
|
27
|
+
'setup_install_done',
|
|
28
|
+
'setup_error',
|
|
29
|
+
'plugin.installed',
|
|
30
|
+
'plugin.degraded',
|
|
25
31
|
];
|
|
32
|
+
// Job statuses that mean a polled job has settled (see project-router-jobs.ts).
|
|
33
|
+
const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'canceled']);
|
|
34
|
+
// Jobs are identified by UUIDs (server/ids.ts newId → randomUUID). When the ref
|
|
35
|
+
// looks like one, the wait loop also polls the job read as a fallback for jobs
|
|
36
|
+
// that never emit a watch-terminal event (e.g. rail-bypass `spawn` jobs).
|
|
37
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
38
|
+
const JOB_POLL_INTERVAL_MS = 5000;
|
|
39
|
+
const TIMEOUT_SUGGESTION = 'timeout ≠ failure — the operation may still be running. Re-watch with the same ref, or poll specrails_jobs(get)/specrails_specs(get) for current state.';
|
|
26
40
|
function isTerminal(type) {
|
|
27
41
|
return TERMINAL_PATTERNS.some((p) => type === p || type.endsWith(p));
|
|
28
42
|
}
|
|
@@ -40,19 +54,35 @@ function watchTool() {
|
|
|
40
54
|
return {
|
|
41
55
|
name: 'specrails_watch',
|
|
42
56
|
title: 'Watch async result',
|
|
43
|
-
description: 'Wait for an asynchronous operation (rail launch, spec generation, chat turn, agent refine, SMASH, loop run) to settle ' +
|
|
44
|
-
'and return its events. Pass the
|
|
45
|
-
'
|
|
57
|
+
description: 'Wait for an asynchronous operation (rail launch, spec generation, chat turn, agent refine, SMASH, loop run, plugin install, setup install) to settle ' +
|
|
58
|
+
'and return its events. Pass the reference (jobId / requestId / conversationId / loopRunId) returned by the action; projectId is optional ' +
|
|
59
|
+
'(defaults to the active project; app-level operations need none). ' +
|
|
60
|
+
'Resolves when a terminal event for that reference is seen, or after untilMs. ' +
|
|
61
|
+
'When the ref is a jobId (or kind:"job" is passed), the job status is also polled every 5s so jobs settle even without a watch-terminal event. ' +
|
|
62
|
+
'Default untilMs 120000 is often shorter than a rail run (max 600000). ' +
|
|
63
|
+
'settled:false means TIMEOUT, not failure — re-watch or poll the domain read (specrails_jobs(get) / specrails_specs(get) / specrails_plugins(health)).',
|
|
46
64
|
tier: 'read',
|
|
47
65
|
inputSchema: {
|
|
48
|
-
projectId: zod_1.z
|
|
49
|
-
|
|
66
|
+
projectId: zod_1.z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Project id the operation runs in (defaults to the active project; omit for app-level operations)'),
|
|
70
|
+
ref: zod_1.z.string().describe('jobId / requestId / conversationId / loopRunId returned by the async action'),
|
|
50
71
|
untilMs: zod_1.z.number().int().min(1000).max(600000).default(120000).describe('Max time to wait (ms)'),
|
|
72
|
+
kind: zod_1.z
|
|
73
|
+
.enum(['job'])
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Force the ref to be treated as a jobId (enables the 5s job-status poll fallback even for non-UUID ids)'),
|
|
51
76
|
},
|
|
52
77
|
handler: (ctx, args) => {
|
|
53
|
-
|
|
78
|
+
// Optional + active-project resolution: an explicit projectId wins, else
|
|
79
|
+
// the active project. When neither exists, project filtering is skipped —
|
|
80
|
+
// the ref-substring match is discriminating enough, and app-level agent
|
|
81
|
+
// operations carry no projectId at all.
|
|
82
|
+
const projectId = args.projectId ?? (0, types_1.getActiveProject)(ctx) ?? undefined;
|
|
54
83
|
const ref = args.ref;
|
|
55
84
|
const untilMs = args.untilMs ?? 120000;
|
|
85
|
+
const pollAsJob = args.kind === 'job' || UUID_RE.test(ref);
|
|
56
86
|
return new Promise((resolve) => {
|
|
57
87
|
const events = [];
|
|
58
88
|
let done = false;
|
|
@@ -61,20 +91,23 @@ function watchTool() {
|
|
|
61
91
|
return;
|
|
62
92
|
done = true;
|
|
63
93
|
clearTimeout(timer);
|
|
94
|
+
if (poller)
|
|
95
|
+
clearInterval(poller);
|
|
64
96
|
unsub();
|
|
65
97
|
resolve({
|
|
66
98
|
settled,
|
|
67
99
|
reason,
|
|
68
100
|
ref,
|
|
69
|
-
projectId,
|
|
101
|
+
projectId: projectId ?? null,
|
|
70
102
|
eventCount: events.length,
|
|
71
103
|
terminalEvent: settled ? events[events.length - 1] : null,
|
|
72
104
|
events: events.slice(-50),
|
|
105
|
+
...(settled ? {} : { suggestion: TIMEOUT_SUGGESTION }),
|
|
73
106
|
});
|
|
74
107
|
};
|
|
75
108
|
const unsub = ctx.eventBus.onMessage((msg) => {
|
|
76
109
|
const msgProjectId = msg.projectId;
|
|
77
|
-
if (msgProjectId && msgProjectId !== projectId)
|
|
110
|
+
if (projectId && msgProjectId && msgProjectId !== projectId)
|
|
78
111
|
return;
|
|
79
112
|
if (!mentionsRef(msg, ref))
|
|
80
113
|
return;
|
|
@@ -83,6 +116,36 @@ function watchTool() {
|
|
|
83
116
|
if (isTerminal(type))
|
|
84
117
|
finish(true, `terminal:${type}`);
|
|
85
118
|
});
|
|
119
|
+
// Job-status poll fallback: some jobs (rail-bypass spawn) never emit a
|
|
120
|
+
// watch-terminal event; poll the job read every 5s and settle when its
|
|
121
|
+
// status is terminal. Polling needs a resolvable project; errors (e.g.
|
|
122
|
+
// the ref is not a job in this project) are ignored and the wait
|
|
123
|
+
// continues on the event path.
|
|
124
|
+
let poller;
|
|
125
|
+
if (pollAsJob && projectId) {
|
|
126
|
+
let polling = false;
|
|
127
|
+
poller = setInterval(() => {
|
|
128
|
+
if (done || polling)
|
|
129
|
+
return;
|
|
130
|
+
polling = true;
|
|
131
|
+
(0, types_1.apiCall)(ctx, 'GET', `/projects/${encodeURIComponent(projectId)}/jobs/${encodeURIComponent(ref)}`)
|
|
132
|
+
.then((r) => {
|
|
133
|
+
const status = r?.job?.status;
|
|
134
|
+
if (typeof status === 'string' && TERMINAL_JOB_STATUSES.has(status)) {
|
|
135
|
+
events.push({ type: 'job.poll_settled', projectId, jobId: ref, status });
|
|
136
|
+
finish(true, `poll:job:${status}`);
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
.catch(() => {
|
|
140
|
+
/* not a job / transient error — keep waiting on events */
|
|
141
|
+
})
|
|
142
|
+
.finally(() => {
|
|
143
|
+
polling = false;
|
|
144
|
+
});
|
|
145
|
+
}, JOB_POLL_INTERVAL_MS);
|
|
146
|
+
if (typeof poller.unref === 'function')
|
|
147
|
+
poller.unref();
|
|
148
|
+
}
|
|
86
149
|
const timer = setTimeout(() => finish(false, 'timeout'), untilMs);
|
|
87
150
|
// Do not keep the event loop alive solely for this watch.
|
|
88
151
|
if (typeof timer.unref === 'function')
|
|
@@ -55,7 +55,7 @@ function serializeInstallConfigYaml(config) {
|
|
|
55
55
|
return lines.join('\n');
|
|
56
56
|
}
|
|
57
57
|
// ─── Agent model helpers ──────────────────────────────────────────────────────
|
|
58
|
-
const VALID_MODEL_ALIASES = ['sonnet', 'opus', 'haiku'];
|
|
58
|
+
const VALID_MODEL_ALIASES = ['sonnet', 'fable', 'opus', 'haiku'];
|
|
59
59
|
exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
|
|
60
60
|
/**
|
|
61
61
|
* Relocate-artifacts: where the agent `.claude/agents/*.md` files live. When the
|
|
@@ -802,6 +802,27 @@ function registerTicketsRoutes(deps) {
|
|
|
802
802
|
.filter((c) => c.length > 0)
|
|
803
803
|
: [];
|
|
804
804
|
const priority = (0, ticket_store_1.isValidPriority)(body.priority) ? body.priority : 'medium';
|
|
805
|
+
// Optional assignee/prerequisites/metadata (same normalization as the
|
|
806
|
+
// PATCH /tickets/:id route) so an MCP commit_draft is a true one-shot
|
|
807
|
+
// insert — no follow-up update call needed for these fields.
|
|
808
|
+
const hasAssignee = body.assignee !== undefined;
|
|
809
|
+
const assignee = typeof body.assignee === 'string' ? body.assignee : null;
|
|
810
|
+
const hasPrerequisites = Array.isArray(body.prerequisites);
|
|
811
|
+
const prerequisites = hasPrerequisites
|
|
812
|
+
? body.prerequisites.filter((p) => typeof p === 'number')
|
|
813
|
+
: [];
|
|
814
|
+
const hasMetadata = typeof body.metadata === 'object' && body.metadata !== null;
|
|
815
|
+
const metadata = hasMetadata ? body.metadata : {};
|
|
816
|
+
// A supplied conversationId must reference a REAL chat conversation in this
|
|
817
|
+
// project (an Explore row in chat_conversations). Agent/desktop conversation
|
|
818
|
+
// ids are NOT valid origins — stamping one would corrupt the ticket's origin
|
|
819
|
+
// linkage ("Continue Explore") and mis-schedule Contract Refine.
|
|
820
|
+
if (conversationId && !(0, db_1.getConversation)(ctx(req).db, conversationId)) {
|
|
821
|
+
res.status(400).json({
|
|
822
|
+
error: "conversationId not found in this project's chat conversations — agent conversations are not valid origins; omit conversationId",
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
805
826
|
// Compose the final ticket body. The title is already its own ticket
|
|
806
827
|
// field, so we deliberately do NOT echo it as a `## Spec Title` heading
|
|
807
828
|
// inside the description. The body is just the structured sections from
|
|
@@ -865,6 +886,14 @@ function registerTicketsRoutes(deps) {
|
|
|
865
886
|
flipTarget.description = descriptionForStore;
|
|
866
887
|
if (labels.length > 0)
|
|
867
888
|
flipTarget.labels = labels;
|
|
889
|
+
// PATCH-route semantics: apply only when the field was supplied;
|
|
890
|
+
// metadata merges keys instead of replacing the object.
|
|
891
|
+
if (hasAssignee)
|
|
892
|
+
flipTarget.assignee = assignee;
|
|
893
|
+
if (hasPrerequisites)
|
|
894
|
+
flipTarget.prerequisites = prerequisites;
|
|
895
|
+
if (hasMetadata)
|
|
896
|
+
flipTarget.metadata = { ...flipTarget.metadata, ...metadata };
|
|
868
897
|
flipTarget.updated_at = now;
|
|
869
898
|
// Preserve prior short_summary on flip when the model/body omits one;
|
|
870
899
|
// overwrite only when a non-null value is provided.
|
|
@@ -898,9 +927,9 @@ function registerTicketsRoutes(deps) {
|
|
|
898
927
|
status: 'todo',
|
|
899
928
|
priority,
|
|
900
929
|
labels,
|
|
901
|
-
assignee
|
|
902
|
-
prerequisites
|
|
903
|
-
metadata
|
|
930
|
+
assignee,
|
|
931
|
+
prerequisites,
|
|
932
|
+
metadata,
|
|
904
933
|
comments: [],
|
|
905
934
|
origin_conversation_id: conversationId,
|
|
906
935
|
is_epic: false,
|
|
@@ -1884,7 +1913,17 @@ function registerTicketsRoutes(deps) {
|
|
|
1884
1913
|
// non-ASCII names survive.
|
|
1885
1914
|
const asciiName = meta.filename.replace(/[\r\n"]/g, '_');
|
|
1886
1915
|
res.setHeader('Content-Disposition', `inline; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(meta.filename)}`);
|
|
1887
|
-
|
|
1916
|
+
// The exists check above is not atomic with the stream's async open — a
|
|
1917
|
+
// concurrent attachment DELETE can rm the file in between, and an unhandled
|
|
1918
|
+
// ReadStream 'error' would take down the sidecar.
|
|
1919
|
+
const stream = fs_1.default.createReadStream(abs);
|
|
1920
|
+
stream.on('error', () => {
|
|
1921
|
+
if (!res.headersSent)
|
|
1922
|
+
res.status(404).json({ error: 'Attachment not found' });
|
|
1923
|
+
else
|
|
1924
|
+
res.destroy();
|
|
1925
|
+
});
|
|
1926
|
+
stream.pipe(res);
|
|
1888
1927
|
});
|
|
1889
1928
|
router.delete('/:projectId/tickets/:ticketId/attachments/:attachmentId', async (req, res) => {
|
|
1890
1929
|
const parsed = parseTicketKey(req.params.ticketId);
|
|
@@ -13,6 +13,7 @@ const child_process_1 = require("child_process");
|
|
|
13
13
|
const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
|
|
14
14
|
const CLAUDE_MODELS = [
|
|
15
15
|
{ value: 'sonnet', label: 'Claude Sonnet', default: true },
|
|
16
|
+
{ value: 'fable', label: 'Claude Fable' },
|
|
16
17
|
{ value: 'opus', label: 'Claude Opus' },
|
|
17
18
|
{ value: 'haiku', label: 'Claude Haiku' },
|
|
18
19
|
];
|
|
@@ -20,11 +21,14 @@ const CLAUDE_MODELS = [
|
|
|
20
21
|
* CLI accepts collapse to the short alias. */
|
|
21
22
|
function normaliseModel(model) {
|
|
22
23
|
switch (model) {
|
|
24
|
+
case 'claude-sonnet-5':
|
|
23
25
|
case 'claude-sonnet-4-6':
|
|
24
26
|
case 'claude-sonnet-4-5':
|
|
25
27
|
case 'claude-sonnet-4-0':
|
|
26
28
|
case 'claude-sonnet-4-20250514':
|
|
27
29
|
return 'sonnet';
|
|
30
|
+
case 'claude-fable-5':
|
|
31
|
+
return 'fable';
|
|
28
32
|
case 'claude-opus-4-8':
|
|
29
33
|
case 'claude-opus-4-5':
|
|
30
34
|
case 'claude-opus-4-1-20250805':
|
|
@@ -317,6 +321,8 @@ exports.claudeAdapter = {
|
|
|
317
321
|
systemPromptArg: true,
|
|
318
322
|
persistentStdin: true,
|
|
319
323
|
supportsReasoningEffort: true,
|
|
324
|
+
reasoningEfforts: ['low', 'medium', 'high', 'xhigh'], // claude-code --effort tiers
|
|
325
|
+
supportsImageInput: true, // images ride as `@<abs-path>` prompt refs
|
|
320
326
|
},
|
|
321
327
|
modelCatalog: () => CLAUDE_MODELS,
|
|
322
328
|
defaultModel: () => 'sonnet',
|