specrails-desktop 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/dist/assets/ActivityFeedPage-DJJlZ3mF.js +1 -0
- package/client/dist/assets/AgentsPage-49JaEDjR.js +86 -0
- package/client/dist/assets/{AnalyticsPage-BD0paa75.js → AnalyticsPage-BUd3gWYC.js} +1 -1
- package/client/dist/assets/{BarChart-D8ZZRab3.js → BarChart-HDe_YoUD.js} +1 -1
- package/client/dist/assets/CodePage-CqPPND47.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-mwd8460_.js → DesktopAnalyticsPage-CgvmSvF0.js} +1 -1
- package/client/dist/assets/DocsDialog-hHFd3Ejs.js +11 -0
- package/client/dist/assets/DocsPage-B4R1aksg.js +11 -0
- package/client/dist/assets/{ExportDropdown-CLYmQhic.js → ExportDropdown-f4dwQjlT.js} +1 -1
- package/client/dist/assets/IntegrationsPage-CX2Ybxx0.js +3 -0
- package/client/dist/assets/JobDetailPage-DN2Jc8Ti.js +16 -0
- package/client/dist/assets/JobsPage-DmdpqijT.js +1 -0
- package/client/dist/assets/code-BwIz8agY.js +1 -0
- package/client/dist/assets/code-CD7yNSK0.js +1 -0
- package/client/dist/assets/code-CDFlxUFC.js +1 -0
- package/client/dist/assets/code-CY85RXZU.js +1 -0
- package/client/dist/assets/code-Cp3Fdng-.js +1 -0
- package/client/dist/assets/code-D24e1Crx.js +1 -0
- package/client/dist/assets/code-DtZBQTi9.js +1 -0
- package/client/dist/assets/code-nKa0fkm_.js +1 -0
- package/client/dist/assets/{cssMode-Cc6ozl-J.js → cssMode-DzNPAYFh.js} +1 -1
- package/client/dist/assets/{dist-js-D3MxtOYa.js → dist-js-COfIfLRE.js} +1 -1
- package/client/dist/assets/{dist-js-BOu_cXw3.js → dist-js-CvScGQU_.js} +1 -1
- package/client/dist/assets/{editor.main-CfXxHimg.js → editor.main-C7Rmw-hR.js} +2 -2
- package/client/dist/assets/{freemarker2-DP7J1gG3.js → freemarker2-Cszs4SVo.js} +1 -1
- package/client/dist/assets/{handlebars-BjRlucw6.js → handlebars-Dp7Lsuym.js} +1 -1
- package/client/dist/assets/{html-OumBQJ-U.js → html-BURidrEm.js} +1 -1
- package/client/dist/assets/{htmlMode-CStc3zXM.js → htmlMode--k5M7GjZ.js} +1 -1
- package/client/dist/assets/index-DBpvYrDK.css +2 -0
- package/client/dist/assets/index-DGIXKRHE.js +142 -0
- package/client/dist/assets/{integrations-Cublz3m6.js → integrations-2C7MkGT0.js} +1 -1
- package/client/dist/assets/{integrations-HIlUxXVs.js → integrations-BDC670cg.js} +1 -1
- package/client/dist/assets/integrations-BqUmRUef.js +1 -0
- package/client/dist/assets/{integrations-DmQYCUvN.js → integrations-C2jQtv-s.js} +1 -1
- package/client/dist/assets/{integrations-DRdbki5W.js → integrations-CB98NeH5.js} +1 -1
- package/client/dist/assets/{integrations-C3p12Ms6.js → integrations-CX4p_bij.js} +1 -1
- package/client/dist/assets/{integrations-DaC4SzzL.js → integrations-_SuVeQIG.js} +1 -1
- package/client/dist/assets/{integrations-Cr6hH7XR.js → integrations-eQPHAYsE.js} +1 -1
- package/client/dist/assets/{javascript-CMk--e7g.js → javascript-kJQz__44.js} +1 -1
- package/client/dist/assets/jira-C-ATCti0.js +1 -0
- package/client/dist/assets/jira-CmVfRM-b.js +1 -0
- package/client/dist/assets/jira-D7bkKAX8.js +1 -0
- package/client/dist/assets/jira-DKImM1YH.js +1 -0
- package/client/dist/assets/jira-DOw8bkIR.js +1 -0
- package/client/dist/assets/jira-DlA-wGp-.js +1 -0
- package/client/dist/assets/jira-Fob8EGxN.js +1 -0
- package/client/dist/assets/jira-xZA2lixb.js +1 -0
- package/client/dist/assets/{jsonMode-C2h3ZcjZ.js → jsonMode-v5JYPpnz.js} +1 -1
- package/client/dist/assets/{lib-DQ2hrj8m.js → lib-Bro9Z0gp.js} +1 -1
- package/client/dist/assets/{liquid-mI3KJrBE.js → liquid-Dl9I6gWt.js} +1 -1
- package/client/dist/assets/{lspLanguageFeatures-DU09ggWi.js → lspLanguageFeatures-CPlEe0NK.js} +1 -1
- package/client/dist/assets/{mdx-C41VDTR_.js → mdx-Byl7TtzQ.js} +1 -1
- package/client/dist/assets/{monaco.contribution-CPObAXMC.js → monaco.contribution-YMAkHQcQ.js} +2 -2
- package/client/dist/assets/{python-Y27rKQtk.js → python-jWQwT6j2.js} +1 -1
- package/client/dist/assets/{razor-Cd5-q9Bp.js → razor-BWS3sP-E.js} +1 -1
- package/client/dist/assets/setup-C0dzw8j4.js +1 -0
- package/client/dist/assets/setup-C1IA-9YS.js +1 -0
- package/client/dist/assets/setup-CpfjaNut.js +1 -0
- package/client/dist/assets/setup-D3rNZA9A.js +1 -0
- package/client/dist/assets/setup-UD2aanGs.js +1 -0
- package/client/dist/assets/setup-WP6WOYQh.js +1 -0
- package/client/dist/assets/setup-gzLG8T6F.js +1 -0
- package/client/dist/assets/setup-pjgmYHx6.js +1 -0
- package/client/dist/assets/specs-4lA_u79w.js +1 -0
- package/client/dist/assets/{specs-D2FzlLn9.js → specs-BHjxcjOf.js} +1 -1
- package/client/dist/assets/{specs-CZ1PsXsC.js → specs-CXNQzPk9.js} +1 -1
- package/client/dist/assets/{specs-Dyc5hYeE.js → specs-DFnc2Huj.js} +1 -1
- package/client/dist/assets/{specs-BFfu3u-a.js → specs-DZCLH2-l.js} +1 -1
- package/client/dist/assets/{specs-B__C8-8a.js → specs-DgmyAE3N.js} +1 -1
- package/client/dist/assets/{specs-DaUTrNF9.js → specs-DicWhvwi.js} +1 -1
- package/client/dist/assets/{specs-k0PyLDVt.js → specs-dkro6lSM.js} +1 -1
- package/client/dist/assets/{tsMode-B0y_xEci.js → tsMode-BbOGOuSV.js} +1 -1
- package/client/dist/assets/{typescript-BzK0OgwW.js → typescript-eBtFQJLs.js} +1 -1
- package/client/dist/assets/{useProjectCache-BxY4aTjd.js → useProjectCache-D9juBhsO.js} +1 -1
- package/client/dist/assets/{workers-rt--R2Qy.js → workers-BvicOoDf.js} +1 -1
- package/client/dist/assets/{xml-eX9QXAmI.js → xml-BJepAPyM.js} +1 -1
- package/client/dist/assets/{yaml-fcsNkpOt.js → yaml-DabgV-eA.js} +1 -1
- package/client/dist/index.html +13 -12
- package/docs/jira-integration-plan.md +321 -0
- package/package.json +1 -1
- package/server/dist/agent-refine-manager.js +128 -153
- package/server/dist/chat-manager.js +242 -0
- package/server/dist/code-explorer-router.js +78 -0
- package/server/dist/command-resolver.js +17 -0
- package/server/dist/contract-refine-runner.js +42 -10
- package/server/dist/db.js +86 -0
- package/server/dist/desktop-db.js +3 -0
- package/server/dist/explore-stdin-session.js +129 -0
- package/server/dist/feature-flags.js +11 -0
- package/server/dist/jira/jira-adf.js +113 -0
- package/server/dist/jira/jira-backlog-config.js +58 -0
- package/server/dist/jira/jira-client.js +279 -0
- package/server/dist/jira/jira-credential-store.js +103 -0
- package/server/dist/jira/jira-db.js +341 -0
- package/server/dist/jira/jira-issue-fields.js +428 -0
- package/server/dist/jira/jira-materializer.js +250 -0
- package/server/dist/jira/jira-status-resolver.js +211 -0
- package/server/dist/jira/jira-sync-manager.js +1014 -0
- package/server/dist/jira/types.js +9 -0
- package/server/dist/jira-router.js +304 -0
- package/server/dist/mobile/mobile-auth.js +16 -0
- package/server/dist/project-registry.js +43 -1
- package/server/dist/project-router-chat.js +218 -0
- package/server/dist/project-router-helpers.js +275 -0
- package/server/dist/project-router-jobs.js +389 -0
- package/server/dist/project-router-settings.js +312 -0
- package/server/dist/project-router-setup.js +456 -0
- package/server/dist/project-router-spending.js +320 -0
- package/server/dist/project-router-terminals.js +312 -0
- package/server/dist/project-router-tickets.js +1815 -0
- package/server/dist/project-router.js +31 -3950
- package/server/dist/providers/claude-adapter.js +23 -0
- package/server/dist/providers/codex-adapter.js +6 -0
- package/server/dist/rails-router.js +12 -0
- package/server/dist/spawn-lifecycle.js +117 -0
- package/client/dist/assets/ActivityFeedPage-BpjXuX2H.js +0 -1
- package/client/dist/assets/AgentsPage-D-7fDbTc.js +0 -86
- package/client/dist/assets/CodePage-B6q6CiYJ.js +0 -2
- package/client/dist/assets/DocsDialog-D_dyF2h9.js +0 -11
- package/client/dist/assets/DocsPage-C9-Ru8wE.js +0 -11
- package/client/dist/assets/IntegrationsPage-3WWtx9hi.js +0 -3
- package/client/dist/assets/JobDetailPage-DgN-79s-.js +0 -16
- package/client/dist/assets/JobsPage-Du8_w1ob.js +0 -1
- package/client/dist/assets/code-AL1rVIMb.js +0 -1
- package/client/dist/assets/code-C0BKpkht.js +0 -1
- package/client/dist/assets/code-C0FTS3ew.js +0 -1
- package/client/dist/assets/code-CPcHxzxw.js +0 -1
- package/client/dist/assets/code-D3ryDniw.js +0 -1
- package/client/dist/assets/code-D3zVVQTj.js +0 -1
- package/client/dist/assets/code-PCmfS3dn.js +0 -1
- package/client/dist/assets/code-exI0G5Wd.js +0 -1
- package/client/dist/assets/index-D17R4Cjc.css +0 -2
- package/client/dist/assets/index-D9G_K4L-.js +0 -142
- package/client/dist/assets/integrations-D28q1kF6.js +0 -1
- package/client/dist/assets/setup--FMCsnQS.js +0 -1
- package/client/dist/assets/setup-B19ZpBNi.js +0 -1
- package/client/dist/assets/setup-BZPmkjSN.js +0 -1
- package/client/dist/assets/setup-BqYA02rS.js +0 -1
- package/client/dist/assets/setup-ChKQDHN9.js +0 -1
- package/client/dist/assets/setup-D2n9jMfM.js +0 -1
- package/client/dist/assets/setup-P3r6YP1D.js +0 -1
- package/client/dist/assets/setup-fnfEbwlv.js +0 -1
- package/client/dist/assets/specs-cKEh2LXt.js +0 -1
- /package/client/dist/assets/{abap-Bw6f2wDG.js → abap-s65oMlhi.js} +0 -0
- /package/client/dist/assets/{activity-BdrPln96.js → activity-BqqwnH_h.js} +0 -0
- /package/client/dist/assets/{activity-BEIp_Y1A.js → activity-C8qqEIoP.js} +0 -0
- /package/client/dist/assets/{activity-CpkRS8Sx.js → activity-CZVM4nlJ.js} +0 -0
- /package/client/dist/assets/{activity-DOUVEjJi.js → activity-Cyy07Tgo.js} +0 -0
- /package/client/dist/assets/{activity-DRwkql_y.js → activity-DlbWCa4y.js} +0 -0
- /package/client/dist/assets/{activity-DKCpESPt.js → activity-Dwq0heud.js} +0 -0
- /package/client/dist/assets/{activity-DcDQ7tjw.js → activity-qFTcMyW9.js} +0 -0
- /package/client/dist/assets/{addon-image-3WCl5Vhd.js → addon-image-CpF0L0jM.js} +0 -0
- /package/client/dist/assets/{addon-ligatures-C5OdliKs.js → addon-ligatures-hXysGZrA.js} +0 -0
- /package/client/dist/assets/{addon-webgl-BbX6pSjl.js → addon-webgl-Cn1slavz.js} +0 -0
- /package/client/dist/assets/{addspec-D33ocMxf.js → addspec-B1FTtI2a.js} +0 -0
- /package/client/dist/assets/{addspec-DFswZ0jK.js → addspec-BCT9vm_c.js} +0 -0
- /package/client/dist/assets/{addspec-DVZ15Jp8.js → addspec-DeDOztDr.js} +0 -0
- /package/client/dist/assets/{addspec-Fkv91Opc.js → addspec-DpRgmfmx.js} +0 -0
- /package/client/dist/assets/{addspec-BEeF5-zc.js → addspec-Dw-0Dg-4.js} +0 -0
- /package/client/dist/assets/{addspec-B5yl4Loj.js → addspec-rp496P_F.js} +0 -0
- /package/client/dist/assets/{addspec-DRE-jZv7.js → addspec-v8j6A7CD.js} +0 -0
- /package/client/dist/assets/{agents-DK-Dlc0i.js → agents-23iPejcA.js} +0 -0
- /package/client/dist/assets/{agents-Q6Ldfpxx.js → agents-BDx1RXcl.js} +0 -0
- /package/client/dist/assets/{agents-TeOSy-ax.js → agents-BFr3kUhK.js} +0 -0
- /package/client/dist/assets/{agents-Bm9rPqnt.js → agents-B_1L9xRg.js} +0 -0
- /package/client/dist/assets/{agents-1nCDWRmP.js → agents-BlPnx-mz.js} +0 -0
- /package/client/dist/assets/{agents-iTqjRajS.js → agents-DcxZHzNr.js} +0 -0
- /package/client/dist/assets/{agents-s87sMGzL.js → agents-G3shOewU.js} +0 -0
- /package/client/dist/assets/{agentstudio-B6Wb59E7.js → agentstudio-B-CMAQqy.js} +0 -0
- /package/client/dist/assets/{agentstudio-D3I62TLJ.js → agentstudio-Bk1eZcv4.js} +0 -0
- /package/client/dist/assets/{agentstudio-DuH9TogZ.js → agentstudio-ChxNuGAu.js} +0 -0
- /package/client/dist/assets/{agentstudio-Kw88_dUF.js → agentstudio-DNlme601.js} +0 -0
- /package/client/dist/assets/{agentstudio-BdidyBzZ.js → agentstudio-DpP9caEE.js} +0 -0
- /package/client/dist/assets/{agentstudio-BSnWLR63.js → agentstudio-Y3G0ddJ2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BADhZ41e.js → agentstudio-kk9RB7Se.js} +0 -0
- /package/client/dist/assets/{aiedit-DJMny-D5.js → aiedit-5ETerMK1.js} +0 -0
- /package/client/dist/assets/{aiedit-D2ji6Qy0.js → aiedit-BBCrOpHq.js} +0 -0
- /package/client/dist/assets/{aiedit-DAhZTvtk.js → aiedit-BMtcGYNE.js} +0 -0
- /package/client/dist/assets/{aiedit-DvrcbwGv.js → aiedit-D9ddlgkM.js} +0 -0
- /package/client/dist/assets/{aiedit-WBSjT_C1.js → aiedit-De0SOH6S.js} +0 -0
- /package/client/dist/assets/{aiedit-BWxHGsYA.js → aiedit-DrfzQroF.js} +0 -0
- /package/client/dist/assets/{aiedit-DOcxERkU.js → aiedit-fMltW101.js} +0 -0
- /package/client/dist/assets/{analytics-C9Zc-rkM.js → analytics-BeTyviO8.js} +0 -0
- /package/client/dist/assets/{analytics-CrPCZRJ-.js → analytics-C4eEO260.js} +0 -0
- /package/client/dist/assets/{analytics-CYj0tfj7.js → analytics-C67cIA1b.js} +0 -0
- /package/client/dist/assets/{analytics-C6EzgtdE.js → analytics-CAguvW28.js} +0 -0
- /package/client/dist/assets/{analytics-CVx3YOc0.js → analytics-DBtt8Mgk.js} +0 -0
- /package/client/dist/assets/{analytics-CnY4kNG3.js → analytics-DUPtODxX.js} +0 -0
- /package/client/dist/assets/{analytics-BIdr0YfL.js → analytics-YIpQvPAc.js} +0 -0
- /package/client/dist/assets/{apex-Cw8_REBo.js → apex-BLUBIldB.js} +0 -0
- /package/client/dist/assets/{attachments-DYHGA2Dj.js → attachments-CCWasu-P.js} +0 -0
- /package/client/dist/assets/{attachments-Dd92KpUH.js → attachments-CHaDUfjB.js} +0 -0
- /package/client/dist/assets/{attachments-DzdU6DV6.js → attachments-CVSAbGNl.js} +0 -0
- /package/client/dist/assets/{attachments-Bcf6BG6V.js → attachments-Chg5poG1.js} +0 -0
- /package/client/dist/assets/{attachments-BW4L3l2L.js → attachments-DazTVJbH.js} +0 -0
- /package/client/dist/assets/{attachments-COcrGRFz.js → attachments-Dn-JImAK.js} +0 -0
- /package/client/dist/assets/{attachments-Bke8sCU4.js → attachments-LDA9kp2X.js} +0 -0
- /package/client/dist/assets/{azcli-Cz6HAoOw.js → azcli-DuWxh9mO.js} +0 -0
- /package/client/dist/assets/{bat-CcJ-xyqL.js → bat-UKoTejQm.js} +0 -0
- /package/client/dist/assets/{bicep-z1WDCKYz.js → bicep-4sTT4B3D.js} +0 -0
- /package/client/dist/assets/{browser-DGITz3fC.js → browser-BDd1dbFa.js} +0 -0
- /package/client/dist/assets/{browser-JsAIGCEW.js → browser-BWSgbfdX.js} +0 -0
- /package/client/dist/assets/{browser-M5-rbPlw.js → browser-D2Y_UAKA.js} +0 -0
- /package/client/dist/assets/{browser-BlYF4OOq.js → browser-DH9SGVfM.js} +0 -0
- /package/client/dist/assets/{browser-Bc-YdlVg.js → browser-DWOVYMlg.js} +0 -0
- /package/client/dist/assets/{browser-CT-ReZGt.js → browser-Dxc_VIRK.js} +0 -0
- /package/client/dist/assets/{browser-5ErDlJoR.js → browser-lTQwcDCI.js} +0 -0
- /package/client/dist/assets/{cameligo-BRewOpfa.js → cameligo-CAAryRYO.js} +0 -0
- /package/client/dist/assets/{chat-DwUm6W9z.js → chat-BO9MvVID.js} +0 -0
- /package/client/dist/assets/{chat-BEGuC03z.js → chat-CPgmgZOj.js} +0 -0
- /package/client/dist/assets/{chat-CboQguCi.js → chat-CUrG1eVg.js} +0 -0
- /package/client/dist/assets/{chat-DRCa9pOt.js → chat-CvOOKB2s.js} +0 -0
- /package/client/dist/assets/{chat-BEW60P_u.js → chat-DIh3hr6y.js} +0 -0
- /package/client/dist/assets/{chat-yoXwguQu.js → chat-UVVZqA57.js} +0 -0
- /package/client/dist/assets/{chat-BQNMD0PL.js → chat-mPn3UlMl.js} +0 -0
- /package/client/dist/assets/{clojure-DBjRWN6g.js → clojure-BlMERO1w.js} +0 -0
- /package/client/dist/assets/{clsx-DnqN-uhr.js → clsx-CnH-HMk3.js} +0 -0
- /package/client/dist/assets/{coffee-Cfk_XHGR.js → coffee-Cj8D-Wl1.js} +0 -0
- /package/client/dist/assets/{commands-sqrqsxyE.js → commands-B-MVT-2F.js} +0 -0
- /package/client/dist/assets/{commands-UD1NzmwX.js → commands-B0yFTp7e.js} +0 -0
- /package/client/dist/assets/{commands-DLrvnPNg.js → commands-BR1kDkHQ.js} +0 -0
- /package/client/dist/assets/{commands-CJxCry-o.js → commands-Cb21pDlG.js} +0 -0
- /package/client/dist/assets/{commands-CfgY-_of.js → commands-DWgp-8W1.js} +0 -0
- /package/client/dist/assets/{commands-B772IyDa.js → commands-ddsl1V91.js} +0 -0
- /package/client/dist/assets/{commands-BDDp6xFG.js → commands-t4frzhB0.js} +0 -0
- /package/client/dist/assets/{common-Dmm1GhdD.js → common-5ilvMOcH.js} +0 -0
- /package/client/dist/assets/{common-DltqHaAe.js → common-B4sqsKp7.js} +0 -0
- /package/client/dist/assets/{common-GbpxfPG8.js → common-BKpVwUIf.js} +0 -0
- /package/client/dist/assets/{common-DeDELLZJ.js → common-BzEC3kJU.js} +0 -0
- /package/client/dist/assets/{common-DnjcgkPH.js → common-CALKUpYm.js} +0 -0
- /package/client/dist/assets/{common-Dard9UNH.js → common-CTEbWVZS.js} +0 -0
- /package/client/dist/assets/{common-DCr6VzJ7.js → common-DQiza2Xp.js} +0 -0
- /package/client/dist/assets/{cpp-BVob6BaP.js → cpp-BPfKnaj_.js} +0 -0
- /package/client/dist/assets/{csharp-C4fbRuOu.js → csharp-gX-x5uD6.js} +0 -0
- /package/client/dist/assets/{csp-DthFP_vT.js → csp-DKGVt8SM.js} +0 -0
- /package/client/dist/assets/{css-CGMH0hcW.js → css-CPMdnAVq.js} +0 -0
- /package/client/dist/assets/{cypher-Pnf68BRV.js → cypher-ClMDrj9S.js} +0 -0
- /package/client/dist/assets/{dart-PMMOtxZX.js → dart-C4zbzpVv.js} +0 -0
- /package/client/dist/assets/{dashboard-BZBADHSj.js → dashboard--Y6yzMlf.js} +0 -0
- /package/client/dist/assets/{dashboard-I19DXBxw.js → dashboard--a4-6oYE.js} +0 -0
- /package/client/dist/assets/{dashboard-CB6Le1yN.js → dashboard-BiJ3CDTG.js} +0 -0
- /package/client/dist/assets/{dashboard-B4ixDVk8.js → dashboard-CiXjk63Z.js} +0 -0
- /package/client/dist/assets/{dashboard-C1MfeUHs.js → dashboard-Cx5VjCea.js} +0 -0
- /package/client/dist/assets/{dashboard-C7SK6xu5.js → dashboard-D7jg25XR.js} +0 -0
- /package/client/dist/assets/{dashboard-CoTpMOBM.js → dashboard-DpGYK2s1.js} +0 -0
- /package/client/dist/assets/{dockerfile-di1nsJCc.js → dockerfile-D9xw73D1.js} +0 -0
- /package/client/dist/assets/{ecl-D_WVcB5M.js → ecl-gqO8tIR9.js} +0 -0
- /package/client/dist/assets/{editor.api2-XLGzZfbc.js → editor.api2-BPnIxMjz.js} +0 -0
- /package/client/dist/assets/{elixir-OAdJEMOn.js → elixir-DSAhVF3_.js} +0 -0
- /package/client/dist/assets/{explore-D2EFgt8J.js → explore-BE5UmlbD.js} +0 -0
- /package/client/dist/assets/{explore-BV5Xxlsn.js → explore-BmTaI8dX.js} +0 -0
- /package/client/dist/assets/{explore-A8Ltoblq.js → explore-CCwkqoWq.js} +0 -0
- /package/client/dist/assets/{explore-4mFpnrKU.js → explore-CMdEoPDx.js} +0 -0
- /package/client/dist/assets/{explore-C3FSE42C.js → explore-CtdCL4QU.js} +0 -0
- /package/client/dist/assets/{explore-B9A3iN2W.js → explore-DHjxSkqQ.js} +0 -0
- /package/client/dist/assets/{explore-BrBJvfjP.js → explore-__BeALjE.js} +0 -0
- /package/client/dist/assets/{flow9-D3QEZjgn.js → flow9-DeQCSPOd.js} +0 -0
- /package/client/dist/assets/{format-command-CwGuwzGA.js → format-command-2VNoNnMv.js} +0 -0
- /package/client/dist/assets/{fsharp-BF0k_8N8.js → fsharp-CEfaXL-S.js} +0 -0
- /package/client/dist/assets/{go-BAQO5Jsz.js → go-Xp1OkZCh.js} +0 -0
- /package/client/dist/assets/{graphql-hdFVFkiV.js → graphql-BwRXrUwe.js} +0 -0
- /package/client/dist/assets/{hcl-DWnl1o-X.js → hcl-u06DtVFk.js} +0 -0
- /package/client/dist/assets/{ini-CB-6OVu3.js → ini-AmeIpFND.js} +0 -0
- /package/client/dist/assets/{java-d1CmfiHX.js → java-CyDbRQjX.js} +0 -0
- /package/client/dist/assets/{jobs-DPPT6bV6.js → jobs-8viuHLDV.js} +0 -0
- /package/client/dist/assets/{jobs-3j3_npyo.js → jobs-AW2eB5D-.js} +0 -0
- /package/client/dist/assets/{jobs-2N3RXDAM.js → jobs-BSm89DL5.js} +0 -0
- /package/client/dist/assets/{jobs-BqEbCCxD.js → jobs-BZ3sQHjZ.js} +0 -0
- /package/client/dist/assets/{jobs-cHYInoau.js → jobs-Bd8AdOTb.js} +0 -0
- /package/client/dist/assets/{jobs-DRzjWI9u.js → jobs-CRtsq_u0.js} +0 -0
- /package/client/dist/assets/{jobs-2f6Hdc72.js → jobs-CSRwFQ6K.js} +0 -0
- /package/client/dist/assets/{jobs-vGfzIDQa.js → jobs-CbEl7WMI.js} +0 -0
- /package/client/dist/assets/{julia-Bgv08lKa.js → julia-BqialFRG.js} +0 -0
- /package/client/dist/assets/{kotlin-u98kaVTf.js → kotlin-Dzz8TWAt.js} +0 -0
- /package/client/dist/assets/{less-CjYwpgg5.js → less-DHRJD3TR.js} +0 -0
- /package/client/dist/assets/{lexon-YTjaAFBB.js → lexon-5Y3QgTmT.js} +0 -0
- /package/client/dist/assets/{lua-BzmkWv27.js → lua-sKvhfPn5.js} +0 -0
- /package/client/dist/assets/{m3-CFwk9fw0.js → m3-DWDVwkFG.js} +0 -0
- /package/client/dist/assets/{markdown-CR5iMpSZ.js → markdown-CD_aSBxW.js} +0 -0
- /package/client/dist/assets/{mips-CcEalc17.js → mips-687T03hg.js} +0 -0
- /package/client/dist/assets/{msdax-BQbkawnr.js → msdax-C1St-dIV.js} +0 -0
- /package/client/dist/assets/{mysql-GTlaaW_P.js → mysql-BG7r8oBS.js} +0 -0
- /package/client/dist/assets/{nav-C2YXcbZS.js → nav-B05EYB0b.js} +0 -0
- /package/client/dist/assets/{nav-D2bOGSEg.js → nav-BNGCq-0y.js} +0 -0
- /package/client/dist/assets/{nav-BEL3MTwK.js → nav-BRInPX8a.js} +0 -0
- /package/client/dist/assets/{nav-CtYwmMgu.js → nav-Bf87DRHD.js} +0 -0
- /package/client/dist/assets/{nav-iH1V5j6o.js → nav-BkVzzFpc.js} +0 -0
- /package/client/dist/assets/{nav-0fwkrgHt.js → nav-BzFLtS1W.js} +0 -0
- /package/client/dist/assets/{nav-ClzOE4mA.js → nav-DBDbQOYn.js} +0 -0
- /package/client/dist/assets/{nav-B_G-TJDW.js → nav-X9sVtUWC.js} +0 -0
- /package/client/dist/assets/{objective-c-Byu1T5if.js → objective-c-Ds1-m05L.js} +0 -0
- /package/client/dist/assets/{pascal-BrfzBfRm.js → pascal-BKK9FpIi.js} +0 -0
- /package/client/dist/assets/{pascaligo-BXXKFUeo.js → pascaligo-SRS3nwtO.js} +0 -0
- /package/client/dist/assets/{perl-B3OikKq-.js → perl-B2hTOlrF.js} +0 -0
- /package/client/dist/assets/{pgsql-CTsa0Acc.js → pgsql-DIQJYNpL.js} +0 -0
- /package/client/dist/assets/{php-DiQh3FUW.js → php-BEaVe8X2.js} +0 -0
- /package/client/dist/assets/{pla-92uH8Fzm.js → pla-oPLHpZ-Q.js} +0 -0
- /package/client/dist/assets/{postiats-BbeWkKUr.js → postiats-D_vzrAzD.js} +0 -0
- /package/client/dist/assets/{powerquery-DgDMzpsm.js → powerquery-BKG6w-FH.js} +0 -0
- /package/client/dist/assets/{powershell-BfdUUzaG.js → powershell-B3dLhDt4.js} +0 -0
- /package/client/dist/assets/{protobuf-BojW2ftW.js → protobuf-DC8SGjcl.js} +0 -0
- /package/client/dist/assets/{pug-BxqTg3IU.js → pug-D5E-4fI0.js} +0 -0
- /package/client/dist/assets/{qsharp-BX_A-MW9.js → qsharp-6vJAWv0x.js} +0 -0
- /package/client/dist/assets/{r-D9BMnxvJ.js → r-CDwsEcbM.js} +0 -0
- /package/client/dist/assets/{redis-5cJqEQJJ.js → redis-CuQbbESS.js} +0 -0
- /package/client/dist/assets/{redshift-d8BBqiwb.js → redshift-B9e1k-qI.js} +0 -0
- /package/client/dist/assets/{restructuredtext-C8a6yIcZ.js → restructuredtext-BiJ5IwaU.js} +0 -0
- /package/client/dist/assets/{ruby-egeh-6KX.js → ruby-B0UAHY9b.js} +0 -0
- /package/client/dist/assets/{rust-a3r9IInB.js → rust-Dg_spmFr.js} +0 -0
- /package/client/dist/assets/{sb-y8iRIDei.js → sb-DjU66I8Q.js} +0 -0
- /package/client/dist/assets/{scala-BPDK2AmK.js → scala-qvStIdfG.js} +0 -0
- /package/client/dist/assets/{scheme-BIWUEoOs.js → scheme-FstEk5Rh.js} +0 -0
- /package/client/dist/assets/{scss-CA-PSzwg.js → scss-w0U3rQLK.js} +0 -0
- /package/client/dist/assets/{settings-CTcwN9RE.js → settings-5tzo0Rn3.js} +0 -0
- /package/client/dist/assets/{settings-D_dujJZI.js → settings-BDAW3trC.js} +0 -0
- /package/client/dist/assets/{settings-Bg0A3zoS.js → settings-BEWv3VEu.js} +0 -0
- /package/client/dist/assets/{settings-BgPqg2nv.js → settings-BORg56um.js} +0 -0
- /package/client/dist/assets/{settings-BSze3_9q.js → settings-D3LurcR5.js} +0 -0
- /package/client/dist/assets/{settings-CSJ0ahZ8.js → settings-DcqWIEM6.js} +0 -0
- /package/client/dist/assets/{settings-DYIV89nV.js → settings-Dfz8QbZS.js} +0 -0
- /package/client/dist/assets/{settings-DDcfx_ca.js → settings-yMubjqYw.js} +0 -0
- /package/client/dist/assets/{shell--LiT1Bja.js → shell-DJ78wREd.js} +0 -0
- /package/client/dist/assets/{solidity-DdqZccZg.js → solidity-1aGIVsdX.js} +0 -0
- /package/client/dist/assets/{sophia-S6-YxNG_.js → sophia-40LqcGjB.js} +0 -0
- /package/client/dist/assets/{sparql-BSf5kMp2.js → sparql-Cz5dqG_g.js} +0 -0
- /package/client/dist/assets/{sql-D7KgjR8G.js → sql-64f62Ni4.js} +0 -0
- /package/client/dist/assets/{st-BnoDa-Ml.js → st-gJe2yG8J.js} +0 -0
- /package/client/dist/assets/{swift-DEUHTkUX.js → swift-C6ME22mv.js} +0 -0
- /package/client/dist/assets/{systemverilog-Tqb_KPnW.js → systemverilog-CEWz259w.js} +0 -0
- /package/client/dist/assets/{tcl-BmBFS2qq.js → tcl-CcLVIi3m.js} +0 -0
- /package/client/dist/assets/{terminal-Bje4ziIa.js → terminal-BYtreaaF.js} +0 -0
- /package/client/dist/assets/{terminal-CSONJOex.js → terminal-C0xx0SjA.js} +0 -0
- /package/client/dist/assets/{terminal-DeWzh6ys.js → terminal-CPpK58RC.js} +0 -0
- /package/client/dist/assets/{terminal-C2WYcFHF.js → terminal-CdxkpafL.js} +0 -0
- /package/client/dist/assets/{terminal-DEqzGtcr.js → terminal-Ciia0wh2.js} +0 -0
- /package/client/dist/assets/{terminal-80yDMgMF.js → terminal-DHIkiWcs.js} +0 -0
- /package/client/dist/assets/{terminal-lkZYR4wJ.js → terminal-DY42QANg.js} +0 -0
- /package/client/dist/assets/{terminal-YOlsJCQj.js → terminal-DoxtVdma.js} +0 -0
- /package/client/dist/assets/{tickets-DYvafSaY.js → tickets-0rM0lIXd.js} +0 -0
- /package/client/dist/assets/{tickets-DNOANUXr.js → tickets-1UIGf_oA.js} +0 -0
- /package/client/dist/assets/{tickets-DlpC_iTg.js → tickets-9kdPXInd.js} +0 -0
- /package/client/dist/assets/{tickets-CF2PYelu.js → tickets-C6pwZwt4.js} +0 -0
- /package/client/dist/assets/{tickets-CB7N30gm.js → tickets-DAjtxAVb.js} +0 -0
- /package/client/dist/assets/{tickets-DU1aqsbr.js → tickets-DNmXcAwu.js} +0 -0
- /package/client/dist/assets/{tickets-clefmXLv.js → tickets-n23kDqJT.js} +0 -0
- /package/client/dist/assets/{tickets-DucYgtdl.js → tickets-tGx5AR5b.js} +0 -0
- /package/client/dist/assets/{twig-BQV8igWC.js → twig-DvsO-WjW.js} +0 -0
- /package/client/dist/assets/{typespec-DlFroUGY.js → typespec-Brkt3IAA.js} +0 -0
- /package/client/dist/assets/{vb-BlrJpIMX.js → vb-r121Uzxt.js} +0 -0
- /package/client/dist/assets/{wgsl-BWgIc6FZ.js → wgsl-BRX8uYh4.js} +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared types for the per-project Jira integration.
|
|
3
|
+
//
|
|
4
|
+
// Design: Desktop is the sync layer. The local `.specrails/local-tickets.json`
|
|
5
|
+
// store stays the canonical read cache (specrails-core reads it unchanged). Jira
|
|
6
|
+
// is the system of record. Every write to Jira goes through a durable
|
|
7
|
+
// transactional outbox in the per-project `jobs.sqlite`. See
|
|
8
|
+
// docs/jira-integration-plan.md.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createJiraRouter = createJiraRouter;
|
|
37
|
+
const express_1 = require("express");
|
|
38
|
+
const jira_db_1 = require("./jira/jira-db");
|
|
39
|
+
const feature_flags_1 = require("./feature-flags");
|
|
40
|
+
// req.projectCtx is declared in project-router / rails-router.
|
|
41
|
+
/**
|
|
42
|
+
* Per-project Jira router, mounted at /api/projects/:projectId/jira. Gated by
|
|
43
|
+
* SPECRAILS_JIRA_SECTION; 404s entirely when the flag is off. Endpoints back the
|
|
44
|
+
* step-by-step setup wizard (test → pick project → optional status map → connect)
|
|
45
|
+
* and the sync/outbox management surfaces. The token is never returned to the
|
|
46
|
+
* client (connection responses carry `hasToken` only).
|
|
47
|
+
*/
|
|
48
|
+
function createJiraRouter() {
|
|
49
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
50
|
+
function ctx(req) {
|
|
51
|
+
return req.projectCtx;
|
|
52
|
+
}
|
|
53
|
+
// Feature-flag gate for every route under /jira.
|
|
54
|
+
router.use((_req, res, next) => {
|
|
55
|
+
if (!(0, feature_flags_1.isJiraEnabled)()) {
|
|
56
|
+
res.status(404).json({ error: 'Jira integration disabled' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
next();
|
|
60
|
+
});
|
|
61
|
+
// ─── Connection ──────────────────────────────────────────────────────────────
|
|
62
|
+
// GET /connection — current connection (token redacted) or { connected: false }.
|
|
63
|
+
router.get('/connection', (req, res) => {
|
|
64
|
+
const c = ctx(req);
|
|
65
|
+
const conn = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
|
|
66
|
+
if (!conn) {
|
|
67
|
+
res.json({ connected: false });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
res.json({ connected: true, connection: conn, outbox: c.jiraSyncManager.outboxCounts() });
|
|
71
|
+
});
|
|
72
|
+
// POST /test — wizard step 1: validate credentials without saving.
|
|
73
|
+
router.post('/test', async (req, res) => {
|
|
74
|
+
const { baseUrl, accountEmail, token } = req.body ?? {};
|
|
75
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
|
|
76
|
+
res.status(400).json({ error: 'baseUrl and token are required' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const result = await ctx(req).jiraSyncManager.probeCredentials({
|
|
80
|
+
baseUrl: baseUrl.trim(),
|
|
81
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
82
|
+
token,
|
|
83
|
+
});
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
res.json(result);
|
|
89
|
+
});
|
|
90
|
+
// POST /discover-projects — wizard step 2: list visible projects.
|
|
91
|
+
router.post('/discover-projects', async (req, res) => {
|
|
92
|
+
const { baseUrl, accountEmail, token, query } = req.body ?? {};
|
|
93
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
|
|
94
|
+
res.status(400).json({ error: 'baseUrl and token are required' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const result = await ctx(req).jiraSyncManager.discoverProjects({
|
|
98
|
+
baseUrl: baseUrl.trim(),
|
|
99
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
100
|
+
token,
|
|
101
|
+
query: isNonEmptyString(query) ? query.trim() : undefined,
|
|
102
|
+
});
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
res.status(400).json({ error: result.error });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
res.json({ projects: result.projects });
|
|
108
|
+
});
|
|
109
|
+
// POST /discover-statuses — wizard step 3 (optional): the project's statuses.
|
|
110
|
+
router.post('/discover-statuses', async (req, res) => {
|
|
111
|
+
const { baseUrl, accountEmail, token, projectKey } = req.body ?? {};
|
|
112
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(projectKey)) {
|
|
113
|
+
res.status(400).json({ error: 'baseUrl, token and projectKey are required' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const result = await ctx(req).jiraSyncManager.discoverStatuses({
|
|
117
|
+
baseUrl: baseUrl.trim(),
|
|
118
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
119
|
+
token,
|
|
120
|
+
projectKey: projectKey.trim(),
|
|
121
|
+
});
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
res.status(400).json({ error: result.error });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
res.json({ statuses: result.statuses });
|
|
127
|
+
});
|
|
128
|
+
// POST /connect — wizard final step: validate + persist + start sync.
|
|
129
|
+
router.post('/connect', async (req, res) => {
|
|
130
|
+
const c = ctx(req);
|
|
131
|
+
const { baseUrl, accountEmail, token, jiraProjectKey, statusMap, discardStatus } = req.body ?? {};
|
|
132
|
+
if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(jiraProjectKey)) {
|
|
133
|
+
res.status(400).json({ error: 'baseUrl, token and jiraProjectKey are required' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const cleanMap = sanitizeStatusMap(statusMap);
|
|
137
|
+
const result = await c.jiraSyncManager.connect({
|
|
138
|
+
baseUrl: baseUrl.trim(),
|
|
139
|
+
accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
|
|
140
|
+
token,
|
|
141
|
+
jiraProjectKey: jiraProjectKey.trim(),
|
|
142
|
+
statusMap: cleanMap,
|
|
143
|
+
discardStatus: isNonEmptyString(discardStatus) ? discardStatus.trim() : null,
|
|
144
|
+
});
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
res.status(201).json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
|
|
150
|
+
});
|
|
151
|
+
// PATCH /connection — toggle enabled (hot-swap local↔Jira) and/or update the
|
|
152
|
+
// discard "move-to" status. `discardStatus: ''`/null clears it.
|
|
153
|
+
router.patch('/connection', (req, res) => {
|
|
154
|
+
const c = ctx(req);
|
|
155
|
+
const existing = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
|
|
156
|
+
if (!existing) {
|
|
157
|
+
res.status(404).json({ error: 'No Jira connection configured' });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const { enabled, discardStatus, statusMap } = req.body ?? {};
|
|
161
|
+
if (typeof enabled === 'boolean') {
|
|
162
|
+
c.jiraSyncManager.setEnabled(enabled);
|
|
163
|
+
}
|
|
164
|
+
if (discardStatus !== undefined) {
|
|
165
|
+
c.jiraSyncManager.setDiscardStatus(isNonEmptyString(discardStatus) ? discardStatus.trim() : null);
|
|
166
|
+
}
|
|
167
|
+
if (statusMap !== undefined) {
|
|
168
|
+
c.jiraSyncManager.setStatusMap(sanitizeStatusMap(statusMap));
|
|
169
|
+
}
|
|
170
|
+
res.json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
|
|
171
|
+
});
|
|
172
|
+
// GET /statuses — the connected project's real statuses (post-connect picker
|
|
173
|
+
// for the discard "move-to" status). Uses the stored credentials.
|
|
174
|
+
router.get('/statuses', async (req, res) => {
|
|
175
|
+
const result = await ctx(req).jiraSyncManager.listStatusesForConnection();
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
res.status(400).json({ error: result.error });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
res.json({ statuses: result.statuses });
|
|
181
|
+
});
|
|
182
|
+
// DELETE /connection — remove the connection + restore local backlog config.
|
|
183
|
+
router.delete('/connection', (req, res) => {
|
|
184
|
+
const c = ctx(req);
|
|
185
|
+
c.jiraSyncManager.disconnect();
|
|
186
|
+
res.json({ connected: false });
|
|
187
|
+
});
|
|
188
|
+
// POST /resume — re-paste of a fresh token after a 401: drain the parked outbox.
|
|
189
|
+
router.post('/resume', (req, res) => {
|
|
190
|
+
ctx(req).jiraSyncManager.resumeAfterReauth();
|
|
191
|
+
res.json({ ok: true });
|
|
192
|
+
});
|
|
193
|
+
// ─── Sync + outbox management ───────────────────────────────────────────────
|
|
194
|
+
// POST /sync — manual "Sync now": a FULL re-fetch (ignores the high-water) so
|
|
195
|
+
// it back-fills any data the cache is missing (e.g. sprint/epic fields).
|
|
196
|
+
router.post('/sync', async (req, res) => {
|
|
197
|
+
const result = await ctx(req).jiraSyncManager.pollOnce(true);
|
|
198
|
+
res.json({ ok: true, upserted: result?.upserted ?? 0 });
|
|
199
|
+
});
|
|
200
|
+
// GET /outbox?state= — list outbox ops (defaults to all).
|
|
201
|
+
router.get('/outbox', (req, res) => {
|
|
202
|
+
const c = ctx(req);
|
|
203
|
+
const state = req.query.state;
|
|
204
|
+
const valid = ['pending', 'inflight', 'done', 'dead'];
|
|
205
|
+
const filter = valid.includes(state) ? state : undefined;
|
|
206
|
+
res.json({ ops: c.jiraSyncManager.listOutbox(filter), counts: c.jiraSyncManager.outboxCounts() });
|
|
207
|
+
});
|
|
208
|
+
// POST /outbox/:id/retry — re-queue a dead-lettered op for a manual retry.
|
|
209
|
+
router.post('/outbox/:id/retry', async (req, res) => {
|
|
210
|
+
const c = ctx(req);
|
|
211
|
+
const id = parseInt(req.params.id, 10);
|
|
212
|
+
if (Number.isNaN(id)) {
|
|
213
|
+
res.status(400).json({ error: 'Invalid op id' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const { retryDeadOutbox } = await Promise.resolve().then(() => __importStar(require('./jira/jira-db')));
|
|
217
|
+
const ok = retryDeadOutbox(c.db, id);
|
|
218
|
+
if (!ok) {
|
|
219
|
+
res.status(404).json({ error: 'Op not found or not in dead state' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
void c.jiraSyncManager.drainOnce().catch(() => undefined);
|
|
223
|
+
res.json({ ok: true });
|
|
224
|
+
});
|
|
225
|
+
// POST /specs — Add Spec when the project source is Jira: create the issue in
|
|
226
|
+
// Jira and materialize it locally. Keeps the generic local ticket-create path
|
|
227
|
+
// untouched.
|
|
228
|
+
router.post('/specs', async (req, res) => {
|
|
229
|
+
const c = ctx(req);
|
|
230
|
+
const { title, description, labels, priority, issueType } = req.body ?? {};
|
|
231
|
+
if (!isNonEmptyString(title)) {
|
|
232
|
+
res.status(400).json({ error: 'title is required' });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const result = await c.jiraSyncManager.createSpec({
|
|
236
|
+
title: title.trim(),
|
|
237
|
+
description: isNonEmptyString(description) ? description : undefined,
|
|
238
|
+
labels: Array.isArray(labels) ? labels.filter((l) => typeof l === 'string') : undefined,
|
|
239
|
+
priority: isNonEmptyString(priority) ? priority : undefined,
|
|
240
|
+
issueType: isNonEmptyString(issueType) ? issueType : undefined,
|
|
241
|
+
});
|
|
242
|
+
if (!result.ok) {
|
|
243
|
+
res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
res.status(201).json({ localId: result.localId, jiraKey: result.jiraKey });
|
|
247
|
+
});
|
|
248
|
+
// POST /specs/:localId/discard — "Move to <discard status>": transition the
|
|
249
|
+
// linked issue to the configured discard status (+ optional reason comment)
|
|
250
|
+
// instead of a destructive delete. Body: { comment?: string }.
|
|
251
|
+
router.post('/specs/:localId/discard', (req, res) => {
|
|
252
|
+
const c = ctx(req);
|
|
253
|
+
const localId = parseInt(req.params.localId, 10);
|
|
254
|
+
if (Number.isNaN(localId)) {
|
|
255
|
+
res.status(400).json({ error: 'Invalid spec id' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const comment = isNonEmptyString(req.body?.comment) ? req.body.comment : null;
|
|
259
|
+
const result = c.jiraSyncManager.discardSpec(localId, comment);
|
|
260
|
+
if (!result.ok) {
|
|
261
|
+
const status = result.reason === 'no-link' ? 404 : 409;
|
|
262
|
+
res.status(status).json({ error: result.reason });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
res.status(202).json({ ok: true });
|
|
266
|
+
});
|
|
267
|
+
// GET /specs/:localId/details — read-only "Jira details" + "Development" payload
|
|
268
|
+
// for the spec detail modal. Resilient: dev-status failures still return fields.
|
|
269
|
+
router.get('/specs/:localId/details', async (req, res) => {
|
|
270
|
+
const c = ctx(req);
|
|
271
|
+
const localId = parseInt(req.params.localId, 10);
|
|
272
|
+
if (Number.isNaN(localId)) {
|
|
273
|
+
res.status(400).json({ error: 'Invalid spec id' });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const result = await c.jiraSyncManager.getSpecDetails(localId);
|
|
277
|
+
if (!result.ok) {
|
|
278
|
+
const status = result.reason === 'no-link' ? 404 : result.reason === 'not-active' ? 409 : (result.status ?? 502);
|
|
279
|
+
res.status(status).json({ error: result.reason });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
res.json(result.details);
|
|
283
|
+
});
|
|
284
|
+
// GET /links — the spec↔issue map (for the badge / diagnostics).
|
|
285
|
+
router.get('/links', (req, res) => {
|
|
286
|
+
res.json({ links: ctx(req).jiraSyncManager.listLinks() });
|
|
287
|
+
});
|
|
288
|
+
return router;
|
|
289
|
+
}
|
|
290
|
+
function isNonEmptyString(v) {
|
|
291
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
292
|
+
}
|
|
293
|
+
function sanitizeStatusMap(raw) {
|
|
294
|
+
if (!raw || typeof raw !== 'object')
|
|
295
|
+
return null;
|
|
296
|
+
const states = ['todo', 'in_progress', 'done', 'cancelled'];
|
|
297
|
+
const out = {};
|
|
298
|
+
for (const s of states) {
|
|
299
|
+
const v = raw[s];
|
|
300
|
+
if (typeof v === 'string' && v.trim())
|
|
301
|
+
out[s] = v.trim();
|
|
302
|
+
}
|
|
303
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
304
|
+
}
|
|
@@ -29,6 +29,10 @@ function createMobileAuthMiddleware(opts) {
|
|
|
29
29
|
const clock = opts.clock ?? (() => Date.now());
|
|
30
30
|
const lastTouch = new Map();
|
|
31
31
|
const ipHits = new Map();
|
|
32
|
+
// Periodically evict stale keys so these maps can't grow unbounded under a
|
|
33
|
+
// multi-source flood (each per-IP timestamp array self-bounds to the 60s
|
|
34
|
+
// window, but the Map KEYS would otherwise live forever, one per distinct IP).
|
|
35
|
+
let lastSweep = clock();
|
|
32
36
|
return function mobileAuth(req, res, next) {
|
|
33
37
|
// 1. Refuse browser-origin requests outright.
|
|
34
38
|
if (req.headers['origin']) {
|
|
@@ -38,6 +42,18 @@ function createMobileAuthMiddleware(opts) {
|
|
|
38
42
|
// 2. Coarse per-IP rate limit.
|
|
39
43
|
const ip = req.socket?.remoteAddress ?? 'unknown';
|
|
40
44
|
const now = clock();
|
|
45
|
+
// Sweep stale tracking at most once per window (cheap, amortized O(1)).
|
|
46
|
+
if (now - lastSweep > 60_000) {
|
|
47
|
+
lastSweep = now;
|
|
48
|
+
for (const [k, v] of ipHits) {
|
|
49
|
+
if (v.length === 0 || now - v[v.length - 1] >= 60_000)
|
|
50
|
+
ipHits.delete(k);
|
|
51
|
+
}
|
|
52
|
+
for (const [k, t] of lastTouch) {
|
|
53
|
+
if (now - t >= 3_600_000)
|
|
54
|
+
lastTouch.delete(k); // drop device touch-cache after 1h idle
|
|
55
|
+
}
|
|
56
|
+
}
|
|
41
57
|
const hits = (ipHits.get(ip) ?? []).filter((t) => now - t < 60_000);
|
|
42
58
|
hits.push(now);
|
|
43
59
|
ipHits.set(ip, hits);
|
|
@@ -24,6 +24,7 @@ const terminal_manager_1 = require("./terminal-manager");
|
|
|
24
24
|
const browser_capture_manager_1 = require("./browser-capture-manager");
|
|
25
25
|
const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
26
26
|
const ticket_store_1 = require("./ticket-store");
|
|
27
|
+
const jira_sync_manager_1 = require("./jira/jira-sync-manager");
|
|
27
28
|
const rails_store_1 = require("./rails-store");
|
|
28
29
|
const desktop_db_1 = require("./desktop-db");
|
|
29
30
|
const config_1 = require("./config");
|
|
@@ -115,6 +116,11 @@ class ProjectRegistry {
|
|
|
115
116
|
catch { /* ignore */ }
|
|
116
117
|
// Tear down the embedded browser (closes pages + persistent context).
|
|
117
118
|
void ctx.browserCaptureManager.shutdown().catch(() => { });
|
|
119
|
+
// Stop the Jira sync poll/drain timers (no children, just intervals).
|
|
120
|
+
try {
|
|
121
|
+
ctx.jiraSyncManager.stop();
|
|
122
|
+
}
|
|
123
|
+
catch { /* ignore */ }
|
|
118
124
|
// Kill any terminal sessions belonging to this project
|
|
119
125
|
try {
|
|
120
126
|
(0, terminal_manager_1.getTerminalManager)().killAllForProject(id);
|
|
@@ -201,6 +207,10 @@ class ProjectRegistry {
|
|
|
201
207
|
}
|
|
202
208
|
catch { /* ignore */ }
|
|
203
209
|
void ctx.browserCaptureManager.shutdown().catch(() => { });
|
|
210
|
+
try {
|
|
211
|
+
ctx.jiraSyncManager.stop();
|
|
212
|
+
}
|
|
213
|
+
catch { /* ignore */ }
|
|
204
214
|
// Release chokidar watchers + abort in-flight generations so a restart
|
|
205
215
|
// does not leak handles/children — mirror removeProject()'s per-project teardown.
|
|
206
216
|
try {
|
|
@@ -250,6 +260,14 @@ class ProjectRegistry {
|
|
|
250
260
|
catch { /* queue_state table may not exist yet */ }
|
|
251
261
|
const webhookManager = this._webhookManager;
|
|
252
262
|
const railJobs = new Map();
|
|
263
|
+
// Jira sync (per-project, inert until a connection is configured). Constructed
|
|
264
|
+
// before QueueManager so the onJobFinished closure can reference it.
|
|
265
|
+
const jiraSyncManager = new jira_sync_manager_1.JiraSyncManager({
|
|
266
|
+
db,
|
|
267
|
+
projectId: project.id,
|
|
268
|
+
projectPath: project.path,
|
|
269
|
+
broadcast: boundBroadcast,
|
|
270
|
+
});
|
|
253
271
|
const queueManager = new queue_manager_1.QueueManager(boundBroadcast, db, undefined, project.path, {
|
|
254
272
|
zombieTimeoutMs: projectZombieTimeout,
|
|
255
273
|
provider: project.provider ?? 'claude',
|
|
@@ -318,6 +336,26 @@ class ProjectRegistry {
|
|
|
318
336
|
timestamp: ticket.updated_at,
|
|
319
337
|
});
|
|
320
338
|
}
|
|
339
|
+
// Jira write-back (inert for non-Jira projects): enqueue the status
|
|
340
|
+
// transition + completion comment per linked ticket. The LOCAL mutation
|
|
341
|
+
// above stays synchronous; only the durable outbox enqueue happens here,
|
|
342
|
+
// wrapped so a Jira failure can never break the job-exit handler.
|
|
343
|
+
if (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated') {
|
|
344
|
+
try {
|
|
345
|
+
const needsReviewIds = completedTicketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
|
|
346
|
+
jiraSyncManager.onJobOutcome({
|
|
347
|
+
ticketIds: completedTicketIds,
|
|
348
|
+
status,
|
|
349
|
+
jobId,
|
|
350
|
+
costUsd: costUsd ?? null,
|
|
351
|
+
durationMs: jobRow?.duration_ms ?? null,
|
|
352
|
+
needsReviewIds,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
console.error('[project-registry] jira onJobOutcome failed:', err);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
321
359
|
}
|
|
322
360
|
catch (err) {
|
|
323
361
|
console.error('[project-registry] failed to apply job outcome to tickets:', err);
|
|
@@ -425,6 +463,10 @@ class ProjectRegistry {
|
|
|
425
463
|
}
|
|
426
464
|
const ticketWatcher = new ticket_watcher_1.TicketWatcher(project.path, project.id, boundBroadcast);
|
|
427
465
|
ticketWatcher.start();
|
|
466
|
+
// Suppress the file-watcher echo for the Jira sync's own writes (the every-60s
|
|
467
|
+
// poll would otherwise trigger a full-board refresh = flicker). Late-bound
|
|
468
|
+
// because the JiraSyncManager is constructed before the watcher.
|
|
469
|
+
jiraSyncManager.setLocalWriteNotifier((rev) => ticketWatcher.notifyDesktopWrite(rev));
|
|
428
470
|
// BrowserCaptureManager — "Add Spec from browser". Constructed for every
|
|
429
471
|
// project regardless of the feature flag; the routes + WS endpoint 404 when
|
|
430
472
|
// the flag is off, and the persistent Chromium context is launched lazily on
|
|
@@ -435,7 +477,7 @@ class ProjectRegistry {
|
|
|
435
477
|
db,
|
|
436
478
|
broadcast: boundBroadcast,
|
|
437
479
|
});
|
|
438
|
-
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, broadcast: boundBroadcast, railJobs };
|
|
480
|
+
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs };
|
|
439
481
|
this._contexts.set(project.id, ctx);
|
|
440
482
|
return ctx;
|
|
441
483
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerChatRoutes = registerChatRoutes;
|
|
4
|
+
const ids_1 = require("./ids");
|
|
5
|
+
const db_1 = require("./db");
|
|
6
|
+
const context_scope_1 = require("./context-scope");
|
|
7
|
+
const spec_models_1 = require("./spec-models");
|
|
8
|
+
const provider_selection_1 = require("./provider-selection");
|
|
9
|
+
const ticket_store_1 = require("./ticket-store");
|
|
10
|
+
const project_router_helpers_1 = require("./project-router-helpers");
|
|
11
|
+
function registerChatRoutes(deps) {
|
|
12
|
+
const { router, registry, ctx, ticketPath } = deps;
|
|
13
|
+
// ─── Chat routes ─────────────────────────────────────────────────────────────
|
|
14
|
+
router.get('/:projectId/chat/conversations', (req, res) => {
|
|
15
|
+
const conversations = (0, db_1.listConversations)(ctx(req).db);
|
|
16
|
+
res.json({ conversations });
|
|
17
|
+
});
|
|
18
|
+
router.post('/:projectId/chat/conversations', (req, res) => {
|
|
19
|
+
const { db, project } = ctx(req);
|
|
20
|
+
// Multi-provider: an optional aiEngine (alias: provider) picks which engine
|
|
21
|
+
// this conversation runs on. It must be installed on the project; omitting
|
|
22
|
+
// it uses the project's primary provider. The chosen provider drives model
|
|
23
|
+
// validation and is persisted on the conversation so resume turns and
|
|
24
|
+
// ai_invocations attribute to the right engine.
|
|
25
|
+
const requestedEngine = req.body?.aiEngine ?? req.body?.provider;
|
|
26
|
+
const engineCheck = (0, provider_selection_1.validateRequestedProvider)(project, requestedEngine);
|
|
27
|
+
if (!engineCheck.ok) {
|
|
28
|
+
res.status(400).json({ error: engineCheck.error });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const provider = engineCheck.provider;
|
|
32
|
+
const rawModel = req.body?.model;
|
|
33
|
+
let model;
|
|
34
|
+
if (rawModel === undefined || rawModel === null || rawModel === '') {
|
|
35
|
+
model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
|
|
36
|
+
}
|
|
37
|
+
else if ((0, spec_models_1.isValidModelForProvider)(rawModel, provider)) {
|
|
38
|
+
model = rawModel;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
res.status(400).json({
|
|
42
|
+
error: `Invalid model "${String(rawModel)}" for provider "${provider}"`,
|
|
43
|
+
allowed: (0, spec_models_1.getModelsForProvider)(provider).map((m) => m.value),
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const rawKind = req.body?.kind;
|
|
48
|
+
const kind = rawKind === 'explore' ? 'explore' : 'sidebar';
|
|
49
|
+
const id = (0, ids_1.newId)();
|
|
50
|
+
const rawScope = req.body?.contextScope;
|
|
51
|
+
if (rawScope !== undefined && kind !== 'explore') {
|
|
52
|
+
res.status(400).json({ error: 'contextScope is only allowed for kind=explore' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let scope;
|
|
56
|
+
if (kind === 'explore') {
|
|
57
|
+
const fallback = (0, context_scope_1.getLastContextScope)(db, 'explore');
|
|
58
|
+
// Defence-in-depth: SMASH / Contract Layer is Claude-only. Strip
|
|
59
|
+
// contractRefine from the scope when the conversation's resolved provider
|
|
60
|
+
// is non-Claude so no downstream code (Contract Refine Runner, SMASH
|
|
61
|
+
// eligibility) ever sees a mismatched flag.
|
|
62
|
+
const safeRawScope = provider !== 'claude' && rawScope != null
|
|
63
|
+
? { ...rawScope, contractRefine: false }
|
|
64
|
+
: rawScope;
|
|
65
|
+
scope = (0, context_scope_1.normalizeContextScope)(safeRawScope ?? fallback, fallback);
|
|
66
|
+
(0, context_scope_1.setLastContextScope)(db, scope);
|
|
67
|
+
console.log(`[project-router] new explore conv ${id} provider=${provider} scope=${JSON.stringify(scope)} rawScope=${JSON.stringify(rawScope)}`);
|
|
68
|
+
}
|
|
69
|
+
// Only persist provider when the project is multi-provider; single-provider
|
|
70
|
+
// projects leave it NULL so behaviour is byte-identical to before.
|
|
71
|
+
const persistProvider = (0, provider_selection_1.isMultiProvider)(project) ? provider : null;
|
|
72
|
+
(0, db_1.createConversation)(db, { id, model, kind, contextScope: scope, provider: persistProvider });
|
|
73
|
+
const conversation = (0, db_1.getConversation)(db, id);
|
|
74
|
+
res.status(201).json({ conversation });
|
|
75
|
+
});
|
|
76
|
+
router.get('/:projectId/chat/conversations/:id', (req, res) => {
|
|
77
|
+
const { db } = ctx(req);
|
|
78
|
+
const conversation = (0, db_1.getConversation)(db, req.params.id);
|
|
79
|
+
if (!conversation) {
|
|
80
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const messages = (0, db_1.getMessages)(db, req.params.id);
|
|
84
|
+
res.json({ conversation, messages });
|
|
85
|
+
});
|
|
86
|
+
router.delete('/:projectId/chat/conversations/:id', (req, res) => {
|
|
87
|
+
const { db, chatManager, broadcast, project, ticketWatcher } = ctx(req);
|
|
88
|
+
const convId = req.params.id;
|
|
89
|
+
const conversation = (0, db_1.getConversation)(db, convId);
|
|
90
|
+
if (!conversation) {
|
|
91
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
(0, db_1.deleteConversation)(db, convId);
|
|
95
|
+
chatManager?.forgetSpecDraft(convId);
|
|
96
|
+
chatManager?.forgetExploreLifecycle(convId);
|
|
97
|
+
// Cascade-clear origin_conversation_id on any ticket that referenced this
|
|
98
|
+
// conversation (application-level "ON DELETE SET NULL").
|
|
99
|
+
try {
|
|
100
|
+
const filePath = ticketPath(req);
|
|
101
|
+
const store = (0, ticket_store_1.mutateStore)(filePath, (s) => {
|
|
102
|
+
for (const id of Object.keys(s.tickets)) {
|
|
103
|
+
if (s.tickets[id].origin_conversation_id === convId) {
|
|
104
|
+
s.tickets[id].origin_conversation_id = null;
|
|
105
|
+
s.tickets[id].updated_at = new Date().toISOString();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
ticketWatcher.notifyDesktopWrite(store.revision);
|
|
110
|
+
// No per-ticket broadcast: the cleared field is metadata-only and the
|
|
111
|
+
// board card visual treatment doesn't depend on it.
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error('[project-router] conversation-cascade ticket update error:', err);
|
|
115
|
+
}
|
|
116
|
+
res.json({ ok: true });
|
|
117
|
+
});
|
|
118
|
+
router.patch('/:projectId/chat/conversations/:id', (req, res) => {
|
|
119
|
+
const { db } = ctx(req);
|
|
120
|
+
const conversation = (0, db_1.getConversation)(db, req.params.id);
|
|
121
|
+
if (!conversation) {
|
|
122
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const { title, model } = req.body ?? {};
|
|
126
|
+
const patch = {};
|
|
127
|
+
if (title !== undefined)
|
|
128
|
+
patch.title = title;
|
|
129
|
+
if (model !== undefined)
|
|
130
|
+
patch.model = model;
|
|
131
|
+
(0, db_1.updateConversation)(db, req.params.id, patch);
|
|
132
|
+
const updated = (0, db_1.getConversation)(db, req.params.id);
|
|
133
|
+
res.json({ ok: true, conversation: updated });
|
|
134
|
+
});
|
|
135
|
+
router.get('/:projectId/chat/conversations/:id/messages', (req, res) => {
|
|
136
|
+
const { db } = ctx(req);
|
|
137
|
+
const conversation = (0, db_1.getConversation)(db, req.params.id);
|
|
138
|
+
if (!conversation) {
|
|
139
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const messages = (0, db_1.getMessages)(db, req.params.id);
|
|
143
|
+
res.json({ messages });
|
|
144
|
+
});
|
|
145
|
+
// Returns the in-memory spec-draft state Claude has accumulated for this
|
|
146
|
+
// conversation. Used by useSpecDraftStream on mount to rehydrate updates
|
|
147
|
+
// that were broadcast while the client wasn't subscribed (refresh /
|
|
148
|
+
// minimize-and-restore). Returns 200 with `null` draft when no state yet.
|
|
149
|
+
router.get('/:projectId/chat/conversations/:id/spec-draft', (req, res) => {
|
|
150
|
+
const { db, chatManager } = ctx(req);
|
|
151
|
+
const conversation = (0, db_1.getConversation)(db, req.params.id);
|
|
152
|
+
if (!conversation) {
|
|
153
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const state = chatManager.getSpecDraftState(req.params.id);
|
|
157
|
+
if (!state) {
|
|
158
|
+
res.json({ draft: null, ready: false, chips: [] });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
res.json({
|
|
162
|
+
draft: state.draft,
|
|
163
|
+
ready: state.ready,
|
|
164
|
+
chips: state.chips,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
router.post('/:projectId/chat/conversations/:id/messages', async (req, res) => {
|
|
168
|
+
const { db, chatManager, project } = ctx(req);
|
|
169
|
+
const conversation = (0, db_1.getConversation)(db, req.params.id);
|
|
170
|
+
if (!conversation) {
|
|
171
|
+
res.status(404).json({ error: 'Conversation not found' });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const text = req.body?.text;
|
|
175
|
+
if (!text || !text.trim()) {
|
|
176
|
+
res.status(400).json({ error: 'text is required' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (chatManager.isActive(req.params.id)) {
|
|
180
|
+
res.status(409).json({ error: 'CONVERSATION_BUSY' });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const lightweight = req.body?.lightweight === true;
|
|
184
|
+
const maxTurns = typeof req.body?.maxTurns === 'number' ? req.body.maxTurns : undefined;
|
|
185
|
+
let attachments;
|
|
186
|
+
const rawAtt = req.body?.attachments;
|
|
187
|
+
if (rawAtt && typeof rawAtt === 'object' && typeof rawAtt.ticketKey === 'string'
|
|
188
|
+
&& Array.isArray(rawAtt.ids)) {
|
|
189
|
+
const ids = rawAtt.ids.filter((x) => typeof x === 'string');
|
|
190
|
+
if (ids.length > 0) {
|
|
191
|
+
attachments = { slug: project.slug, ticketKey: rawAtt.ticketKey, ids };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
res.status(202).json({ ok: true });
|
|
195
|
+
chatManager.sendMessage(req.params.id, text.trim(), { lightweight, maxTurns, attachments }).catch((err) => {
|
|
196
|
+
console.error('[project-router] chat sendMessage error:', err);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
router.delete('/:projectId/chat/conversations/:id/messages/stream', (req, res) => {
|
|
200
|
+
const { chatManager } = ctx(req);
|
|
201
|
+
if (!chatManager.isActive(req.params.id)) {
|
|
202
|
+
res.status(404).json({ error: 'No active stream for this conversation' });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
chatManager.abort(req.params.id);
|
|
206
|
+
res.json({ ok: true });
|
|
207
|
+
});
|
|
208
|
+
// Explore Spec lifecycle: minimize-to-toast hint and restore-from-toast hint.
|
|
209
|
+
// Idempotent; does not mutate persistent state. See design.md D7.
|
|
210
|
+
router.post('/:projectId/chat/conversations/:id/minimize', (req, res) => {
|
|
211
|
+
ctx(req).chatManager.notifyMinimized(req.params.id);
|
|
212
|
+
res.json({ ok: true });
|
|
213
|
+
});
|
|
214
|
+
router.post('/:projectId/chat/conversations/:id/restore', (req, res) => {
|
|
215
|
+
ctx(req).chatManager.notifyRestored(req.params.id);
|
|
216
|
+
res.json({ ok: true });
|
|
217
|
+
});
|
|
218
|
+
}
|