@runeya/runeya 2.0.21 → 2.0.23
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/agent/index.js +103 -9
- package/agent/index.js.map +1 -1
- package/agent-manager-2P4THUVA.js +7 -0
- package/{chunk-PUNE7P6S.js → chunk-Y2R42AXQ.js} +848 -561
- package/chunk-Y2R42AXQ.js.map +1 -0
- package/index.js +92 -43
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/{src-K32WI425.js → src-3TOPUVAK.js} +1354 -1083
- package/src-3TOPUVAK.js.map +1 -0
- package/web/assets/AgentScalarChatInterface.vue-Bk_cXx74.js +13 -0
- package/web/assets/AgentTerminalPage-DqXrABlh.js +1 -0
- package/web/assets/AiChatPage-B3P7qefE.js +1 -0
- package/web/assets/AppLayout-B2e7K0cZ.js +1 -0
- package/web/assets/{AuditPage-CUdrFc3C.js → AuditPage-B55qp9MV.js} +1 -1
- package/web/assets/{CloudAccountPage-am6AxJRB.js → CloudAccountPage-CGyW79m_.js} +1 -1
- package/web/assets/CloudAccountPage-DTity1N0.js +1 -0
- package/web/assets/CloudMessagingPage-7RQRbAOB.js +2 -0
- package/web/assets/CloudMessagingPage-BUnfNsBz.css +1 -0
- package/web/assets/{DashboardPage-pA2caWR9.js → DashboardPage-CwmCbP3t.js} +1 -1
- package/web/assets/{EditorIcon-C9E8hRyM.js → EditorIcon-BaldhaS8.js} +1 -1
- package/web/assets/{EnvironmentForm-XKpxQ-45.js → EnvironmentForm-Da52SUx4.js} +2 -2
- package/web/assets/EnvironmentFormPage-Bab5iOWS.js +1 -0
- package/web/assets/EnvironmentListPage-O1fIWq-g.js +1 -0
- package/web/assets/EnvironmentSelector-BWzLYssP.js +1 -0
- package/web/assets/KanbanBoardPage-BOMHKLHX.css +1 -0
- package/web/assets/KanbanBoardPage-BvtIaHqG.js +4 -0
- package/web/assets/KanbanListPage-BoNcgR3h.css +1 -0
- package/web/assets/KanbanListPage-Cv_QJF00.js +1 -0
- package/web/assets/LaunchProjectPage-uB7iIMG1.js +1 -0
- package/web/assets/{LaunchServicesPage-CViec72c.js → LaunchServicesPage-1ZN1JYqH.js} +1 -1
- package/web/assets/LogViewer-BJxpIK11.js +3 -0
- package/web/assets/{ProcessControls-D97-A3dI.js → ProcessControls-C2EYsD-P.js} +1 -1
- package/web/assets/ProcessDetailPage-DdOfegjU.js +1 -0
- package/web/assets/{ProcessMetrics-CNJ30_ef.js → ProcessMetrics-DpDxpUpO.js} +2 -2
- package/web/assets/ProjectDetailPage-DtwRjTew.js +1 -0
- package/web/assets/ProjectListPage-D9SUNEFu.js +1 -0
- package/web/assets/{RandomJobTestPage-CnY2f-qz.js → RandomJobTestPage-OoAk9WAJ.js} +1 -1
- package/web/assets/{ScenarioEditorPage-CBZp7Q34.css → ScenarioEditorPage-GmFIXPDj.css} +1 -1
- package/web/assets/ScenarioEditorPage-LhEs4hjS.js +14 -0
- package/web/assets/SendToChatDialog-CZWy1K4A.js +1 -0
- package/web/assets/ServiceControlsBar-CU6b2KBJ.js +1 -0
- package/web/assets/{ServiceDetailPage-DhE1LYrn.css → ServiceDetailPage-BvoD3SB6.css} +1 -1
- package/web/assets/ServiceDetailPage-CSaPyV_v.js +2 -0
- package/web/assets/ServiceFormPage-D9NFCmmW.js +1 -0
- package/web/assets/SettingsPage-BMclTDLy.css +1 -0
- package/web/assets/SettingsPage-DXlBFfBq.js +2062 -0
- package/web/assets/{SetupPage-CzabsTON.js → SetupPage-dqqf_M_p.js} +1 -1
- package/web/assets/UnlockPage-ClEawxOC.css +1 -0
- package/web/assets/UnlockPage-DjjpY02d.js +1 -0
- package/web/assets/VariablesTable-B4h9eNC_.css +1 -0
- package/web/assets/VariablesTable-BFgRnjza.js +1 -0
- package/web/assets/{VersionBadge-DUE17MrD.js → VersionBadge-ToW3NGSQ.js} +1 -1
- package/web/assets/WikiPage-Djisdsae.js +1 -0
- package/web/assets/{WikiView-CUBKfTT-.css → WikiView-CaUVD3f1.css} +1 -1
- package/web/assets/WikiView-CiATkY8l.js +1 -0
- package/web/assets/{add-scalar-classes-Cfh7_IWy.js → add-scalar-classes-DGwycuRW.js} +3 -3
- package/web/assets/auth-errors-DEObS1lG.js +1 -0
- package/web/assets/{autocomplete-CrWQ5zMj.js → autocomplete-DP0sGong.js} +1 -1
- package/web/assets/{basic-shape-diamond.svg-pjhwN4JT.js → basic-shape-diamond.svg-B0FzUXGN.js} +1 -1
- package/web/assets/{basic-shape-hexagon.svg-D0yEDOCC.js → basic-shape-hexagon.svg-BuI9rv44.js} +1 -1
- package/web/assets/{basic-shape-primary-circle-ellipse-round.svg-DvZ7CdgE.js → basic-shape-primary-circle-ellipse-round.svg-C0WlStV7.js} +1 -1
- package/web/assets/{basic-shape-primary-square-rectangle.svg-ChB1hiGR.js → basic-shape-primary-square-rectangle.svg-ByMGtNMn.js} +1 -1
- package/web/assets/{basic-shape-shield.svg-Bxrmr3jg.js → basic-shape-shield.svg-h6znjRmZ.js} +1 -1
- package/web/assets/{browser-C2YmKQw8.js → browser-DJeF2lho.js} +1 -1
- package/web/assets/{browser-CvQ_J2u9.js → browser-oekISqUb.js} +1 -1
- package/web/assets/{card-CpyeL2dI.js → card-B7-be48x.js} +1 -1
- package/web/assets/{checkbox-DWNV_RaS.js → checkbox-DDSZK_Pi.js} +1 -1
- package/web/assets/{chip-8cRcc2L3.js → chip-BVSQ-56q.js} +1 -1
- package/web/assets/{client-DNj6VDDb.js → client-DWfGqqLr.js} +1 -1
- package/web/assets/cloud-chat.store-BLFChRpl.js +1 -0
- package/web/assets/cloud-client-GmxBhiFl.js +1 -0
- package/web/assets/{column-DtLr_xcq.js → column-BHRqZfnh.js} +7 -7
- package/web/assets/{computer-device-desktop-monitor.svg-CLGySxTI.js → computer-device-desktop-monitor.svg-CL9XulYR.js} +1 -1
- package/web/assets/{computer-device-desktop.svg-DJ9n7_Sq.js → computer-device-desktop.svg-CKWeVTzO.js} +1 -1
- package/web/assets/{computer-device-laptop.svg-k6zcRWN4.js → computer-device-laptop.svg-wAkdRlEi.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung-back.svg-pwKHMzIK.js → computer-device-mobile-phone-android-samsung-back.svg-DOahzxu1.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung.svg-CR796QT_.js → computer-device-mobile-phone-android-samsung.svg-FBZO3WLS.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x-back.svg-CY2r7WXK.js → computer-device-mobile-phone-iphone-x-back.svg-CCLknBWv.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x.svg-BHISnTWX.js → computer-device-mobile-phone-iphone-x.svg-CWdT8wN4.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet-touch.svg-DQqDUvnk.js → computer-device-mobile-tablet-touch.svg-Bx1A6qUQ.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet.svg-4Ab5vdIo.js → computer-device-mobile-tablet.svg-JpmP6yqf.js} +1 -1
- package/web/assets/{computer-device-network-ethernet-cat6.svg-DJf04S68.js → computer-device-network-ethernet-cat6.svg-D6WFbLbI.js} +1 -1
- package/web/assets/{computer-device-network-lan-www.svg-CaETgcOJ.js → computer-device-network-lan-www.svg-U8NMAi5b.js} +1 -1
- package/web/assets/{computer-device-network-wifi-connection.svg-PndfmFuy.js → computer-device-network-wifi-connection.svg-FuRBLyH4.js} +1 -1
- package/web/assets/{computer-device-network-wifi-router.svg-CBLwEbOV.js → computer-device-network-wifi-router.svg-BMzCP2Aw.js} +1 -1
- package/web/assets/{cssMode-RVPpAuHz.js → cssMode--4DE_809.js} +1 -1
- package/web/assets/{divider-CYFRNVal.js → divider-hPOrl1Iy.js} +1 -1
- package/web/assets/{ecology-science-erlenmeyer-flask.svg-BYXK2uKa.js → ecology-science-erlenmeyer-flask.svg-Bnte5Nm-.js} +1 -1
- package/web/assets/{fieldset-BCXBPIiB.js → fieldset-BSwilV-Y.js} +1 -1
- package/web/assets/{freemarker2-B6guP0jO.js → freemarker2-DUNAk_0P.js} +1 -1
- package/web/assets/{handlebars-BxlL79LN.js → handlebars-CoM2DS_1.js} +1 -1
- package/web/assets/{html-Dr0M60O0.js → html-CgEDNZ_R.js} +1 -1
- package/web/assets/{htmlMode-B63yAhXq.js → htmlMode-QDDBWVzd.js} +1 -1
- package/web/assets/{image-flash-lightning.svg-DVtJFd09.js → image-flash-lightning.svg-ZIxta1IB.js} +1 -1
- package/web/assets/{image-picture-flower.svg-BaBdyWt-.js → image-picture-flower.svg-BXxhgW1Q.js} +1 -1
- package/web/assets/{index-BTYNCNK0.js → index-Dl-HyfHx.js} +244 -244
- package/web/assets/{index-DS4VL_1S.css → index-gKo3WNkm.css} +1 -1
- package/web/assets/{interface-alert-exclamation-diamond.svg-BQYncN9j.js → interface-alert-exclamation-diamond.svg-bVfL6jBy.js} +1 -1
- package/web/assets/{interface-alert-exclamation-triangle-warning.svg-BvQxARpD.js → interface-alert-exclamation-triangle-warning.svg-BrapXyrc.js} +1 -1
- package/web/assets/{interface-alert-information-circle.svg-BbiwUtTt.js → interface-alert-information-circle.svg--W3bUw5n.js} +1 -1
- package/web/assets/{interface-award-crown.svg-BJs2BJ9S.js → interface-award-crown.svg-CBbbHFIN.js} +1 -1
- package/web/assets/{interface-bookmark.svg-WUL572zM.js → interface-bookmark-tag.svg-C4_v-zTY.js} +1 -1
- package/web/assets/{interface-bookmark-tag.svg-huwK9o4X.js → interface-bookmark.svg-CIY69-iu.js} +1 -1
- package/web/assets/{interface-calendar-date-one.svg-CEkOALKM.js → interface-calendar-date-one.svg-CD7WxblA.js} +1 -1
- package/web/assets/{interface-content-book-open-pages.svg-CDxvdidz.js → interface-content-book-open-pages.svg-BamW-eNv.js} +1 -1
- package/web/assets/{interface-content-book-page.svg-ROxK9a-V.js → interface-content-book-page.svg-CpHhLWQE.js} +1 -1
- package/web/assets/{interface-content-file.svg-69XGQGxu.js → interface-content-file.svg-BmqLN3iR.js} +1 -1
- package/web/assets/interface-content-folder.svg-B2phMZXd.js +1 -0
- package/web/assets/{interface-content-folder.svg-BjXgivIz.js → interface-content-folder.svg-ffbcORbT.js} +1 -1
- package/web/assets/{interface-copy-clipboard.svg-BAAZ1QLS.js → interface-copy-clipboard.svg-MdbbmqXS.js} +1 -1
- package/web/assets/{interface-edit-attachment.svg-BCmyBBsd.js → interface-edit-attachment.svg-DtAVj6tp.js} +1 -1
- package/web/assets/{interface-edit-binocular.svg-BI3JPE3j.js → interface-edit-binocular.svg-DfT11Y7N.js} +1 -1
- package/web/assets/{interface-edit-magic-wand.svg-DhECH5yw.js → interface-edit-magic-wand.svg-D4mLRHJs.js} +1 -1
- package/web/assets/{interface-edit-tool-paint-roller.svg-0-Os9Flg.js → interface-edit-tool-paint-roller.svg-Cxa7Ct4C.js} +1 -1
- package/web/assets/{interface-edit-tool-pencil.svg-CM5UI5Rv.js → interface-edit-tool-pencil.svg-IRM3BHKY.js} +1 -1
- package/web/assets/{interface-favorite-award.svg-BZNkuFiE.js → interface-favorite-award.svg-CLGZ65SX.js} +1 -1
- package/web/assets/{interface-favorite-flag.svg-CeHd37SC.js → interface-favorite-flag.svg-ju-VMCAF.js} +1 -1
- package/web/assets/{interface-favorite-heart.svg-D5dJ4sOz.js → interface-favorite-heart.svg-DsWpztN8.js} +1 -1
- package/web/assets/{interface-favorite-star.svg-B7YeWzmv.js → interface-favorite-star.svg-CCPUYvBO.js} +1 -1
- package/web/assets/{interface-favorite-stars-sparkles.svg-BwqCtLSB.js → interface-favorite-stars-sparkles.svg-nf2ra-Mg.js} +1 -1
- package/web/assets/{interface-hierarchy-flowchart.svg-q70-KJ80.js → interface-hierarchy-flowchart.svg-CPrjsUHp.js} +1 -1
- package/web/assets/{interface-home-house.svg-CUco1zSk.js → interface-home-house.svg-DyWL4dgs.js} +1 -1
- package/web/assets/{interface-hyperlink.svg-D6Z-QbxJ.js → interface-hyperlink.svg-DgjI4lGJ.js} +1 -1
- package/web/assets/{interface-lighting-brightness.svg-DDJ8wIbt.js → interface-lighting-brightness.svg-BhomgGV0.js} +1 -1
- package/web/assets/{interface-lock-closed.svg-1GuN2t9d.js → interface-lock-closed.svg-CDDGuaUG.js} +1 -1
- package/web/assets/{interface-lock-open-unlock.svg-CmSL9GCo.js → interface-lock-open-unlock.svg-DxOzefwC.js} +1 -1
- package/web/assets/{interface-login-key.svg-Cnur4uLY.js → interface-login-key.svg-CLs728UT.js} +1 -1
- package/web/assets/{interface-search.svg-3TqtroIY.js → interface-search.svg-CSE2bUM-.js} +1 -1
- package/web/assets/{interface-setting-cog.svg-B7nRfZ1y.js → interface-setting-cog.svg-SfBQ-E_y.js} +1 -1
- package/web/assets/{interface-share-megaphone-bullhorn.svg-DraAk4oI.js → interface-share-megaphone-bullhorn.svg-Cg9QEbsW.js} +1 -1
- package/web/assets/{interface-share-rocket.svg-CGRtk6J4.js → interface-share-rocket.svg-V9fWt6R5.js} +1 -1
- package/web/assets/{interface-share-satellite.svg-COo3kZqu.js → interface-share-satellite.svg-BoAJlAy3.js} +1 -1
- package/web/assets/{interface-share-space-ship.svg-iYhfpKY2.js → interface-share-space-ship.svg-CWoqBiZI.js} +1 -1
- package/web/assets/{interface-share.svg-BqVe3lWw.js → interface-share.svg-DW6AG6rC.js} +1 -1
- package/web/assets/{interface-signal-square.svg-DmGT2kio.js → interface-signal-square.svg-DktTeL2e.js} +1 -1
- package/web/assets/{interface-time-clock-circle.svg-Djt_VZVX.js → interface-time-clock-circle.svg-BBnimglQ.js} +1 -1
- package/web/assets/{interface-time-hour-glass.svg-DxZVLqJ6.js → interface-time-hour-glass.svg-WNSRow4o.js} +1 -1
- package/web/assets/{interface-users-multiple.svg-n8dIXu9r.js → interface-users-multiple.svg-DoeSSDNX.js} +1 -1
- package/web/assets/{interface-weather-moon.svg-CExHl6HQ.js → interface-weather-moon.svg-BzUq31T3.js} +1 -1
- package/web/assets/{javascript-6lVkE6gP.js → javascript-CDsgZK6E.js} +1 -1
- package/web/assets/{jsonMode-CySg6s8T.js → jsonMode-BOQojnkz.js} +1 -1
- package/web/assets/{liquid-DSLZY9bf.js → liquid-CT8_Ig_a.js} +1 -1
- package/web/assets/{lspLanguageFeatures-C6Gb5-pg.js → lspLanguageFeatures-BW3wbtds.js} +1 -1
- package/web/assets/{mail-chat-bubble-square.svg-BqpIkvR_.js → mail-chat-bubble-square.svg-BB3D-jS5.js} +1 -1
- package/web/assets/{mail-send-email-paper-airplane.svg-DUkiRZ1x.js → mail-send-email-paper-airplane.svg-BGUFNdDy.js} +1 -1
- package/web/assets/{mail-send-envelope.svg-DjjLaPUo.js → mail-send-envelope.svg-CiEf_X3v.js} +1 -1
- package/web/assets/{mdx-DuCKQZ3-.js → mdx-BzJqzXG7.js} +1 -1
- package/web/assets/{message-CJ-X_z47.js → message-DQfiz3F1.js} +1 -1
- package/web/assets/{minus-RXlNS6-g.js → minus-Bh-ngAnz.js} +1 -1
- package/web/assets/modal-B6tgGww3.js +1 -0
- package/web/assets/{modal-DfNn2ioJ.js → modal-BH8tCrqE.js} +1 -1
- package/web/assets/{money-cashier-receipt.svg-CUzOL6J5.js → money-cashier-receipt.svg-nbLywLCi.js} +1 -1
- package/web/assets/{money-currency-dollar-pay.svg-CQY_KC9b.js → money-currency-dollar-pay.svg-HtpV_93j.js} +1 -1
- package/web/assets/{money-graph-arrow-increase.svg-BDu5i5aF.js → money-graph-arrow-increase.svg-BG4s2n8-.js} +1 -1
- package/web/assets/{money-graph-bar-chart-increase.svg-xBlBZYiv.js → money-graph-bar-chart-increase.svg-7ivMfz5M.js} +1 -1
- package/web/assets/{multiselect-BbY8SFyO.js → multiselect-CE72JxFr.js} +1 -1
- package/web/assets/{nature-ecology-leaf.svg-C_T6wTIZ.js → nature-ecology-leaf.svg-D60IfAE-.js} +1 -1
- package/web/assets/{password-B_pKmF_I.js → password-CZ-A_ePM.js} +1 -1
- package/web/assets/{phone-telephone.svg-DzUP7zCx.js → phone-telephone.svg-B0OCaSQr.js} +1 -1
- package/web/assets/{plus-DfsTcd_H.js → plus-DZy8IO4Z.js} +1 -1
- package/web/assets/process.store-XR7BoPqt.js +1 -0
- package/web/assets/{programming-bug.svg-C1NvPH7_.js → programming-bug.svg-DDZ7-1a6.js} +1 -1
- package/web/assets/{programming-cloud.svg-hVwd5xzu.js → programming-cloud.svg-BZHqbFE1.js} +1 -1
- package/web/assets/{programming-computer-database-server.svg-DXvcm62E.js → programming-computer-database-server.svg-CPK0axOF.js} +1 -1
- package/web/assets/{programming-computer-database.svg-w2w_GH3u.js → programming-computer-database.svg-Dat_iKWZ.js} +1 -1
- package/web/assets/{programming-module-four-layout.svg-DLcwogv0.js → programming-module-four-layout.svg-DYTRTKmF.js} +1 -1
- package/web/assets/{programming-module-three.svg-Cfjjs8tf.js → programming-module-three.svg-CU563xPB.js} +1 -1
- package/web/assets/{programming-module.svg-ChyMdD4E.js → programming-module.svg-DdRe2d3E.js} +1 -1
- package/web/assets/{programming-script-code.svg-D8N99_E3.js → programming-script-code.svg-D5dDzg1B.js} +1 -1
- package/web/assets/{python-BT-UPVMQ.js → python-C0tzbgs4.js} +1 -1
- package/web/assets/{radiobutton-C9iiy1zq.js → radiobutton-BYEniO96.js} +1 -1
- package/web/assets/{razor-DKUraACi.js → razor-BNCtUdbo.js} +1 -1
- package/web/assets/router-BpJBXGio.js +1 -0
- package/web/assets/{settings.store-PSHvWAzl.js → settings.store-hW2r6ht2.js} +1 -1
- package/web/assets/{shopping-cart.svg-C_kTBKvf.js → shopping-cart.svg-yNDOoxiE.js} +1 -1
- package/web/assets/{shopping-gift-present.svg-DIfbUecw.js → shopping-gift-present.svg-ZAFtf911.js} +1 -1
- package/web/assets/{shopping-shipping-box-parcel-package.svg-DgeqoYij.js → shopping-shipping-box-parcel-package.svg-C_128hOx.js} +1 -1
- package/web/assets/{TiptapEditor-CI1pNNDH.js → src-BTZHXzoT.js} +78 -78
- package/web/assets/src-CmzYYWZd.css +1 -0
- package/web/assets/src-DtlOQm4K.js +1 -0
- package/web/assets/src-ZfecJkW9.css +1 -0
- package/web/assets/{tabpanel-tgozju6m.js → tabpanel-BiifLLS6.js} +2 -2
- package/web/assets/{tag-new-circle.svg-yzrCIErB.js → tag-new-circle.svg-0OOws8vC.js} +1 -1
- package/web/assets/{travel-map-earth-globe.svg-C9CMICQQ.js → travel-map-earth-globe.svg-CvwB8JlN.js} +1 -1
- package/web/assets/trpc-D0HTyuMG.js +1 -0
- package/web/assets/{tsMode-gblCXC3w.js → tsMode-CadFFP73.js} +1 -1
- package/web/assets/{typescript-DeVzlCKf.js → typescript-TsqAe_2a.js} +1 -1
- package/web/assets/useAiChatDrawer-2ELNp1KU.js +1 -0
- package/web/assets/useCloudOrg-BAtUa48Z.css +1 -0
- package/web/assets/useCloudOrg-DuypVndp.js +1 -0
- package/web/assets/{xml-UwUjH4rq.js → xml-bgdRCuvM.js} +1 -1
- package/web/assets/{yaml-DSs0npSB.js → yaml-BRUv0H85.js} +1 -1
- package/web/index.html +2 -2
- package/agent-manager-QPZUYLJA.js +0 -8
- package/chunk-LZWBXYUE.js +0 -354
- package/chunk-LZWBXYUE.js.map +0 -1
- package/chunk-PUNE7P6S.js.map +0 -1
- package/chunk-Q4N37KNN.js +0 -202
- package/chunk-Q4N37KNN.js.map +0 -1
- package/kanban-commands-53WWMCHQ.js +0 -14
- package/kanban-commands-53WWMCHQ.js.map +0 -1
- package/src-K32WI425.js.map +0 -1
- package/web/assets/AgentScalarChatInterface.vue-S7bbKRqw.js +0 -13
- package/web/assets/AgentTerminalPage-c0fCTCzP.js +0 -1
- package/web/assets/AiChatPage-B-kfiUI-.js +0 -1
- package/web/assets/AppLayout-COTo6zQf.js +0 -1
- package/web/assets/CloudAccountPage-H1QGj9tJ.js +0 -1
- package/web/assets/CloudMessagingPage-BEtd8B8B.js +0 -2
- package/web/assets/CloudMessagingPage-sgMxCh6j.css +0 -1
- package/web/assets/EnvironmentFormPage-DMpiq3Zx.js +0 -1
- package/web/assets/EnvironmentListPage-C21qJ3iw.js +0 -1
- package/web/assets/EnvironmentSelector-Be9yMkjY.js +0 -1
- package/web/assets/KanbanBoardPage-CyUTDl_W.css +0 -1
- package/web/assets/KanbanBoardPage-SNCSxTz7.js +0 -4
- package/web/assets/KanbanListPage-B3JpbFlv.css +0 -1
- package/web/assets/KanbanListPage-D-kgPmxI.js +0 -1
- package/web/assets/LaunchProjectPage-BbNDYePE.js +0 -1
- package/web/assets/LogViewer-CHyPfODD.js +0 -3
- package/web/assets/ProcessDetailPage-BlQJDM-W.js +0 -1
- package/web/assets/ProjectDetailPage-DdRYMfcl.js +0 -1
- package/web/assets/ProjectListPage-BDLXGuEj.js +0 -1
- package/web/assets/ScenarioEditorPage-CFNTrVlY.js +0 -14
- package/web/assets/SendToChatDialog-BJjY1MAh.css +0 -1
- package/web/assets/SendToChatDialog-CtGhyaOw.js +0 -1
- package/web/assets/ServiceControlsBar-m_AQmRiR.js +0 -1
- package/web/assets/ServiceDetailPage-RCl6IsBs.js +0 -2
- package/web/assets/ServiceFormPage-CIJ-tFub.js +0 -1
- package/web/assets/SettingsPage-CPF7w35a.css +0 -1
- package/web/assets/SettingsPage-tK-RiJrj.js +0 -2062
- package/web/assets/SetupKeyPage-CzB_AMfr.js +0 -1
- package/web/assets/SetupKeyPage-DoY1Ua1s.css +0 -1
- package/web/assets/TiptapEditor-bdEPMd-W.css +0 -1
- package/web/assets/UnlockPage-DWM5symg.js +0 -1
- package/web/assets/UnlockPage-EZOi-H7d.css +0 -1
- package/web/assets/VariablesTable-Bkv0MOKi.css +0 -1
- package/web/assets/VariablesTable-DoNJ4OIn.js +0 -1
- package/web/assets/WikiPage-COdO-49s.js +0 -1
- package/web/assets/WikiView-BS9HHroX.js +0 -1
- package/web/assets/auth-errors-D-uOLmKt.js +0 -1
- package/web/assets/cloud-chat.store-DHiYOkkN.js +0 -1
- package/web/assets/cloud-client-COpvz58o.js +0 -1
- package/web/assets/interface-content-folder.svg-Bijbx45w.js +0 -1
- package/web/assets/modal-BvVbc_6u.js +0 -1
- package/web/assets/process.store-BLMSPw2l.js +0 -1
- package/web/assets/router-DBNATEGs.js +0 -1
- package/web/assets/trpc-3qbDCsqO.js +0 -1
- package/web/assets/useAiChatDrawer-BtC-0qN-.js +0 -1
- /package/{agent-manager-QPZUYLJA.js.map → agent-manager-2P4THUVA.js.map} +0 -0
- /package/web/assets/{abap-lJFTLuI6.js → abap-DMmvWZUy.js} +0 -0
- /package/web/assets/{apex-Bp0KWav8.js → apex-D89PiTzU.js} +0 -0
- /package/web/assets/{azcli-DBOe7Uvy.js → azcli-ChnjTTA5.js} +0 -0
- /package/web/assets/{bat-Dk_NT_by.js → bat-uLzQ_FhK.js} +0 -0
- /package/web/assets/{bicep-B07Mrlc7.js → bicep-CMOK7Q1v.js} +0 -0
- /package/web/assets/{cameligo-DL5eNXGn.js → cameligo-D1AyojFH.js} +0 -0
- /package/web/assets/{clojure-BzV2Yqu_.js → clojure-D9JoUgXn.js} +0 -0
- /package/web/assets/{coffee-Bjlj5hmf.js → coffee-D-5sSToL.js} +0 -0
- /package/web/assets/{cpp-C9oth5qz.js → cpp-BqT_t_y9.js} +0 -0
- /package/web/assets/{csharp-HyGd049S.js → csharp-CuToYI91.js} +0 -0
- /package/web/assets/{csp-DjCDOBuX.js → csp-CffcxvlO.js} +0 -0
- /package/web/assets/{css-DIL8RTJ3.js → css-CUD0biQW.js} +0 -0
- /package/web/assets/{cypher-DX1zPXL5.js → cypher-ByyWL_9U.js} +0 -0
- /package/web/assets/{dart-BBQZd_ZZ.js → dart-B__ipcUn.js} +0 -0
- /package/web/assets/{dockerfile-Cj0CaWYx.js → dockerfile-DS-_8bWw.js} +0 -0
- /package/web/assets/{ecl-BjmWCTJg.js → ecl-DWsNCBUj.js} +0 -0
- /package/web/assets/{elixir-3uWoEjXX.js → elixir-OfjEy-3g.js} +0 -0
- /package/web/assets/{flow9-D4r739e9.js → flow9-BPX22hxS.js} +0 -0
- /package/web/assets/{fsharp-B3ycAx5T.js → fsharp-CVJcfIUn.js} +0 -0
- /package/web/assets/{go-DFHBjfCl.js → go-CfRHZ3yg.js} +0 -0
- /package/web/assets/{graphql-DBjIbM_e.js → graphql-BZaO3ttK.js} +0 -0
- /package/web/assets/{hcl-Buu_1Hsp.js → hcl-BF9-qUku.js} +0 -0
- /package/web/assets/{ini-CgzZhgz_.js → ini-BUvw6xhr.js} +0 -0
- /package/web/assets/{java-BczDO0q5.js → java-BZ5BNd2x.js} +0 -0
- /package/web/assets/{julia-B142kn-7.js → julia-DN38cgYT.js} +0 -0
- /package/web/assets/{kotlin-42XMP7p1.js → kotlin-CjEJKT8q.js} +0 -0
- /package/web/assets/{less-BCIsKRqU.js → less-DMEH_Nwq.js} +0 -0
- /package/web/assets/{lexon-DTDOH1dQ.js → lexon-CKQUX8Ov.js} +0 -0
- /package/web/assets/{lua-BrH-CeNs.js → lua-CuWjKLJ0.js} +0 -0
- /package/web/assets/{m3-D-eUeHOI.js → m3-CEp1bVrM.js} +0 -0
- /package/web/assets/{markdown-DwpSjZTn.js → markdown-DxtQ_puq.js} +0 -0
- /package/web/assets/{mips-DoGTrjVk.js → mips-Dx7qydpO.js} +0 -0
- /package/web/assets/{msdax-CQ8McnjQ.js → msdax-DpNCaSiO.js} +0 -0
- /package/web/assets/{mysql-Cj3do0ZO.js → mysql-C3xsBDPP.js} +0 -0
- /package/web/assets/{objective-c-BZbR8TG8.js → objective-c-BbaVHCX1.js} +0 -0
- /package/web/assets/{pascal-23PRDHRq.js → pascal-sDju2faq.js} +0 -0
- /package/web/assets/{pascaligo-CxasKTLJ.js → pascaligo-ChCdOH6b.js} +0 -0
- /package/web/assets/{perl-Dhxz2Lhx.js → perl-BCgLFj01.js} +0 -0
- /package/web/assets/{pgsql-BIbUy7yD.js → pgsql-DtKby69f.js} +0 -0
- /package/web/assets/{php-B5qS9qgM.js → php-NYvR17Qb.js} +0 -0
- /package/web/assets/{pla-Bolz9fha.js → pla-gSg-RBG0.js} +0 -0
- /package/web/assets/{postiats-Ba6dE5lj.js → postiats-BQxHyDfy.js} +0 -0
- /package/web/assets/{powerquery-2jW3ONgo.js → powerquery-DzDmbVZN.js} +0 -0
- /package/web/assets/{powershell-CMfBIiJp.js → powershell-yy4Q5BEQ.js} +0 -0
- /package/web/assets/{protobuf-BpaquOUj.js → protobuf-B_hIk9q0.js} +0 -0
- /package/web/assets/{pug-cBG82Ydz.js → pug-CdeEb1ft.js} +0 -0
- /package/web/assets/{qsharp-DtqL8Lmt.js → qsharp-CiSquEgT.js} +0 -0
- /package/web/assets/{r-CKLew4sj.js → r-D1Ocz1x4.js} +0 -0
- /package/web/assets/{redis-BdDxRU-N.js → redis-D42Y12yz.js} +0 -0
- /package/web/assets/{redshift-X4a4MlqW.js → redshift-CS62MYfG.js} +0 -0
- /package/web/assets/{restructuredtext-LA4Fv05h.js → restructuredtext-CKFSMEXw.js} +0 -0
- /package/web/assets/{ruby-CibE_WVM.js → ruby-C4gTaeU3.js} +0 -0
- /package/web/assets/{rust-BK6RsiL0.js → rust-C_f2AiHQ.js} +0 -0
- /package/web/assets/{sb-BnCCt0Qj.js → sb-CdNdpP1r.js} +0 -0
- /package/web/assets/{scala-oaBDrVqj.js → scala-N4EWaFTL.js} +0 -0
- /package/web/assets/{scheme-C-wkLC01.js → scheme-CttIARxx.js} +0 -0
- /package/web/assets/{scss-BceVGOJY.js → scss-v0wtbxaE.js} +0 -0
- /package/web/assets/{shell-CxhBAFA5.js → shell-BJdEWXbc.js} +0 -0
- /package/web/assets/{solidity-D9sXgTfx.js → solidity-DKDa59zZ.js} +0 -0
- /package/web/assets/{sophia-C4VBhIEM.js → sophia--Kw-vDpr.js} +0 -0
- /package/web/assets/{sparql-DbsK2s9y.js → sparql-BI8jS9Pc.js} +0 -0
- /package/web/assets/{sql-CuGfE0wH.js → sql-Dz6z--6O.js} +0 -0
- /package/web/assets/{st-rB6Bqb2h.js → st-CT8D05gv.js} +0 -0
- /package/web/assets/{swift-BtHGe_4T.js → swift-Ayb3KzWx.js} +0 -0
- /package/web/assets/{systemverilog-CgKnzwU4.js → systemverilog-BtWJM9O5.js} +0 -0
- /package/web/assets/{tcl-DyqQWf87.js → tcl-BJvUWpbV.js} +0 -0
- /package/web/assets/{twig-C980XO5P.js → twig-c3YaBCU6.js} +0 -0
- /package/web/assets/{typespec-CVmA80px.js → typespec-gFCWLBoZ.js} +0 -0
- /package/web/assets/{uuid-2Kb5PDpq.js → uuid-B3B8HIK9.js} +0 -0
- /package/web/assets/{vb-rdNQv5VQ.js → vb-CVtfWXV2.js} +0 -0
- /package/web/assets/{w3c-keyname-B2B-WOb1.js → w3c-keyname-nn0G3QC2.js} +0 -0
- /package/web/assets/{wgsl-64oVbimL.js → wgsl-C_huaGQy.js} +0 -0
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
HealthCheckSchema,
|
|
11
11
|
JSLogParser,
|
|
12
12
|
JSLogParserSchema,
|
|
13
|
-
KanbanColumnSchema,
|
|
14
13
|
LOG_BUFFER_SIZE,
|
|
15
14
|
LogSourceConfigSchema,
|
|
16
15
|
NotificationPreferencesSchema,
|
|
@@ -27,24 +26,11 @@ import {
|
|
|
27
26
|
SettingsSchema,
|
|
28
27
|
TraefikConfigSchema,
|
|
29
28
|
UpdateEnvironmentSchema,
|
|
29
|
+
VariableOverrideSchema,
|
|
30
30
|
VariableSchema,
|
|
31
31
|
agentManager,
|
|
32
32
|
agentSpawner,
|
|
33
33
|
agentStore,
|
|
34
|
-
environmentStore,
|
|
35
|
-
getCollection,
|
|
36
|
-
nativeParsers,
|
|
37
|
-
readEntity,
|
|
38
|
-
serviceStore,
|
|
39
|
-
traefikManager,
|
|
40
|
-
writeEntity
|
|
41
|
-
} from "./chunk-PUNE7P6S.js";
|
|
42
|
-
import {
|
|
43
|
-
generateKanbanPopulatePrompt,
|
|
44
|
-
generateKanbanSystemPrompt,
|
|
45
|
-
kanbanStore
|
|
46
|
-
} from "./chunk-LZWBXYUE.js";
|
|
47
|
-
import {
|
|
48
34
|
clearEncryptionKey,
|
|
49
35
|
clearPendingLegacyKey,
|
|
50
36
|
decryptValue,
|
|
@@ -53,24 +39,32 @@ import {
|
|
|
53
39
|
encryptVariableSlots,
|
|
54
40
|
ensureEncryptionKey,
|
|
55
41
|
env,
|
|
42
|
+
environmentStore,
|
|
56
43
|
extractEncryptedVariableSlots,
|
|
44
|
+
getCollection,
|
|
57
45
|
getEncryptionKey,
|
|
58
46
|
getPendingLegacyKey,
|
|
59
47
|
hasEncryptionKey,
|
|
60
48
|
hasPendingLegacyKey,
|
|
61
49
|
isCiMode,
|
|
50
|
+
isEncryptedValue,
|
|
62
51
|
loadFromEnv,
|
|
52
|
+
nativeParsers,
|
|
53
|
+
readEntity,
|
|
54
|
+
serviceStore,
|
|
63
55
|
setEncryptionKey,
|
|
64
56
|
setEncryptionKeyInMemory,
|
|
65
57
|
setPendingLegacyKey,
|
|
66
|
-
stableEncryptValue
|
|
67
|
-
|
|
58
|
+
stableEncryptValue,
|
|
59
|
+
traefikManager,
|
|
60
|
+
writeEntity
|
|
61
|
+
} from "./chunk-Y2R42AXQ.js";
|
|
68
62
|
|
|
69
63
|
// ../server/src/create-server.ts
|
|
70
64
|
import express from "express";
|
|
71
65
|
import cors from "cors";
|
|
72
66
|
import { createServer } from "http";
|
|
73
|
-
import { resolve as
|
|
67
|
+
import { resolve as resolve5 } from "path";
|
|
74
68
|
import { WebSocketServer } from "ws";
|
|
75
69
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
76
70
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
@@ -108,6 +102,10 @@ async function verifyJwt(token) {
|
|
|
108
102
|
import { randomBytes as randomBytes2, randomUUID, timingSafeEqual } from "crypto";
|
|
109
103
|
import { readFile, writeFile, rename, mkdir, chmod } from "fs/promises";
|
|
110
104
|
import { join } from "path";
|
|
105
|
+
function maskApiKey(apiKey) {
|
|
106
|
+
const { key, ...rest } = apiKey;
|
|
107
|
+
return { ...rest, keyPreview: key.slice(-4) };
|
|
108
|
+
}
|
|
111
109
|
var FILE_PUBLIC = "api-keys.json";
|
|
112
110
|
var FILE_PRIVATE = "api-keys.private.json";
|
|
113
111
|
var ApiKeyStore = class {
|
|
@@ -122,7 +120,7 @@ var ApiKeyStore = class {
|
|
|
122
120
|
async persistFile(isPublic) {
|
|
123
121
|
await this.ensureDir();
|
|
124
122
|
const path = this.filePath(isPublic);
|
|
125
|
-
const keys = Array.from(this.keys.values()).filter((k) => k.isPublic === isPublic);
|
|
123
|
+
const keys = Array.from(this.keys.values()).filter((k) => k.isPublic === isPublic).map((k) => ({ ...k, key: encryptValue(k.key) }));
|
|
126
124
|
const tmp = path + ".tmp";
|
|
127
125
|
await writeFile(tmp, JSON.stringify({ keys }, null, 2), "utf-8");
|
|
128
126
|
await rename(tmp, path);
|
|
@@ -137,7 +135,8 @@ var ApiKeyStore = class {
|
|
|
137
135
|
const parsed = ApiKeysFileSchema.safeParse(JSON.parse(raw));
|
|
138
136
|
if (parsed.success) {
|
|
139
137
|
for (const k of parsed.data.keys) {
|
|
140
|
-
|
|
138
|
+
const key = isEncryptedValue(k.key) ? decryptValue(k.key) : k.key;
|
|
139
|
+
this.keys.set(k.id, { ...k, key });
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
142
|
} catch {
|
|
@@ -149,9 +148,18 @@ var ApiKeyStore = class {
|
|
|
149
148
|
await this.loadFile(this.filePath(true));
|
|
150
149
|
await this.loadFile(this.filePath(false));
|
|
151
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Keys as exposed to callers — secrets stripped. Use `reveal` to obtain one
|
|
153
|
+
* deliberately; never hand the whole set of secrets out at once.
|
|
154
|
+
*/
|
|
152
155
|
async list() {
|
|
153
156
|
await this.load();
|
|
154
|
-
return Array.from(this.keys.values());
|
|
157
|
+
return Array.from(this.keys.values()).map(maskApiKey);
|
|
158
|
+
}
|
|
159
|
+
/** The plaintext secret of a single key, or null when unknown. */
|
|
160
|
+
async reveal(id) {
|
|
161
|
+
await this.load();
|
|
162
|
+
return this.keys.get(id)?.key ?? null;
|
|
155
163
|
}
|
|
156
164
|
async create(name, scopes, isPublic) {
|
|
157
165
|
await this.load();
|
|
@@ -527,7 +535,7 @@ var ProjectStore = class {
|
|
|
527
535
|
await this.load();
|
|
528
536
|
const parsed = ProjectSchema.safeParse({ ...fields, id });
|
|
529
537
|
if (!parsed.success) {
|
|
530
|
-
console.error(
|
|
538
|
+
console.error("[project-store] Rejected remote project %s:", id, parsed.error.message);
|
|
531
539
|
return null;
|
|
532
540
|
}
|
|
533
541
|
const incoming = toDiskForm(parsed.data);
|
|
@@ -640,10 +648,13 @@ var EnvironmentOverridesStore = class {
|
|
|
640
648
|
this.diskEncryptedSlots = extractEncryptedVariableSlots(
|
|
641
649
|
parsed
|
|
642
650
|
);
|
|
643
|
-
|
|
651
|
+
const decrypted = decryptVariableSlots(
|
|
644
652
|
parsed,
|
|
645
653
|
"environment-overrides"
|
|
646
654
|
);
|
|
655
|
+
this.data = Object.fromEntries(
|
|
656
|
+
Object.entries(decrypted).map(([id, override]) => [id, { value: override.value }])
|
|
657
|
+
);
|
|
647
658
|
} catch {
|
|
648
659
|
this.data = {};
|
|
649
660
|
}
|
|
@@ -673,9 +684,9 @@ var EnvironmentOverridesStore = class {
|
|
|
673
684
|
await this.load();
|
|
674
685
|
return this.data[variableId] ?? null;
|
|
675
686
|
}
|
|
676
|
-
async setOverride(variableId,
|
|
687
|
+
async setOverride(variableId, override) {
|
|
677
688
|
await this.load();
|
|
678
|
-
this.data[variableId] =
|
|
689
|
+
this.data[variableId] = { value: override.value };
|
|
679
690
|
await this.save();
|
|
680
691
|
}
|
|
681
692
|
async removeOverride(variableId) {
|
|
@@ -728,10 +739,11 @@ var EnvironmentResolver = class {
|
|
|
728
739
|
async resolveEnvironment(environmentId) {
|
|
729
740
|
const visited = /* @__PURE__ */ new Set();
|
|
730
741
|
const result = await this.resolveDag(environmentId, visited);
|
|
731
|
-
const
|
|
732
|
-
for (const [varId,
|
|
733
|
-
|
|
734
|
-
|
|
742
|
+
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
743
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
744
|
+
const override = overridesByKey[variable.key];
|
|
745
|
+
if (override) {
|
|
746
|
+
result.variables[varId] = { ...variable, value: override.variable.value };
|
|
735
747
|
}
|
|
736
748
|
}
|
|
737
749
|
const rawEnv = {};
|
|
@@ -740,12 +752,13 @@ var EnvironmentResolver = class {
|
|
|
740
752
|
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
741
753
|
}
|
|
742
754
|
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
755
|
+
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
743
756
|
const env2 = {};
|
|
744
757
|
const envSecrets = [];
|
|
745
758
|
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
746
759
|
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
747
760
|
env2[variable.key] = value;
|
|
748
|
-
if (isSecret ||
|
|
761
|
+
if (isSecret || secretKeys.has(variable.key)) {
|
|
749
762
|
envSecrets.push(variable.key);
|
|
750
763
|
}
|
|
751
764
|
}
|
|
@@ -760,10 +773,11 @@ var EnvironmentResolver = class {
|
|
|
760
773
|
if (!environmentId) return {};
|
|
761
774
|
const visited = /* @__PURE__ */ new Set();
|
|
762
775
|
const result = await this.resolveDag(environmentId, visited);
|
|
763
|
-
const
|
|
764
|
-
for (const [varId,
|
|
765
|
-
|
|
766
|
-
|
|
776
|
+
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
777
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
778
|
+
const override = overridesByKey[variable.key];
|
|
779
|
+
if (override) {
|
|
780
|
+
result.variables[varId] = { ...variable, value: override.variable.value };
|
|
767
781
|
}
|
|
768
782
|
}
|
|
769
783
|
const rawEnv = {};
|
|
@@ -789,13 +803,15 @@ var EnvironmentResolver = class {
|
|
|
789
803
|
async resolveEnvironmentWithSources(environmentId) {
|
|
790
804
|
const visited = /* @__PURE__ */ new Set();
|
|
791
805
|
const result = await this.resolveDag(environmentId, visited);
|
|
792
|
-
const
|
|
793
|
-
for (const [varId,
|
|
794
|
-
|
|
795
|
-
|
|
806
|
+
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
807
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
808
|
+
const override = overridesByKey[variable.key];
|
|
809
|
+
if (override) {
|
|
810
|
+
result.variables[varId] = { ...variable, value: override.variable.value };
|
|
796
811
|
result.sources[varId] = {
|
|
797
812
|
...result.sources[varId],
|
|
798
|
-
overridden: true
|
|
813
|
+
overridden: true,
|
|
814
|
+
...override.source ? { overriddenBy: override.source } : {}
|
|
799
815
|
};
|
|
800
816
|
}
|
|
801
817
|
}
|
|
@@ -805,12 +821,13 @@ var EnvironmentResolver = class {
|
|
|
805
821
|
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
806
822
|
}
|
|
807
823
|
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys, keySources } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
824
|
+
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
808
825
|
const env2 = {};
|
|
809
826
|
const envSecrets = [];
|
|
810
827
|
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
811
828
|
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
812
829
|
env2[variable.key] = value;
|
|
813
|
-
if (isSecret ||
|
|
830
|
+
if (isSecret || secretKeys.has(variable.key)) {
|
|
814
831
|
envSecrets.push(variable.key);
|
|
815
832
|
}
|
|
816
833
|
}
|
|
@@ -835,6 +852,37 @@ var EnvironmentResolver = class {
|
|
|
835
852
|
};
|
|
836
853
|
}
|
|
837
854
|
/** Check if a raw value string interpolates any key that is secret in the context */
|
|
855
|
+
/**
|
|
856
|
+
* Toutes les clés dont la valeur est, directement ou par interpolation, un
|
|
857
|
+
* secret.
|
|
858
|
+
*
|
|
859
|
+
* Le contexte d'interpolation ne connaît que les secrets *hérités* : les
|
|
860
|
+
* secrets définis dans le même environnement n'y figurent pas, si bien qu'un
|
|
861
|
+
* `AUTH='Bearer {{environment.TOKEN}}'` voisin de son propre `TOKEN` secret
|
|
862
|
+
* ressortait non marqué — et donc non masqué par toute la chaîne en aval.
|
|
863
|
+
*
|
|
864
|
+
* On part des secrets hérités plus ceux déclarés ici, puis on propage
|
|
865
|
+
* jusqu'au point fixe : une valeur qui référence une clé secrète devient
|
|
866
|
+
* secrète à son tour, y compris en chaîne (A secret → B cite A → C cite B).
|
|
867
|
+
*/
|
|
868
|
+
collectSecretKeys(variables, parentSecretKeys) {
|
|
869
|
+
const secretKeys = new Set(parentSecretKeys);
|
|
870
|
+
for (const variable of Object.values(variables)) {
|
|
871
|
+
if (variable.value.isSecret) secretKeys.add(variable.key);
|
|
872
|
+
}
|
|
873
|
+
let changed = true;
|
|
874
|
+
while (changed) {
|
|
875
|
+
changed = false;
|
|
876
|
+
for (const variable of Object.values(variables)) {
|
|
877
|
+
if (secretKeys.has(variable.key)) continue;
|
|
878
|
+
if (this.interpolatesSecretKey(variable.value.value, secretKeys)) {
|
|
879
|
+
secretKeys.add(variable.key);
|
|
880
|
+
changed = true;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return secretKeys;
|
|
885
|
+
}
|
|
838
886
|
interpolatesSecretKey(rawValue, ctxSecretKeys) {
|
|
839
887
|
const refs = [...rawValue.matchAll(/\{\{environment\.([^}]+)\}\}/g)].map((m) => m[1]);
|
|
840
888
|
return refs.some((ref) => ctxSecretKeys.has(ref));
|
|
@@ -905,7 +953,49 @@ var EnvironmentResolver = class {
|
|
|
905
953
|
if (additionalIds.length === 0) return environment.parentIds;
|
|
906
954
|
return [...environment.parentIds, ...additionalIds];
|
|
907
955
|
}
|
|
908
|
-
|
|
956
|
+
/**
|
|
957
|
+
* Project an environment's variables onto their overrides: keep only the
|
|
958
|
+
* variables that have one, and swap in the override's value slot. The key
|
|
959
|
+
* always comes from the definition.
|
|
960
|
+
*
|
|
961
|
+
* Feeding this projection back through `resolveDag` resolves the override
|
|
962
|
+
* chain with the exact same inheritance and dedup-by-key rules as the value
|
|
963
|
+
* chain — so an override set on a parent env is no longer swallowed when a
|
|
964
|
+
* child (or a service env) redeclares the same key.
|
|
965
|
+
*/
|
|
966
|
+
projectOverrides(variables, overrides) {
|
|
967
|
+
const projected = {};
|
|
968
|
+
for (const [varId, variable] of Object.entries(variables)) {
|
|
969
|
+
const override = overrides[varId];
|
|
970
|
+
if (override) {
|
|
971
|
+
projected[varId] = { key: variable.key, value: override.value };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return projected;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Resolve the override chain independently of the value chain and return the
|
|
978
|
+
* winning override per variable key (empty when nothing is overridden).
|
|
979
|
+
*/
|
|
980
|
+
async resolveOverridesByKey(environmentId) {
|
|
981
|
+
const allOverrides = await environmentOverridesStore.getAll();
|
|
982
|
+
if (Object.keys(allOverrides).length === 0) return {};
|
|
983
|
+
const result = await this.resolveDag(
|
|
984
|
+
environmentId,
|
|
985
|
+
/* @__PURE__ */ new Set(),
|
|
986
|
+
(variables) => this.projectOverrides(variables, allOverrides)
|
|
987
|
+
);
|
|
988
|
+
const byKey = {};
|
|
989
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
990
|
+
const source = result.sources[varId];
|
|
991
|
+
byKey[variable.key] = {
|
|
992
|
+
variable,
|
|
993
|
+
...source ? { source: { environmentId: source.environmentId, environmentName: source.environmentName } } : {}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return byKey;
|
|
997
|
+
}
|
|
998
|
+
async resolveDag(environmentId, visited, project) {
|
|
909
999
|
if (visited.has(environmentId)) {
|
|
910
1000
|
throw new Error(`Circular dependency detected in environment inheritance for ${environmentId}`);
|
|
911
1001
|
}
|
|
@@ -918,7 +1008,7 @@ var EnvironmentResolver = class {
|
|
|
918
1008
|
const parentResults = [];
|
|
919
1009
|
for (const parentId of effectiveParentIds) {
|
|
920
1010
|
const parentVisited = new Set(visited);
|
|
921
|
-
const parentResult = await this.resolveDag(parentId, parentVisited);
|
|
1011
|
+
const parentResult = await this.resolveDag(parentId, parentVisited, project);
|
|
922
1012
|
parentResults.push(parentResult);
|
|
923
1013
|
}
|
|
924
1014
|
const mergedVariables = {};
|
|
@@ -967,7 +1057,11 @@ var EnvironmentResolver = class {
|
|
|
967
1057
|
}
|
|
968
1058
|
}
|
|
969
1059
|
}
|
|
970
|
-
for (const
|
|
1060
|
+
for (const key of this.collectSecretKeys(mergedVariables, parentSecretKeys)) {
|
|
1061
|
+
parentSecretKeys.add(key);
|
|
1062
|
+
}
|
|
1063
|
+
const ownVariables = project ? project(environment.variables) : environment.variables;
|
|
1064
|
+
for (const [varId, variable] of Object.entries(ownVariables)) {
|
|
971
1065
|
let wasInherited = varId in mergedVariables;
|
|
972
1066
|
for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
|
|
973
1067
|
if (existingVar.key === variable.key && existingId !== varId) {
|
|
@@ -1763,13 +1857,13 @@ var agentRouter = router({
|
|
|
1763
1857
|
yield { agentId: agent.config.id, status: agent.status };
|
|
1764
1858
|
}
|
|
1765
1859
|
const queue = [];
|
|
1766
|
-
let
|
|
1860
|
+
let resolve6 = null;
|
|
1767
1861
|
const handler = (event) => {
|
|
1768
1862
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
|
|
1769
1863
|
queue.push(event);
|
|
1770
|
-
if (
|
|
1771
|
-
|
|
1772
|
-
|
|
1864
|
+
if (resolve6) {
|
|
1865
|
+
resolve6();
|
|
1866
|
+
resolve6 = null;
|
|
1773
1867
|
}
|
|
1774
1868
|
};
|
|
1775
1869
|
agentManager.on("status", handler);
|
|
@@ -1779,7 +1873,7 @@ var agentRouter = router({
|
|
|
1779
1873
|
yield queue.shift();
|
|
1780
1874
|
} else {
|
|
1781
1875
|
await new Promise((r) => {
|
|
1782
|
-
|
|
1876
|
+
resolve6 = r;
|
|
1783
1877
|
});
|
|
1784
1878
|
}
|
|
1785
1879
|
}
|
|
@@ -1795,14 +1889,14 @@ import { TRPCError as TRPCError5 } from "@trpc/server";
|
|
|
1795
1889
|
var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
|
|
1796
1890
|
async function* forwardEvents(eventName, filter) {
|
|
1797
1891
|
const queue = [];
|
|
1798
|
-
let
|
|
1892
|
+
let resolve6 = null;
|
|
1799
1893
|
const handler = (data) => {
|
|
1800
1894
|
if (filter && !filter(data)) return;
|
|
1801
1895
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
1802
1896
|
queue.push(data);
|
|
1803
|
-
if (
|
|
1804
|
-
|
|
1805
|
-
|
|
1897
|
+
if (resolve6) {
|
|
1898
|
+
resolve6();
|
|
1899
|
+
resolve6 = null;
|
|
1806
1900
|
}
|
|
1807
1901
|
};
|
|
1808
1902
|
agentManager.on(eventName, handler);
|
|
@@ -1812,7 +1906,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
1812
1906
|
yield queue.shift();
|
|
1813
1907
|
} else {
|
|
1814
1908
|
await new Promise((r) => {
|
|
1815
|
-
|
|
1909
|
+
resolve6 = r;
|
|
1816
1910
|
});
|
|
1817
1911
|
}
|
|
1818
1912
|
}
|
|
@@ -1822,7 +1916,7 @@ async function* forwardEvents(eventName, filter) {
|
|
|
1822
1916
|
}
|
|
1823
1917
|
async function* forwardLogBatches(agentId, processId) {
|
|
1824
1918
|
const queue = [];
|
|
1825
|
-
let
|
|
1919
|
+
let resolve6 = null;
|
|
1826
1920
|
const handler = (data) => {
|
|
1827
1921
|
if (data.agentId !== agentId) return;
|
|
1828
1922
|
const batch = data.batch;
|
|
@@ -1831,9 +1925,9 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
1831
1925
|
if (filtered.length === 0) return;
|
|
1832
1926
|
if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
|
|
1833
1927
|
queue.push(filtered);
|
|
1834
|
-
if (
|
|
1835
|
-
|
|
1836
|
-
|
|
1928
|
+
if (resolve6) {
|
|
1929
|
+
resolve6();
|
|
1930
|
+
resolve6 = null;
|
|
1837
1931
|
}
|
|
1838
1932
|
};
|
|
1839
1933
|
agentManager.on("process:log", handler);
|
|
@@ -1843,7 +1937,7 @@ async function* forwardLogBatches(agentId, processId) {
|
|
|
1843
1937
|
yield queue.shift();
|
|
1844
1938
|
} else {
|
|
1845
1939
|
await new Promise((r) => {
|
|
1846
|
-
|
|
1940
|
+
resolve6 = r;
|
|
1847
1941
|
});
|
|
1848
1942
|
}
|
|
1849
1943
|
}
|
|
@@ -2315,7 +2409,9 @@ var environmentRouter = router({
|
|
|
2315
2409
|
// --- Overrides (flat, by variableId) ---
|
|
2316
2410
|
setOverride: protectedProcedure.input(z8.object({
|
|
2317
2411
|
variableId: z8.string().max(128),
|
|
2318
|
-
|
|
2412
|
+
// Only the value slot: the env var name comes from the variable
|
|
2413
|
+
// definition (a `key` sent by an older client is stripped here).
|
|
2414
|
+
variable: VariableOverrideSchema
|
|
2319
2415
|
})).mutation(async ({ input }) => {
|
|
2320
2416
|
await environmentOverridesStore.setOverride(input.variableId, input.variable);
|
|
2321
2417
|
return { success: true };
|
|
@@ -2341,6 +2437,72 @@ var environmentRouter = router({
|
|
|
2341
2437
|
if (!result) throw new TRPCError6({ code: "INTERNAL_SERVER_ERROR", message: "Failed to update variable" });
|
|
2342
2438
|
return result;
|
|
2343
2439
|
}),
|
|
2440
|
+
/**
|
|
2441
|
+
* List everywhere a variable key is used inside a project: environments that
|
|
2442
|
+
* define it, and variables that reference it through {{environment.KEY}}.
|
|
2443
|
+
* Used by the UI to warn before editing a shared variable from a resolved view.
|
|
2444
|
+
*/
|
|
2445
|
+
keyUsages: protectedProcedure.input(z8.object({
|
|
2446
|
+
projectId: z8.string().max(128),
|
|
2447
|
+
key: z8.string().min(1).max(256),
|
|
2448
|
+
/** Environment holding the variable about to be edited — enables inherited-impact detection */
|
|
2449
|
+
environmentId: z8.string().max(128).optional()
|
|
2450
|
+
})).query(async ({ input }) => {
|
|
2451
|
+
const environments = await environmentStore.list(input.projectId);
|
|
2452
|
+
const services = await serviceStore.list();
|
|
2453
|
+
const serviceName = (id) => services.find((s) => s.id === id)?.name;
|
|
2454
|
+
const escaped = input.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2455
|
+
const refPattern = new RegExp(`\\{\\{environment\\.${escaped}\\}\\}`);
|
|
2456
|
+
const definedIn = [];
|
|
2457
|
+
const referencedBy = [];
|
|
2458
|
+
for (const env2 of environments) {
|
|
2459
|
+
const name = serviceName(env2.reference);
|
|
2460
|
+
for (const variable of Object.values(env2.variables ?? {})) {
|
|
2461
|
+
if (variable.key === input.key) {
|
|
2462
|
+
definedIn.push({
|
|
2463
|
+
environmentId: env2.id,
|
|
2464
|
+
environmentName: env2.name,
|
|
2465
|
+
scope: env2.scope ?? "global",
|
|
2466
|
+
...name ? { serviceName: name } : {}
|
|
2467
|
+
});
|
|
2468
|
+
continue;
|
|
2469
|
+
}
|
|
2470
|
+
const slot = variable.value;
|
|
2471
|
+
if (refPattern.test(`${slot.pre}${slot.value}${slot.post}`)) {
|
|
2472
|
+
referencedBy.push({
|
|
2473
|
+
environmentId: env2.id,
|
|
2474
|
+
environmentName: env2.name,
|
|
2475
|
+
scope: env2.scope ?? "global",
|
|
2476
|
+
...name ? { serviceName: name } : {},
|
|
2477
|
+
key: variable.key
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const inheritedBy = [];
|
|
2483
|
+
if (input.environmentId) {
|
|
2484
|
+
for (const env2 of environments) {
|
|
2485
|
+
if (env2.id === input.environmentId) continue;
|
|
2486
|
+
try {
|
|
2487
|
+
const resolved = await environmentResolver.resolveEnvironmentWithSources(env2.id);
|
|
2488
|
+
const inherits = Object.entries(resolved.variables).some(
|
|
2489
|
+
([varId, variable]) => variable.key === input.key && resolved.sources[varId]?.environmentId === input.environmentId
|
|
2490
|
+
);
|
|
2491
|
+
if (inherits) {
|
|
2492
|
+
const name = serviceName(env2.reference);
|
|
2493
|
+
inheritedBy.push({
|
|
2494
|
+
environmentId: env2.id,
|
|
2495
|
+
environmentName: env2.name,
|
|
2496
|
+
scope: env2.scope ?? "global",
|
|
2497
|
+
...name ? { serviceName: name } : {}
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
} catch {
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return { definedIn, referencedBy, inheritedBy };
|
|
2505
|
+
}),
|
|
2344
2506
|
removeVariable: protectedProcedure.input(z8.object({
|
|
2345
2507
|
environmentId: z8.string().max(128),
|
|
2346
2508
|
variableId: z8.string().max(128)
|
|
@@ -2355,10 +2517,11 @@ var environmentRouter = router({
|
|
|
2355
2517
|
|
|
2356
2518
|
// ../server/src/trpc/routers/settings.ts
|
|
2357
2519
|
import { promises as dns } from "dns";
|
|
2520
|
+
import { resolve } from "path";
|
|
2358
2521
|
import { z as z9 } from "zod";
|
|
2359
2522
|
import { TRPCError as TRPCError7 } from "@trpc/server";
|
|
2360
2523
|
var NotificationPreferencesPatchSchema = NotificationPreferencesSchema.partial().strict();
|
|
2361
|
-
function
|
|
2524
|
+
function maskApiKey2(plaintext) {
|
|
2362
2525
|
if (plaintext.length <= 8) return "****";
|
|
2363
2526
|
return plaintext.slice(0, 4) + "****" + plaintext.slice(-4);
|
|
2364
2527
|
}
|
|
@@ -2370,6 +2533,14 @@ var settingsRouter = router({
|
|
|
2370
2533
|
const settings = await settingsManager.getSettings();
|
|
2371
2534
|
return settings;
|
|
2372
2535
|
}),
|
|
2536
|
+
/**
|
|
2537
|
+
* Paths the server is running from (working directory + data directory).
|
|
2538
|
+
* Read-only, purely informational for the settings UI.
|
|
2539
|
+
*/
|
|
2540
|
+
getPaths: protectedProcedure.query(() => ({
|
|
2541
|
+
cwd: process.cwd(),
|
|
2542
|
+
dataDir: resolve(env.DATA_DIR)
|
|
2543
|
+
})),
|
|
2373
2544
|
/**
|
|
2374
2545
|
* Update settings (mode, cloudUrl)
|
|
2375
2546
|
*/
|
|
@@ -2519,7 +2690,7 @@ var settingsRouter = router({
|
|
|
2519
2690
|
let maskedKey = "(aucune)";
|
|
2520
2691
|
if (p.encryptedApiKey) {
|
|
2521
2692
|
try {
|
|
2522
|
-
maskedKey =
|
|
2693
|
+
maskedKey = maskApiKey2(decryptValue(p.encryptedApiKey));
|
|
2523
2694
|
} catch {
|
|
2524
2695
|
maskedKey = "(cl\xE9 invalide)";
|
|
2525
2696
|
}
|
|
@@ -2559,7 +2730,7 @@ var settingsRouter = router({
|
|
|
2559
2730
|
...priv,
|
|
2560
2731
|
aiProviders: [...priv.aiProviders ?? [], newProvider]
|
|
2561
2732
|
});
|
|
2562
|
-
return { ...newProvider, maskedKey: plainKey ?
|
|
2733
|
+
return { ...newProvider, maskedKey: plainKey ? maskApiKey2(plainKey) : "(aucune)", encryptedApiKey: void 0 };
|
|
2563
2734
|
}),
|
|
2564
2735
|
/**
|
|
2565
2736
|
* Update AI provider (apiKey optional — omit to keep existing)
|
|
@@ -2591,7 +2762,7 @@ var settingsRouter = router({
|
|
|
2591
2762
|
let maskedKey = "(aucune)";
|
|
2592
2763
|
if (updated.encryptedApiKey) {
|
|
2593
2764
|
try {
|
|
2594
|
-
maskedKey =
|
|
2765
|
+
maskedKey = maskApiKey2(decryptValue(updated.encryptedApiKey));
|
|
2595
2766
|
} catch {
|
|
2596
2767
|
}
|
|
2597
2768
|
}
|
|
@@ -2729,7 +2900,7 @@ var settingsRouter = router({
|
|
|
2729
2900
|
}
|
|
2730
2901
|
} catch {
|
|
2731
2902
|
}
|
|
2732
|
-
await new Promise((
|
|
2903
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
2733
2904
|
}
|
|
2734
2905
|
}),
|
|
2735
2906
|
/**
|
|
@@ -2916,7 +3087,7 @@ import { z as z11 } from "zod";
|
|
|
2916
3087
|
import { TRPCError as TRPCError8 } from "@trpc/server";
|
|
2917
3088
|
import { execFile as execFile2, spawn } from "child_process";
|
|
2918
3089
|
import { stat as stat2 } from "fs/promises";
|
|
2919
|
-
import { resolve, join as join7 } from "path";
|
|
3090
|
+
import { resolve as resolve2, join as join7 } from "path";
|
|
2920
3091
|
import { homedir as homedir2 } from "os";
|
|
2921
3092
|
|
|
2922
3093
|
// ../server/src/services/user-config-manager.ts
|
|
@@ -3008,9 +3179,9 @@ async function getServiceCwd(serviceId) {
|
|
|
3008
3179
|
return resolveCwd(rawCwd);
|
|
3009
3180
|
}
|
|
3010
3181
|
async function isCommandAvailable(command) {
|
|
3011
|
-
return new Promise((
|
|
3182
|
+
return new Promise((resolve6) => {
|
|
3012
3183
|
execFile2("which", [command], { timeout: 5e3 }, (err) => {
|
|
3013
|
-
|
|
3184
|
+
resolve6(!err);
|
|
3014
3185
|
});
|
|
3015
3186
|
});
|
|
3016
3187
|
}
|
|
@@ -3064,7 +3235,7 @@ var editorRouter = router({
|
|
|
3064
3235
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3065
3236
|
}
|
|
3066
3237
|
const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
|
|
3067
|
-
const resolvedPath =
|
|
3238
|
+
const resolvedPath = resolve2(expanded || ".");
|
|
3068
3239
|
try {
|
|
3069
3240
|
await stat2(resolvedPath);
|
|
3070
3241
|
} catch (err) {
|
|
@@ -3130,10 +3301,19 @@ var apiKeysRouter = router({
|
|
|
3130
3301
|
create: protectedProcedure.input(KeyInputSchema).mutation(
|
|
3131
3302
|
({ input }) => apiKeyStore.create(input.name, input.scopes, input.isPublic)
|
|
3132
3303
|
),
|
|
3304
|
+
/**
|
|
3305
|
+
* Renvoie le secret d'une seule clé, sur demande explicite. `list` ne le
|
|
3306
|
+
* contient pas : une clé ne doit jamais être re-servie en masse.
|
|
3307
|
+
*/
|
|
3308
|
+
reveal: protectedProcedure.input(z12.object({ id: z12.string().max(128) })).query(async ({ input }) => {
|
|
3309
|
+
const key = await apiKeyStore.reveal(input.id);
|
|
3310
|
+
if (!key) throw new TRPCError9({ code: "NOT_FOUND", message: "API key not found" });
|
|
3311
|
+
return { key };
|
|
3312
|
+
}),
|
|
3133
3313
|
update: protectedProcedure.input(KeyInputSchema.extend({ id: z12.string().max(128) })).mutation(async ({ input }) => {
|
|
3134
3314
|
const updated = await apiKeyStore.update(input.id, input.name, input.scopes, input.isPublic);
|
|
3135
3315
|
if (!updated) throw new TRPCError9({ code: "NOT_FOUND", message: "API key not found" });
|
|
3136
|
-
return updated;
|
|
3316
|
+
return maskApiKey(updated);
|
|
3137
3317
|
}),
|
|
3138
3318
|
remove: protectedProcedure.input(z12.object({ id: z12.string().max(128) })).mutation(async ({ input }) => {
|
|
3139
3319
|
const removed = await apiKeyStore.remove(input.id);
|
|
@@ -3160,7 +3340,7 @@ var apiKeysRouter = router({
|
|
|
3160
3340
|
});
|
|
3161
3341
|
|
|
3162
3342
|
// ../server/src/trpc/routers/chat.ts
|
|
3163
|
-
import { z as
|
|
3343
|
+
import { z as z15 } from "zod";
|
|
3164
3344
|
import Anthropic2 from "@anthropic-ai/sdk";
|
|
3165
3345
|
import OpenAI2 from "openai";
|
|
3166
3346
|
import { execFile as execFile3 } from "child_process";
|
|
@@ -3794,6 +3974,18 @@ async function startClaudeTranscriptWatcher() {
|
|
|
3794
3974
|
}
|
|
3795
3975
|
}
|
|
3796
3976
|
}
|
|
3977
|
+
function stopClaudeTranscriptWatcher() {
|
|
3978
|
+
for (const w of watchers) {
|
|
3979
|
+
try {
|
|
3980
|
+
w.close();
|
|
3981
|
+
} catch {
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
watchers.length = 0;
|
|
3985
|
+
for (const t2 of pendingResyncs.values()) clearTimeout(t2);
|
|
3986
|
+
pendingResyncs.clear();
|
|
3987
|
+
started = false;
|
|
3988
|
+
}
|
|
3797
3989
|
|
|
3798
3990
|
// ../server/src/services/codex-sessions.ts
|
|
3799
3991
|
import { readdir as readdir3 } from "fs/promises";
|
|
@@ -3827,7 +4019,7 @@ async function codexSessionExists(sessionId) {
|
|
|
3827
4019
|
}
|
|
3828
4020
|
|
|
3829
4021
|
// ../server/src/trpc/routers/chat.ts
|
|
3830
|
-
import { TRPCError as
|
|
4022
|
+
import { TRPCError as TRPCError11 } from "@trpc/server";
|
|
3831
4023
|
|
|
3832
4024
|
// ../server/src/trpc/shared-schemas.ts
|
|
3833
4025
|
import { z as z13 } from "zod";
|
|
@@ -4185,7 +4377,7 @@ function parse(text) {
|
|
|
4185
4377
|
return result;
|
|
4186
4378
|
}
|
|
4187
4379
|
function getClaudeUsage() {
|
|
4188
|
-
return new Promise((
|
|
4380
|
+
return new Promise((resolve6, reject) => {
|
|
4189
4381
|
const env2 = { ...process.env };
|
|
4190
4382
|
delete env2["CLAUDECODE"];
|
|
4191
4383
|
let claudeBin = "claude";
|
|
@@ -4216,7 +4408,7 @@ function getClaudeUsage() {
|
|
|
4216
4408
|
clearTimeout(hardTimeout);
|
|
4217
4409
|
term?.kill();
|
|
4218
4410
|
const clean = buffer.replaceAll(/\x1b\[[0-9;]*X/g, "\n").replaceAll(/\x1b\[[0-9;]*[mGKHFABCDJsu]/g, "").replaceAll(/\x1b[^a-zA-Z]*[a-zA-Z]/g, "").replaceAll("\r", "");
|
|
4219
|
-
|
|
4411
|
+
resolve6(parse(clean));
|
|
4220
4412
|
}
|
|
4221
4413
|
const hardTimeout = setTimeout(() => {
|
|
4222
4414
|
if (!done) finish();
|
|
@@ -4350,7 +4542,7 @@ function parseCodexStatusText(text) {
|
|
|
4350
4542
|
return entries;
|
|
4351
4543
|
}
|
|
4352
4544
|
function getCodexUsageEntries() {
|
|
4353
|
-
return new Promise((
|
|
4545
|
+
return new Promise((resolve6, reject) => {
|
|
4354
4546
|
let term = null;
|
|
4355
4547
|
try {
|
|
4356
4548
|
term = pty2.spawn("codex", [], {
|
|
@@ -4380,7 +4572,7 @@ function getCodexUsageEntries() {
|
|
|
4380
4572
|
const debugClean = stripAnsi(buffer).slice(-6e3);
|
|
4381
4573
|
process.stdout.write("[codex-usage] raw tail:\n" + debugClean + "\n");
|
|
4382
4574
|
}
|
|
4383
|
-
|
|
4575
|
+
resolve6(parseCodexStatusText(buffer));
|
|
4384
4576
|
};
|
|
4385
4577
|
const hardTimeout = setTimeout(() => {
|
|
4386
4578
|
finish();
|
|
@@ -4494,10 +4686,10 @@ var ActiveSessionRegistry = class {
|
|
|
4494
4686
|
}
|
|
4495
4687
|
}
|
|
4496
4688
|
pushEvent(conversationId, event) {
|
|
4497
|
-
if (process.env.
|
|
4689
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: event - ", event.type);
|
|
4498
4690
|
const session = this.sessions.get(conversationId);
|
|
4499
4691
|
if (!session) return;
|
|
4500
|
-
if (process.env.
|
|
4692
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: event - ", event.type, "session", session.conversationId);
|
|
4501
4693
|
if (event.type === "delta") {
|
|
4502
4694
|
session.fullContent += event.text;
|
|
4503
4695
|
const last = session.parts[session.parts.length - 1];
|
|
@@ -4535,15 +4727,15 @@ var ActiveSessionRegistry = class {
|
|
|
4535
4727
|
if (session.buffer.length >= MAX_BUFFER) {
|
|
4536
4728
|
session.buffer.shift();
|
|
4537
4729
|
}
|
|
4538
|
-
if (process.env.
|
|
4730
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: event - ", event.type, "push buffer");
|
|
4539
4731
|
session.buffer.push(event);
|
|
4540
4732
|
session.totalProduced++;
|
|
4541
|
-
if (process.env.
|
|
4733
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: event - ", event.type, "notify", session.notify.size);
|
|
4542
4734
|
for (const cb of session.notify) cb();
|
|
4543
4735
|
session.notify.clear();
|
|
4544
4736
|
}
|
|
4545
4737
|
markDone(conversationId) {
|
|
4546
|
-
if (process.env.
|
|
4738
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: markdone....", conversationId);
|
|
4547
4739
|
const session = this.sessions.get(conversationId);
|
|
4548
4740
|
if (!session) return;
|
|
4549
4741
|
session.status = "done";
|
|
@@ -4775,15 +4967,15 @@ import { join as join12 } from "path";
|
|
|
4775
4967
|
|
|
4776
4968
|
// ../server/src/services/run-store-utils.ts
|
|
4777
4969
|
import { chmod as chmod7, mkdir as mkdir9, rename as rename8, writeFile as writeFile8 } from "fs/promises";
|
|
4778
|
-
import { dirname as dirname2, resolve as
|
|
4970
|
+
import { dirname as dirname2, resolve as resolve3, sep } from "path";
|
|
4779
4971
|
import { readFile as readFile9 } from "fs/promises";
|
|
4780
4972
|
var SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
4781
4973
|
function resolveConversationRunDir(rootDirName, conversationId) {
|
|
4782
4974
|
if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {
|
|
4783
4975
|
throw new Error("Invalid conversationId");
|
|
4784
4976
|
}
|
|
4785
|
-
const base =
|
|
4786
|
-
const dir =
|
|
4977
|
+
const base = resolve3(env.DATA_DIR, rootDirName);
|
|
4978
|
+
const dir = resolve3(base, conversationId);
|
|
4787
4979
|
if (!dir.startsWith(base + sep)) {
|
|
4788
4980
|
throw new Error("Invalid conversationId");
|
|
4789
4981
|
}
|
|
@@ -5028,7 +5220,7 @@ async function fetchProviderModels(provider, plaintextKey) {
|
|
|
5028
5220
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5029
5221
|
import { mkdir as mkdir11, writeFile as writeFile9, readFile as readFile11, unlink as unlink2, readdir as readdir4 } from "fs/promises";
|
|
5030
5222
|
import { existsSync } from "fs";
|
|
5031
|
-
import { join as join13, extname, resolve as
|
|
5223
|
+
import { join as join13, extname, resolve as resolve4, sep as sep2 } from "path";
|
|
5032
5224
|
var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5033
5225
|
"image/png",
|
|
5034
5226
|
"image/jpeg",
|
|
@@ -5066,8 +5258,8 @@ var ImageStorageService = class {
|
|
|
5066
5258
|
const safeExt = ext.replace(/[^a-z.]/g, "");
|
|
5067
5259
|
const fileName = `${imageId}${safeExt}`;
|
|
5068
5260
|
const filePath = join13(this.dir, fileName);
|
|
5069
|
-
const resolvedPath =
|
|
5070
|
-
const resolvedDir =
|
|
5261
|
+
const resolvedPath = resolve4(filePath);
|
|
5262
|
+
const resolvedDir = resolve4(this.dir);
|
|
5071
5263
|
if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep2)) {
|
|
5072
5264
|
throw new Error("Invalid file path");
|
|
5073
5265
|
}
|
|
@@ -5076,15 +5268,25 @@ var ImageStorageService = class {
|
|
|
5076
5268
|
await writeFile9(`${filePath}.meta.json`, JSON.stringify(meta), { encoding: "utf-8", mode: 384 });
|
|
5077
5269
|
return meta;
|
|
5078
5270
|
}
|
|
5079
|
-
|
|
5271
|
+
/**
|
|
5272
|
+
* Resolve an image on disk.
|
|
5273
|
+
*
|
|
5274
|
+
* `expectedContextId` scopes the lookup: when given, an image stored under a
|
|
5275
|
+
* different context (or under none at all) is reported as missing. Callers
|
|
5276
|
+
* that serve an image through a context-scoped URL MUST pass it — the id
|
|
5277
|
+
* alone is not an authorization, and without the check any imageId could be
|
|
5278
|
+
* fetched under any conversation.
|
|
5279
|
+
*/
|
|
5280
|
+
async getImagePath(imageId, expectedContextId) {
|
|
5080
5281
|
if (!UUID_RE.test(imageId)) return null;
|
|
5081
5282
|
try {
|
|
5082
5283
|
const files = await readdir4(this.dir);
|
|
5083
5284
|
const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
|
|
5084
5285
|
if (!metaFile) return null;
|
|
5085
5286
|
const raw = JSON.parse(await readFile11(join13(this.dir, metaFile), "utf-8"));
|
|
5287
|
+
if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
|
|
5086
5288
|
if (!existsSync(raw.filePath)) return null;
|
|
5087
|
-
return { filePath: raw.filePath, mimeType: raw.mimeType };
|
|
5289
|
+
return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
|
|
5088
5290
|
} catch {
|
|
5089
5291
|
return null;
|
|
5090
5292
|
}
|
|
@@ -5119,8 +5321,8 @@ var ImageStorageService = class {
|
|
|
5119
5321
|
})
|
|
5120
5322
|
);
|
|
5121
5323
|
}
|
|
5122
|
-
async readImageAsBase64(imageId) {
|
|
5123
|
-
const result = await this.getImagePath(imageId);
|
|
5324
|
+
async readImageAsBase64(imageId, expectedContextId) {
|
|
5325
|
+
const result = await this.getImagePath(imageId, expectedContextId);
|
|
5124
5326
|
if (!result) return null;
|
|
5125
5327
|
const buffer = await readFile11(result.filePath);
|
|
5126
5328
|
return { base64: buffer.toString("base64"), mimeType: result.mimeType };
|
|
@@ -5148,8 +5350,8 @@ var ImageStorageService = class {
|
|
|
5148
5350
|
var _singleton;
|
|
5149
5351
|
function getSingleton() {
|
|
5150
5352
|
if (!_singleton) {
|
|
5151
|
-
const dataDir = process.env["
|
|
5152
|
-
_singleton = new ImageStorageService(
|
|
5353
|
+
const dataDir = process.env["RUNEYA_DATA_DIR"] ?? ".runeya";
|
|
5354
|
+
_singleton = new ImageStorageService(resolve4(dataDir, "images"));
|
|
5153
5355
|
}
|
|
5154
5356
|
return _singleton;
|
|
5155
5357
|
}
|
|
@@ -5186,10 +5388,10 @@ function mimeToExt(mime) {
|
|
|
5186
5388
|
}
|
|
5187
5389
|
|
|
5188
5390
|
// ../server/src/services/message-images.ts
|
|
5189
|
-
async function resolveImagesToBase64(images) {
|
|
5391
|
+
async function resolveImagesToBase64(images, contextId) {
|
|
5190
5392
|
const results = await Promise.all(
|
|
5191
5393
|
images.map(async (img) => {
|
|
5192
|
-
const resolved = await imageStorageService.readImageAsBase64(img.imageId);
|
|
5394
|
+
const resolved = await imageStorageService.readImageAsBase64(img.imageId, contextId);
|
|
5193
5395
|
if (!resolved) {
|
|
5194
5396
|
console.warn("[message-images] Image not found (imageId redacted from log)");
|
|
5195
5397
|
return null;
|
|
@@ -5200,111 +5402,495 @@ async function resolveImagesToBase64(images) {
|
|
|
5200
5402
|
return results.filter((r) => r !== null);
|
|
5201
5403
|
}
|
|
5202
5404
|
|
|
5203
|
-
// ../server/src/services/
|
|
5204
|
-
import { spawn as spawn2 } from "child_process";
|
|
5205
|
-
|
|
5206
|
-
// ../server/src/services/ai-runners/scenario-tokens.ts
|
|
5207
|
-
function parseEndToken(output) {
|
|
5208
|
-
const tail = output.trimEnd().slice(-2e3);
|
|
5209
|
-
const branchMatch = tail.match(/<pass\s+branch="(\d+)"\s*\/>/);
|
|
5210
|
-
if (branchMatch) {
|
|
5211
|
-
return { status: "pass", message: "", branchChoice: Number.parseInt(branchMatch[1], 10) };
|
|
5212
|
-
}
|
|
5213
|
-
if (/<pass\s*\/>/.test(tail)) {
|
|
5214
|
-
return { status: "pass", message: "" };
|
|
5215
|
-
}
|
|
5216
|
-
const errorMatch = tail.match(/<error>([\s\S]*?)<\/error>/);
|
|
5217
|
-
if (errorMatch) {
|
|
5218
|
-
return { status: "error", message: errorMatch[1].trim() };
|
|
5219
|
-
}
|
|
5220
|
-
const askMatch = tail.match(/<ask_user>([\s\S]*?)<\/ask_user>/);
|
|
5221
|
-
if (askMatch) {
|
|
5222
|
-
return { status: "ask_user", message: askMatch[1].trim() };
|
|
5223
|
-
}
|
|
5224
|
-
return { status: "none", message: "" };
|
|
5225
|
-
}
|
|
5226
|
-
var TOKEN_INSTRUCTIONS = `
|
|
5227
|
-
## Instructions de signalisation (OBLIGATOIRE)
|
|
5228
|
-
|
|
5229
|
-
\xC0 la fin de CHAQUE r\xE9ponse, tu DOIS terminer par exactement l'un de ces tags XML :
|
|
5230
|
-
|
|
5231
|
-
- Si tu as termin\xE9 avec succ\xE8s (chemin par d\xE9faut) : \`<pass />\`
|
|
5232
|
-
- Si le n\u0153ud propose plusieurs branches de sortie num\xE9rot\xE9es : \`<pass branch="1" />\`, \`<pass branch="2" />\`, etc. (choisis la branche qui correspond le mieux au r\xE9sultat)
|
|
5233
|
-
- Si tu as rencontr\xE9 une erreur bloquante : \`<error>description courte</error>\`
|
|
5234
|
-
- Si tu es bloqu\xE9 par une information que personne dans l'\xE9quipe ne peut fournir (secret, d\xE9cision business, credential externe) : \`<ask_user>ta question</ask_user>\` \u2014 NE PAS utiliser pour demander une confirmation ou un accord ("es-tu pr\xEAt ?", "puis-je continuer ?")
|
|
5235
|
-
|
|
5236
|
-
Ne jamais omettre ce tag. Il doit \xEAtre la toute derni\xE8re ligne de ta r\xE9ponse.
|
|
5237
|
-
`.trim();
|
|
5238
|
-
|
|
5239
|
-
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5405
|
+
// ../server/src/services/kanban-commands.ts
|
|
5240
5406
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
5241
5407
|
|
|
5242
|
-
// ../server/src/
|
|
5243
|
-
import {
|
|
5244
|
-
import {
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5408
|
+
// ../server/src/trpc/routers/cloud-auth.ts
|
|
5409
|
+
import { z as z14 } from "zod";
|
|
5410
|
+
import { TRPCError as TRPCError10 } from "@trpc/server";
|
|
5411
|
+
|
|
5412
|
+
// ../server/src/services/cloud-session-store.ts
|
|
5413
|
+
import { readFile as readFile12, writeFile as writeFile10, rename as rename9, mkdir as mkdir12, chmod as chmod8, unlink as unlink3 } from "fs/promises";
|
|
5414
|
+
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
5415
|
+
import { join as join14 } from "path";
|
|
5416
|
+
var FILENAME4 = "cloud-session.json";
|
|
5417
|
+
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
5418
|
+
var CloudSessionStore = class {
|
|
5419
|
+
session = null;
|
|
5420
|
+
loaded = false;
|
|
5421
|
+
/** Pending login states (CSRF binding between `start` and the callback). */
|
|
5422
|
+
pending = /* @__PURE__ */ new Map();
|
|
5423
|
+
getFilePath() {
|
|
5424
|
+
return join14(env.DATA_DIR, FILENAME4);
|
|
5252
5425
|
}
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
let fh = null;
|
|
5260
|
-
let offset = 0;
|
|
5261
|
-
let pendingBuf = "";
|
|
5262
|
-
let reading = false;
|
|
5263
|
-
let queuedRead = false;
|
|
5264
|
-
const cleanup = async () => {
|
|
5265
|
-
stopped = true;
|
|
5266
|
-
if (watcher) {
|
|
5267
|
-
try {
|
|
5268
|
-
watcher.close();
|
|
5269
|
-
} catch {
|
|
5270
|
-
}
|
|
5271
|
-
watcher = null;
|
|
5272
|
-
}
|
|
5273
|
-
if (fh) {
|
|
5274
|
-
try {
|
|
5275
|
-
await fh.close();
|
|
5276
|
-
} catch {
|
|
5277
|
-
}
|
|
5278
|
-
fh = null;
|
|
5426
|
+
async load() {
|
|
5427
|
+
if (this.loaded) return;
|
|
5428
|
+
try {
|
|
5429
|
+
const raw = await readFile12(this.getFilePath(), "utf-8");
|
|
5430
|
+
this.session = JSON.parse(raw);
|
|
5431
|
+
} catch {
|
|
5279
5432
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5433
|
+
this.loaded = true;
|
|
5434
|
+
}
|
|
5435
|
+
/**
|
|
5436
|
+
* Mint a state value binding a login attempt to this server. Returned to the
|
|
5437
|
+
* cloud page and echoed back on the callback, so a redirect we never
|
|
5438
|
+
* initiated cannot install a token.
|
|
5439
|
+
*/
|
|
5440
|
+
createState(returnTo) {
|
|
5441
|
+
this.sweepStates();
|
|
5442
|
+
const state = randomBytes3(32).toString("hex");
|
|
5443
|
+
this.pending.set(state, { expiresAt: Date.now() + STATE_TTL_MS, returnTo });
|
|
5444
|
+
return state;
|
|
5445
|
+
}
|
|
5446
|
+
/**
|
|
5447
|
+
* Consume a state — single use, constant-time compare, TTL enforced. Returns
|
|
5448
|
+
* the URL to send the browser back to, or null when the state is unknown or
|
|
5449
|
+
* expired (i.e. a callback this server never initiated).
|
|
5450
|
+
*/
|
|
5451
|
+
consumeState(candidate) {
|
|
5452
|
+
this.sweepStates();
|
|
5453
|
+
for (const [state, entry] of this.pending) {
|
|
5454
|
+
const a = Buffer.from(state);
|
|
5455
|
+
const b = Buffer.from(candidate);
|
|
5456
|
+
if (a.length === b.length && timingSafeEqual2(a, b)) {
|
|
5457
|
+
this.pending.delete(state);
|
|
5458
|
+
return entry.expiresAt > Date.now() ? entry.returnTo : null;
|
|
5291
5459
|
}
|
|
5292
5460
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5461
|
+
return null;
|
|
5462
|
+
}
|
|
5463
|
+
sweepStates() {
|
|
5464
|
+
const now = Date.now();
|
|
5465
|
+
for (const [state, entry] of this.pending) {
|
|
5466
|
+
if (entry.expiresAt <= now) this.pending.delete(state);
|
|
5299
5467
|
}
|
|
5300
|
-
|
|
5468
|
+
}
|
|
5469
|
+
async save() {
|
|
5470
|
+
await mkdir12(env.DATA_DIR, { recursive: true });
|
|
5471
|
+
const filePath = this.getFilePath();
|
|
5472
|
+
const tmpPath = filePath + ".tmp";
|
|
5473
|
+
await writeFile10(tmpPath, JSON.stringify(this.session, null, 2), "utf-8");
|
|
5474
|
+
await rename9(tmpPath, filePath);
|
|
5475
|
+
await chmod8(filePath, 384);
|
|
5476
|
+
}
|
|
5477
|
+
/** Store the session token obtained from the one-time-token exchange. */
|
|
5478
|
+
async set(cloudUrl, token) {
|
|
5479
|
+
await this.load();
|
|
5480
|
+
this.session = { cloudUrl, encryptedToken: encryptValue(token) };
|
|
5481
|
+
await this.save();
|
|
5482
|
+
}
|
|
5483
|
+
/** The decrypted session token, or null when not logged in. */
|
|
5484
|
+
async getToken() {
|
|
5485
|
+
await this.load();
|
|
5486
|
+
if (!this.session) return null;
|
|
5301
5487
|
try {
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5488
|
+
return decryptValue(this.session.encryptedToken);
|
|
5489
|
+
} catch {
|
|
5490
|
+
return null;
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
async isLinked() {
|
|
5494
|
+
return await this.getToken() !== null;
|
|
5495
|
+
}
|
|
5496
|
+
/** Forget the session (sign-out). */
|
|
5497
|
+
async clear() {
|
|
5498
|
+
await this.load();
|
|
5499
|
+
this.session = null;
|
|
5500
|
+
this.pending.clear();
|
|
5501
|
+
try {
|
|
5502
|
+
await unlink3(this.getFilePath());
|
|
5503
|
+
} catch {
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
};
|
|
5507
|
+
var cloudSessionStore = new CloudSessionStore();
|
|
5508
|
+
|
|
5509
|
+
// ../server/src/trpc/routers/cloud-auth.ts
|
|
5510
|
+
var DEFAULT_CLOUD_API_URL = "https://api.runeya.dev";
|
|
5511
|
+
var DEFAULT_CLOUD_APP_URL = "https://runeya.dev";
|
|
5512
|
+
function getCloudApiUrl() {
|
|
5513
|
+
return (env.CLOUD_URL ?? DEFAULT_CLOUD_API_URL).replace(/\/+$/, "");
|
|
5514
|
+
}
|
|
5515
|
+
function getCloudAppUrl() {
|
|
5516
|
+
return (env.CLOUD_APP_URL ?? DEFAULT_CLOUD_APP_URL).replace(/\/+$/, "");
|
|
5517
|
+
}
|
|
5518
|
+
function isLocalOrigin(url) {
|
|
5519
|
+
try {
|
|
5520
|
+
const { protocol, hostname } = new URL(url);
|
|
5521
|
+
return (protocol === "http:" || protocol === "https:") && (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]");
|
|
5522
|
+
} catch {
|
|
5523
|
+
return false;
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
async function cloudFetch(path, init = {}) {
|
|
5527
|
+
const token = await cloudSessionStore.getToken();
|
|
5528
|
+
if (!token) {
|
|
5529
|
+
throw new TRPCError10({ code: "UNAUTHORIZED", message: "Not linked to the Runeya cloud." });
|
|
5530
|
+
}
|
|
5531
|
+
return fetch(`${getCloudApiUrl()}${path}`, {
|
|
5532
|
+
method: init.method ?? "GET",
|
|
5533
|
+
headers: {
|
|
5534
|
+
Authorization: `Bearer ${token}`,
|
|
5535
|
+
...init.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
5536
|
+
},
|
|
5537
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
5538
|
+
signal: AbortSignal.timeout(15e3)
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5541
|
+
var PROXY_PREFIXES = ["/organization/", "/project/", "/chat/", "/kanban/", "/user/prefs"];
|
|
5542
|
+
function isAllowedProxyPath(path) {
|
|
5543
|
+
if (!path.startsWith("/") || path.includes("..")) return false;
|
|
5544
|
+
const [pathname] = path.split("?");
|
|
5545
|
+
return PROXY_PREFIXES.some((p) => pathname === p || pathname.startsWith(p));
|
|
5546
|
+
}
|
|
5547
|
+
var cloudAuthRouter = router({
|
|
5548
|
+
/** Build the cloud authorization URL to open in the browser. */
|
|
5549
|
+
startLogin: protectedProcedure.input(z14.object({ returnTo: z14.string().url().max(2048) })).mutation(async ({ input }) => {
|
|
5550
|
+
if (!isLocalOrigin(input.returnTo)) {
|
|
5551
|
+
throw new TRPCError10({ code: "BAD_REQUEST", message: "returnTo must be a local origin." });
|
|
5552
|
+
}
|
|
5553
|
+
const state = cloudSessionStore.createState(input.returnTo);
|
|
5554
|
+
const callback = `http://127.0.0.1:${env.PORT}/api/cloud/callback`;
|
|
5555
|
+
const url = `${getCloudAppUrl()}/link-local?callback=${encodeURIComponent(callback)}&state=${encodeURIComponent(state)}`;
|
|
5556
|
+
return { url };
|
|
5557
|
+
}),
|
|
5558
|
+
/** Whether this machine holds a cloud session. */
|
|
5559
|
+
status: protectedProcedure.query(async () => {
|
|
5560
|
+
return { linked: await cloudSessionStore.isLinked() };
|
|
5561
|
+
}),
|
|
5562
|
+
/** The cloud session (user + org), fetched with the stored token. */
|
|
5563
|
+
session: protectedProcedure.query(async () => {
|
|
5564
|
+
if (!await cloudSessionStore.isLinked()) return null;
|
|
5565
|
+
const res = await cloudFetch("/api/auth/get-session");
|
|
5566
|
+
if (!res.ok) return null;
|
|
5567
|
+
return await res.json();
|
|
5568
|
+
}),
|
|
5569
|
+
/**
|
|
5570
|
+
* Forward a cloud API call, authenticated with this machine's token.
|
|
5571
|
+
*
|
|
5572
|
+
* This is what replaces the browser's direct, cookie-authenticated calls to
|
|
5573
|
+
* the cloud: the web UI asks its own server, which holds the credential.
|
|
5574
|
+
*/
|
|
5575
|
+
proxy: protectedProcedure.input(
|
|
5576
|
+
z14.object({
|
|
5577
|
+
path: z14.string().min(1).max(512),
|
|
5578
|
+
method: z14.enum(["GET", "POST"]).default("GET"),
|
|
5579
|
+
query: z14.record(z14.string().max(128), z14.string().max(2048)).optional(),
|
|
5580
|
+
body: z14.unknown().optional()
|
|
5581
|
+
})
|
|
5582
|
+
).mutation(async ({ input }) => {
|
|
5583
|
+
if (!isAllowedProxyPath(input.path)) {
|
|
5584
|
+
throw new TRPCError10({ code: "FORBIDDEN", message: `Path not allowed: ${input.path}` });
|
|
5585
|
+
}
|
|
5586
|
+
const qs = input.query ? `?${new URLSearchParams(input.query).toString()}` : "";
|
|
5587
|
+
const res = await cloudFetch(`/api/auth${input.path}${qs}`, {
|
|
5588
|
+
method: input.method,
|
|
5589
|
+
...input.method === "POST" ? { body: input.body ?? {} } : {}
|
|
5590
|
+
});
|
|
5591
|
+
const text = await res.text();
|
|
5592
|
+
let data = null;
|
|
5593
|
+
try {
|
|
5594
|
+
data = text ? JSON.parse(text) : null;
|
|
5595
|
+
} catch {
|
|
5596
|
+
data = text;
|
|
5597
|
+
}
|
|
5598
|
+
if (!res.ok) return { data: null, error: { status: res.status, ...data } };
|
|
5599
|
+
return { data, error: null };
|
|
5600
|
+
}),
|
|
5601
|
+
/** Drop the cloud session, both here and on the cloud. */
|
|
5602
|
+
signOut: protectedProcedure.mutation(async () => {
|
|
5603
|
+
if (await cloudSessionStore.isLinked()) {
|
|
5604
|
+
try {
|
|
5605
|
+
await cloudFetch("/api/auth/sign-out", { method: "POST", body: {} });
|
|
5606
|
+
} catch {
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
await cloudSessionStore.clear();
|
|
5610
|
+
return { success: true };
|
|
5611
|
+
})
|
|
5612
|
+
});
|
|
5613
|
+
|
|
5614
|
+
// ../server/src/services/kanban-store.ts
|
|
5615
|
+
async function callCloud(path, init = {}) {
|
|
5616
|
+
try {
|
|
5617
|
+
const res = await cloudFetch(`/api/auth${path}`, init);
|
|
5618
|
+
if (!res.ok) {
|
|
5619
|
+
console.error("[kanban-store] cloud call failed: %s %s", res.status, path);
|
|
5620
|
+
return { ok: false, data: null };
|
|
5621
|
+
}
|
|
5622
|
+
return { ok: true, data: await res.json() };
|
|
5623
|
+
} catch (err) {
|
|
5624
|
+
console.error("[kanban-store] cloud call errored:", err);
|
|
5625
|
+
return { ok: false, data: null };
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
async function resolveOrganizationId() {
|
|
5629
|
+
const session = await callCloud("/get-session");
|
|
5630
|
+
const active = session.data?.session?.activeOrganizationId;
|
|
5631
|
+
if (active) return active;
|
|
5632
|
+
const orgs = await callCloud("/organization/list");
|
|
5633
|
+
return orgs.data?.[0]?.id ?? null;
|
|
5634
|
+
}
|
|
5635
|
+
var KanbanStore = class {
|
|
5636
|
+
async list(includeArchived = false) {
|
|
5637
|
+
const organizationId = await resolveOrganizationId();
|
|
5638
|
+
if (!organizationId) return [];
|
|
5639
|
+
const query = `?organizationId=${encodeURIComponent(organizationId)}` + (includeArchived ? "&includeArchived=true" : "");
|
|
5640
|
+
const res = await callCloud(`/kanban/board/list${query}`);
|
|
5641
|
+
return res.data?.boards ?? [];
|
|
5642
|
+
}
|
|
5643
|
+
async get(id) {
|
|
5644
|
+
const res = await callCloud(`/kanban/board/get?id=${encodeURIComponent(id)}`);
|
|
5645
|
+
return res.data?.board ?? null;
|
|
5646
|
+
}
|
|
5647
|
+
async create(input) {
|
|
5648
|
+
const organizationId = await resolveOrganizationId();
|
|
5649
|
+
if (!organizationId) return null;
|
|
5650
|
+
const res = await callCloud("/kanban/board/create", {
|
|
5651
|
+
method: "POST",
|
|
5652
|
+
body: { organizationId, name: input.name, description: input.description ?? "" }
|
|
5653
|
+
});
|
|
5654
|
+
return res.data?.board ?? null;
|
|
5655
|
+
}
|
|
5656
|
+
async update(input) {
|
|
5657
|
+
const res = await callCloud("/kanban/board/update", {
|
|
5658
|
+
method: "POST",
|
|
5659
|
+
body: input
|
|
5660
|
+
});
|
|
5661
|
+
return res.data?.board ?? null;
|
|
5662
|
+
}
|
|
5663
|
+
async delete(id) {
|
|
5664
|
+
const res = await callCloud("/kanban/board/delete", {
|
|
5665
|
+
method: "POST",
|
|
5666
|
+
body: { id }
|
|
5667
|
+
});
|
|
5668
|
+
return res.data?.success ?? false;
|
|
5669
|
+
}
|
|
5670
|
+
};
|
|
5671
|
+
var kanbanStore = new KanbanStore();
|
|
5672
|
+
|
|
5673
|
+
// ../server/src/services/kanban-commands.ts
|
|
5674
|
+
function generateKanbanSystemPrompt(board, cardId) {
|
|
5675
|
+
let currentColTitle = "";
|
|
5676
|
+
let card = null;
|
|
5677
|
+
for (const col of board.columns) {
|
|
5678
|
+
const found = col.cards.find((c) => c.id === cardId);
|
|
5679
|
+
if (found) {
|
|
5680
|
+
currentColTitle = col.title;
|
|
5681
|
+
card = found;
|
|
5682
|
+
break;
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
const sortedColumns = [...board.columns].sort((a, b) => a.order - b.order);
|
|
5686
|
+
const lines = [];
|
|
5687
|
+
lines.push("## Kanban Board Context");
|
|
5688
|
+
lines.push("");
|
|
5689
|
+
lines.push(`Tu travailles dans le contexte d'une carte Kanban.`);
|
|
5690
|
+
lines.push(`- **Board** : ${board.name}`);
|
|
5691
|
+
if (card) {
|
|
5692
|
+
lines.push(`- **Carte** : ${card.title}`);
|
|
5693
|
+
lines.push(`- **Colonne actuelle** : \`${currentColTitle}\``);
|
|
5694
|
+
}
|
|
5695
|
+
lines.push("");
|
|
5696
|
+
lines.push("### Colonnes disponibles (noms exacts \xE0 utiliser) :");
|
|
5697
|
+
for (const col of sortedColumns) {
|
|
5698
|
+
const cardCount = col.cards.length;
|
|
5699
|
+
const isCurrent = card && col.cards.some((c) => c.id === cardId);
|
|
5700
|
+
lines.push(`- \`${col.title}\` (${cardCount} carte${cardCount !== 1 ? "s" : ""})${isCurrent ? " \u2190 **colonne actuelle**" : ""}`);
|
|
5701
|
+
}
|
|
5702
|
+
lines.push("");
|
|
5703
|
+
if (card) {
|
|
5704
|
+
const comments = (card.history ?? []).filter((e) => e.type === "comment");
|
|
5705
|
+
if (comments.length > 0) {
|
|
5706
|
+
lines.push("### Commentaires existants sur cette carte :");
|
|
5707
|
+
lines.push("");
|
|
5708
|
+
for (const c of comments.slice(-20)) {
|
|
5709
|
+
const who = c.author === "ai" ? "AI" : "Utilisateur";
|
|
5710
|
+
const date = new Date(c.timestamp).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
|
|
5711
|
+
lines.push(`- **${who}** (${date}) : ${c.detail}`);
|
|
5712
|
+
}
|
|
5713
|
+
lines.push("");
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
lines.push("### Outils Kanban disponibles");
|
|
5717
|
+
lines.push("");
|
|
5718
|
+
lines.push(`- **boardId** (\xE0 passer dans tous les outils) : \`${board.id}\``);
|
|
5719
|
+
if (card) lines.push(`- **cardId** (carte actuelle) : \`${card.id}\``);
|
|
5720
|
+
lines.push("");
|
|
5721
|
+
lines.push("Tu disposes des outils suivants pour interagir avec le board :");
|
|
5722
|
+
lines.push("- `kanban_move_card(boardId, cardId, targetColumn)` \u2014 D\xE9placer la carte vers une autre colonne");
|
|
5723
|
+
lines.push("- `kanban_add_comment(boardId, cardId, comment)` \u2014 Ajouter un commentaire sur la carte");
|
|
5724
|
+
lines.push("");
|
|
5725
|
+
lines.push("### OBLIGATOIRE \u2014 D\xE9cision Kanban");
|
|
5726
|
+
lines.push("");
|
|
5727
|
+
lines.push("\xC0 chaque r\xE9ponse, tu DOIS obligatoirement faire les deux actions suivantes :");
|
|
5728
|
+
lines.push("");
|
|
5729
|
+
lines.push("**1. \xC9valuer et d\xE9placer la carte si n\xE9cessaire** via `kanban_move_card` :");
|
|
5730
|
+
lines.push(" - **Au d\xE9but** si tu sais d\xE9j\xE0 que tu vas travailler activement (ex. tu passes en `In progress`).");
|
|
5731
|
+
lines.push(" - **\xC0 la fin** pour une d\xE9cision forte apr\xE8s analyse compl\xE8te (ex. tu conclus que c'est `Done`).");
|
|
5732
|
+
lines.push(" - Si tu ne fais qu'analyser ou poser des questions \u2192 **ne pas d\xE9placer**.");
|
|
5733
|
+
lines.push("");
|
|
5734
|
+
lines.push(" > \u26A0\uFE0F **CRITIQUE** : En fin de r\xE9ponse, tu DOIS TOUJOURS \xE9valuer si la colonne actuelle est encore correcte.");
|
|
5735
|
+
lines.push("");
|
|
5736
|
+
lines.push("**2. Appeler `kanban_add_comment`** r\xE9sumant ce que tu as fait ou constat\xE9 (**\xE0 la fin** de ta r\xE9ponse).");
|
|
5737
|
+
lines.push("");
|
|
5738
|
+
lines.push("> \u{1F6AB} **INTERDIT** : Terminer une r\xE9ponse sans appeler `kanban_add_comment`. C'est non n\xE9gociable.");
|
|
5739
|
+
lines.push("");
|
|
5740
|
+
return lines.join("\n");
|
|
5741
|
+
}
|
|
5742
|
+
function generateKanbanPopulatePrompt(board) {
|
|
5743
|
+
const lines = [];
|
|
5744
|
+
lines.push("## Kanban Board \u2014 Mode Populate");
|
|
5745
|
+
lines.push("");
|
|
5746
|
+
lines.push(`Tu es en mode **populate** sur le tableau Kanban **"${board.name}"**.`);
|
|
5747
|
+
lines.push(`Tu es un outil de gestion de projet. Quand l'utilisateur dit "cr\xE9er une card", "ajouter une t\xE2che", "cr\xE9er une carte", ou toute demande similaire, il parle EXCLUSIVEMENT d'une carte Kanban \xE0 ajouter au tableau \u2014 PAS d'un composant UI, PAS de code HTML ou CSS, PAS d'un composant React/Vue/Angular.`);
|
|
5748
|
+
lines.push("");
|
|
5749
|
+
lines.push("Ton UNIQUE r\xF4le est de cr\xE9er des cartes Kanban pertinentes en r\xE9ponse \xE0 la demande de l'utilisateur.");
|
|
5750
|
+
lines.push("Tu ne dois PAS r\xE9pondre \xE0 la question, ni donner d'explication, ni \xE9crire du code, ni montrer de composant.");
|
|
5751
|
+
lines.push("Tu dois UNIQUEMENT produire des tags XML `<kanban_add_card>` pour cr\xE9er des cartes dans le tableau.");
|
|
5752
|
+
if (board.description) {
|
|
5753
|
+
lines.push("");
|
|
5754
|
+
lines.push(`Description du board : ${board.description}`);
|
|
5755
|
+
}
|
|
5756
|
+
lines.push("");
|
|
5757
|
+
const sortedCols = [...board.columns].sort((a, b) => a.order - b.order);
|
|
5758
|
+
lines.push("### Colonnes existantes (noms exacts \xE0 utiliser) :");
|
|
5759
|
+
for (const col of sortedCols) {
|
|
5760
|
+
const cardCount = col.cards.length;
|
|
5761
|
+
lines.push(`- \`${col.title}\` (${cardCount} carte${cardCount !== 1 ? "s" : ""})`);
|
|
5762
|
+
for (const card of col.cards) {
|
|
5763
|
+
lines.push(` - ${card.title}`);
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
lines.push("");
|
|
5767
|
+
lines.push("### Outil disponible");
|
|
5768
|
+
lines.push("");
|
|
5769
|
+
lines.push(`- **boardId** (\xE0 passer dans tous les outils) : \`${board.id}\``);
|
|
5770
|
+
lines.push("");
|
|
5771
|
+
lines.push("Pour chaque carte \xE0 cr\xE9er, appelle l'outil `kanban_add_card(boardId, column, title, content)` :");
|
|
5772
|
+
lines.push("- `column` : nom exact de la colonne cible");
|
|
5773
|
+
lines.push("- `title` : titre concis de la carte");
|
|
5774
|
+
lines.push("- `content` : contenu markdown d\xE9taill\xE9 et actionnable");
|
|
5775
|
+
lines.push("");
|
|
5776
|
+
lines.push("### R\xE8gles STRICTES");
|
|
5777
|
+
lines.push("");
|
|
5778
|
+
lines.push("1. **NE R\xC9PONDS PAS** \xE0 la question de l'utilisateur. Ne donne aucune explication, aucun texte libre.");
|
|
5779
|
+
lines.push("2. **Appelle uniquement** `kanban_add_card`, rien d'autre.");
|
|
5780
|
+
lines.push("3. Tu ne peux PAS cr\xE9er de nouvelles colonnes. Dispatch les cartes dans les colonnes existantes list\xE9es ci-dessus.");
|
|
5781
|
+
lines.push("4. Utilise les noms **exacts** des colonnes.");
|
|
5782
|
+
lines.push("5. Chaque carte doit avoir un titre concis et un contenu markdown d\xE9taill\xE9 et actionnable.");
|
|
5783
|
+
lines.push("6. D\xE9compose la demande de l'utilisateur en t\xE2ches concr\xE8tes, chacune \xE9tant une carte.");
|
|
5784
|
+
lines.push("7. Dispatch chaque carte dans la colonne la plus appropri\xE9e selon sa nature.");
|
|
5785
|
+
lines.push("");
|
|
5786
|
+
return lines.join("\n");
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5789
|
+
// ../server/src/services/ai-runners/claude-code-runner.ts
|
|
5790
|
+
import { spawn as spawn2 } from "child_process";
|
|
5791
|
+
|
|
5792
|
+
// ../server/src/services/ai-runners/scenario-tokens.ts
|
|
5793
|
+
function parseEndToken(output) {
|
|
5794
|
+
const tail = output.trimEnd().slice(-2e3);
|
|
5795
|
+
const branchMatch = tail.match(/<pass\s+branch="(\d+)"\s*\/>/);
|
|
5796
|
+
if (branchMatch) {
|
|
5797
|
+
return { status: "pass", message: "", branchChoice: Number.parseInt(branchMatch[1], 10) };
|
|
5798
|
+
}
|
|
5799
|
+
if (/<pass\s*\/>/.test(tail)) {
|
|
5800
|
+
return { status: "pass", message: "" };
|
|
5801
|
+
}
|
|
5802
|
+
const errorMatch = tail.match(/<error>([\s\S]*?)<\/error>/);
|
|
5803
|
+
if (errorMatch) {
|
|
5804
|
+
return { status: "error", message: errorMatch[1].trim() };
|
|
5805
|
+
}
|
|
5806
|
+
const askMatch = tail.match(/<ask_user>([\s\S]*?)<\/ask_user>/);
|
|
5807
|
+
if (askMatch) {
|
|
5808
|
+
return { status: "ask_user", message: askMatch[1].trim() };
|
|
5809
|
+
}
|
|
5810
|
+
return { status: "none", message: "" };
|
|
5811
|
+
}
|
|
5812
|
+
var TOKEN_INSTRUCTIONS = `
|
|
5813
|
+
## Instructions de signalisation (OBLIGATOIRE)
|
|
5814
|
+
|
|
5815
|
+
\xC0 la fin de CHAQUE r\xE9ponse, tu DOIS terminer par exactement l'un de ces tags XML :
|
|
5816
|
+
|
|
5817
|
+
- Si tu as termin\xE9 avec succ\xE8s (chemin par d\xE9faut) : \`<pass />\`
|
|
5818
|
+
- Si le n\u0153ud propose plusieurs branches de sortie num\xE9rot\xE9es : \`<pass branch="1" />\`, \`<pass branch="2" />\`, etc. (choisis la branche qui correspond le mieux au r\xE9sultat)
|
|
5819
|
+
- Si tu as rencontr\xE9 une erreur bloquante : \`<error>description courte</error>\`
|
|
5820
|
+
- Si tu es bloqu\xE9 par une information que personne dans l'\xE9quipe ne peut fournir (secret, d\xE9cision business, credential externe) : \`<ask_user>ta question</ask_user>\` \u2014 NE PAS utiliser pour demander une confirmation ou un accord ("es-tu pr\xEAt ?", "puis-je continuer ?")
|
|
5821
|
+
|
|
5822
|
+
Ne jamais omettre ce tag. Il doit \xEAtre la toute derni\xE8re ligne de ta r\xE9ponse.
|
|
5823
|
+
`.trim();
|
|
5824
|
+
|
|
5825
|
+
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5826
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5827
|
+
|
|
5828
|
+
// ../server/src/services/ai-runners/claude-pty-tail.ts
|
|
5829
|
+
import { open } from "fs/promises";
|
|
5830
|
+
import { watch as watch2 } from "fs";
|
|
5831
|
+
async function waitForFile(sessionId, signal, timeoutMs) {
|
|
5832
|
+
const start = Date.now();
|
|
5833
|
+
while (Date.now() - start < timeoutMs) {
|
|
5834
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
5835
|
+
const path = await findClaudeSessionFile(sessionId);
|
|
5836
|
+
if (path) return path;
|
|
5837
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
5838
|
+
}
|
|
5839
|
+
throw new Error(`Transcript file for session ${sessionId} never appeared (timeout ${timeoutMs}ms)`);
|
|
5840
|
+
}
|
|
5841
|
+
function tailTranscript(opts) {
|
|
5842
|
+
const { sessionId, signal, onEntry, fileAppearTimeoutMs = 3e4 } = opts;
|
|
5843
|
+
let stopped = false;
|
|
5844
|
+
let watcher = null;
|
|
5845
|
+
let fh = null;
|
|
5846
|
+
let offset = 0;
|
|
5847
|
+
let pendingBuf = "";
|
|
5848
|
+
let reading = false;
|
|
5849
|
+
let queuedRead = false;
|
|
5850
|
+
const cleanup = async () => {
|
|
5851
|
+
stopped = true;
|
|
5852
|
+
if (watcher) {
|
|
5853
|
+
try {
|
|
5854
|
+
watcher.close();
|
|
5855
|
+
} catch {
|
|
5856
|
+
}
|
|
5857
|
+
watcher = null;
|
|
5858
|
+
}
|
|
5859
|
+
if (fh) {
|
|
5860
|
+
try {
|
|
5861
|
+
await fh.close();
|
|
5862
|
+
} catch {
|
|
5863
|
+
}
|
|
5864
|
+
fh = null;
|
|
5865
|
+
}
|
|
5866
|
+
};
|
|
5867
|
+
const drainBuffer = () => {
|
|
5868
|
+
let idx;
|
|
5869
|
+
while ((idx = pendingBuf.indexOf("\n")) >= 0) {
|
|
5870
|
+
const line = pendingBuf.slice(0, idx).trim();
|
|
5871
|
+
pendingBuf = pendingBuf.slice(idx + 1);
|
|
5872
|
+
if (!line) continue;
|
|
5873
|
+
try {
|
|
5874
|
+
const entry = JSON.parse(line);
|
|
5875
|
+
onEntry(entry);
|
|
5876
|
+
} catch {
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
};
|
|
5880
|
+
const readMore = async () => {
|
|
5881
|
+
if (!fh || stopped) return;
|
|
5882
|
+
if (reading) {
|
|
5883
|
+
queuedRead = true;
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5886
|
+
reading = true;
|
|
5887
|
+
try {
|
|
5888
|
+
while (!stopped) {
|
|
5889
|
+
const st = await fh.stat();
|
|
5890
|
+
if (st.size <= offset) break;
|
|
5891
|
+
const len = st.size - offset;
|
|
5892
|
+
const buf = Buffer.alloc(len);
|
|
5893
|
+
const { bytesRead } = await fh.read(buf, 0, len, offset);
|
|
5308
5894
|
if (bytesRead <= 0) break;
|
|
5309
5895
|
offset += bytesRead;
|
|
5310
5896
|
pendingBuf += buf.slice(0, bytesRead).toString("utf-8");
|
|
@@ -5337,12 +5923,12 @@ function tailTranscript(opts) {
|
|
|
5337
5923
|
const poll = setInterval(() => {
|
|
5338
5924
|
void readMore();
|
|
5339
5925
|
}, 500);
|
|
5340
|
-
await new Promise((
|
|
5926
|
+
await new Promise((resolve6) => {
|
|
5341
5927
|
const tick = setInterval(() => {
|
|
5342
5928
|
if (stopped) {
|
|
5343
5929
|
clearInterval(tick);
|
|
5344
5930
|
clearInterval(poll);
|
|
5345
|
-
|
|
5931
|
+
resolve6();
|
|
5346
5932
|
}
|
|
5347
5933
|
}, 100);
|
|
5348
5934
|
});
|
|
@@ -5361,7 +5947,7 @@ function tailTranscript(opts) {
|
|
|
5361
5947
|
|
|
5362
5948
|
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5363
5949
|
async function getAgentManager() {
|
|
5364
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
5950
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-2P4THUVA.js");
|
|
5365
5951
|
return agentManager2;
|
|
5366
5952
|
}
|
|
5367
5953
|
function buildPtyArgs(params, sessionId, opts) {
|
|
@@ -5431,7 +6017,7 @@ function dispatchEntry(entry, state, cbs) {
|
|
|
5431
6017
|
async function runClaudeViaPty(params) {
|
|
5432
6018
|
const { agentId, signal, skipTokenInstructions } = params;
|
|
5433
6019
|
if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
|
|
5434
|
-
const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId :
|
|
6020
|
+
const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID6();
|
|
5435
6021
|
const args = buildPtyArgs(params, sessionId, { excludeMcp: true });
|
|
5436
6022
|
const agentMgr = await getAgentManager();
|
|
5437
6023
|
const displayArgs = args.map((a) => {
|
|
@@ -5440,7 +6026,7 @@ async function runClaudeViaPty(params) {
|
|
|
5440
6026
|
return a;
|
|
5441
6027
|
});
|
|
5442
6028
|
params.onCommand?.(`claude ${displayArgs.join(" ")}`);
|
|
5443
|
-
return new Promise((
|
|
6029
|
+
return new Promise((resolve6, reject) => {
|
|
5444
6030
|
let settled = false;
|
|
5445
6031
|
let spawnId = null;
|
|
5446
6032
|
const state = { assistantText: "", tools: /* @__PURE__ */ new Map() };
|
|
@@ -5463,7 +6049,7 @@ async function runClaudeViaPty(params) {
|
|
|
5463
6049
|
settled = true;
|
|
5464
6050
|
cleanup();
|
|
5465
6051
|
void killSpawn();
|
|
5466
|
-
|
|
6052
|
+
resolve6(result);
|
|
5467
6053
|
};
|
|
5468
6054
|
const fail = (err) => {
|
|
5469
6055
|
if (settled) return;
|
|
@@ -5536,10 +6122,6 @@ async function runClaudeViaPty(params) {
|
|
|
5536
6122
|
}
|
|
5537
6123
|
|
|
5538
6124
|
// ../server/src/services/ai-runners/claude-code-runner.ts
|
|
5539
|
-
async function getAgentManager2() {
|
|
5540
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-QPZUYLJA.js");
|
|
5541
|
-
return agentManager2;
|
|
5542
|
-
}
|
|
5543
6125
|
function createClaudeStreamState() {
|
|
5544
6126
|
return {
|
|
5545
6127
|
fullContent: "",
|
|
@@ -5762,116 +6344,16 @@ function processClaudeStdoutLines(lines, streamState, params, ctx) {
|
|
|
5762
6344
|
}
|
|
5763
6345
|
var ClaudeCodeRunner = class {
|
|
5764
6346
|
run(params) {
|
|
5765
|
-
const ptyEnabled = params.usePty ?? process.env["RUNEYA_CLAUDE_PTY"] === "1";
|
|
5766
|
-
if (ptyEnabled && params.agentId && !(params.images && params.images.length > 0)) {
|
|
5767
|
-
return runClaudeViaPty(params);
|
|
5768
|
-
}
|
|
5769
6347
|
if (params.agentId) {
|
|
5770
|
-
return
|
|
6348
|
+
return runClaudeViaPty(params);
|
|
5771
6349
|
}
|
|
5772
6350
|
return this.runLocal(params);
|
|
5773
6351
|
}
|
|
5774
|
-
// ── Run via agent (remote spawn, local parsing) ─────────────────────────
|
|
5775
|
-
async runViaAgent(params) {
|
|
5776
|
-
const { agentId, signal, skipTokenInstructions } = params;
|
|
5777
|
-
const { args, env: env2, stdinData, sessionArg } = buildClaudeSpawnConfig(params, { excludeMcp: true });
|
|
5778
|
-
const agentMgr = await getAgentManager2();
|
|
5779
|
-
const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
|
|
5780
|
-
console.log(`[claude-code-runner] [agent:${agentId}] arg size: ${totalArgSize} bytes, session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
|
|
5781
|
-
const displayArgs = args.map((a) => {
|
|
5782
|
-
if (a === params.prompt) return "<prompt>";
|
|
5783
|
-
if (a.includes(TOKEN_INSTRUCTIONS)) return "<system-context>";
|
|
5784
|
-
return a;
|
|
5785
|
-
});
|
|
5786
|
-
params.onCommand?.(`claude ${displayArgs.join(" ")}`);
|
|
5787
|
-
return new Promise((resolve5, reject) => {
|
|
5788
|
-
const streamState = createClaudeStreamState();
|
|
5789
|
-
const ctx = { settled: false, earlyResult: null, lastResultError: "", rawStdoutLines: [] };
|
|
5790
|
-
let stdoutBuffer = "";
|
|
5791
|
-
let stderrBuffer = "";
|
|
5792
|
-
let spawnId = null;
|
|
5793
|
-
let unsubscribe = null;
|
|
5794
|
-
const onAbort = () => {
|
|
5795
|
-
if (spawnId && agentId) {
|
|
5796
|
-
agentMgr.agentMutation(agentId, "ai.kill", { spawnId }).catch(() => {
|
|
5797
|
-
});
|
|
5798
|
-
}
|
|
5799
|
-
};
|
|
5800
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5801
|
-
const cleanup = () => {
|
|
5802
|
-
signal.removeEventListener("abort", onAbort);
|
|
5803
|
-
if (unsubscribe) {
|
|
5804
|
-
unsubscribe();
|
|
5805
|
-
unsubscribe = null;
|
|
5806
|
-
}
|
|
5807
|
-
};
|
|
5808
|
-
agentMgr.agentMutation(agentId, "ai.spawn", {
|
|
5809
|
-
cli: "claude",
|
|
5810
|
-
args,
|
|
5811
|
-
env: env2,
|
|
5812
|
-
stdinData,
|
|
5813
|
-
enableMcp: !!params.mcpConfig,
|
|
5814
|
-
mcpApiBaseUrl: params.apiBaseUrl || void 0
|
|
5815
|
-
}).then(({ spawnId: id }) => {
|
|
5816
|
-
spawnId = id;
|
|
5817
|
-
if (signal.aborted) {
|
|
5818
|
-
cleanup();
|
|
5819
|
-
reject(new Error("Aborted"));
|
|
5820
|
-
return;
|
|
5821
|
-
}
|
|
5822
|
-
unsubscribe = agentMgr.agentSubscribe(
|
|
5823
|
-
agentId,
|
|
5824
|
-
"ai.stream",
|
|
5825
|
-
{ spawnId, fromOffset: 0 },
|
|
5826
|
-
(data) => {
|
|
5827
|
-
const item = data;
|
|
5828
|
-
const chunk = item.chunk;
|
|
5829
|
-
if (chunk.type === "stdout") {
|
|
5830
|
-
stdoutBuffer += chunk.data;
|
|
5831
|
-
const lines = stdoutBuffer.split("\n");
|
|
5832
|
-
stdoutBuffer = lines.pop() ?? "";
|
|
5833
|
-
processClaudeStdoutLines(lines, streamState, params, ctx);
|
|
5834
|
-
}
|
|
5835
|
-
if (chunk.type === "stderr") {
|
|
5836
|
-
stderrBuffer += chunk.data;
|
|
5837
|
-
}
|
|
5838
|
-
if (chunk.type === "exit") {
|
|
5839
|
-
const code = chunk.code ?? 0;
|
|
5840
|
-
cleanup();
|
|
5841
|
-
if (ctx.earlyResult) {
|
|
5842
|
-
resolve5({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
|
|
5843
|
-
return;
|
|
5844
|
-
}
|
|
5845
|
-
if (signal.aborted) {
|
|
5846
|
-
reject(new Error("Aborted"));
|
|
5847
|
-
return;
|
|
5848
|
-
}
|
|
5849
|
-
if (code !== 0 && !streamState.fullContent) {
|
|
5850
|
-
const stderr = stderrBuffer.trim().slice(-2e3);
|
|
5851
|
-
const rawStdout = ctx.rawStdoutLines.slice(-10).join("\n").trim();
|
|
5852
|
-
console.error(`[claude-code-runner] [agent] exit code ${code} | stderr: ${stderr || "(empty)"} | last stdout lines: ${rawStdout || "(empty)"}`);
|
|
5853
|
-
const detail = ctx.lastResultError || stderr || rawStdout;
|
|
5854
|
-
reject(new Error(`Claude Code exited with code ${code}${detail ? `: ${detail}` : ""}`));
|
|
5855
|
-
return;
|
|
5856
|
-
}
|
|
5857
|
-
const { status, message, branchChoice } = skipTokenInstructions ? { status: "none", message: "", branchChoice: void 0 } : parseEndToken(streamState.fullContent);
|
|
5858
|
-
resolve5({ output: streamState.fullContent, tokenStatus: status, tokenMessage: message, branchChoice, actualSessionId: streamState.actualSessionId });
|
|
5859
|
-
}
|
|
5860
|
-
},
|
|
5861
|
-
() => {
|
|
5862
|
-
}
|
|
5863
|
-
);
|
|
5864
|
-
}).catch((err) => {
|
|
5865
|
-
cleanup();
|
|
5866
|
-
reject(new Error(`Failed to spawn claude on agent: ${err.message}`));
|
|
5867
|
-
});
|
|
5868
|
-
});
|
|
5869
|
-
}
|
|
5870
6352
|
// ── Run locally (original implementation) ───────────────────────────────
|
|
5871
6353
|
runLocal(params) {
|
|
5872
6354
|
const { signal, skipTokenInstructions } = params;
|
|
5873
6355
|
const { args, env: env2, stdinData, sessionArg, hasImages: _hasImages } = buildClaudeSpawnConfig(params);
|
|
5874
|
-
return new Promise((
|
|
6356
|
+
return new Promise((resolve6, reject) => {
|
|
5875
6357
|
const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
|
|
5876
6358
|
console.log(`[claude-code-runner] arg size: ${totalArgSize} bytes (${(totalArgSize / 1024 / 1024).toFixed(2)} MB), session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
|
|
5877
6359
|
const displayArgs = args.map((a) => {
|
|
@@ -5913,7 +6395,7 @@ var ClaudeCodeRunner = class {
|
|
|
5913
6395
|
});
|
|
5914
6396
|
proc.on("close", (code) => {
|
|
5915
6397
|
if (ctx.earlyResult) {
|
|
5916
|
-
|
|
6398
|
+
resolve6({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
|
|
5917
6399
|
return;
|
|
5918
6400
|
}
|
|
5919
6401
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -5930,7 +6412,7 @@ var ClaudeCodeRunner = class {
|
|
|
5930
6412
|
return;
|
|
5931
6413
|
}
|
|
5932
6414
|
const { status, message, branchChoice } = skipTokenInstructions ? { status: "none", message: "", branchChoice: void 0 } : parseEndToken(streamState.fullContent);
|
|
5933
|
-
|
|
6415
|
+
resolve6({ output: streamState.fullContent, tokenStatus: status, tokenMessage: message, branchChoice, actualSessionId: streamState.actualSessionId });
|
|
5934
6416
|
});
|
|
5935
6417
|
proc.on("error", (err) => {
|
|
5936
6418
|
if (ctx.settled) return;
|
|
@@ -5986,12 +6468,12 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
5986
6468
|
if (forceId !== void 0 && forceId >= this.nextId) {
|
|
5987
6469
|
this.nextId = forceId + 1;
|
|
5988
6470
|
}
|
|
5989
|
-
return new Promise((
|
|
6471
|
+
return new Promise((resolve6, reject) => {
|
|
5990
6472
|
const timer = setTimeout(() => {
|
|
5991
6473
|
this.pending.delete(id);
|
|
5992
6474
|
reject(new Error(`Request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`));
|
|
5993
6475
|
}, this.requestTimeoutMs);
|
|
5994
|
-
this.pending.set(id, { resolve:
|
|
6476
|
+
this.pending.set(id, { resolve: resolve6, reject, timer });
|
|
5995
6477
|
this.writeLine({ jsonrpc: "2.0", method, id, params: params ?? {} });
|
|
5996
6478
|
});
|
|
5997
6479
|
}
|
|
@@ -6023,8 +6505,8 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
6023
6505
|
this.rl = null;
|
|
6024
6506
|
const proc = this.proc;
|
|
6025
6507
|
this.proc = null;
|
|
6026
|
-
return new Promise((
|
|
6027
|
-
proc.on("exit", () =>
|
|
6508
|
+
return new Promise((resolve6) => {
|
|
6509
|
+
proc.on("exit", () => resolve6());
|
|
6028
6510
|
proc.kill("SIGTERM");
|
|
6029
6511
|
const forceTimer = setTimeout(() => {
|
|
6030
6512
|
proc.kill("SIGKILL");
|
|
@@ -6117,8 +6599,8 @@ async function shutdownCodexAppServer() {
|
|
|
6117
6599
|
await client.shutdown();
|
|
6118
6600
|
}
|
|
6119
6601
|
}
|
|
6120
|
-
async function
|
|
6121
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
6602
|
+
async function getAgentManager2() {
|
|
6603
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-2P4THUVA.js");
|
|
6122
6604
|
return agentManager2;
|
|
6123
6605
|
}
|
|
6124
6606
|
function parseRuneyaCurlCommand(command) {
|
|
@@ -6187,7 +6669,7 @@ var CodexRunner = class {
|
|
|
6187
6669
|
// ── Run via agent (app-server JSON-RPC via agent tRPC routes) ───────────
|
|
6188
6670
|
async runViaAgent(params) {
|
|
6189
6671
|
const { agentId, prompt, systemContext, signal, model, effort, isResume, images, skipTokenInstructions, apiBaseUrl, apiToken, injectedEnv } = params;
|
|
6190
|
-
const agentMgr = await
|
|
6672
|
+
const agentMgr = await getAgentManager2();
|
|
6191
6673
|
if (isResume && params.sessionId && !this.lastThreadId) {
|
|
6192
6674
|
this.lastThreadId = params.sessionId;
|
|
6193
6675
|
}
|
|
@@ -6233,7 +6715,7 @@ ${prompt}`;
|
|
|
6233
6715
|
}
|
|
6234
6716
|
input.push({ type: "text", text: fullPrompt });
|
|
6235
6717
|
const turnRef = { id: null };
|
|
6236
|
-
return new Promise((
|
|
6718
|
+
return new Promise((resolve6, reject) => {
|
|
6237
6719
|
let settled = false;
|
|
6238
6720
|
let fullContent = "";
|
|
6239
6721
|
let thinkingEmitted = false;
|
|
@@ -6282,7 +6764,7 @@ ${prompt}`;
|
|
|
6282
6764
|
params.onDelta(delta);
|
|
6283
6765
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6284
6766
|
if (status !== "none") {
|
|
6285
|
-
settle(() =>
|
|
6767
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6286
6768
|
}
|
|
6287
6769
|
}
|
|
6288
6770
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -6356,7 +6838,7 @@ ${prompt}`;
|
|
|
6356
6838
|
return;
|
|
6357
6839
|
}
|
|
6358
6840
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6359
|
-
settle(() =>
|
|
6841
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6360
6842
|
} else if (method === "turn/errored") {
|
|
6361
6843
|
const error = p["error"];
|
|
6362
6844
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -6431,7 +6913,7 @@ ${prompt}`;
|
|
|
6431
6913
|
sandboxPolicy: { type: "dangerFullAccess" }
|
|
6432
6914
|
});
|
|
6433
6915
|
const turnId = turnRes.turn.id;
|
|
6434
|
-
return new Promise((
|
|
6916
|
+
return new Promise((resolve6, reject) => {
|
|
6435
6917
|
let settled = false;
|
|
6436
6918
|
let fullContent = "";
|
|
6437
6919
|
let thinkingEmitted = false;
|
|
@@ -6457,7 +6939,7 @@ ${prompt}`;
|
|
|
6457
6939
|
params.onDelta(delta);
|
|
6458
6940
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6459
6941
|
if (status !== "none") {
|
|
6460
|
-
settle(() =>
|
|
6942
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6461
6943
|
}
|
|
6462
6944
|
}
|
|
6463
6945
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -6531,7 +7013,7 @@ ${prompt}`;
|
|
|
6531
7013
|
return;
|
|
6532
7014
|
}
|
|
6533
7015
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6534
|
-
settle(() =>
|
|
7016
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6535
7017
|
} else if (method === "turn/errored") {
|
|
6536
7018
|
const error = p["error"];
|
|
6537
7019
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -6552,18 +7034,18 @@ ${prompt}`;
|
|
|
6552
7034
|
|
|
6553
7035
|
// ../server/src/trpc/routers/chat.ts
|
|
6554
7036
|
var execFileAsync2 = promisify2(execFile3);
|
|
6555
|
-
var MessageSchema =
|
|
6556
|
-
role:
|
|
6557
|
-
content:
|
|
7037
|
+
var MessageSchema = z15.object({
|
|
7038
|
+
role: z15.enum(["user", "assistant"]),
|
|
7039
|
+
content: z15.string().max(1e5)
|
|
6558
7040
|
});
|
|
6559
|
-
var ImagePartSchema =
|
|
6560
|
-
type:
|
|
6561
|
-
imageId:
|
|
6562
|
-
url:
|
|
6563
|
-
mimeType:
|
|
6564
|
-
altText:
|
|
7041
|
+
var ImagePartSchema = z15.object({
|
|
7042
|
+
type: z15.literal("image"),
|
|
7043
|
+
imageId: z15.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
7044
|
+
url: z15.string().max(500),
|
|
7045
|
+
mimeType: z15.string().max(100),
|
|
7046
|
+
altText: z15.string().max(500).optional()
|
|
6565
7047
|
});
|
|
6566
|
-
var ImagePartsSchema =
|
|
7048
|
+
var ImagePartsSchema = z15.array(ImagePartSchema).max(5).optional();
|
|
6567
7049
|
var LANGUAGE_INSTRUCTION = "Always respond in the same language the user writes in.";
|
|
6568
7050
|
async function buildSystemPrompt(apiKey) {
|
|
6569
7051
|
const allServices = await serviceStore.list();
|
|
@@ -6720,7 +7202,7 @@ async function* runOpenAI(client, model, initialMessages, apiKey, authToken, sig
|
|
|
6720
7202
|
yield { type: "done", text: "" };
|
|
6721
7203
|
}
|
|
6722
7204
|
async function* drainSession(session, signal, isReconnect) {
|
|
6723
|
-
if (process.env.
|
|
7205
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: drainSession");
|
|
6724
7206
|
if (isReconnect) {
|
|
6725
7207
|
for (const part of session.parts) {
|
|
6726
7208
|
if (part.type === "thinking") {
|
|
@@ -6873,7 +7355,7 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
|
|
|
6873
7355
|
if (result) capturedFinalResult = result;
|
|
6874
7356
|
}
|
|
6875
7357
|
}).then(() => {
|
|
6876
|
-
if (process.env.
|
|
7358
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: claude end...");
|
|
6877
7359
|
if (activeSessionRegistry.get(convId) !== session) return;
|
|
6878
7360
|
if (capturedFinalResult && capturedFinalResult.length > session.fullContent.length) {
|
|
6879
7361
|
const missing = capturedFinalResult.slice(session.fullContent.length);
|
|
@@ -6882,7 +7364,7 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
|
|
|
6882
7364
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
6883
7365
|
activeSessionRegistry.markDone(convId);
|
|
6884
7366
|
}).catch((err) => {
|
|
6885
|
-
if (process.env.
|
|
7367
|
+
if (process.env.RUNEYA_DEBUG) console.error("[chat]: error...", err?.message, err);
|
|
6886
7368
|
if (activeSessionRegistry.get(convId) !== session) return;
|
|
6887
7369
|
activeSessionRegistry.pushEvent(convId, { type: "error", message: `Claude Code error: ${err.message}` });
|
|
6888
7370
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
@@ -6944,24 +7426,24 @@ async function startCodexSession(convId, prompt, opts = {}) {
|
|
|
6944
7426
|
});
|
|
6945
7427
|
return { session, done };
|
|
6946
7428
|
}
|
|
6947
|
-
var MessagePartSchema =
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
type:
|
|
7429
|
+
var MessagePartSchema = z15.discriminatedUnion("type", [
|
|
7430
|
+
z15.object({ type: z15.literal("thinking"), text: z15.string().max(1e5) }),
|
|
7431
|
+
z15.object({ type: z15.literal("text"), text: z15.string().max(1e5) }),
|
|
7432
|
+
z15.object({
|
|
7433
|
+
type: z15.literal("tool"),
|
|
6952
7434
|
id: Id128Schema,
|
|
6953
7435
|
name: Id128Schema,
|
|
6954
|
-
input:
|
|
6955
|
-
rawInput:
|
|
6956
|
-
result:
|
|
6957
|
-
isError:
|
|
6958
|
-
pending:
|
|
7436
|
+
input: z15.record(z15.string(), z15.unknown()).optional(),
|
|
7437
|
+
rawInput: z15.string().max(1e4).optional(),
|
|
7438
|
+
result: z15.unknown().optional(),
|
|
7439
|
+
isError: z15.boolean().optional(),
|
|
7440
|
+
pending: z15.boolean()
|
|
6959
7441
|
})
|
|
6960
7442
|
]);
|
|
6961
|
-
var StoredMessageSchema =
|
|
6962
|
-
role:
|
|
6963
|
-
content:
|
|
6964
|
-
parts:
|
|
7443
|
+
var StoredMessageSchema = z15.object({
|
|
7444
|
+
role: z15.enum(["user", "assistant"]),
|
|
7445
|
+
content: z15.string().max(1e5),
|
|
7446
|
+
parts: z15.array(MessagePartSchema).max(200).optional()
|
|
6965
7447
|
});
|
|
6966
7448
|
var chatRouter = router({
|
|
6967
7449
|
conversations: router({
|
|
@@ -6969,13 +7451,13 @@ var chatRouter = router({
|
|
|
6969
7451
|
const initial = await conversationStore.list();
|
|
6970
7452
|
yield { type: "initial", conversations: initial };
|
|
6971
7453
|
const queue = [];
|
|
6972
|
-
let
|
|
7454
|
+
let resolve6 = null;
|
|
6973
7455
|
const handler = (event) => {
|
|
6974
7456
|
if (queue.length >= 500) queue.shift();
|
|
6975
7457
|
queue.push(event);
|
|
6976
|
-
if (
|
|
6977
|
-
|
|
6978
|
-
|
|
7458
|
+
if (resolve6) {
|
|
7459
|
+
resolve6();
|
|
7460
|
+
resolve6 = null;
|
|
6979
7461
|
}
|
|
6980
7462
|
};
|
|
6981
7463
|
conversationStore.onChanged(handler);
|
|
@@ -6985,7 +7467,7 @@ var chatRouter = router({
|
|
|
6985
7467
|
yield queue.shift();
|
|
6986
7468
|
} else {
|
|
6987
7469
|
await new Promise((r) => {
|
|
6988
|
-
|
|
7470
|
+
resolve6 = r;
|
|
6989
7471
|
});
|
|
6990
7472
|
}
|
|
6991
7473
|
}
|
|
@@ -6994,25 +7476,25 @@ var chatRouter = router({
|
|
|
6994
7476
|
}
|
|
6995
7477
|
}),
|
|
6996
7478
|
list: protectedProcedure.query(() => conversationStore.list()),
|
|
6997
|
-
get: protectedProcedure.input(
|
|
7479
|
+
get: protectedProcedure.input(z15.object({ id: Id128Schema })).query(async ({ input }) => {
|
|
6998
7480
|
const conv = await conversationStore.get(input.id);
|
|
6999
|
-
if (!conv) throw new
|
|
7481
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7000
7482
|
return conv;
|
|
7001
7483
|
}),
|
|
7002
|
-
create: protectedProcedure.input(
|
|
7484
|
+
create: protectedProcedure.input(z15.object({
|
|
7003
7485
|
title: ShortString200Schema.optional(),
|
|
7004
|
-
isCli:
|
|
7005
|
-
runner:
|
|
7486
|
+
isCli: z15.boolean().optional(),
|
|
7487
|
+
runner: z15.enum(["claude-code", "codex"]).optional(),
|
|
7006
7488
|
providerId: Id128Schema.optional(),
|
|
7007
7489
|
scenarioId: Id128Schema.optional(),
|
|
7008
|
-
scenarioInput:
|
|
7490
|
+
scenarioInput: z15.string().max(1e5).optional(),
|
|
7009
7491
|
kanbanBoardId: Id128Schema.optional(),
|
|
7010
7492
|
kanbanCardId: Id128Schema.optional()
|
|
7011
7493
|
})).mutation(async ({ input }) => {
|
|
7012
7494
|
let scenario = null;
|
|
7013
7495
|
if (input.scenarioId) {
|
|
7014
7496
|
scenario = await scenarioStore.get(input.scenarioId);
|
|
7015
|
-
if (!scenario) throw new
|
|
7497
|
+
if (!scenario) throw new TRPCError11({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
7016
7498
|
}
|
|
7017
7499
|
const conv = await conversationStore.create(
|
|
7018
7500
|
input.title,
|
|
@@ -7033,22 +7515,22 @@ var chatRouter = router({
|
|
|
7033
7515
|
}
|
|
7034
7516
|
return conv;
|
|
7035
7517
|
}),
|
|
7036
|
-
update: protectedProcedure.input(
|
|
7518
|
+
update: protectedProcedure.input(z15.object({
|
|
7037
7519
|
id: Id128Schema,
|
|
7038
7520
|
title: ShortString200Schema.optional(),
|
|
7039
|
-
messages:
|
|
7040
|
-
contextUsage:
|
|
7041
|
-
inputTokens:
|
|
7042
|
-
contextWindow:
|
|
7521
|
+
messages: z15.array(StoredMessageSchema).max(500).optional(),
|
|
7522
|
+
contextUsage: z15.object({
|
|
7523
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
7524
|
+
contextWindow: z15.number().int().positive()
|
|
7043
7525
|
}).optional()
|
|
7044
7526
|
})).mutation(async ({ input }) => {
|
|
7045
7527
|
const conv = await conversationStore.update(input.id, { title: input.title, messages: input.messages, contextUsage: input.contextUsage });
|
|
7046
|
-
if (!conv) throw new
|
|
7528
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7047
7529
|
return conv;
|
|
7048
7530
|
}),
|
|
7049
|
-
delete: protectedProcedure.input(
|
|
7531
|
+
delete: protectedProcedure.input(z15.object({ id: Id128Schema })).mutation(async ({ input }) => {
|
|
7050
7532
|
const existing = await conversationStore.get(input.id);
|
|
7051
|
-
if (!existing) throw new
|
|
7533
|
+
if (!existing) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7052
7534
|
for (const agent of agentManager.listAgents()) {
|
|
7053
7535
|
const agentId = agent.config.id;
|
|
7054
7536
|
agentManager.agentQuery(
|
|
@@ -7068,13 +7550,13 @@ var chatRouter = router({
|
|
|
7068
7550
|
(err) => console.error("[chat.delete] Image cleanup error:", err)
|
|
7069
7551
|
);
|
|
7070
7552
|
}),
|
|
7071
|
-
archive: protectedProcedure.input(
|
|
7553
|
+
archive: protectedProcedure.input(z15.object({ id: Id128Schema, archived: z15.boolean() })).mutation(async ({ input }) => {
|
|
7072
7554
|
const conv = await conversationStore.update(input.id, { archived: input.archived });
|
|
7073
|
-
if (!conv) throw new
|
|
7555
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7074
7556
|
return conv;
|
|
7075
7557
|
})
|
|
7076
7558
|
}),
|
|
7077
|
-
listModels: protectedProcedure.input(
|
|
7559
|
+
listModels: protectedProcedure.input(z15.object({ providerId: Id128Schema.optional() }).optional()).query(async ({ input }) => {
|
|
7078
7560
|
const active = await getActiveProvider(input?.providerId);
|
|
7079
7561
|
if (!active) return [];
|
|
7080
7562
|
try {
|
|
@@ -7083,7 +7565,7 @@ var chatRouter = router({
|
|
|
7083
7565
|
return [];
|
|
7084
7566
|
}
|
|
7085
7567
|
}),
|
|
7086
|
-
checkCliAvailability: protectedProcedure.input(
|
|
7568
|
+
checkCliAvailability: protectedProcedure.input(z15.object({ agentId: Id128Schema.optional() }).optional()).query(async ({ input }) => {
|
|
7087
7569
|
if (input?.agentId) {
|
|
7088
7570
|
try {
|
|
7089
7571
|
const res = await agentManager.agentQuery(
|
|
@@ -7101,9 +7583,13 @@ var chatRouter = router({
|
|
|
7101
7583
|
]);
|
|
7102
7584
|
return { available: claudeOk, codexAvailable: codexOk };
|
|
7103
7585
|
}),
|
|
7104
|
-
enhanceScenarioPrompt: protectedProcedure.input(
|
|
7586
|
+
enhanceScenarioPrompt: protectedProcedure.input(z15.object({
|
|
7587
|
+
text: z15.string().max(1e4),
|
|
7588
|
+
providerId: Id128Schema.optional(),
|
|
7589
|
+
model: z15.string().max(200).optional()
|
|
7590
|
+
})).mutation(async ({ input }) => {
|
|
7105
7591
|
const active = await getActiveProvider(input.providerId);
|
|
7106
|
-
if (!active) throw new
|
|
7592
|
+
if (!active) throw new TRPCError11({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
7107
7593
|
const systemPrompt = `You are an expert at writing clear, concise user request briefs for AI-powered workflows.
|
|
7108
7594
|
When given a rough user request, rewrite it to be:
|
|
7109
7595
|
- Clear and well-structured
|
|
@@ -7112,7 +7598,7 @@ When given a rough user request, rewrite it to be:
|
|
|
7112
7598
|
- Concise but complete (no fluff)
|
|
7113
7599
|
Return ONLY the improved text, no explanation, no preamble.`;
|
|
7114
7600
|
const { provider, plaintextKey } = active;
|
|
7115
|
-
const model = provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7601
|
+
const model = input.model || provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7116
7602
|
if (provider.provider === "anthropic") {
|
|
7117
7603
|
const client = new Anthropic2({ apiKey: plaintextKey });
|
|
7118
7604
|
const res = await client.messages.create({
|
|
@@ -7154,39 +7640,39 @@ Return ONLY the improved text, no explanation, no preamble.`;
|
|
|
7154
7640
|
}
|
|
7155
7641
|
}),
|
|
7156
7642
|
getStreamingStatus: protectedProcedure.query(() => activeSessionRegistry.listActive()),
|
|
7157
|
-
stopSession: protectedProcedure.input(
|
|
7158
|
-
if (process.env.
|
|
7643
|
+
stopSession: protectedProcedure.input(z15.object({ conversationId: ConversationIdSchema })).mutation(({ input }) => {
|
|
7644
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: stopSession");
|
|
7159
7645
|
const killed = activeSessionRegistry.kill(input.conversationId);
|
|
7160
|
-
if (!killed) throw new
|
|
7646
|
+
if (!killed) throw new TRPCError11({ code: "NOT_FOUND", message: "No active session for this conversation" });
|
|
7161
7647
|
return { ok: true };
|
|
7162
7648
|
}),
|
|
7163
|
-
streamViaClaudeCode: protectedProcedure.input(
|
|
7649
|
+
streamViaClaudeCode: protectedProcedure.input(z15.object({
|
|
7164
7650
|
conversationId: ConversationIdSchema,
|
|
7165
|
-
prompt:
|
|
7166
|
-
contextNote:
|
|
7651
|
+
prompt: z15.string().max(1e5),
|
|
7652
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7167
7653
|
model: ShortString200Schema.optional(),
|
|
7168
|
-
effort:
|
|
7169
|
-
reconnect:
|
|
7654
|
+
effort: z15.enum(["low", "medium", "high"]).optional(),
|
|
7655
|
+
reconnect: z15.boolean().optional(),
|
|
7170
7656
|
agentId: Id128Schema.optional(),
|
|
7171
|
-
environmentId:
|
|
7172
|
-
imageIds:
|
|
7657
|
+
environmentId: z15.string().max(128).optional(),
|
|
7658
|
+
imageIds: z15.array(z15.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7173
7659
|
imageParts: ImagePartsSchema
|
|
7174
7660
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7175
|
-
if (process.env.
|
|
7661
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: Reconnect");
|
|
7176
7662
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
7177
7663
|
if (input.reconnect && existingSession) {
|
|
7178
7664
|
yield* drainSession(existingSession, signal, true);
|
|
7179
7665
|
return;
|
|
7180
7666
|
}
|
|
7181
|
-
if (!input.prompt) throw new
|
|
7667
|
+
if (!input.prompt) throw new TRPCError11({ code: "BAD_REQUEST", message: "Prompt is required for new messages" });
|
|
7182
7668
|
const conv = await conversationStore.get(input.conversationId);
|
|
7183
|
-
if (!conv) throw new
|
|
7669
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7184
7670
|
console.log(`[kanban-debug] streamViaClaudeCode start \u2014 convId=${conv.id} kanbanBoardId=${conv.kanbanBoardId ?? "null"}`);
|
|
7185
7671
|
const isResume = conv.messages.length > 0;
|
|
7186
7672
|
const userParts = input.imageParts && input.imageParts.length > 0 ? [...input.prompt.trim() ? [{ type: "text", text: input.prompt }] : [], ...input.imageParts] : void 0;
|
|
7187
7673
|
conv.messages.push({ role: "user", content: input.prompt, parts: userParts, contextNote: input.contextNote });
|
|
7188
7674
|
conv.messages.push({ role: "assistant", content: "" });
|
|
7189
|
-
if (process.env.
|
|
7675
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: update");
|
|
7190
7676
|
conversationStore.update(conv.id, { messages: conv.messages }, { silent: true }).catch(() => {
|
|
7191
7677
|
});
|
|
7192
7678
|
const cliPrompt = input.contextNote ? `${input.contextNote}
|
|
@@ -7215,7 +7701,7 @@ ${input.prompt}` : input.prompt;
|
|
|
7215
7701
|
const imagePaths = [];
|
|
7216
7702
|
if (input.imageIds && input.imageIds.length > 0) {
|
|
7217
7703
|
for (const id of input.imageIds) {
|
|
7218
|
-
const result = await imageStorageService.getImagePath(id);
|
|
7704
|
+
const result = await imageStorageService.getImagePath(id, input.conversationId);
|
|
7219
7705
|
if (result) imagePaths.push(result.filePath);
|
|
7220
7706
|
}
|
|
7221
7707
|
}
|
|
@@ -7228,14 +7714,14 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7228
7714
|
try {
|
|
7229
7715
|
session = activeSessionRegistry.registerProviderApiSession(convId, ac);
|
|
7230
7716
|
} catch {
|
|
7231
|
-
throw new
|
|
7717
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7232
7718
|
}
|
|
7233
7719
|
let capturedFinalResult = "";
|
|
7234
7720
|
const resolvedAgentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7235
7721
|
if (resolvedAgentId) {
|
|
7236
7722
|
session.agentId = resolvedAgentId;
|
|
7237
7723
|
}
|
|
7238
|
-
if (process.env.
|
|
7724
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: claude run...");
|
|
7239
7725
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
7240
7726
|
new ClaudeCodeRunner().run({
|
|
7241
7727
|
agentId: resolvedAgentId ?? void 0,
|
|
@@ -7276,18 +7762,18 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7276
7762
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
7277
7763
|
activeSessionRegistry.markDone(convId);
|
|
7278
7764
|
});
|
|
7279
|
-
if (process.env.
|
|
7765
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: drainSession...");
|
|
7280
7766
|
yield* drainSession(session, signal, false);
|
|
7281
7767
|
}),
|
|
7282
|
-
streamViaCodex: protectedProcedure.input(
|
|
7768
|
+
streamViaCodex: protectedProcedure.input(z15.object({
|
|
7283
7769
|
conversationId: ConversationIdSchema,
|
|
7284
|
-
prompt:
|
|
7285
|
-
contextNote:
|
|
7770
|
+
prompt: z15.string().max(1e5),
|
|
7771
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7286
7772
|
model: ShortString200Schema.optional(),
|
|
7287
|
-
reconnect:
|
|
7773
|
+
reconnect: z15.boolean().optional(),
|
|
7288
7774
|
agentId: Id128Schema.optional(),
|
|
7289
|
-
environmentId:
|
|
7290
|
-
imageIds:
|
|
7775
|
+
environmentId: z15.string().max(128).optional(),
|
|
7776
|
+
imageIds: z15.array(z15.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7291
7777
|
imageParts: ImagePartsSchema
|
|
7292
7778
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7293
7779
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7295,9 +7781,9 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7295
7781
|
yield* drainSession(existingSession, signal, true);
|
|
7296
7782
|
return;
|
|
7297
7783
|
}
|
|
7298
|
-
if (!input.prompt) throw new
|
|
7784
|
+
if (!input.prompt) throw new TRPCError11({ code: "BAD_REQUEST", message: "Prompt is required" });
|
|
7299
7785
|
const conv = await conversationStore.get(input.conversationId);
|
|
7300
|
-
if (!conv) throw new
|
|
7786
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7301
7787
|
const isResume = !!conv.codexThreadId;
|
|
7302
7788
|
const codexUserParts = input.imageParts && input.imageParts.length > 0 ? [...input.prompt.trim() ? [{ type: "text", text: input.prompt }] : [], ...input.imageParts] : void 0;
|
|
7303
7789
|
conv.messages.push({ role: "user", content: input.prompt, parts: codexUserParts, contextNote: input.contextNote });
|
|
@@ -7317,14 +7803,14 @@ ${input.prompt}` : input.prompt;
|
|
|
7317
7803
|
systemContext += "\n\n" + (conv.kanbanCardId ? generateKanbanSystemPrompt(kanbanBoard, conv.kanbanCardId) : generateKanbanPopulatePrompt(kanbanBoard));
|
|
7318
7804
|
}
|
|
7319
7805
|
}
|
|
7320
|
-
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" }))) : [];
|
|
7806
|
+
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" })), input.conversationId) : [];
|
|
7321
7807
|
const convId = input.conversationId;
|
|
7322
7808
|
const ac = new AbortController();
|
|
7323
7809
|
let session;
|
|
7324
7810
|
try {
|
|
7325
7811
|
session = activeSessionRegistry.registerProviderApiSession(convId, ac);
|
|
7326
7812
|
} catch {
|
|
7327
|
-
throw new
|
|
7813
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7328
7814
|
}
|
|
7329
7815
|
const resolvedAgentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7330
7816
|
if (resolvedAgentId) {
|
|
@@ -7364,13 +7850,13 @@ ${input.prompt}` : input.prompt;
|
|
|
7364
7850
|
});
|
|
7365
7851
|
yield* drainSession(session, signal, false);
|
|
7366
7852
|
}),
|
|
7367
|
-
send: protectedProcedure.input(
|
|
7853
|
+
send: protectedProcedure.input(z15.object({
|
|
7368
7854
|
conversationId: ConversationIdSchema,
|
|
7369
|
-
messages:
|
|
7855
|
+
messages: z15.array(MessageSchema).min(0).max(100),
|
|
7370
7856
|
model: ShortString200Schema.optional(),
|
|
7371
|
-
contextNote:
|
|
7372
|
-
reconnect:
|
|
7373
|
-
imageIds:
|
|
7857
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7858
|
+
reconnect: z15.boolean().optional(),
|
|
7859
|
+
imageIds: z15.array(z15.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)).max(5).optional(),
|
|
7374
7860
|
imageParts: ImagePartsSchema
|
|
7375
7861
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7376
7862
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7379,12 +7865,12 @@ ${input.prompt}` : input.prompt;
|
|
|
7379
7865
|
return;
|
|
7380
7866
|
}
|
|
7381
7867
|
if (input.messages.length === 0) {
|
|
7382
|
-
throw new
|
|
7868
|
+
throw new TRPCError11({ code: "BAD_REQUEST", message: "Messages are required for new messages" });
|
|
7383
7869
|
}
|
|
7384
7870
|
const conv = await conversationStore.get(input.conversationId);
|
|
7385
7871
|
const active = await getActiveProvider(conv?.providerId);
|
|
7386
7872
|
if (!active) {
|
|
7387
|
-
throw new
|
|
7873
|
+
throw new TRPCError11({
|
|
7388
7874
|
code: "PRECONDITION_FAILED",
|
|
7389
7875
|
message: "No AI provider configured for this conversation. Please select one when starting a new chat."
|
|
7390
7876
|
});
|
|
@@ -7410,12 +7896,12 @@ ${input.prompt}` : input.prompt;
|
|
|
7410
7896
|
system += "\n\n" + (conv.kanbanCardId ? generateKanbanSystemPrompt(kanbanBoard, conv.kanbanCardId) : generateKanbanPopulatePrompt(kanbanBoard));
|
|
7411
7897
|
}
|
|
7412
7898
|
}
|
|
7413
|
-
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" }))) : [];
|
|
7899
|
+
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" })), input.conversationId) : [];
|
|
7414
7900
|
let session;
|
|
7415
7901
|
try {
|
|
7416
7902
|
session = activeSessionRegistry.registerDirectProviderApiSession(input.conversationId);
|
|
7417
7903
|
} catch {
|
|
7418
|
-
throw new
|
|
7904
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7419
7905
|
}
|
|
7420
7906
|
const apiSignal = session.abortController.signal;
|
|
7421
7907
|
const runStream = async () => {
|
|
@@ -7487,25 +7973,25 @@ ${input.prompt}` : input.prompt;
|
|
|
7487
7973
|
// ── PTY passthrough (interactive Claude Code session) ────────────────────
|
|
7488
7974
|
pty: router({
|
|
7489
7975
|
/** Start a PTY session running `claude --resume <sessionId>` (or fresh). */
|
|
7490
|
-
start: protectedProcedure.input(
|
|
7491
|
-
agentId:
|
|
7492
|
-
sessionId:
|
|
7493
|
-
runner:
|
|
7494
|
-
resume:
|
|
7495
|
-
model:
|
|
7496
|
-
cwd:
|
|
7497
|
-
cols:
|
|
7498
|
-
rows:
|
|
7499
|
-
enableMcp:
|
|
7500
|
-
apiBaseUrl:
|
|
7501
|
-
environmentId:
|
|
7502
|
-
attachedContexts:
|
|
7503
|
-
label:
|
|
7504
|
-
text:
|
|
7976
|
+
start: protectedProcedure.input(z15.object({
|
|
7977
|
+
agentId: z15.string().max(128).optional(),
|
|
7978
|
+
sessionId: z15.string().max(128).optional(),
|
|
7979
|
+
runner: z15.enum(["claude-code", "codex"]).default("claude-code"),
|
|
7980
|
+
resume: z15.boolean().default(false),
|
|
7981
|
+
model: z15.string().max(128).optional(),
|
|
7982
|
+
cwd: z15.string().max(4096).optional(),
|
|
7983
|
+
cols: z15.number().int().min(1).max(1e3).default(120),
|
|
7984
|
+
rows: z15.number().int().min(1).max(1e3).default(30),
|
|
7985
|
+
enableMcp: z15.boolean().default(false),
|
|
7986
|
+
apiBaseUrl: z15.string().max(512).optional(),
|
|
7987
|
+
environmentId: z15.string().max(128).optional(),
|
|
7988
|
+
attachedContexts: z15.array(z15.object({
|
|
7989
|
+
label: z15.string().max(200),
|
|
7990
|
+
text: z15.string().max(1e6)
|
|
7505
7991
|
})).max(20).optional()
|
|
7506
7992
|
})).mutation(async ({ input, ctx }) => {
|
|
7507
7993
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7508
|
-
if (!agentId) throw new
|
|
7994
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7509
7995
|
if (input.sessionId) {
|
|
7510
7996
|
try {
|
|
7511
7997
|
const existing = await agentManager.agentQuery(
|
|
@@ -7641,15 +8127,15 @@ Context:
|
|
|
7641
8127
|
return { spawnId: res.spawnId, agentId, reattached: false };
|
|
7642
8128
|
}),
|
|
7643
8129
|
/** Subscribe to PTY output stream (raw text with ANSI). */
|
|
7644
|
-
stream: protectedProcedure.input(
|
|
7645
|
-
agentId:
|
|
7646
|
-
spawnId:
|
|
7647
|
-
fromOffset:
|
|
8130
|
+
stream: protectedProcedure.input(z15.object({
|
|
8131
|
+
agentId: z15.string().max(128).optional(),
|
|
8132
|
+
spawnId: z15.string().max(128),
|
|
8133
|
+
fromOffset: z15.number().int().min(0).default(0)
|
|
7648
8134
|
})).subscription(async function* ({ input, signal }) {
|
|
7649
8135
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7650
|
-
if (!agentId) throw new
|
|
8136
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7651
8137
|
const queue = [];
|
|
7652
|
-
let
|
|
8138
|
+
let resolve6 = null;
|
|
7653
8139
|
let stopped = false;
|
|
7654
8140
|
const unsub = agentManager.agentSubscribe(
|
|
7655
8141
|
agentId,
|
|
@@ -7657,16 +8143,16 @@ Context:
|
|
|
7657
8143
|
{ spawnId: input.spawnId, fromOffset: input.fromOffset },
|
|
7658
8144
|
(data) => {
|
|
7659
8145
|
queue.push(data);
|
|
7660
|
-
if (
|
|
7661
|
-
|
|
7662
|
-
|
|
8146
|
+
if (resolve6) {
|
|
8147
|
+
resolve6();
|
|
8148
|
+
resolve6 = null;
|
|
7663
8149
|
}
|
|
7664
8150
|
},
|
|
7665
8151
|
() => {
|
|
7666
8152
|
stopped = true;
|
|
7667
|
-
if (
|
|
7668
|
-
|
|
7669
|
-
|
|
8153
|
+
if (resolve6) {
|
|
8154
|
+
resolve6();
|
|
8155
|
+
resolve6 = null;
|
|
7670
8156
|
}
|
|
7671
8157
|
}
|
|
7672
8158
|
);
|
|
@@ -7682,7 +8168,7 @@ Context:
|
|
|
7682
8168
|
}
|
|
7683
8169
|
if (stopped) break;
|
|
7684
8170
|
await new Promise((r) => {
|
|
7685
|
-
|
|
8171
|
+
resolve6 = r;
|
|
7686
8172
|
});
|
|
7687
8173
|
}
|
|
7688
8174
|
} finally {
|
|
@@ -7690,27 +8176,27 @@ Context:
|
|
|
7690
8176
|
}
|
|
7691
8177
|
}),
|
|
7692
8178
|
/** Write input to a PTY session (keystrokes from xterm). */
|
|
7693
|
-
write: protectedProcedure.input(
|
|
7694
|
-
agentId:
|
|
7695
|
-
spawnId:
|
|
7696
|
-
data:
|
|
8179
|
+
write: protectedProcedure.input(z15.object({
|
|
8180
|
+
agentId: z15.string().max(128).optional(),
|
|
8181
|
+
spawnId: z15.string().max(128),
|
|
8182
|
+
data: z15.string().max(1e6)
|
|
7697
8183
|
})).mutation(async ({ input }) => {
|
|
7698
8184
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7699
|
-
if (!agentId) throw new
|
|
8185
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7700
8186
|
return agentManager.agentMutation(agentId, "ai.writePty", {
|
|
7701
8187
|
spawnId: input.spawnId,
|
|
7702
8188
|
data: input.data
|
|
7703
8189
|
});
|
|
7704
8190
|
}),
|
|
7705
8191
|
/** Resize a PTY session. */
|
|
7706
|
-
resize: protectedProcedure.input(
|
|
7707
|
-
agentId:
|
|
7708
|
-
spawnId:
|
|
7709
|
-
cols:
|
|
7710
|
-
rows:
|
|
8192
|
+
resize: protectedProcedure.input(z15.object({
|
|
8193
|
+
agentId: z15.string().max(128).optional(),
|
|
8194
|
+
spawnId: z15.string().max(128),
|
|
8195
|
+
cols: z15.number().int().min(1).max(1e3),
|
|
8196
|
+
rows: z15.number().int().min(1).max(1e3)
|
|
7711
8197
|
})).mutation(async ({ input }) => {
|
|
7712
8198
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7713
|
-
if (!agentId) throw new
|
|
8199
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7714
8200
|
return agentManager.agentMutation(agentId, "ai.resizePty", {
|
|
7715
8201
|
spawnId: input.spawnId,
|
|
7716
8202
|
cols: input.cols,
|
|
@@ -7723,14 +8209,14 @@ Context:
|
|
|
7723
8209
|
* picks it up. Returns the tmp path and which clipboard method succeeded
|
|
7724
8210
|
* (null if none — caller should inject `@<path>` instead of Ctrl+V).
|
|
7725
8211
|
*/
|
|
7726
|
-
pasteImage: protectedProcedure.input(
|
|
7727
|
-
agentId:
|
|
7728
|
-
sessionId:
|
|
7729
|
-
mimeType:
|
|
7730
|
-
base64:
|
|
8212
|
+
pasteImage: protectedProcedure.input(z15.object({
|
|
8213
|
+
agentId: z15.string().max(128).optional(),
|
|
8214
|
+
sessionId: z15.string().max(128).optional(),
|
|
8215
|
+
mimeType: z15.string().max(64),
|
|
8216
|
+
base64: z15.string().max(15e6)
|
|
7731
8217
|
})).mutation(async ({ input }) => {
|
|
7732
8218
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7733
|
-
if (!agentId) throw new
|
|
8219
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7734
8220
|
const res = await agentManager.agentMutation(agentId, "ai.pasteImage", {
|
|
7735
8221
|
sessionId: input.sessionId,
|
|
7736
8222
|
mimeType: input.mimeType,
|
|
@@ -7745,7 +8231,7 @@ Context:
|
|
|
7745
8231
|
* Read the transcript Claude Code stores on disk for a given session.
|
|
7746
8232
|
* Returns parsed JSONL entries (user / assistant messages, tool uses, etc.).
|
|
7747
8233
|
*/
|
|
7748
|
-
transcript: protectedProcedure.input(
|
|
8234
|
+
transcript: protectedProcedure.input(z15.object({ sessionId: z15.string().max(128) })).query(async ({ input }) => {
|
|
7749
8235
|
return readClaudeTranscript(input.sessionId);
|
|
7750
8236
|
}),
|
|
7751
8237
|
/**
|
|
@@ -7753,9 +8239,9 @@ Context:
|
|
|
7753
8239
|
* when was the transcript JSONL last touched? Used by the non-PTY view
|
|
7754
8240
|
* to show a "Claude is thinking…" indicator while output streams.
|
|
7755
8241
|
*/
|
|
7756
|
-
activity: protectedProcedure.input(
|
|
7757
|
-
agentId:
|
|
7758
|
-
sessionId:
|
|
8242
|
+
activity: protectedProcedure.input(z15.object({
|
|
8243
|
+
agentId: z15.string().max(128).optional(),
|
|
8244
|
+
sessionId: z15.string().max(128)
|
|
7759
8245
|
})).query(async ({ input }) => {
|
|
7760
8246
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7761
8247
|
let running = false;
|
|
@@ -7781,10 +8267,10 @@ Context:
|
|
|
7781
8267
|
* them on the conversation, so the non-PTY chat view stays in sync.
|
|
7782
8268
|
* Uses the conversation id as session id (PTY sessions are spawned that way).
|
|
7783
8269
|
*/
|
|
7784
|
-
syncTranscript: protectedProcedure.input(
|
|
8270
|
+
syncTranscript: protectedProcedure.input(z15.object({ conversationId: ConversationIdSchema })).mutation(async ({ input }) => {
|
|
7785
8271
|
const conv = await conversationStore.get(input.conversationId);
|
|
7786
8272
|
if (!conv) {
|
|
7787
|
-
throw new
|
|
8273
|
+
throw new TRPCError11({ code: "NOT_FOUND", message: `Conversation ${input.conversationId} not found` });
|
|
7788
8274
|
}
|
|
7789
8275
|
const synced = await syncConversationFromClaudeTranscript(conv.id);
|
|
7790
8276
|
const after = await conversationStore.get(conv.id);
|
|
@@ -7795,17 +8281,17 @@ Context:
|
|
|
7795
8281
|
* with the given sessionId. Used to benchmark the PTY → tRPC → xterm.js
|
|
7796
8282
|
* pipeline. Open the conversation in PTY mode to attach to it.
|
|
7797
8283
|
*/
|
|
7798
|
-
debugStress: protectedProcedure.input(
|
|
7799
|
-
sessionId:
|
|
7800
|
-
agentId:
|
|
7801
|
-
bytesPerSec:
|
|
7802
|
-
chunkSize:
|
|
7803
|
-
durationMs:
|
|
7804
|
-
cols:
|
|
7805
|
-
rows:
|
|
8284
|
+
debugStress: protectedProcedure.input(z15.object({
|
|
8285
|
+
sessionId: z15.string().max(128),
|
|
8286
|
+
agentId: z15.string().max(128).optional(),
|
|
8287
|
+
bytesPerSec: z15.number().int().min(100).max(1e7).default(5e4),
|
|
8288
|
+
chunkSize: z15.number().int().min(1).max(65536).default(256),
|
|
8289
|
+
durationMs: z15.number().int().min(0).max(36e5).default(0),
|
|
8290
|
+
cols: z15.number().int().min(1).max(1e3).default(120),
|
|
8291
|
+
rows: z15.number().int().min(1).max(1e3).default(30)
|
|
7806
8292
|
})).mutation(async ({ input }) => {
|
|
7807
8293
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7808
|
-
if (!agentId) throw new
|
|
8294
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7809
8295
|
const stressSessionId = `${input.sessionId}-stress-${Date.now().toString(36)}`;
|
|
7810
8296
|
const { existsSync: existsSync3 } = await import("fs");
|
|
7811
8297
|
const candidates = [
|
|
@@ -7822,7 +8308,7 @@ Context:
|
|
|
7822
8308
|
}
|
|
7823
8309
|
}
|
|
7824
8310
|
if (!scriptPath) {
|
|
7825
|
-
throw new
|
|
8311
|
+
throw new TRPCError11({
|
|
7826
8312
|
code: "INTERNAL_SERVER_ERROR",
|
|
7827
8313
|
message: `pty-stress-gen.mjs not found near ${fileURLToPath(import.meta.url)}`
|
|
7828
8314
|
});
|
|
@@ -7847,12 +8333,12 @@ Context:
|
|
|
7847
8333
|
return { spawnId: res.spawnId, agentId, stressSessionId };
|
|
7848
8334
|
}),
|
|
7849
8335
|
/** Kill a PTY session. */
|
|
7850
|
-
kill: protectedProcedure.input(
|
|
7851
|
-
agentId:
|
|
7852
|
-
spawnId:
|
|
8336
|
+
kill: protectedProcedure.input(z15.object({
|
|
8337
|
+
agentId: z15.string().max(128).optional(),
|
|
8338
|
+
spawnId: z15.string().max(128)
|
|
7853
8339
|
})).mutation(async ({ input }) => {
|
|
7854
8340
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7855
|
-
if (!agentId) throw new
|
|
8341
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7856
8342
|
return agentManager.agentMutation(agentId, "ai.kill", {
|
|
7857
8343
|
spawnId: input.spawnId
|
|
7858
8344
|
});
|
|
@@ -7861,125 +8347,125 @@ Context:
|
|
|
7861
8347
|
});
|
|
7862
8348
|
|
|
7863
8349
|
// ../server/src/trpc/routers/git.ts
|
|
7864
|
-
import { z as
|
|
7865
|
-
import { TRPCError as
|
|
8350
|
+
import { z as z16 } from "zod";
|
|
8351
|
+
import { TRPCError as TRPCError12 } from "@trpc/server";
|
|
7866
8352
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
7867
8353
|
import OpenAI3 from "openai";
|
|
7868
|
-
var SERVICE_ID_SCHEMA =
|
|
7869
|
-
var SAFE_PATH_SCHEMA =
|
|
8354
|
+
var SERVICE_ID_SCHEMA = z16.string().max(128);
|
|
8355
|
+
var SAFE_PATH_SCHEMA = z16.string().min(1).max(4096).refine((p) => !p.startsWith("/") && !p.includes("../"), {
|
|
7870
8356
|
message: "Path must be relative and not traverse up"
|
|
7871
8357
|
});
|
|
7872
|
-
var REMOTE_SCHEMA =
|
|
7873
|
-
var BRANCH_SCHEMA =
|
|
8358
|
+
var REMOTE_SCHEMA = z16.string().min(1).max(128).regex(/^[a-zA-Z0-9_\-.]+$/);
|
|
8359
|
+
var BRANCH_SCHEMA = z16.string().min(1).max(256).regex(/^[a-zA-Z0-9._\-/]+$/);
|
|
7874
8360
|
async function resolveServiceContext(serviceId) {
|
|
7875
8361
|
const service = await serviceStore.get(serviceId);
|
|
7876
8362
|
if (!service) {
|
|
7877
|
-
throw new
|
|
8363
|
+
throw new TRPCError12({ code: "NOT_FOUND", message: "Service not found" });
|
|
7878
8364
|
}
|
|
7879
8365
|
const agentId = await resolveEffectiveAgentId(service);
|
|
7880
8366
|
if (!agentId) {
|
|
7881
|
-
throw new
|
|
8367
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent configured for this service" });
|
|
7882
8368
|
}
|
|
7883
8369
|
return { agentId, cwd: service.cwd || "." };
|
|
7884
8370
|
}
|
|
7885
8371
|
var gitTargetShape = {
|
|
7886
8372
|
serviceId: SERVICE_ID_SCHEMA.optional(),
|
|
7887
|
-
agentId:
|
|
8373
|
+
agentId: z16.string().max(128).optional()
|
|
7888
8374
|
};
|
|
7889
8375
|
async function resolveGitContext(input) {
|
|
7890
8376
|
if (input.serviceId) return resolveServiceContext(input.serviceId);
|
|
7891
8377
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7892
8378
|
if (!agentId) {
|
|
7893
|
-
throw new
|
|
8379
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent available" });
|
|
7894
8380
|
}
|
|
7895
8381
|
return { agentId, cwd: "." };
|
|
7896
8382
|
}
|
|
7897
8383
|
var gitRouter = router({
|
|
7898
8384
|
/** Get full git status */
|
|
7899
|
-
status: protectedProcedure.input(
|
|
8385
|
+
status: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
7900
8386
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7901
8387
|
return agentManager.agentQuery(agentId, "git.status", { cwd });
|
|
7902
8388
|
}),
|
|
7903
8389
|
/** Get commit log */
|
|
7904
|
-
log: protectedProcedure.input(
|
|
8390
|
+
log: protectedProcedure.input(z16.object({ ...gitTargetShape, limit: z16.number().int().min(1).max(200).default(50) })).query(async ({ input }) => {
|
|
7905
8391
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7906
8392
|
return agentManager.agentQuery(agentId, "git.log", { cwd, limit: input.limit });
|
|
7907
8393
|
}),
|
|
7908
8394
|
/** List local and remote branches */
|
|
7909
|
-
branches: protectedProcedure.input(
|
|
8395
|
+
branches: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
7910
8396
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7911
8397
|
return agentManager.agentQuery(agentId, "git.branches", { cwd });
|
|
7912
8398
|
}),
|
|
7913
8399
|
/** Checkout a branch */
|
|
7914
|
-
checkout: protectedProcedure.input(
|
|
8400
|
+
checkout: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA, create: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
7915
8401
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7916
8402
|
return agentManager.agentMutation(agentId, "git.checkout", { cwd, branch: input.branch, create: input.create });
|
|
7917
8403
|
}),
|
|
7918
8404
|
/** Stage files */
|
|
7919
|
-
stage: protectedProcedure.input(
|
|
8405
|
+
stage: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7920
8406
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7921
8407
|
return agentManager.agentMutation(agentId, "git.stage", { cwd, paths: input.paths });
|
|
7922
8408
|
}),
|
|
7923
8409
|
/** Stage all files */
|
|
7924
|
-
stageAll: protectedProcedure.input(
|
|
8410
|
+
stageAll: protectedProcedure.input(z16.object({ ...gitTargetShape })).mutation(async ({ input }) => {
|
|
7925
8411
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7926
8412
|
return agentManager.agentMutation(agentId, "git.stageAll", { cwd });
|
|
7927
8413
|
}),
|
|
7928
8414
|
/** Unstage files */
|
|
7929
|
-
unstage: protectedProcedure.input(
|
|
8415
|
+
unstage: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7930
8416
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7931
8417
|
return agentManager.agentMutation(agentId, "git.unstage", { cwd, paths: input.paths });
|
|
7932
8418
|
}),
|
|
7933
8419
|
/** Unstage all files */
|
|
7934
|
-
unstageAll: protectedProcedure.input(
|
|
8420
|
+
unstageAll: protectedProcedure.input(z16.object({ ...gitTargetShape })).mutation(async ({ input }) => {
|
|
7935
8421
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7936
8422
|
return agentManager.agentMutation(agentId, "git.unstageAll", { cwd });
|
|
7937
8423
|
}),
|
|
7938
8424
|
/** Commit staged changes */
|
|
7939
|
-
commit: protectedProcedure.input(
|
|
8425
|
+
commit: protectedProcedure.input(z16.object({ ...gitTargetShape, message: z16.string().min(1).max(4096) })).mutation(async ({ input }) => {
|
|
7940
8426
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7941
8427
|
return agentManager.agentMutation(agentId, "git.commit", { cwd, message: input.message });
|
|
7942
8428
|
}),
|
|
7943
8429
|
/** Push to remote */
|
|
7944
|
-
push: protectedProcedure.input(
|
|
8430
|
+
push: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin"), setUpstream: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
7945
8431
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7946
8432
|
return agentManager.agentMutation(agentId, "git.push", { cwd, remote: input.remote, setUpstream: input.setUpstream });
|
|
7947
8433
|
}),
|
|
7948
8434
|
/** Pull from remote */
|
|
7949
|
-
pull: protectedProcedure.input(
|
|
8435
|
+
pull: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin") })).mutation(async ({ input }) => {
|
|
7950
8436
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7951
8437
|
return agentManager.agentMutation(agentId, "git.pull", { cwd, remote: input.remote });
|
|
7952
8438
|
}),
|
|
7953
8439
|
/** Fetch from remote */
|
|
7954
|
-
fetch: protectedProcedure.input(
|
|
8440
|
+
fetch: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin") })).mutation(async ({ input }) => {
|
|
7955
8441
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7956
8442
|
return agentManager.agentMutation(agentId, "git.fetch", { cwd, remote: input.remote });
|
|
7957
8443
|
}),
|
|
7958
8444
|
/** Get diff for a file or the whole index */
|
|
7959
|
-
diff: protectedProcedure.input(
|
|
8445
|
+
diff: protectedProcedure.input(z16.object({
|
|
7960
8446
|
...gitTargetShape,
|
|
7961
|
-
path:
|
|
7962
|
-
staged:
|
|
7963
|
-
untracked:
|
|
8447
|
+
path: z16.string().max(4096).optional(),
|
|
8448
|
+
staged: z16.boolean().default(false),
|
|
8449
|
+
untracked: z16.boolean().default(false)
|
|
7964
8450
|
})).query(async ({ input }) => {
|
|
7965
8451
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7966
8452
|
return agentManager.agentQuery(agentId, "git.diff", { cwd, path: input.path, staged: input.staged, untracked: input.untracked });
|
|
7967
8453
|
}),
|
|
7968
8454
|
/** Discard changes to working tree files */
|
|
7969
|
-
discard: protectedProcedure.input(
|
|
8455
|
+
discard: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7970
8456
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7971
8457
|
return agentManager.agentMutation(agentId, "git.discard", { cwd, paths: input.paths });
|
|
7972
8458
|
}),
|
|
7973
8459
|
/** Generate a commit message from staged diffs using AI (diff fetched from agent, AI runs on server) */
|
|
7974
|
-
generateCommitMessage: protectedProcedure.input(
|
|
8460
|
+
generateCommitMessage: protectedProcedure.input(z16.object({ ...gitTargetShape, providerId: OptionalProviderIdSchema, model: z16.string().max(200).optional() })).mutation(async ({ input }) => {
|
|
7975
8461
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7976
8462
|
const { diff } = await agentManager.agentQuery(agentId, "git.stagedDiff", { cwd });
|
|
7977
8463
|
if (!diff.trim()) {
|
|
7978
|
-
throw new
|
|
8464
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No staged changes to generate a commit message from" });
|
|
7979
8465
|
}
|
|
7980
|
-
const active = await getActiveProvider();
|
|
8466
|
+
const active = await getActiveProvider(input.providerId);
|
|
7981
8467
|
if (!active) {
|
|
7982
|
-
throw new
|
|
8468
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
7983
8469
|
}
|
|
7984
8470
|
const systemPrompt = `You are a git commit message generator. Given a git diff, write a concise and meaningful conventional commit message (e.g. feat:, fix:, refactor:, chore:, docs:, style:, test:).
|
|
7985
8471
|
Rules:
|
|
@@ -7988,7 +8474,7 @@ Rules:
|
|
|
7988
8474
|
- Write in English
|
|
7989
8475
|
- Return ONLY the commit message, nothing else`;
|
|
7990
8476
|
const { provider, plaintextKey } = active;
|
|
7991
|
-
const model = provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
8477
|
+
const model = input.model || provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7992
8478
|
if (provider.provider === "anthropic") {
|
|
7993
8479
|
const client = new Anthropic3({ apiKey: plaintextKey });
|
|
7994
8480
|
const res = await client.messages.create({
|
|
@@ -8012,48 +8498,48 @@ Rules:
|
|
|
8012
8498
|
});
|
|
8013
8499
|
return { message: res.choices[0]?.message?.content?.trim() ?? "" };
|
|
8014
8500
|
}
|
|
8015
|
-
throw new
|
|
8501
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "Unsupported AI provider" });
|
|
8016
8502
|
}),
|
|
8017
8503
|
/** Merge a branch into the current branch */
|
|
8018
|
-
merge: protectedProcedure.input(
|
|
8504
|
+
merge: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA, noFf: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
8019
8505
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8020
8506
|
return agentManager.agentMutation(agentId, "git.merge", { cwd, branch: input.branch, noFf: input.noFf });
|
|
8021
8507
|
}),
|
|
8022
8508
|
/** Rebase current branch onto another branch */
|
|
8023
|
-
rebase: protectedProcedure.input(
|
|
8509
|
+
rebase: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA })).mutation(async ({ input }) => {
|
|
8024
8510
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8025
8511
|
return agentManager.agentMutation(agentId, "git.rebase", { cwd, branch: input.branch });
|
|
8026
8512
|
}),
|
|
8027
8513
|
/** List stash entries */
|
|
8028
|
-
stashList: protectedProcedure.input(
|
|
8514
|
+
stashList: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
8029
8515
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8030
8516
|
return agentManager.agentQuery(agentId, "git.stashList", { cwd });
|
|
8031
8517
|
}),
|
|
8032
8518
|
/** Stash current changes */
|
|
8033
|
-
stashPush: protectedProcedure.input(
|
|
8519
|
+
stashPush: protectedProcedure.input(z16.object({ ...gitTargetShape, message: z16.string().max(4096).optional(), includeUntracked: z16.boolean().default(true) })).mutation(async ({ input }) => {
|
|
8034
8520
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8035
8521
|
return agentManager.agentMutation(agentId, "git.stashPush", { cwd, message: input.message, includeUntracked: input.includeUntracked });
|
|
8036
8522
|
}),
|
|
8037
8523
|
/** Pop a stash entry */
|
|
8038
|
-
stashPop: protectedProcedure.input(
|
|
8524
|
+
stashPop: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8039
8525
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8040
8526
|
return agentManager.agentMutation(agentId, "git.stashPop", { cwd, index: input.index });
|
|
8041
8527
|
}),
|
|
8042
8528
|
/** Apply a stash entry without dropping it */
|
|
8043
|
-
stashApply: protectedProcedure.input(
|
|
8529
|
+
stashApply: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8044
8530
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8045
8531
|
return agentManager.agentMutation(agentId, "git.stashApply", { cwd, index: input.index });
|
|
8046
8532
|
}),
|
|
8047
8533
|
/** Drop a stash entry */
|
|
8048
|
-
stashDrop: protectedProcedure.input(
|
|
8534
|
+
stashDrop: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8049
8535
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8050
8536
|
return agentManager.agentMutation(agentId, "git.stashDrop", { cwd, index: input.index });
|
|
8051
8537
|
}),
|
|
8052
8538
|
/** Watch .git/ for changes and stream updated status */
|
|
8053
|
-
onStatusChange: protectedProcedure.input(
|
|
8539
|
+
onStatusChange: protectedProcedure.input(z16.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
|
|
8054
8540
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8055
8541
|
const queue = [];
|
|
8056
|
-
let
|
|
8542
|
+
let resolve6 = null;
|
|
8057
8543
|
let done = false;
|
|
8058
8544
|
let unsubscribe;
|
|
8059
8545
|
try {
|
|
@@ -8063,21 +8549,21 @@ Rules:
|
|
|
8063
8549
|
{ cwd },
|
|
8064
8550
|
(data) => {
|
|
8065
8551
|
queue.push(data);
|
|
8066
|
-
if (
|
|
8067
|
-
|
|
8068
|
-
|
|
8552
|
+
if (resolve6) {
|
|
8553
|
+
resolve6();
|
|
8554
|
+
resolve6 = null;
|
|
8069
8555
|
}
|
|
8070
8556
|
},
|
|
8071
8557
|
() => {
|
|
8072
8558
|
done = true;
|
|
8073
|
-
if (
|
|
8074
|
-
|
|
8075
|
-
|
|
8559
|
+
if (resolve6) {
|
|
8560
|
+
resolve6();
|
|
8561
|
+
resolve6 = null;
|
|
8076
8562
|
}
|
|
8077
8563
|
}
|
|
8078
8564
|
);
|
|
8079
8565
|
} catch (err) {
|
|
8080
|
-
throw new
|
|
8566
|
+
throw new TRPCError12({
|
|
8081
8567
|
code: "BAD_REQUEST",
|
|
8082
8568
|
message: `Cannot subscribe to git status: ${err.message}`
|
|
8083
8569
|
});
|
|
@@ -8088,7 +8574,7 @@ Rules:
|
|
|
8088
8574
|
yield queue.shift();
|
|
8089
8575
|
} else {
|
|
8090
8576
|
await new Promise((r) => {
|
|
8091
|
-
|
|
8577
|
+
resolve6 = r;
|
|
8092
8578
|
});
|
|
8093
8579
|
}
|
|
8094
8580
|
}
|
|
@@ -8097,49 +8583,49 @@ Rules:
|
|
|
8097
8583
|
}
|
|
8098
8584
|
}),
|
|
8099
8585
|
/** List remotes */
|
|
8100
|
-
remotes: protectedProcedure.input(
|
|
8586
|
+
remotes: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
8101
8587
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8102
8588
|
return agentManager.agentQuery(agentId, "git.remotes", { cwd });
|
|
8103
8589
|
})
|
|
8104
8590
|
});
|
|
8105
8591
|
|
|
8106
8592
|
// ../server/src/trpc/routers/scenario.ts
|
|
8107
|
-
import { randomUUID as
|
|
8108
|
-
import { TRPCError as
|
|
8109
|
-
import { z as
|
|
8593
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
8594
|
+
import { TRPCError as TRPCError15 } from "@trpc/server";
|
|
8595
|
+
import { z as z17 } from "zod";
|
|
8110
8596
|
|
|
8111
8597
|
// ../server/src/trpc/crud-procedure-utils.ts
|
|
8112
|
-
import { TRPCError as
|
|
8598
|
+
import { TRPCError as TRPCError13 } from "@trpc/server";
|
|
8113
8599
|
async function getOrNotFound(getter, notFoundMessage) {
|
|
8114
8600
|
const entity = await getter();
|
|
8115
|
-
if (!entity) throw new
|
|
8601
|
+
if (!entity) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8116
8602
|
return entity;
|
|
8117
8603
|
}
|
|
8118
8604
|
async function createOrBadRequest(creator) {
|
|
8119
8605
|
try {
|
|
8120
8606
|
return await creator();
|
|
8121
8607
|
} catch (err) {
|
|
8122
|
-
throw new
|
|
8608
|
+
throw new TRPCError13({ code: "BAD_REQUEST", message: err.message });
|
|
8123
8609
|
}
|
|
8124
8610
|
}
|
|
8125
8611
|
async function updateOrThrow(updater, notFoundMessage) {
|
|
8126
8612
|
try {
|
|
8127
8613
|
const entity = await updater();
|
|
8128
|
-
if (!entity) throw new
|
|
8614
|
+
if (!entity) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8129
8615
|
return entity;
|
|
8130
8616
|
} catch (err) {
|
|
8131
|
-
if (err instanceof
|
|
8132
|
-
throw new
|
|
8617
|
+
if (err instanceof TRPCError13) throw err;
|
|
8618
|
+
throw new TRPCError13({ code: "BAD_REQUEST", message: err.message });
|
|
8133
8619
|
}
|
|
8134
8620
|
}
|
|
8135
8621
|
async function deleteOrNotFound(deleter, notFoundMessage) {
|
|
8136
8622
|
const deleted = await deleter();
|
|
8137
|
-
if (!deleted) throw new
|
|
8623
|
+
if (!deleted) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8138
8624
|
return { ok: true };
|
|
8139
8625
|
}
|
|
8140
8626
|
|
|
8141
8627
|
// ../server/src/trpc/ai-generation-procedure-utils.ts
|
|
8142
|
-
import { TRPCError as
|
|
8628
|
+
import { TRPCError as TRPCError14 } from "@trpc/server";
|
|
8143
8629
|
|
|
8144
8630
|
// ../server/src/services/ai-generation.ts
|
|
8145
8631
|
import Anthropic4 from "@anthropic-ai/sdk";
|
|
@@ -8195,9 +8681,9 @@ async function* streamProviderText(input) {
|
|
|
8195
8681
|
// ../server/src/trpc/ai-generation-procedure-utils.ts
|
|
8196
8682
|
async function* streamWithActiveProvider(opts) {
|
|
8197
8683
|
const active = await getActiveProvider(opts.providerId);
|
|
8198
|
-
if (!active) throw new
|
|
8684
|
+
if (!active) throw new TRPCError14({ code: "BAD_REQUEST", message: "Aucun fournisseur IA configur\xE9" });
|
|
8199
8685
|
const { provider, plaintextKey } = active;
|
|
8200
|
-
const model = provider.model
|
|
8686
|
+
const model = opts.model || provider.model || DEFAULT_MODELS[provider.provider] || "";
|
|
8201
8687
|
try {
|
|
8202
8688
|
for await (const text of streamProviderText({
|
|
8203
8689
|
provider,
|
|
@@ -8213,16 +8699,16 @@ async function* streamWithActiveProvider(opts) {
|
|
|
8213
8699
|
yield { type: "done", text: "" };
|
|
8214
8700
|
} catch (err) {
|
|
8215
8701
|
if (String(err?.message ?? "").startsWith("Unsupported provider:")) {
|
|
8216
|
-
throw new
|
|
8702
|
+
throw new TRPCError14({ code: "BAD_REQUEST", message: `Fournisseur non support\xE9: ${provider.provider}` });
|
|
8217
8703
|
}
|
|
8218
8704
|
throw err;
|
|
8219
8705
|
}
|
|
8220
8706
|
}
|
|
8221
8707
|
|
|
8222
8708
|
// ../server/src/services/scenario-runner.ts
|
|
8223
|
-
import { mkdir as
|
|
8224
|
-
import { randomUUID as
|
|
8225
|
-
import { join as
|
|
8709
|
+
import { mkdir as mkdir13 } from "fs/promises";
|
|
8710
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8711
|
+
import { join as join15 } from "path";
|
|
8226
8712
|
|
|
8227
8713
|
// ../server/src/services/scenario-presets.ts
|
|
8228
8714
|
var BUILT_IN_PRESETS = [
|
|
@@ -8436,21 +8922,21 @@ async function* tapBus(conversationId, signal) {
|
|
|
8436
8922
|
}
|
|
8437
8923
|
if (bus.done) return;
|
|
8438
8924
|
if (signal.aborted) return;
|
|
8439
|
-
await new Promise((
|
|
8925
|
+
await new Promise((resolve6) => {
|
|
8440
8926
|
const onAbort = () => {
|
|
8441
8927
|
bus.listeners.delete(notify);
|
|
8442
|
-
|
|
8928
|
+
resolve6();
|
|
8443
8929
|
};
|
|
8444
8930
|
const notify = () => {
|
|
8445
8931
|
signal.removeEventListener("abort", onAbort);
|
|
8446
|
-
|
|
8932
|
+
resolve6();
|
|
8447
8933
|
};
|
|
8448
8934
|
bus.listeners.add(notify);
|
|
8449
8935
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
8450
8936
|
if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
|
|
8451
8937
|
bus.listeners.delete(notify);
|
|
8452
8938
|
signal.removeEventListener("abort", onAbort);
|
|
8453
|
-
|
|
8939
|
+
resolve6();
|
|
8454
8940
|
}
|
|
8455
8941
|
});
|
|
8456
8942
|
}
|
|
@@ -8563,7 +9049,7 @@ function applyHireDirectivesFromOutput(manager, scenario, output) {
|
|
|
8563
9049
|
}
|
|
8564
9050
|
const hired = [];
|
|
8565
9051
|
for (const payload of hireDirectives) {
|
|
8566
|
-
const id = `hire-${
|
|
9052
|
+
const id = `hire-${randomUUID7().slice(0, 8)}`;
|
|
8567
9053
|
const position = computeHirePosition(manager, scenario.nodes);
|
|
8568
9054
|
ensureGroupCanContainNode(scenario, manager.groupId ?? null, position);
|
|
8569
9055
|
const node = {
|
|
@@ -8927,9 +9413,6 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
8927
9413
|
model,
|
|
8928
9414
|
effort: "normal",
|
|
8929
9415
|
signal,
|
|
8930
|
-
// Scenarios default to interactive PTY to stay on subscription pricing.
|
|
8931
|
-
// Override with RUNEYA_CLAUDE_PTY=0 to fall back to headless `-p` mode.
|
|
8932
|
-
usePty: aiCli === "claude-code" && process.env["RUNEYA_CLAUDE_PTY"] !== "0",
|
|
8933
9416
|
onDelta: (text) => {
|
|
8934
9417
|
accumulated += text;
|
|
8935
9418
|
onDelta(text);
|
|
@@ -8957,9 +9440,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
8957
9440
|
publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
|
|
8958
9441
|
await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
|
|
8959
9442
|
`);
|
|
8960
|
-
const answer = await new Promise((
|
|
9443
|
+
const answer = await new Promise((resolve6, reject) => {
|
|
8961
9444
|
const key = pendingNodeKey(conversationId, node.id);
|
|
8962
|
-
pendingNodeInputs.set(key,
|
|
9445
|
+
pendingNodeInputs.set(key, resolve6);
|
|
8963
9446
|
signal.addEventListener("abort", () => {
|
|
8964
9447
|
pendingNodeInputs.delete(key);
|
|
8965
9448
|
reject(new Error("Aborted"));
|
|
@@ -8999,7 +9482,7 @@ async function appendNodeOutput(conversationId, node, output) {
|
|
|
8999
9482
|
if (!dir) return;
|
|
9000
9483
|
const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
|
|
9001
9484
|
const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
|
|
9002
|
-
const filePath =
|
|
9485
|
+
const filePath = join15(dir, `${slug}_${safeId}.md`);
|
|
9003
9486
|
const content = `## Output
|
|
9004
9487
|
|
|
9005
9488
|
${output}
|
|
@@ -9012,9 +9495,9 @@ async function appendToReadme(conversationId, bus, text) {
|
|
|
9012
9495
|
const line = `${text}
|
|
9013
9496
|
`;
|
|
9014
9497
|
try {
|
|
9015
|
-
await
|
|
9498
|
+
await mkdir13(dir, { recursive: true });
|
|
9016
9499
|
const { appendFile: appendFile2 } = await import("fs/promises");
|
|
9017
|
-
await appendFile2(
|
|
9500
|
+
await appendFile2(join15(dir, "README.md"), line, "utf-8");
|
|
9018
9501
|
} catch {
|
|
9019
9502
|
}
|
|
9020
9503
|
publishToBus(bus, { type: "readme_append", text: line });
|
|
@@ -9034,7 +9517,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
|
|
|
9034
9517
|
|
|
9035
9518
|
${content}
|
|
9036
9519
|
`;
|
|
9037
|
-
const filePath =
|
|
9520
|
+
const filePath = join15(dir, "FINAL.md");
|
|
9038
9521
|
await atomicWriteSecure(filePath, fullContent);
|
|
9039
9522
|
await appendToReadme(
|
|
9040
9523
|
conversationId,
|
|
@@ -9364,9 +9847,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
|
|
|
9364
9847
|
publish2({ type: "node_waiting_input", nodeId: node.id, question });
|
|
9365
9848
|
await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
|
|
9366
9849
|
`);
|
|
9367
|
-
const answer = await new Promise((
|
|
9850
|
+
const answer = await new Promise((resolve6, reject) => {
|
|
9368
9851
|
const key = pendingNodeKey(conversationId, node.id);
|
|
9369
|
-
pendingNodeInputs.set(key,
|
|
9852
|
+
pendingNodeInputs.set(key, resolve6);
|
|
9370
9853
|
signal.addEventListener("abort", () => {
|
|
9371
9854
|
pendingNodeInputs.delete(key);
|
|
9372
9855
|
reject(new Error("Aborted"));
|
|
@@ -9703,21 +10186,21 @@ Modify the scenario. Keep existing ids when possible.`;
|
|
|
9703
10186
|
}
|
|
9704
10187
|
var scenarioRouter = router({
|
|
9705
10188
|
list: protectedProcedure.query(() => scenarioStore.list()),
|
|
9706
|
-
get: protectedProcedure.input(
|
|
9707
|
-
create: protectedProcedure.input(
|
|
10189
|
+
get: protectedProcedure.input(z17.object({ id: Id128Schema })).query(({ input }) => getOrNotFound(() => scenarioStore.get(input.id), "Scenario not found")),
|
|
10190
|
+
create: protectedProcedure.input(z17.object({
|
|
9708
10191
|
name: NameSchema,
|
|
9709
|
-
icon:
|
|
10192
|
+
icon: z17.string().max(64).optional(),
|
|
9710
10193
|
description: DescriptionSchema.optional(),
|
|
9711
10194
|
leaderId: Id128Schema,
|
|
9712
|
-
groups:
|
|
9713
|
-
nodes:
|
|
9714
|
-
traversalMode:
|
|
9715
|
-
presetId:
|
|
9716
|
-
isPublished:
|
|
10195
|
+
groups: z17.array(ScenarioGroupSchema).max(100).optional(),
|
|
10196
|
+
nodes: z17.array(ScenarioNodeSchema).min(1).max(500),
|
|
10197
|
+
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
10198
|
+
presetId: z17.string().max(128).optional(),
|
|
10199
|
+
isPublished: z17.boolean().optional()
|
|
9717
10200
|
})).mutation(({ input }) => {
|
|
9718
10201
|
const now = Date.now();
|
|
9719
10202
|
return createOrBadRequest(() => scenarioStore.create({
|
|
9720
|
-
id:
|
|
10203
|
+
id: randomUUID8(),
|
|
9721
10204
|
name: input.name,
|
|
9722
10205
|
icon: input.icon,
|
|
9723
10206
|
description: input.description,
|
|
@@ -9731,17 +10214,17 @@ var scenarioRouter = router({
|
|
|
9731
10214
|
updatedAt: now
|
|
9732
10215
|
}));
|
|
9733
10216
|
}),
|
|
9734
|
-
update: protectedProcedure.input(
|
|
10217
|
+
update: protectedProcedure.input(z17.object({
|
|
9735
10218
|
id: Id128Schema,
|
|
9736
10219
|
name: NameSchema.optional(),
|
|
9737
|
-
icon:
|
|
10220
|
+
icon: z17.string().max(64).optional(),
|
|
9738
10221
|
description: DescriptionSchema.optional(),
|
|
9739
10222
|
leaderId: Id128Schema.optional(),
|
|
9740
|
-
groups:
|
|
9741
|
-
nodes:
|
|
9742
|
-
traversalMode:
|
|
9743
|
-
presetId:
|
|
9744
|
-
isPublished:
|
|
10223
|
+
groups: z17.array(ScenarioGroupSchema).max(100).optional(),
|
|
10224
|
+
nodes: z17.array(ScenarioNodeSchema).min(1).max(500).optional(),
|
|
10225
|
+
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
10226
|
+
presetId: z17.string().max(128).optional(),
|
|
10227
|
+
isPublished: z17.boolean().optional()
|
|
9745
10228
|
})).mutation(({ input }) => updateOrThrow(
|
|
9746
10229
|
() => scenarioStore.update(input.id, {
|
|
9747
10230
|
name: input.name,
|
|
@@ -9757,18 +10240,18 @@ var scenarioRouter = router({
|
|
|
9757
10240
|
}),
|
|
9758
10241
|
"Scenario not found"
|
|
9759
10242
|
)),
|
|
9760
|
-
delete: protectedProcedure.input(
|
|
9761
|
-
run: protectedProcedure.input(
|
|
10243
|
+
delete: protectedProcedure.input(z17.object({ id: Id128Schema })).mutation(({ input }) => deleteOrNotFound(() => scenarioStore.delete(input.id), "Scenario not found")),
|
|
10244
|
+
run: protectedProcedure.input(z17.object({
|
|
9762
10245
|
conversationId: ConversationIdSchema,
|
|
9763
10246
|
scenarioId: Id128Schema,
|
|
9764
|
-
userRequest:
|
|
9765
|
-
runnerOverride:
|
|
10247
|
+
userRequest: z17.string().max(1e5),
|
|
10248
|
+
runnerOverride: z17.enum(["claude-code", "codex"]).optional(),
|
|
9766
10249
|
modelOverride: Id128Schema.optional(),
|
|
9767
10250
|
agentId: Id128Schema.optional(),
|
|
9768
|
-
resume:
|
|
10251
|
+
resume: z17.boolean().optional()
|
|
9769
10252
|
})).subscription(async function* ({ input, signal }) {
|
|
9770
10253
|
const scenario = await scenarioStore.get(input.scenarioId);
|
|
9771
|
-
if (!scenario) throw new
|
|
10254
|
+
if (!scenario) throw new TRPCError15({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
9772
10255
|
const canStart = input.userRequest.trim().length > 0 || !!input.resume;
|
|
9773
10256
|
if (!isScenarioRunning(input.conversationId) && canStart) {
|
|
9774
10257
|
let userRequest = input.userRequest;
|
|
@@ -9784,52 +10267,54 @@ var scenarioRouter = router({
|
|
|
9784
10267
|
}
|
|
9785
10268
|
yield* tapBus(input.conversationId, signal ?? new AbortController().signal);
|
|
9786
10269
|
}),
|
|
9787
|
-
stop: protectedProcedure.input(
|
|
10270
|
+
stop: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).mutation(({ input }) => {
|
|
9788
10271
|
abortScenario(input.conversationId);
|
|
9789
10272
|
return { ok: true };
|
|
9790
10273
|
}),
|
|
9791
|
-
sendNodeInput: protectedProcedure.input(
|
|
10274
|
+
sendNodeInput: protectedProcedure.input(z17.object({
|
|
9792
10275
|
conversationId: ConversationIdSchema,
|
|
9793
10276
|
nodeId: Id128Schema,
|
|
9794
|
-
text:
|
|
10277
|
+
text: z17.string().min(1).max(1e5)
|
|
9795
10278
|
})).mutation(({ input }) => {
|
|
9796
10279
|
const resolved = resolveNodeInput(input.conversationId, input.nodeId, input.text);
|
|
9797
|
-
if (!resolved) throw new
|
|
10280
|
+
if (!resolved) throw new TRPCError15({ code: "NOT_FOUND", message: "No pending input request for this node" });
|
|
9798
10281
|
return { ok: true };
|
|
9799
10282
|
}),
|
|
9800
|
-
isRunning: protectedProcedure.input(
|
|
9801
|
-
runState: protectedProcedure.input(
|
|
10283
|
+
isRunning: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(({ input }) => ({ running: isScenarioRunning(input.conversationId) })),
|
|
10284
|
+
runState: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(({ input }) => {
|
|
9802
10285
|
const runState = scenarioRunStore.getRunState(input.conversationId);
|
|
9803
10286
|
return runState ?? { nodeStates: {}, nodeOutputs: {}, activeNodeId: void 0 };
|
|
9804
10287
|
}),
|
|
9805
|
-
getPersistedRunState: protectedProcedure.input(
|
|
10288
|
+
getPersistedRunState: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9806
10289
|
return await scenarioRunStore.loadPersistedRunState(input.conversationId);
|
|
9807
10290
|
}),
|
|
9808
|
-
getReadme: protectedProcedure.input(
|
|
10291
|
+
getReadme: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9809
10292
|
return await scenarioRunStore.readReadme(input.conversationId);
|
|
9810
10293
|
}),
|
|
9811
|
-
getSnapshot: protectedProcedure.input(
|
|
10294
|
+
getSnapshot: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9812
10295
|
return await scenarioRunStore.loadScenarioSnapshot(input.conversationId);
|
|
9813
10296
|
}),
|
|
9814
|
-
streamGenerateWithAI: protectedProcedure.input(
|
|
9815
|
-
prompt:
|
|
10297
|
+
streamGenerateWithAI: protectedProcedure.input(z17.object({
|
|
10298
|
+
prompt: z17.string().min(1).max(1e4),
|
|
9816
10299
|
providerId: OptionalProviderIdSchema,
|
|
9817
|
-
|
|
10300
|
+
model: z17.string().max(200).optional(),
|
|
10301
|
+
current: z17.object({
|
|
9818
10302
|
leaderId: Id128Schema,
|
|
9819
|
-
groups:
|
|
9820
|
-
nodes:
|
|
10303
|
+
groups: z17.array(ScenarioGroupSchema).max(100),
|
|
10304
|
+
nodes: z17.array(ScenarioNodeSchema).max(500)
|
|
9821
10305
|
}).optional()
|
|
9822
10306
|
})).subscription(async function* ({ input, signal }) {
|
|
9823
10307
|
const userMessage = buildScenarioUserMessage(input.prompt, input.current);
|
|
9824
10308
|
yield* streamWithActiveProvider({
|
|
9825
10309
|
providerId: input.providerId,
|
|
10310
|
+
model: input.model,
|
|
9826
10311
|
systemPrompt: SCENARIO_SYSTEM_PROMPT,
|
|
9827
10312
|
userMessage,
|
|
9828
10313
|
maxTokens: 8192,
|
|
9829
10314
|
signal: signal ?? void 0
|
|
9830
10315
|
});
|
|
9831
10316
|
}),
|
|
9832
|
-
streamRunEvents: protectedProcedure.input(
|
|
10317
|
+
streamRunEvents: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).subscription(async function* ({ input }) {
|
|
9833
10318
|
const convId = input.conversationId;
|
|
9834
10319
|
const runState = scenarioRunStore.getRunState(convId);
|
|
9835
10320
|
if (runState) {
|
|
@@ -9843,24 +10328,24 @@ var scenarioRouter = router({
|
|
|
9843
10328
|
* open a debug PTY with `claude --resume <sessionId>` on the right agent.
|
|
9844
10329
|
* Falls back to the persisted run-state for past nodes.
|
|
9845
10330
|
*/
|
|
9846
|
-
getNodeSession: protectedProcedure.input(
|
|
10331
|
+
getNodeSession: protectedProcedure.input(z17.object({
|
|
9847
10332
|
conversationId: ConversationIdSchema,
|
|
9848
10333
|
nodeId: Id128Schema
|
|
9849
10334
|
})).query(async ({ input }) => {
|
|
9850
10335
|
return await scenarioRunStore.getNodeSessionPersistent(input.conversationId, input.nodeId);
|
|
9851
10336
|
}),
|
|
9852
10337
|
listPresets: protectedProcedure.query(() => listPresets()),
|
|
9853
|
-
getPreset: protectedProcedure.input(
|
|
10338
|
+
getPreset: protectedProcedure.input(z17.object({ id: Id128Schema })).query(({ input }) => {
|
|
9854
10339
|
const preset = getPreset(input.id);
|
|
9855
|
-
if (!preset) throw new
|
|
10340
|
+
if (!preset) throw new TRPCError15({ code: "NOT_FOUND", message: "Preset not found" });
|
|
9856
10341
|
return preset;
|
|
9857
10342
|
})
|
|
9858
10343
|
});
|
|
9859
10344
|
|
|
9860
10345
|
// ../server/src/trpc/routers/debug.ts
|
|
9861
|
-
import { randomUUID as
|
|
9862
|
-
import { TRPCError as
|
|
9863
|
-
import { z as
|
|
10346
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
10347
|
+
import { TRPCError as TRPCError16 } from "@trpc/server";
|
|
10348
|
+
import { z as z18 } from "zod";
|
|
9864
10349
|
var MAX_BUFFER3 = 300;
|
|
9865
10350
|
var jobs = /* @__PURE__ */ new Map();
|
|
9866
10351
|
function publish(job, event) {
|
|
@@ -9897,31 +10382,31 @@ async function* tapJob(job, signal) {
|
|
|
9897
10382
|
yield evt;
|
|
9898
10383
|
}
|
|
9899
10384
|
if (job.done || signal.aborted) return;
|
|
9900
|
-
await new Promise((
|
|
10385
|
+
await new Promise((resolve6) => {
|
|
9901
10386
|
const onAbort = () => {
|
|
9902
10387
|
job.listeners.delete(notify);
|
|
9903
|
-
|
|
10388
|
+
resolve6();
|
|
9904
10389
|
};
|
|
9905
10390
|
const notify = () => {
|
|
9906
10391
|
signal.removeEventListener("abort", onAbort);
|
|
9907
|
-
|
|
10392
|
+
resolve6();
|
|
9908
10393
|
};
|
|
9909
10394
|
job.listeners.add(notify);
|
|
9910
10395
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9911
10396
|
if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
|
|
9912
10397
|
job.listeners.delete(notify);
|
|
9913
10398
|
signal.removeEventListener("abort", onAbort);
|
|
9914
|
-
|
|
10399
|
+
resolve6();
|
|
9915
10400
|
}
|
|
9916
10401
|
});
|
|
9917
10402
|
}
|
|
9918
10403
|
}
|
|
9919
10404
|
var debugRouter = router({
|
|
9920
|
-
startRandomJob: protectedProcedure.input(
|
|
9921
|
-
intervalMs:
|
|
9922
|
-
maxTicks:
|
|
10405
|
+
startRandomJob: protectedProcedure.input(z18.object({
|
|
10406
|
+
intervalMs: z18.number().int().min(100).max(5e3).optional(),
|
|
10407
|
+
maxTicks: z18.number().int().min(1).max(1e4).optional()
|
|
9923
10408
|
}).optional()).mutation(({ input }) => {
|
|
9924
|
-
const jobId =
|
|
10409
|
+
const jobId = randomUUID9();
|
|
9925
10410
|
const intervalMs = input?.intervalMs ?? 400;
|
|
9926
10411
|
const maxTicks = input?.maxTicks ?? 500;
|
|
9927
10412
|
const job = {
|
|
@@ -9953,15 +10438,15 @@ var debugRouter = router({
|
|
|
9953
10438
|
jobs.set(jobId, job);
|
|
9954
10439
|
return { jobId, intervalMs, maxTicks, startedAt: job.startedAt };
|
|
9955
10440
|
}),
|
|
9956
|
-
streamRandomJob: protectedProcedure.input(
|
|
10441
|
+
streamRandomJob: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).subscription(async function* ({ input, signal }) {
|
|
9957
10442
|
const job = jobs.get(input.jobId);
|
|
9958
10443
|
if (!job) {
|
|
9959
|
-
throw new
|
|
10444
|
+
throw new TRPCError16({ code: "NOT_FOUND", message: "Random job not found" });
|
|
9960
10445
|
}
|
|
9961
10446
|
yield* tapJob(job, signal ?? new AbortController().signal);
|
|
9962
10447
|
}),
|
|
9963
|
-
stopRandomJob: protectedProcedure.input(
|
|
9964
|
-
randomJobStatus: protectedProcedure.input(
|
|
10448
|
+
stopRandomJob: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).mutation(({ input }) => ({ ok: stopJob(input.jobId, "stopped") })),
|
|
10449
|
+
randomJobStatus: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).query(({ input }) => {
|
|
9965
10450
|
const job = jobs.get(input.jobId);
|
|
9966
10451
|
return {
|
|
9967
10452
|
running: !!job && !job.done,
|
|
@@ -9970,132 +10455,11 @@ var debugRouter = router({
|
|
|
9970
10455
|
})
|
|
9971
10456
|
});
|
|
9972
10457
|
|
|
9973
|
-
// ../server/src/trpc/routers/kanban.ts
|
|
9974
|
-
import { z as z18 } from "zod";
|
|
9975
|
-
import { TRPCError as TRPCError16 } from "@trpc/server";
|
|
9976
|
-
async function deleteConversationsForIds(conversationIds) {
|
|
9977
|
-
await Promise.all(conversationIds.map(async (id) => {
|
|
9978
|
-
await conversationStore.delete(id);
|
|
9979
|
-
await scenarioRunStore.deleteRunDir(id);
|
|
9980
|
-
imageStorageService.deleteByContextId(id).catch(
|
|
9981
|
-
(err) => console.error("[kanban] Image cleanup error:", err)
|
|
9982
|
-
);
|
|
9983
|
-
}));
|
|
9984
|
-
}
|
|
9985
|
-
var kanbanRouter = router({
|
|
9986
|
-
list: protectedProcedure.input(z18.object({ includeArchived: z18.boolean().optional() }).optional()).query(async ({ input }) => {
|
|
9987
|
-
return kanbanStore.list(input?.includeArchived ?? false);
|
|
9988
|
-
}),
|
|
9989
|
-
get: protectedProcedure.input(z18.object({ id: z18.string().max(128) })).query(async ({ input }) => {
|
|
9990
|
-
const board = await kanbanStore.get(input.id);
|
|
9991
|
-
if (!board) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.id} not found` });
|
|
9992
|
-
return board;
|
|
9993
|
-
}),
|
|
9994
|
-
create: protectedProcedure.input(z18.object({
|
|
9995
|
-
name: z18.string().min(1).max(200),
|
|
9996
|
-
description: z18.string().max(1e4).optional()
|
|
9997
|
-
})).mutation(async ({ input }) => {
|
|
9998
|
-
return kanbanStore.create(input);
|
|
9999
|
-
}),
|
|
10000
|
-
update: protectedProcedure.input(z18.object({
|
|
10001
|
-
id: z18.string().max(128),
|
|
10002
|
-
name: z18.string().min(1).max(200).optional(),
|
|
10003
|
-
description: z18.string().max(1e4).optional(),
|
|
10004
|
-
columns: z18.array(KanbanColumnSchema).optional(),
|
|
10005
|
-
archived: z18.boolean().optional()
|
|
10006
|
-
})).mutation(async ({ input }) => {
|
|
10007
|
-
let removedCardIds = [];
|
|
10008
|
-
if (input.columns !== void 0) {
|
|
10009
|
-
const existing = await kanbanStore.get(input.id);
|
|
10010
|
-
if (existing) {
|
|
10011
|
-
const oldCardIds = new Set(existing.columns.flatMap((col) => col.cards.map((c) => c.id)));
|
|
10012
|
-
const newCardIds = new Set(input.columns.flatMap((col) => col.cards.map((c) => c.id)));
|
|
10013
|
-
removedCardIds = Array.from(oldCardIds).filter((id) => !newCardIds.has(id));
|
|
10014
|
-
}
|
|
10015
|
-
}
|
|
10016
|
-
const board = await kanbanStore.update(input);
|
|
10017
|
-
if (!board) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.id} not found` });
|
|
10018
|
-
if (removedCardIds.length > 0) {
|
|
10019
|
-
const convsByCard = await Promise.all(removedCardIds.map((cardId) => conversationStore.listByKanbanCardId(cardId)));
|
|
10020
|
-
const convIds = convsByCard.flat().map((c) => c.id);
|
|
10021
|
-
if (convIds.length > 0) await deleteConversationsForIds(convIds);
|
|
10022
|
-
}
|
|
10023
|
-
return board;
|
|
10024
|
-
}),
|
|
10025
|
-
moveCard: protectedProcedure.input(z18.object({
|
|
10026
|
-
boardId: z18.string().max(128),
|
|
10027
|
-
cardId: z18.string().max(128),
|
|
10028
|
-
targetColumn: z18.string().min(1).max(200)
|
|
10029
|
-
})).mutation(async ({ input }) => {
|
|
10030
|
-
const board = await kanbanStore.get(input.boardId);
|
|
10031
|
-
if (!board) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.boardId} not found` });
|
|
10032
|
-
const { executeKanbanCommands } = await import("./kanban-commands-53WWMCHQ.js");
|
|
10033
|
-
await executeKanbanCommands(input.boardId, input.cardId, [{ type: "move_card", columnTitle: input.targetColumn }]);
|
|
10034
|
-
return { success: true };
|
|
10035
|
-
}),
|
|
10036
|
-
addComment: protectedProcedure.input(z18.object({
|
|
10037
|
-
boardId: z18.string().max(128),
|
|
10038
|
-
cardId: z18.string().max(128),
|
|
10039
|
-
comment: z18.string().min(1).max(1e4)
|
|
10040
|
-
})).mutation(async ({ input }) => {
|
|
10041
|
-
const board = await kanbanStore.get(input.boardId);
|
|
10042
|
-
if (!board) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.boardId} not found` });
|
|
10043
|
-
const { executeKanbanCommands } = await import("./kanban-commands-53WWMCHQ.js");
|
|
10044
|
-
await executeKanbanCommands(input.boardId, input.cardId, [{ type: "add_comment", comment: input.comment }]);
|
|
10045
|
-
return { success: true };
|
|
10046
|
-
}),
|
|
10047
|
-
addCard: protectedProcedure.input(z18.object({
|
|
10048
|
-
boardId: z18.string().max(128),
|
|
10049
|
-
column: z18.string().min(1).max(200),
|
|
10050
|
-
title: z18.string().min(1).max(500),
|
|
10051
|
-
content: z18.string().max(5e4).optional()
|
|
10052
|
-
})).mutation(async ({ input }) => {
|
|
10053
|
-
const board = await kanbanStore.get(input.boardId);
|
|
10054
|
-
if (!board) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.boardId} not found` });
|
|
10055
|
-
const { executeKanbanBoardCommands } = await import("./kanban-commands-53WWMCHQ.js");
|
|
10056
|
-
await executeKanbanBoardCommands(input.boardId, [{ type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }]);
|
|
10057
|
-
return { success: true };
|
|
10058
|
-
}),
|
|
10059
|
-
delete: protectedProcedure.input(z18.object({ id: z18.string().max(128) })).mutation(async ({ input }) => {
|
|
10060
|
-
const conversations = await conversationStore.listByKanbanBoardId(input.id);
|
|
10061
|
-
const deleted = await kanbanStore.delete(input.id);
|
|
10062
|
-
if (!deleted) throw new TRPCError16({ code: "NOT_FOUND", message: `Board ${input.id} not found` });
|
|
10063
|
-
await deleteConversationsForIds(conversations.map((c) => c.id));
|
|
10064
|
-
return { success: true };
|
|
10065
|
-
}),
|
|
10066
|
-
onChanged: protectedProcedure.subscription(async function* () {
|
|
10067
|
-
const queue = [];
|
|
10068
|
-
let resolve5 = null;
|
|
10069
|
-
const handler = (event) => {
|
|
10070
|
-
if (queue.length >= 200) queue.shift();
|
|
10071
|
-
queue.push(event);
|
|
10072
|
-
if (resolve5) {
|
|
10073
|
-
resolve5();
|
|
10074
|
-
resolve5 = null;
|
|
10075
|
-
}
|
|
10076
|
-
};
|
|
10077
|
-
kanbanStore.onChanged(handler);
|
|
10078
|
-
try {
|
|
10079
|
-
while (true) {
|
|
10080
|
-
if (queue.length > 0) {
|
|
10081
|
-
yield queue.shift();
|
|
10082
|
-
} else {
|
|
10083
|
-
await new Promise((r) => {
|
|
10084
|
-
resolve5 = r;
|
|
10085
|
-
});
|
|
10086
|
-
}
|
|
10087
|
-
}
|
|
10088
|
-
} finally {
|
|
10089
|
-
kanbanStore.offChanged(handler);
|
|
10090
|
-
}
|
|
10091
|
-
})
|
|
10092
|
-
});
|
|
10093
|
-
|
|
10094
10458
|
// ../server/src/trpc/routers/kanban-queue.ts
|
|
10095
10459
|
import { z as z19 } from "zod";
|
|
10096
10460
|
|
|
10097
10461
|
// ../server/src/services/kanban-queue.ts
|
|
10098
|
-
import { randomUUID as
|
|
10462
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10099
10463
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
10100
10464
|
var KanbanQueueService = class extends EventEmitter4 {
|
|
10101
10465
|
items = [];
|
|
@@ -10118,7 +10482,7 @@ var KanbanQueueService = class extends EventEmitter4 {
|
|
|
10118
10482
|
return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
|
|
10119
10483
|
}
|
|
10120
10484
|
const queueItem = {
|
|
10121
|
-
id:
|
|
10485
|
+
id: randomUUID10(),
|
|
10122
10486
|
...item,
|
|
10123
10487
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10124
10488
|
};
|
|
@@ -10310,13 +10674,13 @@ var kanbanQueueRouter = router({
|
|
|
10310
10674
|
}),
|
|
10311
10675
|
onChanged: protectedProcedure.subscription(async function* () {
|
|
10312
10676
|
const queue = [];
|
|
10313
|
-
let
|
|
10677
|
+
let resolve6 = null;
|
|
10314
10678
|
const handler = () => {
|
|
10315
10679
|
if (queue.length >= 50) queue.shift();
|
|
10316
10680
|
queue.push(kanbanQueue.getState());
|
|
10317
|
-
if (
|
|
10318
|
-
|
|
10319
|
-
|
|
10681
|
+
if (resolve6) {
|
|
10682
|
+
resolve6();
|
|
10683
|
+
resolve6 = null;
|
|
10320
10684
|
}
|
|
10321
10685
|
};
|
|
10322
10686
|
kanbanQueue.on("changed", handler);
|
|
@@ -10327,7 +10691,7 @@ var kanbanQueueRouter = router({
|
|
|
10327
10691
|
yield queue.shift();
|
|
10328
10692
|
} else {
|
|
10329
10693
|
await new Promise((r) => {
|
|
10330
|
-
|
|
10694
|
+
resolve6 = r;
|
|
10331
10695
|
});
|
|
10332
10696
|
}
|
|
10333
10697
|
}
|
|
@@ -10400,7 +10764,7 @@ var terminalRouter = router({
|
|
|
10400
10764
|
sessionId: z20.string().max(128)
|
|
10401
10765
|
})).subscription(async function* ({ input }) {
|
|
10402
10766
|
const queue = [];
|
|
10403
|
-
let
|
|
10767
|
+
let resolve6 = null;
|
|
10404
10768
|
let done = false;
|
|
10405
10769
|
let unsubscribe;
|
|
10406
10770
|
try {
|
|
@@ -10410,16 +10774,16 @@ var terminalRouter = router({
|
|
|
10410
10774
|
{ sessionId: input.sessionId },
|
|
10411
10775
|
(data) => {
|
|
10412
10776
|
if (typeof data === "string") queue.push(data);
|
|
10413
|
-
if (
|
|
10414
|
-
|
|
10415
|
-
|
|
10777
|
+
if (resolve6) {
|
|
10778
|
+
resolve6();
|
|
10779
|
+
resolve6 = null;
|
|
10416
10780
|
}
|
|
10417
10781
|
},
|
|
10418
10782
|
() => {
|
|
10419
10783
|
done = true;
|
|
10420
|
-
if (
|
|
10421
|
-
|
|
10422
|
-
|
|
10784
|
+
if (resolve6) {
|
|
10785
|
+
resolve6();
|
|
10786
|
+
resolve6 = null;
|
|
10423
10787
|
}
|
|
10424
10788
|
}
|
|
10425
10789
|
);
|
|
@@ -10435,7 +10799,7 @@ var terminalRouter = router({
|
|
|
10435
10799
|
yield queue.shift();
|
|
10436
10800
|
} else {
|
|
10437
10801
|
await new Promise((r) => {
|
|
10438
|
-
|
|
10802
|
+
resolve6 = r;
|
|
10439
10803
|
});
|
|
10440
10804
|
}
|
|
10441
10805
|
}
|
|
@@ -10452,7 +10816,7 @@ var utilityRouter = router({
|
|
|
10452
10816
|
wait: protectedProcedure.input(z21.object({
|
|
10453
10817
|
ms: z21.number().int().min(0).max(MAX_WAIT_MS)
|
|
10454
10818
|
})).mutation(async ({ input }) => {
|
|
10455
|
-
await new Promise((
|
|
10819
|
+
await new Promise((resolve6) => setTimeout(resolve6, input.ms));
|
|
10456
10820
|
return { waited: input.ms };
|
|
10457
10821
|
})
|
|
10458
10822
|
});
|
|
@@ -10460,8 +10824,8 @@ var utilityRouter = router({
|
|
|
10460
10824
|
// ../server/src/trpc/routers/wiki.ts
|
|
10461
10825
|
import { z as z22 } from "zod";
|
|
10462
10826
|
import { TRPCError as TRPCError18 } from "@trpc/server";
|
|
10463
|
-
import { readFile as
|
|
10464
|
-
import { resolve as resolvePath, join as
|
|
10827
|
+
import { readFile as readFile13, readdir as readdir5, stat as stat3, realpath, writeFile as writeFile11, mkdir as mkdir14, rename as rename10, rm as rm2 } from "fs/promises";
|
|
10828
|
+
import { resolve as resolvePath, join as join16, relative, sep as sep3, dirname as dirname3 } from "path";
|
|
10465
10829
|
var SCOPE_SCHEMA = z22.union([
|
|
10466
10830
|
z22.literal("global"),
|
|
10467
10831
|
z22.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
|
|
@@ -10498,13 +10862,13 @@ async function resolveScope(scope) {
|
|
|
10498
10862
|
throw new TRPCError18({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
|
|
10499
10863
|
}
|
|
10500
10864
|
const dataDirAbs = resolvePath(env.DATA_DIR);
|
|
10501
|
-
await
|
|
10865
|
+
await mkdir14(dataDirAbs, { recursive: true });
|
|
10502
10866
|
const base = dirname3(await realpath(dataDirAbs));
|
|
10503
10867
|
const target = resolvePath(base, sub);
|
|
10504
10868
|
if (target !== base && !(target + sep3).startsWith(base + sep3)) {
|
|
10505
10869
|
throw new TRPCError18({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
|
|
10506
10870
|
}
|
|
10507
|
-
await
|
|
10871
|
+
await mkdir14(target, { recursive: true });
|
|
10508
10872
|
const root = await realpath(target);
|
|
10509
10873
|
if (root !== base && !(root + sep3).startsWith(base + sep3)) {
|
|
10510
10874
|
throw new TRPCError18({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
|
|
@@ -10531,7 +10895,7 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
10531
10895
|
for (const e of entries) {
|
|
10532
10896
|
if (counter.n >= MAX_FILES) break;
|
|
10533
10897
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
10534
|
-
const abs =
|
|
10898
|
+
const abs = join16(dir, e.name);
|
|
10535
10899
|
const rel = relative(root, abs).split(sep3).join("/");
|
|
10536
10900
|
if (e.isDirectory()) {
|
|
10537
10901
|
if (IGNORED_DIRS.has(e.name)) continue;
|
|
@@ -10599,7 +10963,7 @@ var wikiRouter = router({
|
|
|
10599
10963
|
const s = await stat3(file);
|
|
10600
10964
|
if (!s.isFile()) throw new TRPCError18({ code: "BAD_REQUEST", message: "Pas un fichier" });
|
|
10601
10965
|
if (s.size > MAX_FILE_BYTES) throw new TRPCError18({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
|
|
10602
|
-
const content = await
|
|
10966
|
+
const content = await readFile13(file, "utf-8");
|
|
10603
10967
|
return { path: input.path, content };
|
|
10604
10968
|
}),
|
|
10605
10969
|
write: protectedProcedure.input(z22.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z22.string().max(5e6) })).mutation(async ({ input }) => {
|
|
@@ -10611,8 +10975,8 @@ var wikiRouter = router({
|
|
|
10611
10975
|
return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
|
|
10612
10976
|
}
|
|
10613
10977
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
10614
|
-
await
|
|
10615
|
-
await
|
|
10978
|
+
await mkdir14(dirname3(file), { recursive: true });
|
|
10979
|
+
await writeFile11(file, input.content, "utf-8");
|
|
10616
10980
|
return { path: input.path, ok: true };
|
|
10617
10981
|
}),
|
|
10618
10982
|
createFile: protectedProcedure.input(z22.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -10630,8 +10994,8 @@ var wikiRouter = router({
|
|
|
10630
10994
|
} catch (err) {
|
|
10631
10995
|
if (err instanceof TRPCError18) throw err;
|
|
10632
10996
|
}
|
|
10633
|
-
await
|
|
10634
|
-
await
|
|
10997
|
+
await mkdir14(dirname3(file), { recursive: true });
|
|
10998
|
+
await writeFile11(file, "", "utf-8");
|
|
10635
10999
|
return { path: input.path, ok: true };
|
|
10636
11000
|
}),
|
|
10637
11001
|
createDir: protectedProcedure.input(z22.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -10640,7 +11004,7 @@ var wikiRouter = router({
|
|
|
10640
11004
|
return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
|
|
10641
11005
|
}
|
|
10642
11006
|
const dir = await resolveSafeLocal(target.root, input.path, false);
|
|
10643
|
-
await
|
|
11007
|
+
await mkdir14(dir, { recursive: true });
|
|
10644
11008
|
return { path: input.path, ok: true };
|
|
10645
11009
|
}),
|
|
10646
11010
|
rename: protectedProcedure.input(z22.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -10656,8 +11020,8 @@ var wikiRouter = router({
|
|
|
10656
11020
|
} catch (err) {
|
|
10657
11021
|
if (err instanceof TRPCError18) throw err;
|
|
10658
11022
|
}
|
|
10659
|
-
await
|
|
10660
|
-
await
|
|
11023
|
+
await mkdir14(dirname3(to), { recursive: true });
|
|
11024
|
+
await rename10(from, to);
|
|
10661
11025
|
return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
|
|
10662
11026
|
}),
|
|
10663
11027
|
ensureRoot: protectedProcedure.input(z22.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
|
|
@@ -10686,83 +11050,24 @@ var wikiRouter = router({
|
|
|
10686
11050
|
});
|
|
10687
11051
|
|
|
10688
11052
|
// ../server/src/trpc/routers/config.ts
|
|
10689
|
-
var configRouter = router({
|
|
10690
|
-
cloud: publicProcedure.query(() => ({
|
|
10691
|
-
apiUrl: env.CLOUD_URL,
|
|
10692
|
-
appUrl: env.CLOUD_APP_URL
|
|
10693
|
-
}))
|
|
10694
|
-
});
|
|
10695
|
-
|
|
10696
|
-
// ../server/src/trpc/routers/cloud-sync.ts
|
|
10697
|
-
import { z as z24 } from "zod";
|
|
10698
|
-
import { TRPCError as TRPCError20 } from "@trpc/server";
|
|
10699
|
-
|
|
10700
|
-
// ../server/src/services/cloud-sync-store.ts
|
|
10701
|
-
import { readFile as readFile13, writeFile as writeFile11, rename as rename10, mkdir as mkdir14, chmod as chmod8 } from "fs/promises";
|
|
10702
|
-
import { join as join16 } from "path";
|
|
10703
|
-
var FILENAME4 = "cloud-sync.json";
|
|
10704
|
-
var CloudSyncStore = class {
|
|
10705
|
-
links = /* @__PURE__ */ new Map();
|
|
10706
|
-
loaded = false;
|
|
10707
|
-
getFilePath() {
|
|
10708
|
-
return join16(env.DATA_DIR, FILENAME4);
|
|
10709
|
-
}
|
|
10710
|
-
async load() {
|
|
10711
|
-
if (this.loaded) return;
|
|
10712
|
-
try {
|
|
10713
|
-
const raw = await readFile13(this.getFilePath(), "utf-8");
|
|
10714
|
-
const data = JSON.parse(raw);
|
|
10715
|
-
for (const l of data) this.links.set(l.orgId, { orgId: l.orgId, cloudUrl: l.cloudUrl });
|
|
10716
|
-
} catch {
|
|
10717
|
-
}
|
|
10718
|
-
this.loaded = true;
|
|
10719
|
-
}
|
|
10720
|
-
async save() {
|
|
10721
|
-
await mkdir14(env.DATA_DIR, { recursive: true });
|
|
10722
|
-
const filePath = this.getFilePath();
|
|
10723
|
-
const tmpPath = filePath + ".tmp";
|
|
10724
|
-
const data = JSON.stringify(Array.from(this.links.values()), null, 2);
|
|
10725
|
-
await writeFile11(tmpPath, data, "utf-8");
|
|
10726
|
-
await rename10(tmpPath, filePath);
|
|
10727
|
-
await chmod8(filePath, 384);
|
|
10728
|
-
}
|
|
10729
|
-
/** Store (or replace) the sync link for an organization. */
|
|
10730
|
-
async upsert(orgId, cloudUrl) {
|
|
10731
|
-
await this.load();
|
|
10732
|
-
this.links.set(orgId, { orgId, cloudUrl });
|
|
10733
|
-
await this.save();
|
|
10734
|
-
}
|
|
10735
|
-
async remove(orgId) {
|
|
10736
|
-
await this.load();
|
|
10737
|
-
if (this.links.delete(orgId)) await this.save();
|
|
10738
|
-
}
|
|
10739
|
-
/** All sync links (for the bridge on startup). */
|
|
10740
|
-
async list() {
|
|
10741
|
-
await this.load();
|
|
10742
|
-
return Array.from(this.links.values());
|
|
10743
|
-
}
|
|
10744
|
-
async get(orgId) {
|
|
10745
|
-
await this.load();
|
|
10746
|
-
return this.links.get(orgId) ?? null;
|
|
10747
|
-
}
|
|
10748
|
-
};
|
|
10749
|
-
var cloudSyncStore = new CloudSyncStore();
|
|
11053
|
+
var configRouter = router({
|
|
11054
|
+
cloud: publicProcedure.query(() => ({
|
|
11055
|
+
apiUrl: env.CLOUD_URL,
|
|
11056
|
+
appUrl: env.CLOUD_APP_URL
|
|
11057
|
+
}))
|
|
11058
|
+
});
|
|
10750
11059
|
|
|
10751
|
-
// ../server/src/
|
|
10752
|
-
import {
|
|
10753
|
-
import
|
|
11060
|
+
// ../server/src/trpc/routers/cloud-sync.ts
|
|
11061
|
+
import { z as z23 } from "zod";
|
|
11062
|
+
import { TRPCError as TRPCError19 } from "@trpc/server";
|
|
10754
11063
|
|
|
10755
|
-
// ../server/src/services/cloud-
|
|
10756
|
-
import { readFile as readFile14, writeFile as writeFile12, rename as rename11, mkdir as mkdir15, chmod as chmod9
|
|
10757
|
-
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
11064
|
+
// ../server/src/services/cloud-sync-store.ts
|
|
11065
|
+
import { readFile as readFile14, writeFile as writeFile12, rename as rename11, mkdir as mkdir15, chmod as chmod9 } from "fs/promises";
|
|
10758
11066
|
import { join as join17 } from "path";
|
|
10759
|
-
var FILENAME5 = "cloud-
|
|
10760
|
-
var
|
|
10761
|
-
|
|
10762
|
-
session = null;
|
|
11067
|
+
var FILENAME5 = "cloud-sync.json";
|
|
11068
|
+
var CloudSyncStore = class {
|
|
11069
|
+
links = /* @__PURE__ */ new Map();
|
|
10763
11070
|
loaded = false;
|
|
10764
|
-
/** Pending login states (CSRF binding between `start` and the callback). */
|
|
10765
|
-
pending = /* @__PURE__ */ new Map();
|
|
10766
11071
|
getFilePath() {
|
|
10767
11072
|
return join17(env.DATA_DIR, FILENAME5);
|
|
10768
11073
|
}
|
|
@@ -10770,86 +11075,46 @@ var CloudSessionStore = class {
|
|
|
10770
11075
|
if (this.loaded) return;
|
|
10771
11076
|
try {
|
|
10772
11077
|
const raw = await readFile14(this.getFilePath(), "utf-8");
|
|
10773
|
-
|
|
11078
|
+
const data = JSON.parse(raw);
|
|
11079
|
+
for (const l of data) this.links.set(l.orgId, { orgId: l.orgId, cloudUrl: l.cloudUrl });
|
|
10774
11080
|
} catch {
|
|
10775
11081
|
}
|
|
10776
11082
|
this.loaded = true;
|
|
10777
11083
|
}
|
|
10778
|
-
/**
|
|
10779
|
-
* Mint a state value binding a login attempt to this server. Returned to the
|
|
10780
|
-
* cloud page and echoed back on the callback, so a redirect we never
|
|
10781
|
-
* initiated cannot install a token.
|
|
10782
|
-
*/
|
|
10783
|
-
createState(returnTo) {
|
|
10784
|
-
this.sweepStates();
|
|
10785
|
-
const state = randomBytes3(32).toString("hex");
|
|
10786
|
-
this.pending.set(state, { expiresAt: Date.now() + STATE_TTL_MS, returnTo });
|
|
10787
|
-
return state;
|
|
10788
|
-
}
|
|
10789
|
-
/**
|
|
10790
|
-
* Consume a state — single use, constant-time compare, TTL enforced. Returns
|
|
10791
|
-
* the URL to send the browser back to, or null when the state is unknown or
|
|
10792
|
-
* expired (i.e. a callback this server never initiated).
|
|
10793
|
-
*/
|
|
10794
|
-
consumeState(candidate) {
|
|
10795
|
-
this.sweepStates();
|
|
10796
|
-
for (const [state, entry] of this.pending) {
|
|
10797
|
-
const a = Buffer.from(state);
|
|
10798
|
-
const b = Buffer.from(candidate);
|
|
10799
|
-
if (a.length === b.length && timingSafeEqual2(a, b)) {
|
|
10800
|
-
this.pending.delete(state);
|
|
10801
|
-
return entry.expiresAt > Date.now() ? entry.returnTo : null;
|
|
10802
|
-
}
|
|
10803
|
-
}
|
|
10804
|
-
return null;
|
|
10805
|
-
}
|
|
10806
|
-
sweepStates() {
|
|
10807
|
-
const now = Date.now();
|
|
10808
|
-
for (const [state, entry] of this.pending) {
|
|
10809
|
-
if (entry.expiresAt <= now) this.pending.delete(state);
|
|
10810
|
-
}
|
|
10811
|
-
}
|
|
10812
11084
|
async save() {
|
|
10813
11085
|
await mkdir15(env.DATA_DIR, { recursive: true });
|
|
10814
11086
|
const filePath = this.getFilePath();
|
|
10815
11087
|
const tmpPath = filePath + ".tmp";
|
|
10816
|
-
|
|
11088
|
+
const data = JSON.stringify(Array.from(this.links.values()), null, 2);
|
|
11089
|
+
await writeFile12(tmpPath, data, "utf-8");
|
|
10817
11090
|
await rename11(tmpPath, filePath);
|
|
10818
11091
|
await chmod9(filePath, 384);
|
|
10819
11092
|
}
|
|
10820
|
-
/** Store
|
|
10821
|
-
async
|
|
11093
|
+
/** Store (or replace) the sync link for an organization. */
|
|
11094
|
+
async upsert(orgId, cloudUrl) {
|
|
10822
11095
|
await this.load();
|
|
10823
|
-
this.
|
|
11096
|
+
this.links.set(orgId, { orgId, cloudUrl });
|
|
10824
11097
|
await this.save();
|
|
10825
11098
|
}
|
|
10826
|
-
|
|
10827
|
-
async getToken() {
|
|
11099
|
+
async remove(orgId) {
|
|
10828
11100
|
await this.load();
|
|
10829
|
-
if (
|
|
10830
|
-
try {
|
|
10831
|
-
return decryptValue(this.session.encryptedToken);
|
|
10832
|
-
} catch {
|
|
10833
|
-
return null;
|
|
10834
|
-
}
|
|
11101
|
+
if (this.links.delete(orgId)) await this.save();
|
|
10835
11102
|
}
|
|
10836
|
-
|
|
10837
|
-
|
|
11103
|
+
/** All sync links (for the bridge on startup). */
|
|
11104
|
+
async list() {
|
|
11105
|
+
await this.load();
|
|
11106
|
+
return Array.from(this.links.values());
|
|
10838
11107
|
}
|
|
10839
|
-
|
|
10840
|
-
async clear() {
|
|
11108
|
+
async get(orgId) {
|
|
10841
11109
|
await this.load();
|
|
10842
|
-
this.
|
|
10843
|
-
this.pending.clear();
|
|
10844
|
-
try {
|
|
10845
|
-
await unlink3(this.getFilePath());
|
|
10846
|
-
} catch {
|
|
10847
|
-
}
|
|
11110
|
+
return this.links.get(orgId) ?? null;
|
|
10848
11111
|
}
|
|
10849
11112
|
};
|
|
10850
|
-
var
|
|
11113
|
+
var cloudSyncStore = new CloudSyncStore();
|
|
10851
11114
|
|
|
10852
11115
|
// ../server/src/services/cloud-sync-bridge.ts
|
|
11116
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
11117
|
+
import * as Y from "yjs";
|
|
10853
11118
|
var LOCAL_ORIGIN = "local-bridge";
|
|
10854
11119
|
var REMOTE_ORIGIN = "remote";
|
|
10855
11120
|
var RECONNECT_DELAY_MS = 3e3;
|
|
@@ -10865,7 +11130,7 @@ var OrgSyncConnection = class {
|
|
|
10865
11130
|
projects = getCollection(this.doc, "projects");
|
|
10866
11131
|
services = getCollection(this.doc, "services");
|
|
10867
11132
|
environments = getCollection(this.doc, "environments");
|
|
10868
|
-
conn =
|
|
11133
|
+
conn = randomUUID11();
|
|
10869
11134
|
link;
|
|
10870
11135
|
stopped = false;
|
|
10871
11136
|
initializing = true;
|
|
@@ -10965,7 +11230,7 @@ var OrgSyncConnection = class {
|
|
|
10965
11230
|
await this.pushUpdate(diff);
|
|
10966
11231
|
} catch (err) {
|
|
10967
11232
|
if (err instanceof SyncAuthError) throw err;
|
|
10968
|
-
console.error(
|
|
11233
|
+
console.error("[cloud-sync] initial push failed for org %s:", this.link.orgId, err.message);
|
|
10969
11234
|
}
|
|
10970
11235
|
}
|
|
10971
11236
|
async pushUpdate(update) {
|
|
@@ -10983,7 +11248,7 @@ var OrgSyncConnection = class {
|
|
|
10983
11248
|
this.abort?.abort();
|
|
10984
11249
|
return;
|
|
10985
11250
|
}
|
|
10986
|
-
if (!this.stopped) console.error(
|
|
11251
|
+
if (!this.stopped) console.error("[cloud-sync] push failed for org %s:", this.link.orgId, err.message);
|
|
10987
11252
|
}
|
|
10988
11253
|
}
|
|
10989
11254
|
/**
|
|
@@ -11005,7 +11270,7 @@ var OrgSyncConnection = class {
|
|
|
11005
11270
|
await this.delay(UNAUTHENTICATED_RETRY_MS);
|
|
11006
11271
|
continue;
|
|
11007
11272
|
}
|
|
11008
|
-
if (!this.stopped) console.error(
|
|
11273
|
+
if (!this.stopped) console.error("[cloud-sync] stream error for org %s:", this.link.orgId, err.message);
|
|
11009
11274
|
}
|
|
11010
11275
|
await this.delay(RECONNECT_DELAY_MS);
|
|
11011
11276
|
}
|
|
@@ -11095,7 +11360,7 @@ var OrgSyncConnection = class {
|
|
|
11095
11360
|
});
|
|
11096
11361
|
await projectStore.addEnvironment(projectId, localEnv.id);
|
|
11097
11362
|
} catch (err) {
|
|
11098
|
-
console.error(
|
|
11363
|
+
console.error("[cloud-sync] env bootstrap failed for project %s:", projectId, err.message);
|
|
11099
11364
|
}
|
|
11100
11365
|
}
|
|
11101
11366
|
/**
|
|
@@ -11198,7 +11463,7 @@ var CloudSyncBridge = class {
|
|
|
11198
11463
|
try {
|
|
11199
11464
|
await conn.start();
|
|
11200
11465
|
} catch (err) {
|
|
11201
|
-
console.error(
|
|
11466
|
+
console.error("[cloud-sync] failed to start org %s:", link.orgId, err.message);
|
|
11202
11467
|
}
|
|
11203
11468
|
}
|
|
11204
11469
|
disconnect(orgId) {
|
|
@@ -11215,113 +11480,6 @@ var CloudSyncBridge = class {
|
|
|
11215
11480
|
};
|
|
11216
11481
|
var cloudSyncBridge = new CloudSyncBridge();
|
|
11217
11482
|
|
|
11218
|
-
// ../server/src/trpc/routers/cloud-auth.ts
|
|
11219
|
-
import { z as z23 } from "zod";
|
|
11220
|
-
import { TRPCError as TRPCError19 } from "@trpc/server";
|
|
11221
|
-
var DEFAULT_CLOUD_API_URL = "https://api.runeya.dev";
|
|
11222
|
-
var DEFAULT_CLOUD_APP_URL = "https://runeya.dev";
|
|
11223
|
-
function getCloudApiUrl() {
|
|
11224
|
-
return (env.CLOUD_URL ?? DEFAULT_CLOUD_API_URL).replace(/\/+$/, "");
|
|
11225
|
-
}
|
|
11226
|
-
function getCloudAppUrl() {
|
|
11227
|
-
return (env.CLOUD_APP_URL ?? DEFAULT_CLOUD_APP_URL).replace(/\/+$/, "");
|
|
11228
|
-
}
|
|
11229
|
-
function isLocalOrigin(url) {
|
|
11230
|
-
try {
|
|
11231
|
-
const { protocol, hostname } = new URL(url);
|
|
11232
|
-
return (protocol === "http:" || protocol === "https:") && (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]");
|
|
11233
|
-
} catch {
|
|
11234
|
-
return false;
|
|
11235
|
-
}
|
|
11236
|
-
}
|
|
11237
|
-
async function cloudFetch(path, init = {}) {
|
|
11238
|
-
const token = await cloudSessionStore.getToken();
|
|
11239
|
-
if (!token) {
|
|
11240
|
-
throw new TRPCError19({ code: "UNAUTHORIZED", message: "Not linked to the Runeya cloud." });
|
|
11241
|
-
}
|
|
11242
|
-
return fetch(`${getCloudApiUrl()}${path}`, {
|
|
11243
|
-
method: init.method ?? "GET",
|
|
11244
|
-
headers: {
|
|
11245
|
-
Authorization: `Bearer ${token}`,
|
|
11246
|
-
...init.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
11247
|
-
},
|
|
11248
|
-
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
11249
|
-
signal: AbortSignal.timeout(15e3)
|
|
11250
|
-
});
|
|
11251
|
-
}
|
|
11252
|
-
var PROXY_PREFIXES = ["/organization/", "/project/", "/chat/", "/user/prefs"];
|
|
11253
|
-
function isAllowedProxyPath(path) {
|
|
11254
|
-
if (!path.startsWith("/") || path.includes("..")) return false;
|
|
11255
|
-
const [pathname] = path.split("?");
|
|
11256
|
-
return PROXY_PREFIXES.some((p) => pathname === p || pathname.startsWith(p));
|
|
11257
|
-
}
|
|
11258
|
-
var cloudAuthRouter = router({
|
|
11259
|
-
/** Build the cloud authorization URL to open in the browser. */
|
|
11260
|
-
startLogin: protectedProcedure.input(z23.object({ returnTo: z23.string().url().max(2048) })).mutation(async ({ input }) => {
|
|
11261
|
-
if (!isLocalOrigin(input.returnTo)) {
|
|
11262
|
-
throw new TRPCError19({ code: "BAD_REQUEST", message: "returnTo must be a local origin." });
|
|
11263
|
-
}
|
|
11264
|
-
const state = cloudSessionStore.createState(input.returnTo);
|
|
11265
|
-
const callback = `http://127.0.0.1:${env.PORT}/api/cloud/callback`;
|
|
11266
|
-
const url = `${getCloudAppUrl()}/link-local?callback=${encodeURIComponent(callback)}&state=${encodeURIComponent(state)}`;
|
|
11267
|
-
return { url };
|
|
11268
|
-
}),
|
|
11269
|
-
/** Whether this machine holds a cloud session. */
|
|
11270
|
-
status: protectedProcedure.query(async () => {
|
|
11271
|
-
return { linked: await cloudSessionStore.isLinked() };
|
|
11272
|
-
}),
|
|
11273
|
-
/** The cloud session (user + org), fetched with the stored token. */
|
|
11274
|
-
session: protectedProcedure.query(async () => {
|
|
11275
|
-
if (!await cloudSessionStore.isLinked()) return null;
|
|
11276
|
-
const res = await cloudFetch("/api/auth/get-session");
|
|
11277
|
-
if (!res.ok) return null;
|
|
11278
|
-
return await res.json();
|
|
11279
|
-
}),
|
|
11280
|
-
/**
|
|
11281
|
-
* Forward a cloud API call, authenticated with this machine's token.
|
|
11282
|
-
*
|
|
11283
|
-
* This is what replaces the browser's direct, cookie-authenticated calls to
|
|
11284
|
-
* the cloud: the web UI asks its own server, which holds the credential.
|
|
11285
|
-
*/
|
|
11286
|
-
proxy: protectedProcedure.input(
|
|
11287
|
-
z23.object({
|
|
11288
|
-
path: z23.string().min(1).max(512),
|
|
11289
|
-
method: z23.enum(["GET", "POST"]).default("GET"),
|
|
11290
|
-
query: z23.record(z23.string().max(128), z23.string().max(2048)).optional(),
|
|
11291
|
-
body: z23.unknown().optional()
|
|
11292
|
-
})
|
|
11293
|
-
).mutation(async ({ input }) => {
|
|
11294
|
-
if (!isAllowedProxyPath(input.path)) {
|
|
11295
|
-
throw new TRPCError19({ code: "FORBIDDEN", message: `Path not allowed: ${input.path}` });
|
|
11296
|
-
}
|
|
11297
|
-
const qs = input.query ? `?${new URLSearchParams(input.query).toString()}` : "";
|
|
11298
|
-
const res = await cloudFetch(`/api/auth${input.path}${qs}`, {
|
|
11299
|
-
method: input.method,
|
|
11300
|
-
...input.method === "POST" ? { body: input.body ?? {} } : {}
|
|
11301
|
-
});
|
|
11302
|
-
const text = await res.text();
|
|
11303
|
-
let data = null;
|
|
11304
|
-
try {
|
|
11305
|
-
data = text ? JSON.parse(text) : null;
|
|
11306
|
-
} catch {
|
|
11307
|
-
data = text;
|
|
11308
|
-
}
|
|
11309
|
-
if (!res.ok) return { data: null, error: { status: res.status, ...data } };
|
|
11310
|
-
return { data, error: null };
|
|
11311
|
-
}),
|
|
11312
|
-
/** Drop the cloud session, both here and on the cloud. */
|
|
11313
|
-
signOut: protectedProcedure.mutation(async () => {
|
|
11314
|
-
if (await cloudSessionStore.isLinked()) {
|
|
11315
|
-
try {
|
|
11316
|
-
await cloudFetch("/api/auth/sign-out", { method: "POST", body: {} });
|
|
11317
|
-
} catch {
|
|
11318
|
-
}
|
|
11319
|
-
}
|
|
11320
|
-
await cloudSessionStore.clear();
|
|
11321
|
-
return { success: true };
|
|
11322
|
-
})
|
|
11323
|
-
});
|
|
11324
|
-
|
|
11325
11483
|
// ../server/src/services/master-password.ts
|
|
11326
11484
|
import { randomBytes as randomBytes4, createCipheriv, createDecipheriv } from "crypto";
|
|
11327
11485
|
import { readFile as readFile15, writeFile as writeFile13, chmod as chmod10, stat as stat4, rename as rename12 } from "fs/promises";
|
|
@@ -11492,25 +11650,25 @@ async function hasEncryptedKeyFile(dataDir) {
|
|
|
11492
11650
|
async function reconcileOrgCanary(orgId) {
|
|
11493
11651
|
const key = getEncryptionKey();
|
|
11494
11652
|
if (!key) {
|
|
11495
|
-
throw new
|
|
11653
|
+
throw new TRPCError19({ code: "PRECONDITION_FAILED", message: "Encryption key not loaded." });
|
|
11496
11654
|
}
|
|
11497
11655
|
const local = await readVerifyFile(env.DATA_DIR);
|
|
11498
11656
|
if (!local) {
|
|
11499
|
-
throw new
|
|
11657
|
+
throw new TRPCError19({ code: "PRECONDITION_FAILED", message: "This machine has no key canary yet." });
|
|
11500
11658
|
}
|
|
11501
11659
|
const res = await cloudFetch("/api/auth/organization/canary", {
|
|
11502
11660
|
method: "POST",
|
|
11503
11661
|
body: { organizationId: orgId, canary: local }
|
|
11504
11662
|
});
|
|
11505
11663
|
if (!res.ok) {
|
|
11506
|
-
throw new
|
|
11664
|
+
throw new TRPCError19({ code: "BAD_GATEWAY", message: "Could not reach the organization on the cloud." });
|
|
11507
11665
|
}
|
|
11508
11666
|
const { canary } = await res.json();
|
|
11509
11667
|
if (typeof canary !== "string") {
|
|
11510
|
-
throw new
|
|
11668
|
+
throw new TRPCError19({ code: "BAD_GATEWAY", message: "The cloud returned no key canary." });
|
|
11511
11669
|
}
|
|
11512
11670
|
if (!matchesCanary(canary, key)) {
|
|
11513
|
-
throw new
|
|
11671
|
+
throw new TRPCError19({
|
|
11514
11672
|
code: "FORBIDDEN",
|
|
11515
11673
|
message: "This organization uses a different encryption key. Ask a member for the key already in use, then set it up on this machine before syncing."
|
|
11516
11674
|
});
|
|
@@ -11524,9 +11682,9 @@ var cloudSyncRouter = router({
|
|
|
11524
11682
|
* to sync a project whose secrets will not open.
|
|
11525
11683
|
*/
|
|
11526
11684
|
link: protectedProcedure.input(
|
|
11527
|
-
|
|
11528
|
-
orgId:
|
|
11529
|
-
cloudUrl:
|
|
11685
|
+
z23.object({
|
|
11686
|
+
orgId: z23.string().min(1).max(128),
|
|
11687
|
+
cloudUrl: z23.string().url().max(2048)
|
|
11530
11688
|
})
|
|
11531
11689
|
).mutation(async ({ input }) => {
|
|
11532
11690
|
await reconcileOrgCanary(input.orgId);
|
|
@@ -11535,7 +11693,7 @@ var cloudSyncRouter = router({
|
|
|
11535
11693
|
return { success: true };
|
|
11536
11694
|
}),
|
|
11537
11695
|
/** Stop syncing an org and forget its token. */
|
|
11538
|
-
unlink: protectedProcedure.input(
|
|
11696
|
+
unlink: protectedProcedure.input(z23.object({ orgId: z23.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
11539
11697
|
cloudSyncBridge.disconnect(input.orgId);
|
|
11540
11698
|
await cloudSyncStore.remove(input.orgId);
|
|
11541
11699
|
return { success: true };
|
|
@@ -11572,7 +11730,6 @@ var appRouter = router({
|
|
|
11572
11730
|
scenario: scenarioRouter,
|
|
11573
11731
|
git: gitRouter,
|
|
11574
11732
|
debug: debugRouter,
|
|
11575
|
-
kanban: kanbanRouter,
|
|
11576
11733
|
kanbanQueue: kanbanQueueRouter,
|
|
11577
11734
|
terminal: terminalRouter,
|
|
11578
11735
|
utility: utilityRouter,
|
|
@@ -11689,7 +11846,7 @@ async function ensureDataGitignore(dataDir) {
|
|
|
11689
11846
|
// ../server/src/migrations/migrate-from-legacy.ts
|
|
11690
11847
|
import { createRequire } from "module";
|
|
11691
11848
|
import { randomBytes as randomBytes5, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
|
|
11692
|
-
import { randomUUID as
|
|
11849
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
11693
11850
|
import {
|
|
11694
11851
|
readFile as readFile16,
|
|
11695
11852
|
writeFile as writeFile15,
|
|
@@ -11831,8 +11988,8 @@ async function atomicWrite(path, data) {
|
|
|
11831
11988
|
await rename13(tmp, path);
|
|
11832
11989
|
}
|
|
11833
11990
|
async function runLegacyMigration(dataDir) {
|
|
11834
|
-
const { resolve:
|
|
11835
|
-
const abCwd =
|
|
11991
|
+
const { resolve: resolve6 } = await import("path");
|
|
11992
|
+
const abCwd = resolve6(process.cwd());
|
|
11836
11993
|
const legacyRoot = join21(abCwd, env.DATA_DIR);
|
|
11837
11994
|
const legacyKeyPath = join21(legacyRoot, "dbs", "encryption-key.json");
|
|
11838
11995
|
try {
|
|
@@ -11901,11 +12058,11 @@ async function runLegacyMigration(dataDir) {
|
|
|
11901
12058
|
const data = await readEncrypted(join21(parsersDir, file));
|
|
11902
12059
|
if (data?.id) legacyParsers.push(data);
|
|
11903
12060
|
}
|
|
11904
|
-
const projectMonoId =
|
|
12061
|
+
const projectMonoId = randomUUID12();
|
|
11905
12062
|
const envIdMap = {};
|
|
11906
12063
|
const labelToKey = {};
|
|
11907
12064
|
for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
|
|
11908
|
-
envIdMap[name] =
|
|
12065
|
+
envIdMap[name] = randomUUID12();
|
|
11909
12066
|
if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
|
|
11910
12067
|
}
|
|
11911
12068
|
const runeEnvs = [];
|
|
@@ -11930,7 +12087,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11930
12087
|
if (hasInvalidInterpolation(value)) continue;
|
|
11931
12088
|
const cleaned = cleanVarKey(key);
|
|
11932
12089
|
if (inheritedVars[cleaned] === value) continue;
|
|
11933
|
-
const varId =
|
|
12090
|
+
const varId = randomUUID12();
|
|
11934
12091
|
variables[varId] = makeVariable(cleaned, value);
|
|
11935
12092
|
varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
|
|
11936
12093
|
}
|
|
@@ -11951,7 +12108,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11951
12108
|
const serviceIdMap = {};
|
|
11952
12109
|
const runeServices = [];
|
|
11953
12110
|
for (const [name, legacy] of Object.entries(legacyServices)) {
|
|
11954
|
-
const id =
|
|
12111
|
+
const id = randomUUID12();
|
|
11955
12112
|
serviceIdMap[name] = id;
|
|
11956
12113
|
const rootPath = legacy.rootPath || "";
|
|
11957
12114
|
const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
|
|
@@ -11960,7 +12117,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11960
12117
|
const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
|
|
11961
12118
|
if (!parts.length) continue;
|
|
11962
12119
|
commands.push({
|
|
11963
|
-
id: cmd.id ||
|
|
12120
|
+
id: cmd.id || randomUUID12(),
|
|
11964
12121
|
label: "Launch",
|
|
11965
12122
|
command: parts.join(" "),
|
|
11966
12123
|
cwd: cmd.spawnOptions?.cwd || "",
|
|
@@ -12021,7 +12178,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12021
12178
|
const legacyBootstrapCmds = container.bootstrap?.commands || [];
|
|
12022
12179
|
if (legacyBootstrapCmds.length > 0) {
|
|
12023
12180
|
dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
|
|
12024
|
-
id: bs.id ||
|
|
12181
|
+
id: bs.id || randomUUID12(),
|
|
12025
12182
|
label: bs.label || `Bootstrap ${idx + 1}`,
|
|
12026
12183
|
command: bs.cmd || "",
|
|
12027
12184
|
...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
|
|
@@ -12069,7 +12226,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12069
12226
|
cwd: serviceCwd,
|
|
12070
12227
|
runner: dockerConfig ? "docker" : "native",
|
|
12071
12228
|
shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
|
|
12072
|
-
id: s.id ||
|
|
12229
|
+
id: s.id || randomUUID12(),
|
|
12073
12230
|
label: s.label || s.spawnCmd || "",
|
|
12074
12231
|
command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
|
|
12075
12232
|
...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
|
|
@@ -12109,7 +12266,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12109
12266
|
);
|
|
12110
12267
|
if (!svcScopedEnv) {
|
|
12111
12268
|
svcScopedEnv = {
|
|
12112
|
-
id:
|
|
12269
|
+
id: randomUUID12(),
|
|
12113
12270
|
projectId: projectMonoId,
|
|
12114
12271
|
name: `${service?.name || name} (${globalEnv.name})`,
|
|
12115
12272
|
scope: "service",
|
|
@@ -12133,7 +12290,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12133
12290
|
const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
|
|
12134
12291
|
if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
|
|
12135
12292
|
const cleaned = cleanVarKey(varKey);
|
|
12136
|
-
const varId =
|
|
12293
|
+
const varId = randomUUID12();
|
|
12137
12294
|
svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
|
|
12138
12295
|
existingKeys.add(cleaned);
|
|
12139
12296
|
}
|
|
@@ -12153,7 +12310,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12153
12310
|
);
|
|
12154
12311
|
if (!svcScopedEnv) {
|
|
12155
12312
|
svcScopedEnv = {
|
|
12156
|
-
id:
|
|
12313
|
+
id: randomUUID12(),
|
|
12157
12314
|
projectId: projectMonoId,
|
|
12158
12315
|
name: `${service?.name || serviceName} (${globalEnv.name})`,
|
|
12159
12316
|
scope: "service",
|
|
@@ -12174,7 +12331,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12174
12331
|
const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
|
|
12175
12332
|
let varId = baseEntry?.[0];
|
|
12176
12333
|
if (!varId) {
|
|
12177
|
-
varId =
|
|
12334
|
+
varId = randomUUID12();
|
|
12178
12335
|
svcScopedEnv.variables[varId] = {
|
|
12179
12336
|
key: cleaned,
|
|
12180
12337
|
value: { value: "", pre: "", post: "", isSecret: false, injectIntoAi: false }
|
|
@@ -12204,13 +12361,13 @@ async function runLegacyMigration(dataDir) {
|
|
|
12204
12361
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
12205
12362
|
}
|
|
12206
12363
|
} else {
|
|
12207
|
-
flatOverrides[
|
|
12364
|
+
flatOverrides[randomUUID12()] = makeVariable(cleaned, value);
|
|
12208
12365
|
}
|
|
12209
12366
|
} else {
|
|
12210
12367
|
const cleaned = cleanVarKey(key);
|
|
12211
12368
|
const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
|
|
12212
12369
|
if (!existingVarId) {
|
|
12213
|
-
const varId =
|
|
12370
|
+
const varId = randomUUID12();
|
|
12214
12371
|
env2.variables[varId] = makeVariable(cleaned, value);
|
|
12215
12372
|
varIdLookup.set(`${envId}:${cleaned}`, varId);
|
|
12216
12373
|
}
|
|
@@ -12298,9 +12455,9 @@ async function findLegacyKeyInBackup(dataDir) {
|
|
|
12298
12455
|
return null;
|
|
12299
12456
|
}
|
|
12300
12457
|
async function runOverrideOnlyMigration(dataDir) {
|
|
12301
|
-
const { resolve:
|
|
12302
|
-
const abDataDir =
|
|
12303
|
-
const abCwd =
|
|
12458
|
+
const { resolve: resolve6 } = await import("path");
|
|
12459
|
+
const abDataDir = resolve6(dataDir);
|
|
12460
|
+
const abCwd = resolve6(process.cwd());
|
|
12304
12461
|
const candidateDirs = [
|
|
12305
12462
|
join21(abCwd, ".runeya", "dbs", "overrides"),
|
|
12306
12463
|
join21(abDataDir, "dbs", "overrides")
|
|
@@ -12395,7 +12552,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12395
12552
|
if (isSameAsBase(value, baseVar)) continue;
|
|
12396
12553
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
12397
12554
|
} else {
|
|
12398
|
-
flatOverrides[
|
|
12555
|
+
flatOverrides[randomUUID12()] = makeVariable(cleaned, value);
|
|
12399
12556
|
}
|
|
12400
12557
|
}
|
|
12401
12558
|
}
|
|
@@ -12432,7 +12589,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12432
12589
|
);
|
|
12433
12590
|
if (!svcScopedEnv) {
|
|
12434
12591
|
svcScopedEnv = {
|
|
12435
|
-
id:
|
|
12592
|
+
id: randomUUID12(),
|
|
12436
12593
|
projectId: globalEnv["projectId"],
|
|
12437
12594
|
name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
|
|
12438
12595
|
scope: "service",
|
|
@@ -12454,7 +12611,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12454
12611
|
const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
|
|
12455
12612
|
let varId = baseEntry?.[0];
|
|
12456
12613
|
if (!varId) {
|
|
12457
|
-
varId =
|
|
12614
|
+
varId = randomUUID12();
|
|
12458
12615
|
svcScopedEnv["variables"][varId] = {
|
|
12459
12616
|
key: cleaned,
|
|
12460
12617
|
value: { value: "", pre: "", post: "", isSecret: false, injectIntoAi: false }
|
|
@@ -12903,7 +13060,7 @@ async function createLocalServer() {
|
|
|
12903
13060
|
}
|
|
12904
13061
|
}
|
|
12905
13062
|
if (!existsSync2(env.DATA_DIR)) await mkdir17(env.DATA_DIR);
|
|
12906
|
-
const absoluteDataDir =
|
|
13063
|
+
const absoluteDataDir = resolve5(env.DATA_DIR);
|
|
12907
13064
|
console.log(`[server] Data directory: ${absoluteDataDir}`);
|
|
12908
13065
|
await ensureDataGitignore(env.DATA_DIR);
|
|
12909
13066
|
await settingsManager.getSettings();
|
|
@@ -13012,7 +13169,102 @@ async function createLocalServer() {
|
|
|
13012
13169
|
res.status(502).send("Could not reach the Runeya cloud.");
|
|
13013
13170
|
}
|
|
13014
13171
|
});
|
|
13015
|
-
|
|
13172
|
+
function proxyCloudStream(localPath, upstreamPath) {
|
|
13173
|
+
app.get(localPath, async (req, res) => {
|
|
13174
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13175
|
+
try {
|
|
13176
|
+
await verifyJwt(jwt);
|
|
13177
|
+
} catch {
|
|
13178
|
+
res.status(401).end();
|
|
13179
|
+
return;
|
|
13180
|
+
}
|
|
13181
|
+
const organizationId = typeof req.query["organizationId"] === "string" ? req.query["organizationId"] : "";
|
|
13182
|
+
if (!organizationId) {
|
|
13183
|
+
res.status(400).end();
|
|
13184
|
+
return;
|
|
13185
|
+
}
|
|
13186
|
+
const token = await cloudSessionStore.getToken();
|
|
13187
|
+
if (!token) {
|
|
13188
|
+
res.status(401).end();
|
|
13189
|
+
return;
|
|
13190
|
+
}
|
|
13191
|
+
const upstreamAbort = new AbortController();
|
|
13192
|
+
req.on("close", () => upstreamAbort.abort());
|
|
13193
|
+
try {
|
|
13194
|
+
const upstream = await fetch(
|
|
13195
|
+
`${getCloudApiUrl()}${upstreamPath}?organizationId=${encodeURIComponent(organizationId)}`,
|
|
13196
|
+
{
|
|
13197
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
13198
|
+
signal: upstreamAbort.signal
|
|
13199
|
+
}
|
|
13200
|
+
);
|
|
13201
|
+
if (!upstream.ok || !upstream.body) {
|
|
13202
|
+
res.status(upstream.status === 401 ? 401 : 502).end();
|
|
13203
|
+
return;
|
|
13204
|
+
}
|
|
13205
|
+
res.writeHead(200, {
|
|
13206
|
+
"Content-Type": "text/event-stream",
|
|
13207
|
+
"Cache-Control": "no-cache",
|
|
13208
|
+
Connection: "keep-alive",
|
|
13209
|
+
"X-Accel-Buffering": "no"
|
|
13210
|
+
});
|
|
13211
|
+
const reader = upstream.body.getReader();
|
|
13212
|
+
for (; ; ) {
|
|
13213
|
+
const { done, value } = await reader.read();
|
|
13214
|
+
if (done) break;
|
|
13215
|
+
res.write(value);
|
|
13216
|
+
}
|
|
13217
|
+
res.end();
|
|
13218
|
+
} catch {
|
|
13219
|
+
if (!res.headersSent) res.status(502).end();
|
|
13220
|
+
else res.end();
|
|
13221
|
+
}
|
|
13222
|
+
});
|
|
13223
|
+
}
|
|
13224
|
+
app.post(
|
|
13225
|
+
"/api/cloud/kanban/image",
|
|
13226
|
+
express.raw({ type: () => true, limit: "64mb" }),
|
|
13227
|
+
async (req, res) => {
|
|
13228
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13229
|
+
try {
|
|
13230
|
+
await verifyJwt(jwt);
|
|
13231
|
+
} catch {
|
|
13232
|
+
res.status(401).end();
|
|
13233
|
+
return;
|
|
13234
|
+
}
|
|
13235
|
+
const organizationId = typeof req.query["organizationId"] === "string" ? req.query["organizationId"] : "";
|
|
13236
|
+
if (!organizationId) {
|
|
13237
|
+
res.status(400).end();
|
|
13238
|
+
return;
|
|
13239
|
+
}
|
|
13240
|
+
const token = await cloudSessionStore.getToken();
|
|
13241
|
+
if (!token) {
|
|
13242
|
+
res.status(401).end();
|
|
13243
|
+
return;
|
|
13244
|
+
}
|
|
13245
|
+
const upstreamQuery = new URLSearchParams({ organizationId });
|
|
13246
|
+
if (typeof req.query["name"] === "string") upstreamQuery.set("name", req.query["name"]);
|
|
13247
|
+
try {
|
|
13248
|
+
const upstream = await fetch(
|
|
13249
|
+
`${getCloudApiUrl()}/api/kanban/image?${upstreamQuery.toString()}`,
|
|
13250
|
+
{
|
|
13251
|
+
method: "POST",
|
|
13252
|
+
headers: {
|
|
13253
|
+
Authorization: `Bearer ${token}`,
|
|
13254
|
+
"Content-Type": String(req.headers["content-type"] ?? "application/octet-stream")
|
|
13255
|
+
},
|
|
13256
|
+
body: req.body,
|
|
13257
|
+
// A 55 MB body over a home upstream link takes minutes, not seconds.
|
|
13258
|
+
signal: AbortSignal.timeout(3e5)
|
|
13259
|
+
}
|
|
13260
|
+
);
|
|
13261
|
+
res.status(upstream.status).json(await upstream.json().catch(() => ({})));
|
|
13262
|
+
} catch {
|
|
13263
|
+
res.status(502).json({ error: "upload-failed" });
|
|
13264
|
+
}
|
|
13265
|
+
}
|
|
13266
|
+
);
|
|
13267
|
+
app.delete("/api/cloud/kanban/file/:id", async (req, res) => {
|
|
13016
13268
|
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13017
13269
|
try {
|
|
13018
13270
|
await verifyJwt(jwt);
|
|
@@ -13020,9 +13272,27 @@ async function createLocalServer() {
|
|
|
13020
13272
|
res.status(401).end();
|
|
13021
13273
|
return;
|
|
13022
13274
|
}
|
|
13023
|
-
const
|
|
13024
|
-
if (!
|
|
13025
|
-
res.status(
|
|
13275
|
+
const token = await cloudSessionStore.getToken();
|
|
13276
|
+
if (!token) {
|
|
13277
|
+
res.status(401).end();
|
|
13278
|
+
return;
|
|
13279
|
+
}
|
|
13280
|
+
try {
|
|
13281
|
+
const upstream = await fetch(
|
|
13282
|
+
`${getCloudApiUrl()}/api/kanban/file/${encodeURIComponent(req.params.id)}`,
|
|
13283
|
+
{ method: "DELETE", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(15e3) }
|
|
13284
|
+
);
|
|
13285
|
+
res.status(upstream.status).end();
|
|
13286
|
+
} catch {
|
|
13287
|
+
res.status(502).end();
|
|
13288
|
+
}
|
|
13289
|
+
});
|
|
13290
|
+
app.get("/api/cloud/kanban/file/:id", async (req, res) => {
|
|
13291
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13292
|
+
try {
|
|
13293
|
+
await verifyJwt(jwt);
|
|
13294
|
+
} catch {
|
|
13295
|
+
res.status(401).end();
|
|
13026
13296
|
return;
|
|
13027
13297
|
}
|
|
13028
13298
|
const token = await cloudSessionStore.getToken();
|
|
@@ -13030,38 +13300,27 @@ async function createLocalServer() {
|
|
|
13030
13300
|
res.status(401).end();
|
|
13031
13301
|
return;
|
|
13032
13302
|
}
|
|
13033
|
-
const upstreamAbort = new AbortController();
|
|
13034
|
-
req.on("close", () => upstreamAbort.abort());
|
|
13035
13303
|
try {
|
|
13036
13304
|
const upstream = await fetch(
|
|
13037
|
-
`${getCloudApiUrl()}/api/
|
|
13038
|
-
{
|
|
13039
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
13040
|
-
signal: upstreamAbort.signal
|
|
13041
|
-
}
|
|
13305
|
+
`${getCloudApiUrl()}/api/kanban/file/${encodeURIComponent(req.params.id)}`,
|
|
13306
|
+
{ headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(3e5) }
|
|
13042
13307
|
);
|
|
13043
|
-
if (!upstream.ok
|
|
13044
|
-
res.status(upstream.status
|
|
13308
|
+
if (!upstream.ok) {
|
|
13309
|
+
res.status(upstream.status).end();
|
|
13045
13310
|
return;
|
|
13046
13311
|
}
|
|
13047
|
-
res.
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
const reader = upstream.body.getReader();
|
|
13054
|
-
for (; ; ) {
|
|
13055
|
-
const { done, value } = await reader.read();
|
|
13056
|
-
if (done) break;
|
|
13057
|
-
res.write(value);
|
|
13058
|
-
}
|
|
13059
|
-
res.end();
|
|
13312
|
+
res.set("Content-Type", upstream.headers.get("content-type") ?? "application/octet-stream");
|
|
13313
|
+
const disposition = upstream.headers.get("content-disposition");
|
|
13314
|
+
if (disposition) res.set("Content-Disposition", disposition);
|
|
13315
|
+
res.set("X-Content-Type-Options", "nosniff");
|
|
13316
|
+
res.set("Content-Security-Policy", "default-src 'none'; sandbox");
|
|
13317
|
+
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
13060
13318
|
} catch {
|
|
13061
|
-
|
|
13062
|
-
else res.end();
|
|
13319
|
+
res.status(502).end();
|
|
13063
13320
|
}
|
|
13064
13321
|
});
|
|
13322
|
+
proxyCloudStream("/api/cloud/chat/stream", "/api/chat/stream");
|
|
13323
|
+
proxyCloudStream("/api/cloud/kanban/stream", "/api/kanban/stream");
|
|
13065
13324
|
app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
|
|
13066
13325
|
try {
|
|
13067
13326
|
const body = req.body;
|
|
@@ -13242,7 +13501,18 @@ async function createLocalServer() {
|
|
|
13242
13501
|
});
|
|
13243
13502
|
const CONV_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
13244
13503
|
const IMAGE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13245
|
-
|
|
13504
|
+
const uploadSingleImage = (req, res, next) => {
|
|
13505
|
+
upload.single("image")(req, res, (err) => {
|
|
13506
|
+
if (err) {
|
|
13507
|
+
const isTooLarge = err.code === "LIMIT_FILE_SIZE";
|
|
13508
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
13509
|
+
res.status(isTooLarge ? 413 : 400).json({ error: message });
|
|
13510
|
+
return;
|
|
13511
|
+
}
|
|
13512
|
+
next();
|
|
13513
|
+
});
|
|
13514
|
+
};
|
|
13515
|
+
app.post("/api/images/upload", uploadSingleImage, async (req, res) => {
|
|
13246
13516
|
try {
|
|
13247
13517
|
if (!req.file) {
|
|
13248
13518
|
res.status(400).json({ error: "No image file provided" });
|
|
@@ -13280,7 +13550,7 @@ async function createLocalServer() {
|
|
|
13280
13550
|
res.status(400).json({ error: "Invalid imageId" });
|
|
13281
13551
|
return;
|
|
13282
13552
|
}
|
|
13283
|
-
const result = await imageStorageService.getImagePath(imageId);
|
|
13553
|
+
const result = await imageStorageService.getImagePath(imageId, conversationId);
|
|
13284
13554
|
if (!result) {
|
|
13285
13555
|
res.status(404).json({ error: "Image not found" });
|
|
13286
13556
|
return;
|
|
@@ -13362,6 +13632,7 @@ async function createLocalServer() {
|
|
|
13362
13632
|
const shutdown = async () => {
|
|
13363
13633
|
console.log("[server] Shutting down...");
|
|
13364
13634
|
clearInterval(wsTickInterval);
|
|
13635
|
+
stopClaudeTranscriptWatcher();
|
|
13365
13636
|
imageCleanupService.stop();
|
|
13366
13637
|
cloudSyncBridge.stopAll();
|
|
13367
13638
|
await Promise.all([
|
|
@@ -13370,12 +13641,12 @@ async function createLocalServer() {
|
|
|
13370
13641
|
]);
|
|
13371
13642
|
server.closeAllConnections();
|
|
13372
13643
|
await Promise.race([
|
|
13373
|
-
new Promise((
|
|
13374
|
-
new Promise((
|
|
13644
|
+
new Promise((resolve6) => wss.close(() => resolve6())),
|
|
13645
|
+
new Promise((resolve6) => setTimeout(resolve6, 2e3))
|
|
13375
13646
|
]);
|
|
13376
13647
|
await Promise.race([
|
|
13377
|
-
new Promise((
|
|
13378
|
-
new Promise((
|
|
13648
|
+
new Promise((resolve6) => server.close(() => resolve6())),
|
|
13649
|
+
new Promise((resolve6) => setTimeout(resolve6, 2e3))
|
|
13379
13650
|
]);
|
|
13380
13651
|
console.log("[server] Shutdown complete");
|
|
13381
13652
|
};
|
|
@@ -13396,7 +13667,7 @@ var PullEnvError = class extends Error {
|
|
|
13396
13667
|
};
|
|
13397
13668
|
async function promptPassword() {
|
|
13398
13669
|
process.stderr.write("Master password: ");
|
|
13399
|
-
return new Promise((
|
|
13670
|
+
return new Promise((resolve6) => {
|
|
13400
13671
|
const stdin = process.stdin;
|
|
13401
13672
|
const wasRaw = stdin.isRaw;
|
|
13402
13673
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
@@ -13409,7 +13680,7 @@ async function promptPassword() {
|
|
|
13409
13680
|
stdin.pause();
|
|
13410
13681
|
stdin.removeListener("data", onData);
|
|
13411
13682
|
process.stderr.write("\n");
|
|
13412
|
-
|
|
13683
|
+
resolve6(input);
|
|
13413
13684
|
} else if (ch === "") {
|
|
13414
13685
|
process.exit(1);
|
|
13415
13686
|
} else if (ch === "\x7F" || ch === "\b") {
|
|
@@ -13466,13 +13737,13 @@ async function pullEnv(opts) {
|
|
|
13466
13737
|
|
|
13467
13738
|
// ../server/src/index.ts
|
|
13468
13739
|
dotenv.config({ quiet: true });
|
|
13469
|
-
if (process.env["
|
|
13470
|
-
const resolved = resolvePath2(process.env["
|
|
13740
|
+
if (process.env["RUNEYA_DATA_DIR"]) {
|
|
13741
|
+
const resolved = resolvePath2(process.env["RUNEYA_DATA_DIR"], "..");
|
|
13471
13742
|
process.chdir(resolved);
|
|
13472
|
-
console.log(`[server] Working directory: ${resolved} (from
|
|
13743
|
+
console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
|
|
13473
13744
|
}
|
|
13474
13745
|
function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
|
|
13475
|
-
return new Promise((
|
|
13746
|
+
return new Promise((resolve6, reject) => {
|
|
13476
13747
|
let attempts = 0;
|
|
13477
13748
|
const wssErrorHandler = () => {
|
|
13478
13749
|
};
|
|
@@ -13493,7 +13764,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
|
|
|
13493
13764
|
server.listen(port, host, () => {
|
|
13494
13765
|
server.removeListener("error", onError);
|
|
13495
13766
|
wss?.removeListener("error", wssErrorHandler);
|
|
13496
|
-
|
|
13767
|
+
resolve6();
|
|
13497
13768
|
});
|
|
13498
13769
|
};
|
|
13499
13770
|
tryListen();
|
|
@@ -13540,4 +13811,4 @@ export {
|
|
|
13540
13811
|
createLocalServer,
|
|
13541
13812
|
pullEnv
|
|
13542
13813
|
};
|
|
13543
|
-
//# sourceMappingURL=src-
|
|
13814
|
+
//# sourceMappingURL=src-3TOPUVAK.js.map
|