specrails-desktop 2.29.0 → 2.29.2
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-ClpD5CFj.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-CSPs9Ofc.js +1 -0
- package/client/dist/assets/AgentModeCodePane-DwcyklA5.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-C69bqlMa.js +1 -0
- package/client/dist/assets/AgentsPage-pRiRr9-W.js +86 -0
- package/client/dist/assets/AnalyticsPage-C14exKsC.js +1 -0
- package/client/dist/assets/BarChart-nBOMNfiy.js +33 -0
- package/client/dist/assets/CodePage-CK76JKm9.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-Cnv0f3XQ.js +1 -0
- package/client/dist/assets/DocsDialog-CHUm_Ym0.js +11 -0
- package/client/dist/assets/DocsPage-COI-oDok.js +11 -0
- package/client/dist/assets/ExportDropdown-BrtfWzEG.js +1 -0
- package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-CypM7R1F.js +19 -0
- package/client/dist/assets/JobDetailModal-B_W0H9dw.js +1 -0
- package/client/dist/assets/JobDetailPage-CeeXOoKS.js +1 -0
- package/client/dist/assets/JobsPage-CwhJG9jw.js +1 -0
- package/client/dist/assets/LoopBuilderPage-C51t1WOM.js +7 -0
- package/client/dist/assets/LoopPreviewModal-Y2HgtIJu.js +1 -0
- package/client/dist/assets/LoopsPage-Bvn1Kw5V.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-DiFvZDPW.js +1 -0
- package/client/dist/assets/PluginsPage-BGPRJpdf.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-D_GTyGLc.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-szwBhd7h.js +1 -0
- package/client/dist/assets/TicketDetailModal-9UEyK974.js +14 -0
- package/client/dist/assets/Trans-B_vKZ-Hb.js +1 -0
- package/client/dist/assets/activity-DiyjsgQV.js +1 -0
- package/client/dist/assets/addon-image-BRmmRHgQ.js +1 -0
- package/client/dist/assets/addon-ligatures-qIZUPvRV.js +2 -0
- package/client/dist/assets/{addon-webgl-DMbGeI_u.js → addon-webgl-C2d7lriQ.js} +3 -3
- package/client/dist/assets/addspec-Rw8uVBC1.js +1 -0
- package/client/dist/assets/agent-4rmXoo_p.js +1 -0
- package/client/dist/assets/agent-BxZuNGih.js +1 -0
- package/client/dist/assets/agent-C3uXOYv_.js +1 -0
- package/client/dist/assets/agent-CA8Y4wbw.js +1 -0
- package/client/dist/assets/agent-CASKrhG12.js +1 -0
- package/client/dist/assets/agent-Cv-jud5n2.js +1 -0
- package/client/dist/assets/agent-DitUxYh2.js +1 -0
- package/client/dist/assets/agent-Doow4X7o.js +1 -0
- package/client/dist/assets/agents-DhG7mg6-.js +1 -0
- package/client/dist/assets/agentstudio-BIUe1Im2.js +1 -0
- package/client/dist/assets/aiedit-iIjz2_Qk.js +1 -0
- package/client/dist/assets/analytics-DZ8Aoq3I.js +1 -0
- package/client/dist/assets/api-B2M7CtDw.js +1 -0
- package/client/dist/assets/arrow-left-CpIR4j7G.js +1 -0
- package/client/dist/assets/arrow-up-right-9DoMn7fD.js +1 -0
- package/client/dist/assets/attachments-B53PG733.js +1 -0
- package/client/dist/assets/brain-DI9lHkKY.js +1 -0
- package/client/dist/assets/browser-CXUcnTMI.js +1 -0
- package/client/dist/assets/button-BUIIBQPT.js +1 -0
- package/client/dist/assets/chat-BGD7MXAy.js +1 -0
- package/client/dist/assets/chevron-right-CViMxucY.js +1 -0
- package/client/dist/assets/chunk-KS7C4IRE-Cx5sqopX.js +3 -0
- package/client/dist/assets/circle-x-CMYsN4ZH.js +1 -0
- package/client/dist/assets/code-BDXEM4ml.js +1 -0
- package/client/dist/assets/commands-DyUZQ1FI.js +1 -0
- package/client/dist/assets/common-CD1QHriA2.js +1 -0
- package/client/dist/assets/common-CwwPaRxx.js +1 -0
- package/client/dist/assets/common-DVQuhs-m.js +1 -0
- package/client/dist/assets/common-Dg2jaX7d.js +1 -0
- package/client/dist/assets/common-DuBwj6tK.js +1 -0
- package/client/dist/assets/common-MRj9MHcc.js +1 -0
- package/client/dist/assets/common-O4ZpnMLp.js +1 -0
- package/client/dist/assets/common-fyeF0v7C2.js +1 -0
- package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +1 -0
- package/client/dist/assets/{css.worker-Wv5dxAWO.js → css.worker-yKcq13f2.js} +13 -13
- package/client/dist/assets/{cssMode-DBHbkBvI.js → cssMode-BM4z9OX0.js} +1 -1
- package/client/dist/assets/dashboard-B_NkSHYm.js +1 -0
- package/client/dist/assets/dashboard-ClBb_3pq.js +1 -0
- package/client/dist/assets/dashboard-CpCmJvMB.js +1 -0
- package/client/dist/assets/dashboard-Czohu35O2.js +1 -0
- package/client/dist/assets/dashboard-DcjbD0UK.js +1 -0
- package/client/dist/assets/dashboard-Dvx5F3we.js +1 -0
- package/client/dist/assets/dashboard-R1bOPjBh2.js +1 -0
- package/client/dist/assets/dashboard-owcDucDi.js +1 -0
- package/client/dist/assets/dialog-CzIekCw0.js +45 -0
- package/client/dist/assets/dist-Cez5CRkX.js +1 -0
- package/client/dist/assets/dist-bVqfYmby.js +1 -0
- package/client/dist/assets/{dist-js-UjXbEWhN.js → dist-js-CcBCbb32.js} +1 -1
- package/client/dist/assets/{dist-js-BTAcxhPb.js → dist-js-VwztRkGj.js} +1 -1
- package/client/dist/assets/download-CFHyyHcw.js +1 -0
- package/client/dist/assets/editor.api2-4ndOupAA.js +872 -0
- package/client/dist/assets/{editor.main-C9J2ikJK.js → editor.main-DxrmGfoq.js} +2 -2
- package/client/dist/assets/editor.worker-tytyn86m.js +26 -0
- package/client/dist/assets/explore-DgBACn39.js +1 -0
- package/client/dist/assets/external-link-BcU5EWZp.js +1 -0
- package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
- package/client/dist/assets/formatDistanceToNow-ByHIauTQ.js +1 -0
- package/client/dist/assets/{freemarker2-J5iIIIK-.js → freemarker2-CELlTDJ2.js} +1 -1
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-BxHAksaU.js +1 -0
- package/client/dist/assets/git-branch-BRM0F5GN.js +1 -0
- package/client/dist/assets/{handlebars-DSw1l_tw.js → handlebars-NEjHJ7o_.js} +1 -1
- package/client/dist/assets/{html-h2-wE1Lj.js → html-CA10zN5A.js} +1 -1
- package/client/dist/assets/{html.worker-CQP8QQsS.js → html.worker-DSJ9zmd7.js} +12 -12
- package/client/dist/assets/{htmlMode-Cc_ij7lW.js → htmlMode-89rPYiyq.js} +1 -1
- package/client/dist/assets/i18n-CgaRxpII.js +2 -0
- package/client/dist/assets/index-BflYQpWm.css +2 -0
- package/client/dist/assets/index-C1_-BTXy.js +72 -0
- package/client/dist/assets/integrations-mB-7zoyT.js +1 -0
- package/client/dist/assets/{javascript-C1q3xv6N.js → javascript-B1gDmwzS.js} +1 -1
- package/client/dist/assets/jira-CmKWERE0.js +1 -0
- package/client/dist/assets/jira-api-BgFIr8eD.js +1 -0
- package/client/dist/assets/job-time-BCL7wee9.js +1 -0
- package/client/dist/assets/jobs-B-sDHEay2.js +1 -0
- package/client/dist/assets/jobs-BCtGfgUA.js +1 -0
- package/client/dist/assets/jobs-BHR_EnDI.js +1 -0
- package/client/dist/assets/jobs-BMbZJjJJ.js +1 -0
- package/client/dist/assets/jobs-CVFD8rar.js +1 -0
- package/client/dist/assets/jobs-DoSaFzAa.js +1 -0
- package/client/dist/assets/jobs-DzSIR8V62.js +1 -0
- package/client/dist/assets/jobs-lk1_Dq1U.js +1 -0
- package/client/dist/assets/json.worker-Oa9UrkKE.js +58 -0
- package/client/dist/assets/{jsonMode-CeaCdEhr.js → jsonMode-xHLMfzhF.js} +1 -1
- package/client/dist/assets/jsx-runtime-zu2_FqZY.js +1 -0
- package/client/dist/assets/lib-C7Mhm-gT.js +7 -0
- package/client/dist/assets/lib-CGdyxIWg.js +29 -0
- package/client/dist/assets/{liquid-eFsVK0V-.js → liquid-BY6hCUXG.js} +1 -1
- package/client/dist/assets/loader-circle-DcygOQpC.js +1 -0
- package/client/dist/assets/loops-BzfXWEwO.js +1 -0
- package/client/dist/assets/{lspLanguageFeatures-DT3-bbhn.js → lspLanguageFeatures-ira2aEJh.js} +1 -1
- package/client/dist/assets/mcp-uDkXYu4L.js +1 -0
- package/client/dist/assets/{mdx-Fuwnh1cX.js → mdx-BNx0xr3N.js} +1 -1
- package/client/dist/assets/monaco.contribution-Bt92R3Vw.js +2 -0
- package/client/dist/assets/nav-myFaXRHD.js +1 -0
- package/client/dist/assets/pencil-C9RkrIPZ.js +1 -0
- package/client/dist/assets/play-DNlrawbS.js +1 -0
- package/client/dist/assets/plus-CsnjfkmX.js +1 -0
- package/client/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/client/dist/assets/{python-BgoEs6Cj.js → python-DLdbtmNw.js} +1 -1
- package/client/dist/assets/{razor-CM1sPMMq.js → razor-B9gdhAbR.js} +1 -1
- package/client/dist/assets/react-dom-DiWzBDP9.js +1 -0
- package/client/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/client/dist/assets/save-28sIlCdr.js +1 -0
- package/client/dist/assets/search-Cq_gE3Ik.js +1 -0
- package/client/dist/assets/settings-B0ypw9uw.js +1 -0
- package/client/dist/assets/{settings-Bp24JHlv2.js → settings-B9ilOSYO.js} +1 -1
- package/client/dist/assets/{settings-Bx2ObkTR2.js → settings-BqlWHRHA.js} +1 -1
- package/client/dist/assets/{settings-C_T0-j9d.js → settings-D8dlqt4d.js} +1 -1
- package/client/dist/assets/{settings-DhaFKpcw2.js → settings-DAZqjX2a2.js} +1 -1
- package/client/dist/assets/{settings-B_v3zgJf.js → settings-DRh39v1S2.js} +1 -1
- package/client/dist/assets/{settings-BemR_e3F.js → settings-Mj1OJbfd2.js} +1 -1
- package/client/dist/assets/{settings-BvIwQ3A1.js → settings-lRReaSYm.js} +1 -1
- package/client/dist/assets/setup-CmK87LuS.js +1 -0
- package/client/dist/assets/sparkles-DrY8hb0X.js +1 -0
- package/client/dist/assets/specs-DII0C8gw.js +1 -0
- package/client/dist/assets/spending-C4p06nen.js +1 -0
- package/client/dist/assets/terminal-BFRZnMPP.js +1 -0
- package/client/dist/assets/tickets-B8BuAaj5.js +1 -0
- package/client/dist/assets/tickets-B_yh4qQk.js +1 -0
- package/client/dist/assets/tickets-Bs37scwY2.js +1 -0
- package/client/dist/assets/tickets-CFk2n2Or.js +1 -0
- package/client/dist/assets/tickets-DQp7JS2R2.js +1 -0
- package/client/dist/assets/tickets-D_yDquEw.js +1 -0
- package/client/dist/assets/tickets-DpmdE8tv2.js +1 -0
- package/client/dist/assets/tickets-pAUJkDTu.js +1 -0
- package/client/dist/assets/trash-2-BOU3WdZZ.js +1 -0
- package/client/dist/assets/triangle-alert-H9T4jAmh.js +1 -0
- package/client/dist/assets/{ts.worker-METxwbDZ.js → ts.worker-BtjBFNHX.js} +48 -33
- package/client/dist/assets/{tsMode-BmT8WogI.js → tsMode-DEjSThU9.js} +1 -1
- package/client/dist/assets/{typescript-CQrQPsoW.js → typescript-CRRnD2Ts.js} +1 -1
- package/client/dist/assets/upload-DYMjAQ6M.js +1 -0
- package/client/dist/assets/useBrowserCaptureSession-2OqX0vap.js +1 -0
- package/client/dist/assets/useDesktop-X8YB9xzv.js +1 -0
- package/client/dist/assets/useSharedWebSocket-DaPh7QBL.js +1 -0
- package/client/dist/assets/utils-BC4DHlz2.js +1 -0
- package/client/dist/assets/value-s-gzBeyF.js +1 -0
- package/client/dist/assets/{workers-U0UXyLUf.js → workers-uOo7WLaa.js} +1 -1
- package/client/dist/assets/x-CBMI9ejn.js +1 -0
- package/client/dist/assets/{xml-WRxJs8_I.js → xml-EDREH2X7.js} +1 -1
- package/client/dist/assets/{yaml-CjpSTjJU.js → yaml-BjaDVX1E.js} +1 -1
- package/client/dist/assets/zap-CHo3aCD1.js +1 -0
- package/client/dist/index.html +73 -32
- package/docs/guide/en/integrations/5-mcp-server.md +2 -2
- package/docs/internals/safe-pr-review-flow.md +190 -132
- package/docs/mcp.md +15 -5
- package/package.json +11 -7
- package/server/dist/active-pr-continuation.js +16 -1
- package/server/dist/agent-chat-manager.js +360 -181
- package/server/dist/agent-cwd-manager.js +52 -8
- package/server/dist/agent-generator.js +90 -48
- package/server/dist/agent-mcp-config.js +69 -36
- package/server/dist/agent-operator-prompt.js +3 -2
- package/server/dist/agent-refine-manager.js +47 -6
- package/server/dist/agent-tier.js +13 -11
- package/server/dist/artifact-registry.js +33 -2
- package/server/dist/auth.js +5 -4
- package/server/dist/browser-capture-manager.js +92 -29
- package/server/dist/chat-manager.js +302 -46
- package/server/dist/contract-refine-runner.js +19 -6
- package/server/dist/db.js +665 -8
- package/server/dist/desktop-router.js +1 -2
- package/server/dist/headroom-manager.js +1310 -121
- package/server/dist/headroom-routing.js +120 -2
- package/server/dist/index.js +48 -13
- package/server/dist/interactive-job-session.js +237 -82
- package/server/dist/jira/jira-adf.js +10 -0
- package/server/dist/jira/jira-db.js +15 -3
- package/server/dist/jira/jira-sync-manager.js +182 -13
- package/server/dist/job-listing.js +140 -0
- package/server/dist/job-spawn-idempotency.js +67 -0
- package/server/dist/loop-run-manager.js +508 -98
- package/server/dist/loop-runs-store.js +266 -13
- package/server/dist/mcp/agent-capability.js +71 -0
- package/server/dist/mcp/guide.js +5 -2
- package/server/dist/mcp/mcp-server.js +5 -10
- package/server/dist/mcp/mcp-token.js +5 -5
- package/server/dist/mcp/tools/jobs.js +20 -13
- package/server/dist/mcp/tools/types.js +20 -42
- package/server/dist/mobile/mobile-router.js +1 -1
- package/server/dist/pr-lifecycle.js +77 -0
- package/server/dist/pr-publisher.js +94 -27
- package/server/dist/process-admission.js +82 -0
- package/server/dist/project-registry.js +572 -174
- package/server/dist/project-router-git.js +13 -2
- package/server/dist/project-router-jobs.js +111 -55
- package/server/dist/project-router-tickets.js +32 -2
- package/server/dist/providers/claude-adapter.js +21 -13
- package/server/dist/queue-manager.js +2332 -623
- package/server/dist/rail-isolated-launch.js +1141 -280
- package/server/dist/rail-isolation.js +4 -2
- package/server/dist/rail-pr-decision.js +756 -270
- package/server/dist/rail-pr-delivery.js +2 -1
- package/server/dist/rail-pr-store.js +317 -18
- package/server/dist/rail-pr-ticket-effects.js +328 -0
- package/server/dist/rail-worktree-release.js +28 -6
- package/server/dist/rails-router.js +149 -112
- package/server/dist/rails-store.js +106 -3
- package/server/dist/ticket-store.js +23 -1
- package/server/dist/transient-children.js +13 -0
- package/server/dist/util/cli-prompt.js +42 -8
- package/server/dist/util/secure-token-file.js +84 -0
- package/server/dist/worktree-manager.js +70 -21
- package/client/dist/assets/ActivityFeedPage-Be-6L0Ld.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-CpZ-dd8O.js +0 -1
- package/client/dist/assets/AgentIntegrationsModal-DO1pN_Ez.js +0 -1
- package/client/dist/assets/AgentModeCodePane-DLltTOqp.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-Dp5YvmW0.js +0 -1
- package/client/dist/assets/AgentsPage-ehHuVUds.js +0 -86
- package/client/dist/assets/AnalyticsPage-C4Ik95FX.js +0 -1
- package/client/dist/assets/BarChart-DVZgrhJd.js +0 -33
- package/client/dist/assets/CodePage-D4OCMfxL.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-2Xf-52x2.js +0 -1
- package/client/dist/assets/DocsDialog-CHihLjWd.js +0 -11
- package/client/dist/assets/DocsPage-1vEk7XbH.js +0 -11
- package/client/dist/assets/ExportDropdown-DLHFQxVn.js +0 -1
- package/client/dist/assets/IntegrationsPage-CCb2YuyJ.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-CxJJEnjw.js +0 -19
- package/client/dist/assets/JobDetailModal-CJI7qRkm.js +0 -1
- package/client/dist/assets/JobDetailPage-DwA9bbwx.js +0 -1
- package/client/dist/assets/JobsPage-BDIyFfd5.js +0 -1
- package/client/dist/assets/LoopBuilderPage-CpBaW59t.js +0 -7
- package/client/dist/assets/LoopPreviewModal-CwpKcv0Y.js +0 -1
- package/client/dist/assets/LoopsPage-BO0Pa8oz.js +0 -1
- package/client/dist/assets/PluginsPage-BBGTBpGh.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-M9zGQo0l.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-eBI0zL5P.js +0 -1
- package/client/dist/assets/activity-Dbl71S-E.js +0 -1
- package/client/dist/assets/addon-image-CwoZj17F.js +0 -1
- package/client/dist/assets/addon-ligatures-CWDrED80.js +0 -2
- package/client/dist/assets/addspec-DrXqP8ip.js +0 -1
- package/client/dist/assets/agent-BEXYxDbs.js +0 -1
- package/client/dist/assets/agent-BoXSzaBk2.js +0 -1
- package/client/dist/assets/agent-C2-rROL52.js +0 -1
- package/client/dist/assets/agent-CeRTdGT12.js +0 -1
- package/client/dist/assets/agent-DZ8zTrQT.js +0 -1
- package/client/dist/assets/agent-DqyzT33X.js +0 -1
- package/client/dist/assets/agent-fQI_6tsA.js +0 -1
- package/client/dist/assets/agent-tJdAobaM.js +0 -1
- package/client/dist/assets/agents-CGDn_3kB.js +0 -1
- package/client/dist/assets/agentstudio-Dxe_94PK.js +0 -1
- package/client/dist/assets/aiedit-DrfHQnlz.js +0 -1
- package/client/dist/assets/analytics-CxDYcBl9.js +0 -1
- package/client/dist/assets/attachments-DvxQbBTC.js +0 -1
- package/client/dist/assets/brain-DecvjjPU.js +0 -1
- package/client/dist/assets/browser-DM8G151q.js +0 -1
- package/client/dist/assets/chat-0dg5hOo_.js +0 -1
- package/client/dist/assets/chunk-BNv3lrIs.js +0 -1
- package/client/dist/assets/code-B62fClk-.js +0 -1
- package/client/dist/assets/commands-DCJ-R9E4.js +0 -1
- package/client/dist/assets/common-BfQD173j.js +0 -1
- package/client/dist/assets/common-CF8iGXrG.js +0 -1
- package/client/dist/assets/common-Cfmwcrl52.js +0 -1
- package/client/dist/assets/common-D2cimsul.js +0 -1
- package/client/dist/assets/common-DH7fjyL1.js +0 -1
- package/client/dist/assets/common-DXrmj6WD.js +0 -1
- package/client/dist/assets/common-DZbXlmq72.js +0 -1
- package/client/dist/assets/common-DtXmzX2o2.js +0 -1
- package/client/dist/assets/dashboard-5C2FOZQX.js +0 -1
- package/client/dist/assets/dashboard-B6iQ-IL_.js +0 -1
- package/client/dist/assets/dashboard-BFfnCtq52.js +0 -1
- package/client/dist/assets/dashboard-BIC8KtRf2.js +0 -1
- package/client/dist/assets/dashboard-CEgg9NAr.js +0 -1
- package/client/dist/assets/dashboard-CiOyDh9L.js +0 -1
- package/client/dist/assets/dashboard-D6x5OnNj2.js +0 -1
- package/client/dist/assets/dashboard-DBuLfWo9.js +0 -1
- package/client/dist/assets/editor.api2-C3B0tp2x.js +0 -872
- package/client/dist/assets/editor.worker-Bd9IXS8d.js +0 -26
- package/client/dist/assets/explore-_XyU5e7R.js +0 -1
- package/client/dist/assets/index-BVbgKXUu.css +0 -2
- package/client/dist/assets/index-OBDI7bHm.js +0 -158
- package/client/dist/assets/integrations-By80EBkf.js +0 -1
- package/client/dist/assets/jira-DeIjwCK3.js +0 -1
- package/client/dist/assets/jobs-BFIdeDTw.js +0 -1
- package/client/dist/assets/jobs-BJ-Gcao22.js +0 -1
- package/client/dist/assets/jobs-DAGiPVIS2.js +0 -1
- package/client/dist/assets/jobs-DAU8C_tZ.js +0 -1
- package/client/dist/assets/jobs-Da0GaaSc.js +0 -1
- package/client/dist/assets/jobs-DrYmsKy5.js +0 -1
- package/client/dist/assets/jobs-P3unxtxo2.js +0 -1
- package/client/dist/assets/jobs-p360zdf1.js +0 -1
- package/client/dist/assets/json.worker-DzV-CpCQ.js +0 -58
- package/client/dist/assets/lib-C5ZxrPSR.js +0 -7
- package/client/dist/assets/loops-CO6q75W8.js +0 -1
- package/client/dist/assets/mcp-HwBbQDk0.js +0 -1
- package/client/dist/assets/monaco.contribution-Desq7oq4.js +0 -2
- package/client/dist/assets/nav-BTg8s-cD.js +0 -1
- package/client/dist/assets/preload-helper-DSXbuxSR.js +0 -1
- package/client/dist/assets/settings-BYCMtxOz.js +0 -1
- package/client/dist/assets/setup-D2CmgAro.js +0 -1
- package/client/dist/assets/specs-PGIOzMtc.js +0 -1
- package/client/dist/assets/terminal-CFt56A5f.js +0 -1
- package/client/dist/assets/tickets-BHv3ewvq.js +0 -1
- package/client/dist/assets/tickets-BIxU86j12.js +0 -1
- package/client/dist/assets/tickets-C2BhwzwR.js +0 -1
- package/client/dist/assets/tickets-D1IeX2EW.js +0 -1
- package/client/dist/assets/tickets-DZut8Ryt.js +0 -1
- package/client/dist/assets/tickets-LfK6LzKl2.js +0 -1
- package/client/dist/assets/tickets-dQfsc7Ph.js +0 -1
- package/client/dist/assets/tickets-sVun0I6v.js +0 -1
- package/client/dist/assets/upload-lgjKJgrl.js +0 -1
- package/client/dist/assets/value-Bc0_jlJA.js +0 -1
- /package/client/dist/assets/{abap-D0E4FfVW.js → abap-08VXUWAP.js} +0 -0
- /package/client/dist/assets/{activity-BbWiDfn-2.js → activity-B-rp4h_v.js} +0 -0
- /package/client/dist/assets/{activity-DvECTYGt.js → activity-C6kJpm9q2.js} +0 -0
- /package/client/dist/assets/{activity-D3Ic-HTS.js → activity-CGx4kb_j.js} +0 -0
- /package/client/dist/assets/{activity-BZZlEG3a.js → activity-CnoX2bH7.js} +0 -0
- /package/client/dist/assets/{activity-DtzRy92x.js → activity-DAyekdlt2.js} +0 -0
- /package/client/dist/assets/{activity-FleOlK0l2.js → activity-DCxtpT7j.js} +0 -0
- /package/client/dist/assets/{activity-DFbvNqhE2.js → activity-JzwRaWPD.js} +0 -0
- /package/client/dist/assets/{addspec-CFxGvmJR2.js → addspec-BXjvkh5q.js} +0 -0
- /package/client/dist/assets/{addspec-NBbcTbLc.js → addspec-C4bwrmpi.js} +0 -0
- /package/client/dist/assets/{addspec-BKrH2Ucz.js → addspec-CArHXUDM.js} +0 -0
- /package/client/dist/assets/{addspec-CR8MB5Fr2.js → addspec-DTKJcXiR.js} +0 -0
- /package/client/dist/assets/{addspec-B_LQMHW3.js → addspec-DbNBbrha2.js} +0 -0
- /package/client/dist/assets/{addspec-DaA-CbTt.js → addspec-htHGZHOe2.js} +0 -0
- /package/client/dist/assets/{addspec-Bw_YQwo62.js → addspec-lr_LP6jk.js} +0 -0
- /package/client/dist/assets/{agents-BHgn--3j.js → agents-6ZOJXWVk2.js} +0 -0
- /package/client/dist/assets/{agents-VsyFhGOS2.js → agents-BZ0XQKjg.js} +0 -0
- /package/client/dist/assets/{agents-HFCqQH0Z.js → agents-BqWklnHR.js} +0 -0
- /package/client/dist/assets/{agents-B_qGaB0F.js → agents-Bz-thHkB2.js} +0 -0
- /package/client/dist/assets/{agents-CP8_ezYG.js → agents-CJclElx6.js} +0 -0
- /package/client/dist/assets/{agents-B9pFSJQI2.js → agents-D_l9L0VA.js} +0 -0
- /package/client/dist/assets/{agents-CV-_xBvX2.js → agents-u_C0jPB5.js} +0 -0
- /package/client/dist/assets/{agentstudio-BEp7Sb46.js → agentstudio-BF19hbXj.js} +0 -0
- /package/client/dist/assets/{agentstudio-Br3WD7Xs.js → agentstudio-BLGvxWBx2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BOjG6tgJ.js → agentstudio-D9UKiWTj2.js} +0 -0
- /package/client/dist/assets/{agentstudio-D2A5peWV2.js → agentstudio-Drs37nIM.js} +0 -0
- /package/client/dist/assets/{agentstudio-CuTt3ZGe2.js → agentstudio-nEnKkcXM.js} +0 -0
- /package/client/dist/assets/{agentstudio-RsxnZtUA.js → agentstudio-pyFcSBKy.js} +0 -0
- /package/client/dist/assets/{agentstudio-RfZavULt2.js → agentstudio-zfTzQh3U.js} +0 -0
- /package/client/dist/assets/{aiedit-DnBzjlVz2.js → aiedit-B0KUbpB6.js} +0 -0
- /package/client/dist/assets/{aiedit-D0Syl-mC.js → aiedit-BoG24nlg2.js} +0 -0
- /package/client/dist/assets/{aiedit-CcugGPbS.js → aiedit-CjlSRQVP.js} +0 -0
- /package/client/dist/assets/{aiedit-DrIjSojo.js → aiedit-DCjA7jLJ.js} +0 -0
- /package/client/dist/assets/{aiedit-CV520D4W2.js → aiedit-DNXxq_Dr.js} +0 -0
- /package/client/dist/assets/{aiedit-26p5A_HY2.js → aiedit-DQ-0_pvE.js} +0 -0
- /package/client/dist/assets/{aiedit-a-Z-g5iC.js → aiedit-olnv3QSt2.js} +0 -0
- /package/client/dist/assets/{analytics-DEe5BMz8.js → analytics-BC7mwR4s.js} +0 -0
- /package/client/dist/assets/{analytics-4HvXb0Vn2.js → analytics-BiNg8edP.js} +0 -0
- /package/client/dist/assets/{analytics-CpSTNfui.js → analytics-C0s_T2GN.js} +0 -0
- /package/client/dist/assets/{analytics-4-eskbtX.js → analytics-CdsAPAwZ2.js} +0 -0
- /package/client/dist/assets/{analytics-Bpe41fnX2.js → analytics-Cr1_q-lC.js} +0 -0
- /package/client/dist/assets/{analytics-DcOITHla2.js → analytics-IEsKZvpW2.js} +0 -0
- /package/client/dist/assets/{analytics-Bd9rHGjV.js → analytics-SRPf84OT.js} +0 -0
- /package/client/dist/assets/{apex-D_X_8J4d.js → apex-BWPQTe0t.js} +0 -0
- /package/client/dist/assets/{atom-one-dark-BMDPFwRZ.js → atom-one-dark-qiGhVQxg.js} +0 -0
- /package/client/dist/assets/{attachments-CgOQ9K-6.js → attachments-D0wXXt9T2.js} +0 -0
- /package/client/dist/assets/{attachments-DlEQu56g2.js → attachments-DenIjuVI.js} +0 -0
- /package/client/dist/assets/{attachments-CA2f3qL3.js → attachments-DhO7LPfy.js} +0 -0
- /package/client/dist/assets/{attachments-D2jwJXQ42.js → attachments-DoH5w0x4.js} +0 -0
- /package/client/dist/assets/{attachments-CHDbhKeH.js → attachments-Q9yNtiEM.js} +0 -0
- /package/client/dist/assets/{attachments-CKAY5aYG2.js → attachments-V_WZAYXx2.js} +0 -0
- /package/client/dist/assets/{attachments-jVdKzAXn.js → attachments-eaeScpjM.js} +0 -0
- /package/client/dist/assets/{auth-DpZq5p1e.js → auth-D_5bQv5W.js} +0 -0
- /package/client/dist/assets/{azcli-PZLnZ3jH.js → azcli-Bc_sGQ0U.js} +0 -0
- /package/client/dist/assets/{bat-KlBOxpO4.js → bat-i0X4ZdIN.js} +0 -0
- /package/client/dist/assets/{bicep-CgG9oF3V.js → bicep-B5-_aFwp.js} +0 -0
- /package/client/dist/assets/{browser-Cl-FwPXs.js → browser-C25DkQiO.js} +0 -0
- /package/client/dist/assets/{browser-JsiohNmW.js → browser-CiJ7-L32.js} +0 -0
- /package/client/dist/assets/{browser-BbJ_q_OT2.js → browser-CvgDySd9.js} +0 -0
- /package/client/dist/assets/{browser-BQFDxrRq.js → browser-CyR1Kp3m2.js} +0 -0
- /package/client/dist/assets/{browser-Do2jf8_N.js → browser-DwZljcRh2.js} +0 -0
- /package/client/dist/assets/{browser-C4VJ5CW12.js → browser-DxP-alBK.js} +0 -0
- /package/client/dist/assets/{browser-kA31eYhu2.js → browser-rRCwi7Y7.js} +0 -0
- /package/client/dist/assets/{cameligo-6R45j6Ph.js → cameligo-DMUM7wLl.js} +0 -0
- /package/client/dist/assets/{chat-BalviUAD.js → chat-2rIB8HhT.js} +0 -0
- /package/client/dist/assets/{chat-Ds8GvCnv.js → chat-BR8oe53x2.js} +0 -0
- /package/client/dist/assets/{chat-D5xEtsf3.js → chat-Bua03tty.js} +0 -0
- /package/client/dist/assets/{chat-SaG-ng1x2.js → chat-CZpAQan1.js} +0 -0
- /package/client/dist/assets/{chat-CJddjN3D2.js → chat-CdY5SGvS.js} +0 -0
- /package/client/dist/assets/{chat-n43nZGvn.js → chat-DZyu3ImY2.js} +0 -0
- /package/client/dist/assets/{chat-BMWD_Rx12.js → chat-D_I7nU5K.js} +0 -0
- /package/client/dist/assets/{clojure-CLAAMO7J.js → clojure-Cm7r79vr.js} +0 -0
- /package/client/dist/assets/{clsx-CNmW0Nip.js → clsx-CjueKrWZ.js} +0 -0
- /package/client/dist/assets/{code-C9U6M5-e2.js → code-76gS0IdL.js} +0 -0
- /package/client/dist/assets/{code-BotxM43N2.js → code-C1VwxlML.js} +0 -0
- /package/client/dist/assets/{code-HDK4P6Rx.js → code-CnMZyJpr.js} +0 -0
- /package/client/dist/assets/{code-DUe1Bouz.js → code-D20ef9Cf2.js} +0 -0
- /package/client/dist/assets/{code-Bit_z2Xd2.js → code-DdOXaWRY.js} +0 -0
- /package/client/dist/assets/{code-F_KjccGf.js → code-Duohuf9l.js} +0 -0
- /package/client/dist/assets/{code-CIPmDEd0.js → code-geHmknB92.js} +0 -0
- /package/client/dist/assets/{coffee-AruSygo-.js → coffee-Ba7i2nA0.js} +0 -0
- /package/client/dist/assets/{commands-BFQZc9AG.js → commands-B9RZO4qS.js} +0 -0
- /package/client/dist/assets/{commands-BMkC5-ki.js → commands-BEwbAjtD2.js} +0 -0
- /package/client/dist/assets/{commands-KBbhsnw8.js → commands-Bbxu8kLU.js} +0 -0
- /package/client/dist/assets/{commands-CzFSoIiq2.js → commands-CDU3qv68.js} +0 -0
- /package/client/dist/assets/{commands-CWX9WmDX2.js → commands-CzezgtQj.js} +0 -0
- /package/client/dist/assets/{commands-BllkJauO.js → commands-DTQBF_q52.js} +0 -0
- /package/client/dist/assets/{commands-cKTaLwwV2.js → commands-hOtKuOXc.js} +0 -0
- /package/client/dist/assets/{cpp-DEE5r4Pw.js → cpp-C7h46wYY.js} +0 -0
- /package/client/dist/assets/{csharp-BVXJUvbK.js → csharp-BKxtCVv1.js} +0 -0
- /package/client/dist/assets/{csp-cXOF0JoV.js → csp-bTuwJoIa.js} +0 -0
- /package/client/dist/assets/{css-C_zQuAoy.js → css-DIMkf-bt.js} +0 -0
- /package/client/dist/assets/{cypher-t95m_Q-O.js → cypher-CVaqCwHa.js} +0 -0
- /package/client/dist/assets/{dart-B4r1Tg-e.js → dart-onAF5SnQ.js} +0 -0
- /package/client/dist/assets/{dockerfile-6mt7zHQw.js → dockerfile-DZFCIeNp.js} +0 -0
- /package/client/dist/assets/{ecl-DEY_Fz_Q.js → ecl-D05T4iGw.js} +0 -0
- /package/client/dist/assets/{elixir-R6-q1c5t.js → elixir-6RTg0lbw.js} +0 -0
- /package/client/dist/assets/{explore-CAc8Qzu32.js → explore-BG99VGpL.js} +0 -0
- /package/client/dist/assets/{explore-BO_OinBD.js → explore-C0TjprJW2.js} +0 -0
- /package/client/dist/assets/{explore-Bhy7WWXv.js → explore-C2dgEo7q.js} +0 -0
- /package/client/dist/assets/{explore-ELbrSZyQ2.js → explore-C8NDHbob.js} +0 -0
- /package/client/dist/assets/{explore-DW_YTkQh.js → explore-CWWgzQGR.js} +0 -0
- /package/client/dist/assets/{explore-DCZL9gRN2.js → explore-CcQXpdtS.js} +0 -0
- /package/client/dist/assets/{explore-BEpIs343.js → explore-DsXAgnmJ2.js} +0 -0
- /package/client/dist/assets/{flow9-CNQvHBOt.js → flow9-C5_-GSwl.js} +0 -0
- /package/client/dist/assets/{format-command-Db1viey1.js → format-command-Dfa1o2d-.js} +0 -0
- /package/client/dist/assets/{fsharp-vPZkFXwv.js → fsharp-C8Ef5oNN.js} +0 -0
- /package/client/dist/assets/{go-KUzBMYJ_.js → go-C-y9NEjX.js} +0 -0
- /package/client/dist/assets/{graphql-C3W0rCsD.js → graphql-fmXr3nnJ.js} +0 -0
- /package/client/dist/assets/{hcl-BOOMFSNX.js → hcl-CpzslTdj.js} +0 -0
- /package/client/dist/assets/{ini-CFat1WJr.js → ini-sBoK_t0W.js} +0 -0
- /package/client/dist/assets/{integrations-CG2dp3Kd.js → integrations-CH0zy1Mw2.js} +0 -0
- /package/client/dist/assets/{integrations-CUNe3t9l2.js → integrations-CIAjhd7L.js} +0 -0
- /package/client/dist/assets/{integrations-CWjh5VRk.js → integrations-CZdwOe8A.js} +0 -0
- /package/client/dist/assets/{integrations-RJfzXkPx2.js → integrations-Ct7omMld.js} +0 -0
- /package/client/dist/assets/{integrations-CAjq5tdW2.js → integrations-D6MSxy3-.js} +0 -0
- /package/client/dist/assets/{integrations-JOHSCvB3.js → integrations-DbZa-Zyc.js} +0 -0
- /package/client/dist/assets/{integrations-CP_aiV_8.js → integrations-b8pH-vfl2.js} +0 -0
- /package/client/dist/assets/{java-Dee-CNAM.js → java-BEtHBSE6.js} +0 -0
- /package/client/dist/assets/{jira-CQ7mrZde.js → jira-BySfUuQZ.js} +0 -0
- /package/client/dist/assets/{jira-DIRkZk9U.js → jira-Byw2C0F8.js} +0 -0
- /package/client/dist/assets/{jira-C5UjxHE4.js → jira-CNeYi06w2.js} +0 -0
- /package/client/dist/assets/{jira-ZdKb-50F2.js → jira-D11gPhxz.js} +0 -0
- /package/client/dist/assets/{jira-DTYu3hJi2.js → jira-D38Gj7AT.js} +0 -0
- /package/client/dist/assets/{jira-B5wszJ1-2.js → jira-gAvOyije.js} +0 -0
- /package/client/dist/assets/{jira-DtZpokjJ.js → jira-pRaL7JQo2.js} +0 -0
- /package/client/dist/assets/{julia-CGNlLYuU.js → julia-Bri6UV-V.js} +0 -0
- /package/client/dist/assets/{kotlin-CY7lhknJ.js → kotlin-BOotOW0E.js} +0 -0
- /package/client/dist/assets/{less-D0KN7hfJ.js → less-B9JPFI3C.js} +0 -0
- /package/client/dist/assets/{lexon-CuRJufPs.js → lexon-CfSJPG6W.js} +0 -0
- /package/client/dist/assets/{loops-DKzYFcq6.js → loops-CPpgMapU.js} +0 -0
- /package/client/dist/assets/{loops-Du-bNVpc2.js → loops-CWWbKuVL.js} +0 -0
- /package/client/dist/assets/{loops-DDs4KPu_.js → loops-Crc9It8o2.js} +0 -0
- /package/client/dist/assets/{loops-Bi4ywYGn.js → loops-DH2hZcbK2.js} +0 -0
- /package/client/dist/assets/{loops-DvGbSfxw2.js → loops-DS5TmvIU2.js} +0 -0
- /package/client/dist/assets/{loops-D3wdUkZV.js → loops-P2f0cN8-.js} +0 -0
- /package/client/dist/assets/{loops-BjK8lmoZ2.js → loops-fJV-9hH7.js} +0 -0
- /package/client/dist/assets/{lua-DHvH62Zf.js → lua-CsQS60Ue.js} +0 -0
- /package/client/dist/assets/{m3-tlthQ8Fo.js → m3-D-oSqn_W.js} +0 -0
- /package/client/dist/assets/{markdown-Dbx1LgUZ.js → markdown-Cimd5fb3.js} +0 -0
- /package/client/dist/assets/{mcp-DfmzoT5t.js → mcp-Czd_iyp02.js} +0 -0
- /package/client/dist/assets/{mcp-BG-OCJca2.js → mcp-D2pc5GGx.js} +0 -0
- /package/client/dist/assets/{mcp-CrWdq5db.js → mcp-D4ZjtIx32.js} +0 -0
- /package/client/dist/assets/{mcp-CbJ04oFS2.js → mcp-DIfBUv2D2.js} +0 -0
- /package/client/dist/assets/{mcp-Dd844F5_.js → mcp-DsTwmjST.js} +0 -0
- /package/client/dist/assets/{mcp-zSJ2TsJj.js → mcp-SMkn8boG.js} +0 -0
- /package/client/dist/assets/{mcp-DyULurYS2.js → mcp-fSfiVYAc.js} +0 -0
- /package/client/dist/assets/{mips-D_O3IUz_.js → mips-CIPQ_RoX.js} +0 -0
- /package/client/dist/assets/{msdax-C_ZmCnRt.js → msdax-DauUninz.js} +0 -0
- /package/client/dist/assets/{mysql-C-dCEYSX.js → mysql-SOo6toE5.js} +0 -0
- /package/client/dist/assets/{nav-DUuw9VIU2.js → nav-BRJBxaGt2.js} +0 -0
- /package/client/dist/assets/{nav-D0Erv9ZJ2.js → nav-CAGKv3_F.js} +0 -0
- /package/client/dist/assets/{nav-BY_4EWRv.js → nav-CNskRJKQ2.js} +0 -0
- /package/client/dist/assets/{nav-CVXtv0wX2.js → nav-DY93irBL.js} +0 -0
- /package/client/dist/assets/{nav-B2_ToNqS.js → nav-DhjvYgqt.js} +0 -0
- /package/client/dist/assets/{nav-S4HM94vW.js → nav-Gb-r4wop.js} +0 -0
- /package/client/dist/assets/{nav-BAMkVEId.js → nav-TvumAFD22.js} +0 -0
- /package/client/dist/assets/{objective-c-DZY9Aqxd.js → objective-c-FvmIjYaQ.js} +0 -0
- /package/client/dist/assets/{pascal-BVHLXoor.js → pascal-DrH0SRf2.js} +0 -0
- /package/client/dist/assets/{pascaligo-XdG_6u3k.js → pascaligo-D-ptJ9y-.js} +0 -0
- /package/client/dist/assets/{perl-b9QV0BcD.js → perl-oz_6vUea.js} +0 -0
- /package/client/dist/assets/{pgsql-Du5FDOgT.js → pgsql-DTj74zXo.js} +0 -0
- /package/client/dist/assets/{php-WJD0z2Z_.js → php-nr791fC2.js} +0 -0
- /package/client/dist/assets/{pla-CbciTf2w.js → pla-CopQ2nXW.js} +0 -0
- /package/client/dist/assets/{postiats-C1QxmvVq.js → postiats-43DmfD33.js} +0 -0
- /package/client/dist/assets/{powerquery-BEYDhPLZ.js → powerquery-D3hlyOfw.js} +0 -0
- /package/client/dist/assets/{powershell-CfaYw4XL.js → powershell-DmHpPYUd.js} +0 -0
- /package/client/dist/assets/{protobuf-pwZEDOYa.js → protobuf-C531GsRP.js} +0 -0
- /package/client/dist/assets/{pug-5ftqSPVe.js → pug-Z5eAx3Zn.js} +0 -0
- /package/client/dist/assets/{qsharp-uI6qZMq4.js → qsharp-DkqhCAOL.js} +0 -0
- /package/client/dist/assets/{r-BK54P1EG.js → r-BwWrilGY.js} +0 -0
- /package/client/dist/assets/{redis-DI0yd7kN.js → redis-ClamHrr6.js} +0 -0
- /package/client/dist/assets/{redshift-Dov26eyI.js → redshift-DT7zqm-g.js} +0 -0
- /package/client/dist/assets/{restructuredtext-QqJ_1dC-.js → restructuredtext-BYgofb2h.js} +0 -0
- /package/client/dist/assets/{ruby-5WjveeVb.js → ruby-DezsRK8O.js} +0 -0
- /package/client/dist/assets/{rust-BzVSfqFH.js → rust-DdL9SqIa.js} +0 -0
- /package/client/dist/assets/{sb-DMIUfT-3.js → sb-CcwsVR0C.js} +0 -0
- /package/client/dist/assets/{scala-CSKA7lqt.js → scala-DHpiXF5c.js} +0 -0
- /package/client/dist/assets/{scheme-CojOP2Qc.js → scheme-BeGwcela.js} +0 -0
- /package/client/dist/assets/{scss-DoBZGJG6.js → scss-gp-XZpBa.js} +0 -0
- /package/client/dist/assets/{setup-JeBwoXoM.js → setup-CGZjy2IF2.js} +0 -0
- /package/client/dist/assets/{setup-1vN48B-m2.js → setup-CXa0Sx16.js} +0 -0
- /package/client/dist/assets/{setup-BmJ0Kx16.js → setup-D2ktKKMF2.js} +0 -0
- /package/client/dist/assets/{setup-T_ApeQDV2.js → setup-DG2H7ha5.js} +0 -0
- /package/client/dist/assets/{setup-DQWsu2TR.js → setup-DK5bHDUq2.js} +0 -0
- /package/client/dist/assets/{setup-q0wRHUp1.js → setup-DrWlIQTZ.js} +0 -0
- /package/client/dist/assets/{setup-mXnzww4f.js → setup-rzHUVgAu.js} +0 -0
- /package/client/dist/assets/{shell-YOaE4udl.js → shell-CC2rA5mh.js} +0 -0
- /package/client/dist/assets/{solidity-l3jSyVLu.js → solidity-BEEn4gHE.js} +0 -0
- /package/client/dist/assets/{sophia-BhSGTaQJ.js → sophia-CRfGWb83.js} +0 -0
- /package/client/dist/assets/{sparql-qhk6QD9w.js → sparql-D_Lu-MrJ.js} +0 -0
- /package/client/dist/assets/{specs-BGRKhM8t.js → specs-BcyxaCwf2.js} +0 -0
- /package/client/dist/assets/{specs-GkiL-snY.js → specs-C1q9geAi.js} +0 -0
- /package/client/dist/assets/{specs-DbtIzV8a.js → specs-CLZKysq-2.js} +0 -0
- /package/client/dist/assets/{specs-Ce5y2iFc2.js → specs-DZMS9o54.js} +0 -0
- /package/client/dist/assets/{specs-CT8exGOy2.js → specs-Dm6zUI-R.js} +0 -0
- /package/client/dist/assets/{specs-C6J2zrLM.js → specs-V3TvhKLX.js} +0 -0
- /package/client/dist/assets/{specs-DZDoZsDq.js → specs-dV3Mq5Mu2.js} +0 -0
- /package/client/dist/assets/{sql-DoO3y21-.js → sql-NEE52Syq.js} +0 -0
- /package/client/dist/assets/{st-ChZAY8oG.js → st-DbInun42.js} +0 -0
- /package/client/dist/assets/{swift-D290Tr7_.js → swift-Bxkupp3x.js} +0 -0
- /package/client/dist/assets/{systemverilog-DsxzgJ-o.js → systemverilog-Bz4Y3fRF.js} +0 -0
- /package/client/dist/assets/{tcl-BKDUHAAR.js → tcl-DISqw1ZD.js} +0 -0
- /package/client/dist/assets/{terminal-BTpE7iki.js → terminal-BZiIu3rf.js} +0 -0
- /package/client/dist/assets/{terminal-DEyt0prF.js → terminal-C2NAd7BW2.js} +0 -0
- /package/client/dist/assets/{terminal-a6Uq8B0p.js → terminal-IahE4LW92.js} +0 -0
- /package/client/dist/assets/{terminal-DDk_bTw0.js → terminal-QpXZU2Bn2.js} +0 -0
- /package/client/dist/assets/{terminal-BNSg2lR7.js → terminal-ScjyXNxQ.js} +0 -0
- /package/client/dist/assets/{terminal-Cvabyzm42.js → terminal-d7GadXbN.js} +0 -0
- /package/client/dist/assets/{terminal-D0z12_sJ2.js → terminal-tITa81iz.js} +0 -0
- /package/client/dist/assets/{twig-UpZQaM8G.js → twig-De2hgUGE.js} +0 -0
- /package/client/dist/assets/{typespec-DzaKvN8i.js → typespec-B8J7ngcE.js} +0 -0
- /package/client/dist/assets/{vb-CZpbb7tk.js → vb-DV3o63ZY.js} +0 -0
- /package/client/dist/assets/{wgsl-CNt3OIvl.js → wgsl-DpFanUEy.js} +0 -0
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ProjectRegistry = void 0;
|
|
7
|
+
exports.emitRecoveredPrDelivery = emitRecoveredPrDelivery;
|
|
8
|
+
exports.reprojectActivePrDeliveries = reprojectActivePrDeliveries;
|
|
7
9
|
const path_1 = __importDefault(require("path"));
|
|
8
10
|
const fs_1 = __importDefault(require("fs"));
|
|
9
11
|
const os_1 = __importDefault(require("os"));
|
|
@@ -37,10 +39,49 @@ const loop_run_manager_1 = require("./loop-run-manager");
|
|
|
37
39
|
const loop_executors_1 = require("./loop-executors");
|
|
38
40
|
const rail_isolated_launch_1 = require("./rail-isolated-launch");
|
|
39
41
|
const rail_isolation_1 = require("./rail-isolation");
|
|
42
|
+
const rail_pr_store_1 = require("./rail-pr-store");
|
|
43
|
+
const agent_chat_registry_1 = require("./agent-chat-registry");
|
|
44
|
+
const rail_pr_ticket_effects_1 = require("./rail-pr-ticket-effects");
|
|
40
45
|
const loop_runs_store_1 = require("./loop-runs-store");
|
|
41
46
|
const rails_store_1 = require("./rails-store");
|
|
42
47
|
const desktop_db_1 = require("./desktop-db");
|
|
43
48
|
const config_1 = require("./config");
|
|
49
|
+
const process_admission_1 = require("./process-admission");
|
|
50
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
51
|
+
/** The two live queue states are the only non-terminal members of JobStatus.
|
|
52
|
+
* Deriving terminality from that invariant avoids a second status list that can
|
|
53
|
+
* silently fall behind the queue contract when a new terminal outcome is added. */
|
|
54
|
+
function isTerminalJobStatus(status) {
|
|
55
|
+
return status !== 'queued' && status !== 'running';
|
|
56
|
+
}
|
|
57
|
+
/** Rehydrate both authoritative PR-decision surfaces after startup recovery.
|
|
58
|
+
* Recovery has already committed the row; broadcasts/cards are advisory and
|
|
59
|
+
* may be retried by normal hydration if a surface is unavailable. */
|
|
60
|
+
function emitRecoveredPrDelivery(ctx, deliveryId) {
|
|
61
|
+
const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
|
|
62
|
+
if (!row)
|
|
63
|
+
return;
|
|
64
|
+
const snapshot = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
65
|
+
try {
|
|
66
|
+
ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, snapshot));
|
|
67
|
+
}
|
|
68
|
+
catch { /* durable row is authoritative */ }
|
|
69
|
+
if (!row.origin_conversation_id)
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
(0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(ctx.project.id, snapshot));
|
|
73
|
+
}
|
|
74
|
+
catch { /* conversation hydration can retry */ }
|
|
75
|
+
}
|
|
76
|
+
/** Migration/recovery or a crash after a terminal CAS can repair the ledger
|
|
77
|
+
* without touching system envelopes persisted in agent conversations. Project
|
|
78
|
+
* active AND terminal origin-linked rows so obsolete cards cannot stay pinned. */
|
|
79
|
+
function reprojectActivePrDeliveries(ctx) {
|
|
80
|
+
const rows = (0, rail_pr_store_1.listOriginLinkedPrDeliveries)(ctx.db);
|
|
81
|
+
for (const row of rows)
|
|
82
|
+
emitRecoveredPrDelivery(ctx, row.id);
|
|
83
|
+
return rows.length;
|
|
84
|
+
}
|
|
44
85
|
// ─── ProjectRegistry ──────────────────────────────────────────────────────────
|
|
45
86
|
class ProjectRegistry {
|
|
46
87
|
_desktopDb;
|
|
@@ -125,30 +166,65 @@ class ProjectRegistry {
|
|
|
125
166
|
}
|
|
126
167
|
addProject(opts) {
|
|
127
168
|
const row = (0, desktop_db_1.addProject)(this._desktopDb, opts);
|
|
169
|
+
let previousRegistryEntry;
|
|
170
|
+
let mirroredRegistryEntry;
|
|
171
|
+
let registryMirrored = false;
|
|
128
172
|
// Mirror the new project into the shared artifact registry so specrails-core
|
|
129
173
|
// resolves its relocated artifacts. Wrapped so a registry write failure never
|
|
130
174
|
// breaks project creation — the startup reconcile will recreate the entry.
|
|
131
175
|
try {
|
|
132
|
-
(0, artifact_registry_1.
|
|
176
|
+
const mutation = (0, artifact_registry_1.mirrorProjectEntryWithPrevious)({
|
|
133
177
|
repoPath: row.path,
|
|
134
178
|
slug: row.slug,
|
|
135
179
|
providers: row.providers,
|
|
136
180
|
primaryProvider: row.provider,
|
|
137
181
|
desktopProjectId: row.id,
|
|
138
182
|
});
|
|
183
|
+
previousRegistryEntry = mutation.previousEntry;
|
|
184
|
+
mirroredRegistryEntry = mutation.entry;
|
|
185
|
+
registryMirrored = true;
|
|
139
186
|
}
|
|
140
187
|
catch (err) {
|
|
141
188
|
console.error('[project-registry] registry mirror failed (non-fatal):', err);
|
|
142
189
|
}
|
|
143
|
-
|
|
190
|
+
try {
|
|
191
|
+
const context = this._loadProjectContext(row);
|
|
192
|
+
this._failedProjects.delete(row.id);
|
|
193
|
+
return context;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
// Registration is one logical operation. A row that cannot be hydrated is
|
|
197
|
+
// unusable through the API and would make every retry hit UNIQUE forever.
|
|
198
|
+
this._contexts.delete(row.id);
|
|
199
|
+
this._failedProjects.delete(row.id);
|
|
200
|
+
// The mirror may have ADOPTED a pre-existing core-standalone entry. Undo
|
|
201
|
+
// only a successful mirror and restore that entry verbatim; deleting it
|
|
202
|
+
// would strand core's relocated artifacts. A fresh mirror has no prior
|
|
203
|
+
// entry, so the same rollback removes the newly-created projection.
|
|
204
|
+
if (registryMirrored && mirroredRegistryEntry) {
|
|
205
|
+
try {
|
|
206
|
+
(0, artifact_registry_1.restoreRegistryEntry)(row.path, previousRegistryEntry, mirroredRegistryEntry);
|
|
207
|
+
}
|
|
208
|
+
catch { /* best effort */ }
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
(0, desktop_db_1.removeProject)(this._desktopDb, row.id);
|
|
212
|
+
}
|
|
213
|
+
catch { /* best effort */ }
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
144
216
|
}
|
|
145
217
|
removeProject(id) {
|
|
146
218
|
// Resolve the repo path BEFORE the DB row is deleted below so we can drop the
|
|
147
219
|
// shared artifact-registry entry. Prefer the live context, fall back to the
|
|
148
220
|
// desktop DB row (project may be registered-but-not-loaded, e.g. M9 failure).
|
|
149
|
-
const
|
|
221
|
+
const persistedProject = (0, desktop_db_1.getProject)(this._desktopDb, id);
|
|
222
|
+
const repoPath = this._contexts.get(id)?.project.path ?? persistedProject?.path;
|
|
150
223
|
const ctx = this._contexts.get(id);
|
|
151
224
|
if (ctx) {
|
|
225
|
+
// Invalidate async route continuations before any killed child can emit a
|
|
226
|
+
// late `close` against the DB that this removal is about to close.
|
|
227
|
+
(0, process_admission_1.beginProjectProcessQuiescence)(id);
|
|
152
228
|
// Tear down spawners BEFORE closing the DB. QueueManager.shutdown() drops
|
|
153
229
|
// its DB handle so a late child 'close' can't run prepared statements on
|
|
154
230
|
// the closed connection (which would crash the app) and terminates any
|
|
@@ -156,10 +232,21 @@ class ProjectRegistry {
|
|
|
156
232
|
// kills in-flight chat/Explore children and clears their idle timers.
|
|
157
233
|
// SetupManager.abort() stops the 3s install poll and kills install/enrich
|
|
158
234
|
// children. All are idempotent no-ops when nothing is running.
|
|
235
|
+
let queueRecoveryComplete = false;
|
|
159
236
|
try {
|
|
160
|
-
ctx.queueManager.shutdown
|
|
237
|
+
queueRecoveryComplete = typeof ctx.queueManager.shutdown === 'function'
|
|
238
|
+
? ctx.queueManager.shutdown() !== false
|
|
239
|
+
: true;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.error(`[project-registry] queue shutdown failed for ${id}:`, err);
|
|
243
|
+
}
|
|
244
|
+
if (!queueRecoveryComplete) {
|
|
245
|
+
// The context remains registered with its DB intact. A retry can drain
|
|
246
|
+
// the durable callback/outbox once the external ticket store recovers;
|
|
247
|
+
// deleting the data dir here would make that effect unrecoverable.
|
|
248
|
+
throw new Error('Project removal deferred: terminal job recovery is still pending');
|
|
161
249
|
}
|
|
162
|
-
catch { /* ignore */ }
|
|
163
250
|
try {
|
|
164
251
|
ctx.chatManager.shutdown();
|
|
165
252
|
}
|
|
@@ -238,24 +325,20 @@ class ProjectRegistry {
|
|
|
238
325
|
ctx.db.close();
|
|
239
326
|
}
|
|
240
327
|
catch { /* ignore */ }
|
|
241
|
-
// B54: remove the ENTIRE app-managed data dir for this project, not just
|
|
242
|
-
// the telemetry subdir. It also holds user-mcp.json (a copy of the user's
|
|
243
|
-
// MCP config that can contain API keys), profile snapshots, codex-home, and
|
|
244
|
-
// terminal shim dirs — all secret-bearing residue that previously survived
|
|
245
|
-
// project removal. Guard on a non-empty slug so we never rm the projects/
|
|
246
|
-
// root itself.
|
|
247
|
-
try {
|
|
248
|
-
const slug = ctx.project.slug;
|
|
249
|
-
if (slug && slug.trim() && !slug.includes('/') && !slug.includes('..')) {
|
|
250
|
-
const projectDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug);
|
|
251
|
-
if (fs_1.default.existsSync(projectDir)) {
|
|
252
|
-
fs_1.default.rmSync(projectDir, { recursive: true, force: true });
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
catch { /* ignore — non-fatal */ }
|
|
257
328
|
this._contexts.delete(id);
|
|
258
329
|
}
|
|
330
|
+
// B54: remove the ENTIRE app-managed data dir even when context hydration
|
|
331
|
+
// failed. The persisted row is authoritative for registered-but-not-loaded
|
|
332
|
+
// projects and contains the slug needed for safe cleanup.
|
|
333
|
+
try {
|
|
334
|
+
const slug = ctx?.project.slug ?? persistedProject?.slug;
|
|
335
|
+
if (slug && slug.trim() && !slug.includes('/') && !slug.includes('..')) {
|
|
336
|
+
const projectDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug);
|
|
337
|
+
if (fs_1.default.existsSync(projectDir))
|
|
338
|
+
fs_1.default.rmSync(projectDir, { recursive: true, force: true });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch { /* ignore — non-fatal */ }
|
|
259
342
|
// Drop the relocated WORKSPACE for an adopted project whose REGISTRY slug
|
|
260
343
|
// differs from the desktop slug. The per-project data-dir rm above only
|
|
261
344
|
// removes `projects/<desktop-slug>`; an adopted repo's workspace lives under
|
|
@@ -288,6 +371,7 @@ class ProjectRegistry {
|
|
|
288
371
|
console.error('[project-registry] registry remove failed (non-fatal):', err);
|
|
289
372
|
}
|
|
290
373
|
}
|
|
374
|
+
this._failedProjects.delete(id);
|
|
291
375
|
(0, desktop_db_1.removeProject)(this._desktopDb, id);
|
|
292
376
|
}
|
|
293
377
|
getContext(id) {
|
|
@@ -376,17 +460,19 @@ class ProjectRegistry {
|
|
|
376
460
|
const existing = this._contexts.get(project.id);
|
|
377
461
|
if (existing)
|
|
378
462
|
return existing;
|
|
463
|
+
// A context is visible synchronously, but no subprocess launch may enter
|
|
464
|
+
// until durable loop callbacks and isolated worktrees have been reconciled.
|
|
465
|
+
(0, process_admission_1.beginProjectProcessQuiescence)(project.id);
|
|
379
466
|
const db = (0, db_1.initDb)(project.db_path);
|
|
380
467
|
// Bind broadcast with projectId so all WS messages carry context.
|
|
381
468
|
// Also wire agent status: when a queued job reaches a terminal state,
|
|
382
469
|
// clear current_job_id on any agent that was assigned to it.
|
|
383
|
-
const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'canceled']);
|
|
384
470
|
const boundBroadcast = (msg) => {
|
|
385
471
|
const enriched = { ...msg, projectId: project.id };
|
|
386
472
|
this._broadcast(enriched);
|
|
387
473
|
if (msg.type === 'queue') {
|
|
388
474
|
for (const job of msg.jobs) {
|
|
389
|
-
if (
|
|
475
|
+
if (isTerminalJobStatus(job.status)) {
|
|
390
476
|
(0, desktop_db_1.clearAgentJob)(this._desktopDb, job.id);
|
|
391
477
|
}
|
|
392
478
|
}
|
|
@@ -405,6 +491,10 @@ class ProjectRegistry {
|
|
|
405
491
|
catch { /* queue_state table may not exist yet */ }
|
|
406
492
|
const webhookManager = this._webhookManager;
|
|
407
493
|
const railJobs = new Map();
|
|
494
|
+
const ticketStorePath = () => {
|
|
495
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
496
|
+
return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
497
|
+
};
|
|
408
498
|
// Jira sync (per-project, inert until a connection is configured). Constructed
|
|
409
499
|
// before QueueManager so the onJobFinished closure can reference it.
|
|
410
500
|
const jiraSyncManager = new jira_sync_manager_1.JiraSyncManager({
|
|
@@ -419,6 +509,26 @@ class ProjectRegistry {
|
|
|
419
509
|
projectId: project.id,
|
|
420
510
|
projectSlug: project.slug,
|
|
421
511
|
desktopPort: this._desktopPort,
|
|
512
|
+
onJobAdmission: (projectDb, job) => {
|
|
513
|
+
const ticketIds = (0, ticket_store_1.extractTicketIdsFromCommand)(job.command);
|
|
514
|
+
(0, rails_store_1.claimTicketOutcomeOwners)(projectDb, ticketIds, job.id);
|
|
515
|
+
const wanted = new Set(ticketIds);
|
|
516
|
+
for (const rail of (0, rails_store_1.getRails)(projectDb)) {
|
|
517
|
+
const assigned = rail.ticketIds.filter((ticketId) => wanted.has(ticketId));
|
|
518
|
+
if (assigned.length > 0)
|
|
519
|
+
(0, rails_store_1.claimRailTickets)(projectDb, rail.railIndex, assigned, job.id);
|
|
520
|
+
}
|
|
521
|
+
// Distinguish current launches from genuinely pre-migration rows even
|
|
522
|
+
// when their current ticket owner later changes or becomes unreadable.
|
|
523
|
+
// This update shares the queue-admission transaction with the claims.
|
|
524
|
+
job.causalOwnership = true;
|
|
525
|
+
const marked = projectDb.prepare(`
|
|
526
|
+
UPDATE queued_jobs SET causal_ownership = 1 WHERE id = ?
|
|
527
|
+
`).run(job.id);
|
|
528
|
+
if (marked.changes !== 1) {
|
|
529
|
+
throw new Error(`Failed to persist causal admission for job ${job.id}`);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
422
532
|
getCostAlertThreshold: () => {
|
|
423
533
|
const val = (0, desktop_db_1.getDesktopSetting)(this._desktopDb, 'cost_alert_threshold_usd');
|
|
424
534
|
return val != null ? parseFloat(val) : null;
|
|
@@ -434,6 +544,9 @@ class ProjectRegistry {
|
|
|
434
544
|
return { budget, totalSpend };
|
|
435
545
|
},
|
|
436
546
|
onBudgetExceeded: (event, data) => {
|
|
547
|
+
if (event === 'desktop_daily_budget_exceeded') {
|
|
548
|
+
this._pauseAllQueuesForDesktopBudget();
|
|
549
|
+
}
|
|
437
550
|
// Deliver the budget event to this project's subscribed webhooks — the
|
|
438
551
|
// WS broadcast alone never reached webhook subscribers, so a
|
|
439
552
|
// daily_budget_exceeded / desktop_daily_budget_exceeded subscription was
|
|
@@ -444,15 +557,32 @@ class ProjectRegistry {
|
|
|
444
557
|
catch { /* best-effort */ }
|
|
445
558
|
},
|
|
446
559
|
onJobFinished: (jobId, status, costUsd, opts) => {
|
|
447
|
-
|
|
560
|
+
// Recovery can deliver this callback without another queue snapshot.
|
|
561
|
+
// Clear the assignment here as the authoritative terminal effect; the
|
|
562
|
+
// bound queue broadcast above remains an idempotent live-path fallback.
|
|
563
|
+
if (isTerminalJobStatus(status)) {
|
|
564
|
+
(0, desktop_db_1.clearAgentJob)(this._desktopDb, jobId);
|
|
565
|
+
}
|
|
566
|
+
const jobRow = db.prepare('SELECT command, duration_ms, causal_ownership FROM jobs WHERE id = ?').get(jobId);
|
|
567
|
+
// Recovery callbacks carry their own immutable inputs. Job history is
|
|
568
|
+
// independently deletable/purgeable once a row is terminal; replaying
|
|
569
|
+
// from that row would otherwise checkpoint an empty callback and strand
|
|
570
|
+
// the ticket/rail state forever.
|
|
571
|
+
const recoveryCommand = opts?.recoveryReplay ? opts.recoveryCommand : undefined;
|
|
572
|
+
const effectiveCommand = recoveryCommand ?? jobRow?.command ?? '';
|
|
573
|
+
const effectiveDurationMs = opts?.recoveryReplay
|
|
574
|
+
? (opts.recoveryDurationMs ?? jobRow?.duration_ms ?? null)
|
|
575
|
+
: (jobRow?.duration_ms ?? null);
|
|
576
|
+
const ticketOutcomeStatus = status === 'skipped'
|
|
577
|
+
? 'canceled'
|
|
578
|
+
: status;
|
|
579
|
+
const affectsTickets = status === 'completed' || status === 'failed' ||
|
|
580
|
+
status === 'canceled' || status === 'zombie_terminated' || status === 'skipped';
|
|
448
581
|
const event = status === 'completed' ? 'job.completed' : status === 'canceled' ? 'job.canceled' : 'job.failed';
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
costUsd: costUsd ?? null,
|
|
454
|
-
durationMs: jobRow?.duration_ms ?? null,
|
|
455
|
-
});
|
|
582
|
+
const criticalFailures = [];
|
|
583
|
+
let outcomeStore = null;
|
|
584
|
+
let changedTicketIds = [];
|
|
585
|
+
let effectTicketIds = [];
|
|
456
586
|
// Broadcast rail.job_completed if this job was launched by a rail
|
|
457
587
|
const railMeta = railJobs.get(jobId);
|
|
458
588
|
if (railMeta) {
|
|
@@ -461,11 +591,28 @@ class ProjectRegistry {
|
|
|
461
591
|
// Determine ticket IDs: from rail metadata, or parse from command as fallback
|
|
462
592
|
// (railJobs Map is in-memory and lost on server restart)
|
|
463
593
|
let completedTicketIds = railMeta?.ticketIds ?? [];
|
|
464
|
-
if (completedTicketIds.length === 0 &&
|
|
465
|
-
|
|
594
|
+
if (completedTicketIds.length === 0 && opts?.recoveryReplay && opts.recoveryTicketIds) {
|
|
595
|
+
completedTicketIds = [...new Set(opts.recoveryTicketIds)];
|
|
596
|
+
}
|
|
597
|
+
if (completedTicketIds.length === 0 && effectiveCommand) {
|
|
598
|
+
const matches = effectiveCommand.match(/#(\d+)/g);
|
|
466
599
|
if (matches)
|
|
467
600
|
completedTicketIds = matches.map((m) => parseInt(m.slice(1), 10));
|
|
468
601
|
}
|
|
602
|
+
const ticketOwners = new Map();
|
|
603
|
+
let ownershipReadable = true;
|
|
604
|
+
try {
|
|
605
|
+
for (const ticketId of completedTicketIds) {
|
|
606
|
+
ticketOwners.set(ticketId, (0, rails_store_1.ticketOutcomeOwner)(db, ticketId));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
catch (err) {
|
|
610
|
+
ownershipReadable = false;
|
|
611
|
+
criticalFailures.push(`ticket ownership: ${err instanceof Error ? err.message : String(err)}`);
|
|
612
|
+
}
|
|
613
|
+
const callbackCausalOwnership = opts?.recoveryReplay
|
|
614
|
+
? opts.recoveryCausalOwnership === true
|
|
615
|
+
: jobRow?.causal_ownership === 1;
|
|
469
616
|
// Apply the job outcome to its tickets. Success promotes todo/in_progress
|
|
470
617
|
// → done (→ Specs Done) — or → on_review under the ask-first PR-delivery
|
|
471
618
|
// methodology (below); failure/cancel/zombie reverts in_progress → todo
|
|
@@ -482,7 +629,7 @@ class ProjectRegistry {
|
|
|
482
629
|
// paths) ⇒ 'done', byte-identical to the pre-change promotion.
|
|
483
630
|
const completedStatus = opts?.ticketCompletionStatus ?? 'done';
|
|
484
631
|
if (completedTicketIds.length > 0 &&
|
|
485
|
-
|
|
632
|
+
affectsTickets) {
|
|
486
633
|
try {
|
|
487
634
|
// Relocate-artifacts gate: write the job outcome to the workspace
|
|
488
635
|
// ticket store when relocated, else the repo-relative store (legacy).
|
|
@@ -491,53 +638,44 @@ class ProjectRegistry {
|
|
|
491
638
|
? outcomeExec.ticketsPath
|
|
492
639
|
: (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
493
640
|
const now = new Date().toISOString();
|
|
494
|
-
|
|
641
|
+
const causallyOwnedTicketIds = completedTicketIds.filter((ticketId) => {
|
|
642
|
+
if (!ownershipReadable)
|
|
643
|
+
return false;
|
|
644
|
+
const owner = ticketOwners.get(ticketId) ?? null;
|
|
645
|
+
return owner === jobId || (!callbackCausalOwnership && owner === null);
|
|
646
|
+
});
|
|
495
647
|
const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
|
|
496
|
-
|
|
648
|
+
changedTicketIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, causallyOwnedTicketIds, ticketOutcomeStatus, now, {
|
|
649
|
+
completedStatus,
|
|
650
|
+
effectId: jobId,
|
|
651
|
+
causalOwnerConfirmed: true,
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
outcomeStore = store;
|
|
655
|
+
effectTicketIds = causallyOwnedTicketIds.filter((ticketId) => {
|
|
656
|
+
const marker = store.tickets[String(ticketId)]?.metadata.specrails_outcome;
|
|
657
|
+
return marker?.owner_id === jobId && marker.applied_effect_id === jobId;
|
|
497
658
|
});
|
|
498
|
-
for (const tid of
|
|
659
|
+
for (const tid of changedTicketIds) {
|
|
499
660
|
const ticket = store.tickets[String(tid)];
|
|
500
661
|
if (!ticket)
|
|
501
662
|
continue;
|
|
502
|
-
boundBroadcast({
|
|
503
|
-
type: 'ticket_updated',
|
|
504
|
-
ticket: ticket,
|
|
505
|
-
projectId: project.id,
|
|
506
|
-
timestamp: ticket.updated_at,
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
// Jira write-back (inert for non-Jira projects): enqueue the status
|
|
510
|
-
// transition + completion comment per linked ticket. The LOCAL mutation
|
|
511
|
-
// above stays synchronous; only the durable outbox enqueue happens here,
|
|
512
|
-
// wrapped so a Jira failure can never break the job-exit handler.
|
|
513
|
-
if (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated') {
|
|
514
663
|
try {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
const needsReviewIds = completedTicketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
|
|
524
|
-
jiraSyncManager.onJobOutcome({
|
|
525
|
-
ticketIds: completedTicketIds,
|
|
526
|
-
status,
|
|
527
|
-
jobId,
|
|
528
|
-
costUsd: costUsd ?? null,
|
|
529
|
-
durationMs: jobRow?.duration_ms ?? null,
|
|
530
|
-
needsReviewIds,
|
|
531
|
-
});
|
|
532
|
-
}
|
|
664
|
+
boundBroadcast({
|
|
665
|
+
type: 'ticket_updated',
|
|
666
|
+
ticket: ticket,
|
|
667
|
+
projectId: project.id,
|
|
668
|
+
timestamp: ticket.updated_at,
|
|
669
|
+
});
|
|
533
670
|
}
|
|
534
|
-
catch
|
|
535
|
-
|
|
671
|
+
catch {
|
|
672
|
+
// The file mutation is authoritative; a startup client reloads it.
|
|
536
673
|
}
|
|
537
674
|
}
|
|
538
675
|
}
|
|
539
676
|
catch (err) {
|
|
540
677
|
console.error('[project-registry] failed to apply job outcome to tickets:', err);
|
|
678
|
+
criticalFailures.push(`ticket outcome: ${err instanceof Error ? err.message : String(err)}`);
|
|
541
679
|
}
|
|
542
680
|
}
|
|
543
681
|
// Release the job's tickets from any rail that still holds them. The
|
|
@@ -549,40 +687,113 @@ class ProjectRegistry {
|
|
|
549
687
|
// rails (not just railMeta.railIndex) so it also heals after a server
|
|
550
688
|
// restart, when the in-memory railJobs map is lost.
|
|
551
689
|
if (completedTicketIds.length > 0 &&
|
|
552
|
-
|
|
690
|
+
affectsTickets) {
|
|
553
691
|
try {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
692
|
+
const releasableTicketIds = completedTicketIds.filter((ticketId) => {
|
|
693
|
+
if (!ownershipReadable)
|
|
694
|
+
return false;
|
|
695
|
+
const owner = ticketOwners.get(ticketId) ?? null;
|
|
696
|
+
return owner === jobId || (!callbackCausalOwnership && owner === null);
|
|
697
|
+
});
|
|
698
|
+
const releasedRails = (0, rails_store_1.releaseRailTicketsOwnedBy)(db, jobId, releasableTicketIds, {
|
|
699
|
+
railIndex: railMeta?.railIndex ?? null,
|
|
700
|
+
allowUnowned: !callbackCausalOwnership,
|
|
701
|
+
});
|
|
702
|
+
for (const released of releasedRails) {
|
|
703
|
+
const rail = released.rail;
|
|
704
|
+
try {
|
|
705
|
+
boundBroadcast({
|
|
706
|
+
type: 'rail.updated',
|
|
707
|
+
projectId: project.id,
|
|
708
|
+
railIndex: rail.railIndex,
|
|
709
|
+
changed: 'tickets',
|
|
710
|
+
ticketIds: rail.ticketIds,
|
|
711
|
+
name: rail.name ?? null,
|
|
712
|
+
mode: rail.mode,
|
|
713
|
+
profileName: rail.profileName ?? null,
|
|
714
|
+
aiEngine: rail.aiEngine ?? null,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// Persisted rail state is authoritative; WS delivery is advisory.
|
|
719
|
+
}
|
|
570
720
|
}
|
|
571
721
|
}
|
|
572
722
|
catch (err) {
|
|
573
723
|
console.error('[project-registry] failed to release rail tickets after job exit:', err);
|
|
724
|
+
criticalFailures.push(`rail release: ${err instanceof Error ? err.message : String(err)}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// QueueManager's recovery outbox checkpoints the callback only when it
|
|
728
|
+
// returns normally. Ticket-file and rail mutations are the two critical
|
|
729
|
+
// domain invariants, so surface either failure during a recovery replay.
|
|
730
|
+
// Live exits keep their long-standing best-effort behavior because they
|
|
731
|
+
// have no durable retry owner; they still continue to Jira/webhooks below.
|
|
732
|
+
// Both critical operations are idempotent, so partial replay is safe.
|
|
733
|
+
if (criticalFailures.length > 0 && opts?.recoveryReplay) {
|
|
734
|
+
throw new Error(`critical job outcome effects failed: ${criticalFailures.join('; ')}`);
|
|
735
|
+
}
|
|
736
|
+
// Jira is already backed by its own idempotent durable outbox. Keep it
|
|
737
|
+
// best-effort for this callback: a Jira configuration/network fault must
|
|
738
|
+
// never prevent the local recovery checkpoint.
|
|
739
|
+
if (outcomeStore &&
|
|
740
|
+
effectTicketIds.length > 0 &&
|
|
741
|
+
affectsTickets) {
|
|
742
|
+
try {
|
|
743
|
+
if (status === 'completed' && completedStatus === 'on_review') {
|
|
744
|
+
// On a replay after the ticket mutation committed but a later rail
|
|
745
|
+
// mutation failed, changedTicketIds is empty. Derive the intended
|
|
746
|
+
// review set from the converged store so Jira still receives it.
|
|
747
|
+
const reviewIds = effectTicketIds.filter((tid) => outcomeStore?.tickets[String(tid)]?.status === 'on_review');
|
|
748
|
+
jiraSyncManager.onRailReview(reviewIds, jobId);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
const needsReviewIds = effectTicketIds.filter((tid) => outcomeStore?.tickets[String(tid)]?.needs_review === true);
|
|
752
|
+
jiraSyncManager.onJobOutcome({
|
|
753
|
+
ticketIds: effectTicketIds,
|
|
754
|
+
status: ticketOutcomeStatus,
|
|
755
|
+
jobId,
|
|
756
|
+
costUsd: costUsd ?? null,
|
|
757
|
+
durationMs: effectiveDurationMs,
|
|
758
|
+
needsReviewIds,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
console.error('[project-registry] jira onJobOutcome failed:', err);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Webhook delivery is fire-and-forget and non-durable by design. A bad
|
|
767
|
+
// registration/desktop DB must not hold the local recovery outbox open.
|
|
768
|
+
if (status !== 'skipped') {
|
|
769
|
+
try {
|
|
770
|
+
webhookManager.deliver(project.id, event, {
|
|
771
|
+
jobId,
|
|
772
|
+
command: effectiveCommand,
|
|
773
|
+
status,
|
|
774
|
+
costUsd: costUsd ?? null,
|
|
775
|
+
durationMs: effectiveDurationMs,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (err) {
|
|
779
|
+
console.error('[project-registry] webhook delivery scheduling failed:', err);
|
|
574
780
|
}
|
|
575
781
|
}
|
|
576
782
|
// Broadcast rail.job_completed if we know the rail index
|
|
577
783
|
if (railMeta) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
784
|
+
try {
|
|
785
|
+
boundBroadcast({
|
|
786
|
+
type: 'rail.job_completed',
|
|
787
|
+
projectId: project.id,
|
|
788
|
+
railIndex: railMeta.railIndex,
|
|
789
|
+
jobId,
|
|
790
|
+
status,
|
|
791
|
+
ticketIds: completedTicketIds,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
// Advisory only; local ticket/rail state already committed.
|
|
796
|
+
}
|
|
586
797
|
}
|
|
587
798
|
},
|
|
588
799
|
});
|
|
@@ -670,20 +881,14 @@ class ProjectRegistry {
|
|
|
670
881
|
});
|
|
671
882
|
// ── Loops engine (the Loops feature) ──────────────────────────────────────
|
|
672
883
|
const railLoopRuns = new Map();
|
|
673
|
-
//
|
|
674
|
-
//
|
|
884
|
+
// Capture before runtime construction. The status transition is deliberately
|
|
885
|
+
// delayed until onLoopRunFinished exists so crash recovery replays ticket,
|
|
886
|
+
// rail and Jira invariants instead of bypassing them with raw SQL.
|
|
887
|
+
let orphanLoopRuns = [];
|
|
675
888
|
try {
|
|
676
|
-
|
|
677
|
-
if (orphans > 0)
|
|
678
|
-
console.log(`[loops] reconciled ${orphans} orphan loop run(s) for ${project.slug}`);
|
|
889
|
+
orphanLoopRuns = (0, loop_runs_store_1.listActiveLoopRuns)(db, project.id);
|
|
679
890
|
}
|
|
680
891
|
catch { /* non-fatal */ }
|
|
681
|
-
// Sweep worktrees left behind by a crashed parallel-rail fan-out (no-op +
|
|
682
|
-
// no git calls when isolation was never used). Best-effort, non-blocking.
|
|
683
|
-
void (0, rail_isolated_launch_1.reconcileRailWorktrees)(db, project.path)
|
|
684
|
-
.then((n) => { if (n > 0)
|
|
685
|
-
console.log(`[loops] reconciled ${n} orphan worktree(s) for ${project.slug}`); })
|
|
686
|
-
.catch(() => { });
|
|
687
892
|
// Loop executors resolve their base env LAZILY per step: a RELOCATED project
|
|
688
893
|
// injects core's env-first artifact indirection (tickets/backlog/profiles/
|
|
689
894
|
// state → the workspace) so an ISOLATED worktree run — whose cwd-relative
|
|
@@ -724,93 +929,286 @@ class ProjectRegistry {
|
|
|
724
929
|
// emitted loop.run_completed; this mirrors onJobFinished's ticket/rail
|
|
725
930
|
// release (kept separate so the critical job path is untouched).
|
|
726
931
|
const onLoopRunFinished = (runId, outcome, opts) => {
|
|
932
|
+
const intent = (0, loop_runs_store_1.getLoopTerminalRecovery)(db, runId);
|
|
933
|
+
let payload = null;
|
|
934
|
+
if (intent) {
|
|
935
|
+
try {
|
|
936
|
+
const parsed = JSON.parse(intent.payload);
|
|
937
|
+
if (parsed?.runId !== runId || !Array.isArray(parsed.ticketIds)) {
|
|
938
|
+
throw new Error('loop terminal payload identity is invalid');
|
|
939
|
+
}
|
|
940
|
+
payload = parsed;
|
|
941
|
+
}
|
|
942
|
+
catch (err) {
|
|
943
|
+
console.error(`[loops] invalid terminal recovery payload for ${runId}:`, err);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
727
947
|
const meta = railLoopRuns.get(runId);
|
|
728
|
-
if (!meta)
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
948
|
+
if (!intent && meta?.requiresTerminalIntent) {
|
|
949
|
+
// Current launches atomically settle their run/job/outbox. If there is
|
|
950
|
+
// no outbox, the launch either never committed or failed before the
|
|
951
|
+
// engine acquired terminal authority. Do not release/revert tickets by
|
|
952
|
+
// falling through to the pre-outbox compatibility path.
|
|
953
|
+
if (!(0, loop_runs_store_1.getLoopRun)(db, runId))
|
|
954
|
+
railLoopRuns.delete(runId);
|
|
733
955
|
return;
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
956
|
+
}
|
|
957
|
+
if (payload && payload.outcomeFinalized === false) {
|
|
958
|
+
const finalizedOutcome = outcome;
|
|
959
|
+
payload = {
|
|
960
|
+
...payload,
|
|
961
|
+
outcome: finalizedOutcome,
|
|
962
|
+
jobStatus: finalizedOutcome === 'success'
|
|
963
|
+
? 'completed'
|
|
964
|
+
: finalizedOutcome === 'stopped' || finalizedOutcome === 'blocked' || finalizedOutcome === 'stalled'
|
|
965
|
+
? 'canceled'
|
|
966
|
+
: 'failed',
|
|
967
|
+
outcomeFinalized: true,
|
|
968
|
+
};
|
|
969
|
+
try {
|
|
970
|
+
// Monotonic decision commit precedes effect delivery. A rail/ticket
|
|
971
|
+
// failure may roll back its own transaction, but can never revert a
|
|
972
|
+
// verified isolated result to the conservative startup fallback.
|
|
973
|
+
db.prepare(`UPDATE loop_terminal_recovery SET payload = ? WHERE run_id = ?`)
|
|
974
|
+
.run(JSON.stringify(payload), runId);
|
|
975
|
+
}
|
|
976
|
+
catch (err) {
|
|
977
|
+
console.error(`[loops] failed to persist final outcome for ${runId}:`, err);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const ticketIds = payload?.ticketIds ?? meta?.ticketIds ?? [];
|
|
982
|
+
const railIndex = payload?.railIndex ?? meta?.railIndex ?? null;
|
|
983
|
+
const effectiveOutcome = payload?.outcome ?? outcome;
|
|
984
|
+
const status = effectiveOutcome === 'success' ? 'completed'
|
|
985
|
+
: effectiveOutcome === 'stopped' || effectiveOutcome === 'blocked' || effectiveOutcome === 'stalled'
|
|
986
|
+
? 'canceled'
|
|
739
987
|
: 'failed';
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
// prMode) always win — idempotent with the default. ABSENT opts
|
|
744
|
-
// (shared-cwd rail runs, standalone loop runs, isolation-unavailable
|
|
745
|
-
// fallbacks) derive the default from the PR-delivery flag, read ONCE here
|
|
746
|
-
// and reused by the Jira split below so one settle can never split across
|
|
747
|
-
// the two paths. Kill-switch off ⇒ 'done', the legacy promotion
|
|
748
|
-
// byte-identical; failures ignore the field.
|
|
749
|
-
const completedStatus = opts?.ticketCompletionStatus ?? ((0, rail_isolation_1.isRailPrDeliveryEnabled)() ? 'on_review' : 'done');
|
|
988
|
+
const completedStatus = payload?.ticketCompletionStatus
|
|
989
|
+
?? opts?.ticketCompletionStatus
|
|
990
|
+
?? ((0, rail_isolation_1.isRailPrDeliveryEnabled)() ? 'on_review' : 'done');
|
|
750
991
|
try {
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
992
|
+
const deliver = db.transaction(() => {
|
|
993
|
+
let changedIds = [];
|
|
994
|
+
let effectTicketIds = [];
|
|
995
|
+
let store = null;
|
|
996
|
+
const causallyOwnedTicketIds = ticketIds.filter((ticketId) => {
|
|
997
|
+
const owner = (0, rails_store_1.ticketOutcomeOwner)(db, ticketId);
|
|
998
|
+
return owner === runId || (payload?.causalOwnership !== true && owner === null);
|
|
999
|
+
});
|
|
1000
|
+
if (causallyOwnedTicketIds.length > 0) {
|
|
1001
|
+
store = (0, ticket_store_1.mutateStore)(ticketStorePath(), (s) => {
|
|
1002
|
+
changedIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, causallyOwnedTicketIds, status, new Date().toISOString(), {
|
|
1003
|
+
completedStatus,
|
|
1004
|
+
effectId: runId,
|
|
1005
|
+
causalOwnerConfirmed: true,
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
effectTicketIds = causallyOwnedTicketIds.filter((ticketId) => {
|
|
1009
|
+
const marker = store?.tickets[String(ticketId)]?.metadata.specrails_outcome;
|
|
1010
|
+
return marker?.owner_id === runId && marker.applied_effect_id === runId;
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
const released = (0, rails_store_1.releaseRailTicketsOwnedBy)(db, runId, causallyOwnedTicketIds, {
|
|
1014
|
+
railIndex,
|
|
1015
|
+
allowUnowned: payload ? payload.causalOwnership !== true : true,
|
|
1016
|
+
});
|
|
1017
|
+
// Jira has its own idempotent outbox keyed by runId/ticketId. Only
|
|
1018
|
+
// tickets whose causal marker belongs to this run may be enqueued.
|
|
1019
|
+
if (store && effectTicketIds.length > 0) {
|
|
1020
|
+
try {
|
|
1021
|
+
if (status === 'completed' && completedStatus === 'on_review') {
|
|
1022
|
+
const reviewIds = effectTicketIds.filter((ticketId) => store?.tickets[String(ticketId)]?.status === 'on_review');
|
|
1023
|
+
jiraSyncManager.onRailReview(reviewIds, runId);
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
const needsReviewIds = effectTicketIds.filter((ticketId) => store?.tickets[String(ticketId)]?.needs_review === true);
|
|
1027
|
+
jiraSyncManager.onJobOutcome({
|
|
1028
|
+
ticketIds: effectTicketIds,
|
|
1029
|
+
status,
|
|
1030
|
+
jobId: runId,
|
|
1031
|
+
costUsd: null,
|
|
1032
|
+
durationMs: null,
|
|
1033
|
+
needsReviewIds,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
catch (err) {
|
|
1038
|
+
console.error('[project-registry] jira loop onJobOutcome failed:', err);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (intent)
|
|
1042
|
+
(0, loop_runs_store_1.completeLoopTerminalRecovery)(db, runId);
|
|
1043
|
+
return { changedIds, store, released };
|
|
757
1044
|
});
|
|
758
|
-
|
|
759
|
-
|
|
1045
|
+
const delivered = deliver();
|
|
1046
|
+
railLoopRuns.delete(runId);
|
|
1047
|
+
for (const ticketId of delivered.changedIds) {
|
|
1048
|
+
const ticket = delivered.store?.tickets[String(ticketId)];
|
|
760
1049
|
if (!ticket)
|
|
761
1050
|
continue;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1051
|
+
try {
|
|
1052
|
+
boundBroadcast({
|
|
1053
|
+
type: 'ticket_updated',
|
|
1054
|
+
ticket: ticket,
|
|
1055
|
+
projectId: project.id,
|
|
1056
|
+
timestamp: ticket.updated_at,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
catch { /* persisted file is authoritative */ }
|
|
1060
|
+
}
|
|
1061
|
+
for (const released of delivered.released) {
|
|
1062
|
+
try {
|
|
1063
|
+
boundBroadcast({
|
|
1064
|
+
type: 'rail.updated',
|
|
1065
|
+
projectId: project.id,
|
|
1066
|
+
railIndex: released.rail.railIndex,
|
|
1067
|
+
changed: 'tickets',
|
|
1068
|
+
ticketIds: released.rail.ticketIds,
|
|
1069
|
+
name: released.rail.name ?? null,
|
|
1070
|
+
mode: released.rail.mode,
|
|
1071
|
+
profileName: released.rail.profileName ?? null,
|
|
1072
|
+
aiEngine: released.rail.aiEngine ?? null,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
catch { /* advisory */ }
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
catch (err) {
|
|
1079
|
+
// Durable intents remain pending. Ticket JSON may already contain the
|
|
1080
|
+
// effect marker; replay is safe and cannot outrank a later owner.
|
|
1081
|
+
console.error(`[loops] terminal effects failed for ${runId}:`, err);
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb };
|
|
1085
|
+
this._contexts.set(project.id, ctx);
|
|
1086
|
+
const loopRecoveryOk = this._recoverOrphanLoopRuns(project, db, railLoopRuns, onLoopRunFinished, orphanLoopRuns);
|
|
1087
|
+
if (loopRecoveryOk) {
|
|
1088
|
+
// Decision tokens belong to the dead process. Clear them before any
|
|
1089
|
+
// recovery projection while admission is still closed; otherwise every
|
|
1090
|
+
// action on the card remains disabled and cannot trigger lease reclaim.
|
|
1091
|
+
const orphanedDecisionLeases = (0, rail_pr_store_1.clearOrphanedPrDeliveryOperations)(db);
|
|
1092
|
+
if (orphanedDecisionLeases > 0) {
|
|
1093
|
+
console.log(`[safe-pr] cleared ${orphanedDecisionLeases} orphaned decision lease(s) for ${project.slug}`);
|
|
1094
|
+
}
|
|
1095
|
+
// Worktree/delivery recovery owns the same repo mutex as launch
|
|
1096
|
+
// allocation. Admission opens only after it settles, so a request cannot
|
|
1097
|
+
// reuse a ticket-keyed path while startup still inspects it.
|
|
1098
|
+
void (0, rail_isolated_launch_1.reconcileRailWorktrees)(db, project.path)
|
|
1099
|
+
.then(async (n) => {
|
|
1100
|
+
if (n > 0)
|
|
1101
|
+
console.log(`[loops] reconciled ${n} orphan worktree(s) for ${project.slug}`);
|
|
1102
|
+
if (this._contexts.get(project.id) === ctx) {
|
|
1103
|
+
const effectDeps = {
|
|
1104
|
+
db,
|
|
1105
|
+
project: { id: project.id, slug: project.slug, path: project.path },
|
|
1106
|
+
broadcast: boundBroadcast,
|
|
1107
|
+
jiraSyncManager,
|
|
1108
|
+
};
|
|
1109
|
+
const ticketEffects = await (0, rail_pr_ticket_effects_1.replayRailPrTicketEffectsUntilSettled)(effectDeps, {
|
|
1110
|
+
isCurrent: () => this._contexts.get(project.id) === ctx,
|
|
1111
|
+
onAttempt: (result) => {
|
|
1112
|
+
if (result.attempted > 0) {
|
|
1113
|
+
console.log(`[safe-pr] replayed ${result.completed}/${result.attempted} ticket effect(s) for ${project.slug}; ${result.pending} pending`);
|
|
1114
|
+
// A failed attempt persists cleanup_incomplete on the terminal
|
|
1115
|
+
// delivery; project it immediately while admission remains
|
|
1116
|
+
// closed so both cards explain the recovery state.
|
|
1117
|
+
for (const deliveryId of result.attemptedDeliveryIds) {
|
|
1118
|
+
emitRecoveredPrDelivery(ctx, deliveryId);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
767
1122
|
});
|
|
1123
|
+
if (!ticketEffects.settled || this._contexts.get(project.id) !== ctx)
|
|
1124
|
+
return;
|
|
1125
|
+
reprojectActivePrDeliveries(ctx);
|
|
1126
|
+
(0, process_admission_1.openProjectProcessAdmission)(project.id);
|
|
768
1127
|
}
|
|
1128
|
+
})
|
|
1129
|
+
.catch((err) => {
|
|
1130
|
+
console.error(`[loops] isolated recovery failed for ${project.slug}; admission remains closed:`, err);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
return ctx;
|
|
1134
|
+
}
|
|
1135
|
+
_recoverOrphanLoopRuns(project, db, railLoopRuns, onLoopRunFinished, orphans) {
|
|
1136
|
+
try {
|
|
1137
|
+
const recoveredSteps = (0, loop_run_manager_1.recoverOrphanLoopStepAccounting)(db);
|
|
1138
|
+
if (recoveredSteps > 0) {
|
|
1139
|
+
console.log(`[loops] recovered ${recoveredSteps} interrupted step invocation(s) for ${project.slug}`);
|
|
1140
|
+
}
|
|
1141
|
+
const countByRail = new Map();
|
|
1142
|
+
for (const run of orphans) {
|
|
1143
|
+
if (run.rail_index != null)
|
|
1144
|
+
countByRail.set(run.rail_index, (countByRail.get(run.rail_index) ?? 0) + 1);
|
|
1145
|
+
}
|
|
1146
|
+
const rails = (0, rails_store_1.getRails)(db);
|
|
1147
|
+
const legacyTicketIds = new Map();
|
|
1148
|
+
for (const run of orphans) {
|
|
1149
|
+
if (run.rail_index == null)
|
|
1150
|
+
continue;
|
|
1151
|
+
let persistedIds = [];
|
|
769
1152
|
try {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// Done transition rides the later PR-merge / manual move.
|
|
774
|
-
jiraSyncManager.onRailReview(changedIds, runId);
|
|
775
|
-
}
|
|
776
|
-
else {
|
|
777
|
-
const needsReviewIds = ticketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
|
|
778
|
-
jiraSyncManager.onJobOutcome({ ticketIds, status, jobId: runId, costUsd: null, durationMs: null, needsReviewIds });
|
|
1153
|
+
const parsed = JSON.parse(run.ticket_ids_json ?? '[]');
|
|
1154
|
+
if (Array.isArray(parsed)) {
|
|
1155
|
+
persistedIds = parsed.filter((id) => Number.isSafeInteger(id) && id > 0);
|
|
779
1156
|
}
|
|
780
1157
|
}
|
|
781
|
-
catch
|
|
782
|
-
|
|
1158
|
+
catch { /* pre-migration malformed value → legacy fallback */ }
|
|
1159
|
+
let ticketIds = persistedIds.length > 0
|
|
1160
|
+
? persistedIds
|
|
1161
|
+
: (run.ticket_id == null ? [] : [run.ticket_id]);
|
|
1162
|
+
// Compatibility only for rows launched before migration 45. New rows
|
|
1163
|
+
// persist the exact set and never use this ambiguous heuristic.
|
|
1164
|
+
if (persistedIds.length === 0 && (countByRail.get(run.rail_index) ?? 0) === 1) {
|
|
1165
|
+
const rail = rails.find((candidate) => candidate.railIndex === run.rail_index);
|
|
1166
|
+
if (rail && rail.ticketIds.length > 0)
|
|
1167
|
+
ticketIds = [...rail.ticketIds];
|
|
783
1168
|
}
|
|
1169
|
+
legacyTicketIds.set(run.id, ticketIds);
|
|
1170
|
+
railLoopRuns.set(run.id, { railIndex: run.rail_index, ticketIds });
|
|
784
1171
|
}
|
|
785
|
-
|
|
786
|
-
|
|
1172
|
+
if (orphans.length > 0) {
|
|
1173
|
+
(0, loop_runs_store_1.reconcileOrphanLoopRuns)(db, new Date().toISOString(), legacyTicketIds);
|
|
787
1174
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
profileName: rail.profileName ?? null,
|
|
803
|
-
aiEngine: rail.aiEngine ?? null,
|
|
804
|
-
});
|
|
1175
|
+
// Includes orphan intents created above AND normally-completed runs whose
|
|
1176
|
+
// process died after the atomic settle but before its `.then` callback.
|
|
1177
|
+
const pending = (0, loop_runs_store_1.listPendingLoopTerminalRecoveries)(db);
|
|
1178
|
+
for (const row of pending) {
|
|
1179
|
+
let terminalOutcome = 'failed';
|
|
1180
|
+
try {
|
|
1181
|
+
const payload = JSON.parse(row.payload);
|
|
1182
|
+
terminalOutcome = payload.outcome;
|
|
1183
|
+
if (payload.outcomeFinalized === false) {
|
|
1184
|
+
const durableRun = (0, loop_runs_store_1.getLoopRun)(db, row.run_id);
|
|
1185
|
+
if (durableRun?.status === 'completed' && durableRun.final_outcome) {
|
|
1186
|
+
terminalOutcome = durableRun.final_outcome;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
805
1189
|
}
|
|
1190
|
+
catch { /* handler logs and retains malformed intent */ }
|
|
1191
|
+
onLoopRunFinished(row.run_id, terminalOutcome);
|
|
806
1192
|
}
|
|
807
|
-
|
|
808
|
-
console.
|
|
1193
|
+
if (orphans.length > 0) {
|
|
1194
|
+
console.log(`[loops] reconciled ${orphans.length} orphan loop run(s) for ${project.slug}`);
|
|
809
1195
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
catch (err) {
|
|
1199
|
+
console.error(`[loops] orphan reconciliation failed for ${project.slug}:`, err);
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
/** Enforce the app-wide budget through every per-project queue authority. */
|
|
1204
|
+
_pauseAllQueuesForDesktopBudget() {
|
|
1205
|
+
for (const context of this.listContexts()) {
|
|
1206
|
+
try {
|
|
1207
|
+
if (!context.queueManager.isPaused())
|
|
1208
|
+
context.queueManager.pause();
|
|
1209
|
+
}
|
|
1210
|
+
catch { /* a concurrently removed project is best-effort */ }
|
|
1211
|
+
}
|
|
814
1212
|
}
|
|
815
1213
|
}
|
|
816
1214
|
exports.ProjectRegistry = ProjectRegistry;
|