@runeya/runeya 2.0.21 → 2.0.24
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-EGBDYHL5.js} +1540 -1119
- package/src-EGBDYHL5.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,608 @@ 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
|
+
async function executeKanbanCommands(boardId, cardId, commands, conversationId) {
|
|
5675
|
+
if (commands.length === 0) return;
|
|
5676
|
+
let board = await kanbanStore.get(boardId);
|
|
5677
|
+
if (!board) {
|
|
5678
|
+
console.warn(`[kanban-commands] Board ${boardId} not found, skipping ${commands.length} command(s)`);
|
|
5679
|
+
return;
|
|
5680
|
+
}
|
|
5681
|
+
const cardExistsInBoard = () => board.columns.some((col) => col.cards.some((c) => c.id === cardId));
|
|
5682
|
+
if (!cardExistsInBoard()) {
|
|
5683
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
5684
|
+
console.warn(`[kanban-commands] Card ${cardId} not found in board, retry ${attempt}/5...`);
|
|
5685
|
+
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
5686
|
+
board = await kanbanStore.get(boardId);
|
|
5687
|
+
if (!board) return;
|
|
5688
|
+
if (cardExistsInBoard()) break;
|
|
5689
|
+
}
|
|
5690
|
+
if (!cardExistsInBoard()) {
|
|
5691
|
+
console.error(`[kanban-commands] Card ${cardId} still not found after retries, skipping ${commands.length} command(s)`);
|
|
5692
|
+
return;
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
for (const cmd of commands) {
|
|
5696
|
+
if (cmd.type === "move_card" && cmd.columnTitle) {
|
|
5697
|
+
const moved = moveCard(board, cardId, cmd.columnTitle);
|
|
5698
|
+
if (!moved) console.warn(`[kanban-commands] move_card failed: card=${cardId} target="${cmd.columnTitle}"`);
|
|
5699
|
+
} else if (cmd.type === "add_comment" && cmd.comment) {
|
|
5700
|
+
addComment(board, cardId, cmd.comment, conversationId);
|
|
5701
|
+
} else if (cmd.type === "add_card" && cmd.columnTitle && cmd.cardTitle) {
|
|
5702
|
+
addCardToColumn(board, cmd.columnTitle, cmd.cardTitle, cmd.cardContent);
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
await kanbanStore.update({ id: board.id, columns: board.columns });
|
|
5706
|
+
}
|
|
5707
|
+
async function executeKanbanBoardCommands(boardId, commands) {
|
|
5708
|
+
if (commands.length === 0) return;
|
|
5709
|
+
const board = await kanbanStore.get(boardId);
|
|
5710
|
+
if (!board) return;
|
|
5711
|
+
for (const cmd of commands) {
|
|
5712
|
+
if (cmd.type === "add_card" && cmd.columnTitle && cmd.cardTitle) {
|
|
5713
|
+
addCardToColumn(board, cmd.columnTitle, cmd.cardTitle, cmd.cardContent);
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
await kanbanStore.update({ id: board.id, columns: board.columns });
|
|
5717
|
+
}
|
|
5718
|
+
function moveCard(board, cardId, targetColTitle) {
|
|
5719
|
+
let sourceCol = "";
|
|
5720
|
+
let card = null;
|
|
5721
|
+
let sourceColIdx = -1;
|
|
5722
|
+
let cardIdx = -1;
|
|
5723
|
+
for (let ci = 0; ci < board.columns.length; ci++) {
|
|
5724
|
+
const col = board.columns[ci];
|
|
5725
|
+
const idx = col.cards.findIndex((c) => c.id === cardId);
|
|
5726
|
+
if (idx !== -1) {
|
|
5727
|
+
sourceCol = col.title;
|
|
5728
|
+
card = col.cards[idx];
|
|
5729
|
+
sourceColIdx = ci;
|
|
5730
|
+
cardIdx = idx;
|
|
5731
|
+
break;
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
if (!card || sourceColIdx === -1) return false;
|
|
5735
|
+
const targetCol = board.columns.find(
|
|
5736
|
+
(c) => c.title.toLowerCase() === targetColTitle.toLowerCase()
|
|
5737
|
+
);
|
|
5738
|
+
if (!targetCol) {
|
|
5739
|
+
console.warn(`[kanban-commands] Target column "${targetColTitle}" not found. Available: ${board.columns.map((c) => c.title).join(", ")}`);
|
|
5740
|
+
return false;
|
|
5741
|
+
}
|
|
5742
|
+
if (targetCol.id === board.columns[sourceColIdx].id) return true;
|
|
5743
|
+
board.columns[sourceColIdx].cards.splice(cardIdx, 1);
|
|
5744
|
+
card.order = targetCol.cards.length;
|
|
5745
|
+
card.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5746
|
+
card.history = [...card.history ?? [], {
|
|
5747
|
+
type: "moved",
|
|
5748
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5749
|
+
detail: `"${sourceCol}" \u2192 "${targetCol.title}"`,
|
|
5750
|
+
author: "ai"
|
|
5751
|
+
}];
|
|
5752
|
+
targetCol.cards.push(card);
|
|
5753
|
+
return true;
|
|
5754
|
+
}
|
|
5755
|
+
function addCardToColumn(board, columnTitle, cardTitle, cardContent) {
|
|
5756
|
+
const col = board.columns.find((c) => c.title.toLowerCase() === columnTitle.toLowerCase());
|
|
5757
|
+
if (!col) return;
|
|
5758
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5759
|
+
const card = {
|
|
5760
|
+
id: randomUUID5(),
|
|
5761
|
+
title: cardTitle,
|
|
5762
|
+
markdown: cardContent ?? "",
|
|
5763
|
+
images: [],
|
|
5764
|
+
order: col.cards.length,
|
|
5765
|
+
history: [{ type: "created", timestamp: now, detail: `AI \u2014 In column "${col.title}"` }],
|
|
5766
|
+
createdAt: now,
|
|
5767
|
+
updatedAt: now
|
|
5768
|
+
};
|
|
5769
|
+
col.cards.push(card);
|
|
5770
|
+
}
|
|
5771
|
+
function addComment(board, cardId, comment, conversationId) {
|
|
5772
|
+
for (const col of board.columns) {
|
|
5773
|
+
const card = col.cards.find((c) => c.id === cardId);
|
|
5774
|
+
if (card) {
|
|
5775
|
+
card.history = [...card.history ?? [], {
|
|
5776
|
+
type: "comment",
|
|
5777
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5778
|
+
detail: comment,
|
|
5779
|
+
author: "ai",
|
|
5780
|
+
...conversationId ? { conversationId } : {}
|
|
5781
|
+
}];
|
|
5782
|
+
card.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
function generateKanbanSystemPrompt(board, cardId) {
|
|
5788
|
+
let currentColTitle = "";
|
|
5789
|
+
let card = null;
|
|
5790
|
+
for (const col of board.columns) {
|
|
5791
|
+
const found = col.cards.find((c) => c.id === cardId);
|
|
5792
|
+
if (found) {
|
|
5793
|
+
currentColTitle = col.title;
|
|
5794
|
+
card = found;
|
|
5795
|
+
break;
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
const sortedColumns = [...board.columns].sort((a, b) => a.order - b.order);
|
|
5799
|
+
const lines = [];
|
|
5800
|
+
lines.push("## Kanban Board Context");
|
|
5801
|
+
lines.push("");
|
|
5802
|
+
lines.push(`Tu travailles dans le contexte d'une carte Kanban.`);
|
|
5803
|
+
lines.push(`- **Board** : ${board.name}`);
|
|
5804
|
+
if (card) {
|
|
5805
|
+
lines.push(`- **Carte** : ${card.title}`);
|
|
5806
|
+
lines.push(`- **Colonne actuelle** : \`${currentColTitle}\``);
|
|
5807
|
+
}
|
|
5808
|
+
lines.push("");
|
|
5809
|
+
lines.push("### Colonnes disponibles (noms exacts \xE0 utiliser) :");
|
|
5810
|
+
for (const col of sortedColumns) {
|
|
5811
|
+
const cardCount = col.cards.length;
|
|
5812
|
+
const isCurrent = card && col.cards.some((c) => c.id === cardId);
|
|
5813
|
+
lines.push(`- \`${col.title}\` (${cardCount} carte${cardCount !== 1 ? "s" : ""})${isCurrent ? " \u2190 **colonne actuelle**" : ""}`);
|
|
5814
|
+
}
|
|
5815
|
+
lines.push("");
|
|
5816
|
+
if (card) {
|
|
5817
|
+
const comments = (card.history ?? []).filter((e) => e.type === "comment");
|
|
5818
|
+
if (comments.length > 0) {
|
|
5819
|
+
lines.push("### Commentaires existants sur cette carte :");
|
|
5820
|
+
lines.push("");
|
|
5821
|
+
for (const c of comments.slice(-20)) {
|
|
5822
|
+
const who = c.author === "ai" ? "AI" : "Utilisateur";
|
|
5823
|
+
const date = new Date(c.timestamp).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
|
|
5824
|
+
lines.push(`- **${who}** (${date}) : ${c.detail}`);
|
|
5825
|
+
}
|
|
5826
|
+
lines.push("");
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
lines.push("### Outils Kanban disponibles");
|
|
5830
|
+
lines.push("");
|
|
5831
|
+
lines.push(`- **boardId** (\xE0 passer dans tous les outils) : \`${board.id}\``);
|
|
5832
|
+
if (card) lines.push(`- **cardId** (carte actuelle) : \`${card.id}\``);
|
|
5833
|
+
lines.push("");
|
|
5834
|
+
lines.push("Tu disposes des outils suivants pour interagir avec le board :");
|
|
5835
|
+
lines.push("- `kanban_move_card(boardId, cardId, targetColumn)` \u2014 D\xE9placer la carte vers une autre colonne");
|
|
5836
|
+
lines.push("- `kanban_add_comment(boardId, cardId, comment)` \u2014 Ajouter un commentaire sur la carte");
|
|
5837
|
+
lines.push("");
|
|
5838
|
+
lines.push("### OBLIGATOIRE \u2014 D\xE9cision Kanban");
|
|
5839
|
+
lines.push("");
|
|
5840
|
+
lines.push("\xC0 chaque r\xE9ponse, tu DOIS obligatoirement faire les deux actions suivantes :");
|
|
5841
|
+
lines.push("");
|
|
5842
|
+
lines.push("**1. \xC9valuer et d\xE9placer la carte si n\xE9cessaire** via `kanban_move_card` :");
|
|
5843
|
+
lines.push(" - **Au d\xE9but** si tu sais d\xE9j\xE0 que tu vas travailler activement (ex. tu passes en `In progress`).");
|
|
5844
|
+
lines.push(" - **\xC0 la fin** pour une d\xE9cision forte apr\xE8s analyse compl\xE8te (ex. tu conclus que c'est `Done`).");
|
|
5845
|
+
lines.push(" - Si tu ne fais qu'analyser ou poser des questions \u2192 **ne pas d\xE9placer**.");
|
|
5846
|
+
lines.push("");
|
|
5847
|
+
lines.push(" > \u26A0\uFE0F **CRITIQUE** : En fin de r\xE9ponse, tu DOIS TOUJOURS \xE9valuer si la colonne actuelle est encore correcte.");
|
|
5848
|
+
lines.push("");
|
|
5849
|
+
lines.push("**2. Appeler `kanban_add_comment`** r\xE9sumant ce que tu as fait ou constat\xE9 (**\xE0 la fin** de ta r\xE9ponse).");
|
|
5850
|
+
lines.push("");
|
|
5851
|
+
lines.push("> \u{1F6AB} **INTERDIT** : Terminer une r\xE9ponse sans appeler `kanban_add_comment`. C'est non n\xE9gociable.");
|
|
5852
|
+
lines.push("");
|
|
5853
|
+
return lines.join("\n");
|
|
5854
|
+
}
|
|
5855
|
+
function generateKanbanPopulatePrompt(board) {
|
|
5856
|
+
const lines = [];
|
|
5857
|
+
lines.push("## Kanban Board \u2014 Mode Populate");
|
|
5858
|
+
lines.push("");
|
|
5859
|
+
lines.push(`Tu es en mode **populate** sur le tableau Kanban **"${board.name}"**.`);
|
|
5860
|
+
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.`);
|
|
5861
|
+
lines.push("");
|
|
5862
|
+
lines.push("Ton UNIQUE r\xF4le est de cr\xE9er des cartes Kanban pertinentes en r\xE9ponse \xE0 la demande de l'utilisateur.");
|
|
5863
|
+
lines.push("Tu ne dois PAS r\xE9pondre \xE0 la question, ni donner d'explication, ni \xE9crire du code, ni montrer de composant.");
|
|
5864
|
+
lines.push("Tu dois UNIQUEMENT produire des tags XML `<kanban_add_card>` pour cr\xE9er des cartes dans le tableau.");
|
|
5865
|
+
if (board.description) {
|
|
5866
|
+
lines.push("");
|
|
5867
|
+
lines.push(`Description du board : ${board.description}`);
|
|
5868
|
+
}
|
|
5869
|
+
lines.push("");
|
|
5870
|
+
const sortedCols = [...board.columns].sort((a, b) => a.order - b.order);
|
|
5871
|
+
lines.push("### Colonnes existantes (noms exacts \xE0 utiliser) :");
|
|
5872
|
+
for (const col of sortedCols) {
|
|
5873
|
+
const cardCount = col.cards.length;
|
|
5874
|
+
lines.push(`- \`${col.title}\` (${cardCount} carte${cardCount !== 1 ? "s" : ""})`);
|
|
5875
|
+
for (const card of col.cards) {
|
|
5876
|
+
lines.push(` - ${card.title}`);
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
lines.push("");
|
|
5880
|
+
lines.push("### Outil disponible");
|
|
5881
|
+
lines.push("");
|
|
5882
|
+
lines.push(`- **boardId** (\xE0 passer dans tous les outils) : \`${board.id}\``);
|
|
5883
|
+
lines.push("");
|
|
5884
|
+
lines.push("Pour chaque carte \xE0 cr\xE9er, appelle l'outil `kanban_add_card(boardId, column, title, content)` :");
|
|
5885
|
+
lines.push("- `column` : nom exact de la colonne cible");
|
|
5886
|
+
lines.push("- `title` : titre concis de la carte");
|
|
5887
|
+
lines.push("- `content` : contenu markdown d\xE9taill\xE9 et actionnable");
|
|
5888
|
+
lines.push("");
|
|
5889
|
+
lines.push("### R\xE8gles STRICTES");
|
|
5890
|
+
lines.push("");
|
|
5891
|
+
lines.push("1. **NE R\xC9PONDS PAS** \xE0 la question de l'utilisateur. Ne donne aucune explication, aucun texte libre.");
|
|
5892
|
+
lines.push("2. **Appelle uniquement** `kanban_add_card`, rien d'autre.");
|
|
5893
|
+
lines.push("3. Tu ne peux PAS cr\xE9er de nouvelles colonnes. Dispatch les cartes dans les colonnes existantes list\xE9es ci-dessus.");
|
|
5894
|
+
lines.push("4. Utilise les noms **exacts** des colonnes.");
|
|
5895
|
+
lines.push("5. Chaque carte doit avoir un titre concis et un contenu markdown d\xE9taill\xE9 et actionnable.");
|
|
5896
|
+
lines.push("6. D\xE9compose la demande de l'utilisateur en t\xE2ches concr\xE8tes, chacune \xE9tant une carte.");
|
|
5897
|
+
lines.push("7. Dispatch chaque carte dans la colonne la plus appropri\xE9e selon sa nature.");
|
|
5898
|
+
lines.push("");
|
|
5899
|
+
return lines.join("\n");
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
// ../server/src/services/ai-runners/claude-code-runner.ts
|
|
5903
|
+
import { spawn as spawn2 } from "child_process";
|
|
5904
|
+
|
|
5905
|
+
// ../server/src/services/ai-runners/scenario-tokens.ts
|
|
5906
|
+
function parseEndToken(output) {
|
|
5907
|
+
const tail = output.trimEnd().slice(-2e3);
|
|
5908
|
+
const branchMatch = tail.match(/<pass\s+branch="(\d+)"\s*\/>/);
|
|
5909
|
+
if (branchMatch) {
|
|
5910
|
+
return { status: "pass", message: "", branchChoice: Number.parseInt(branchMatch[1], 10) };
|
|
5911
|
+
}
|
|
5912
|
+
if (/<pass\s*\/>/.test(tail)) {
|
|
5913
|
+
return { status: "pass", message: "" };
|
|
5914
|
+
}
|
|
5915
|
+
const errorMatch = tail.match(/<error>([\s\S]*?)<\/error>/);
|
|
5916
|
+
if (errorMatch) {
|
|
5917
|
+
return { status: "error", message: errorMatch[1].trim() };
|
|
5918
|
+
}
|
|
5919
|
+
const askMatch = tail.match(/<ask_user>([\s\S]*?)<\/ask_user>/);
|
|
5920
|
+
if (askMatch) {
|
|
5921
|
+
return { status: "ask_user", message: askMatch[1].trim() };
|
|
5922
|
+
}
|
|
5923
|
+
return { status: "none", message: "" };
|
|
5924
|
+
}
|
|
5925
|
+
var TOKEN_INSTRUCTIONS = `
|
|
5926
|
+
## Instructions de signalisation (OBLIGATOIRE)
|
|
5927
|
+
|
|
5928
|
+
\xC0 la fin de CHAQUE r\xE9ponse, tu DOIS terminer par exactement l'un de ces tags XML :
|
|
5929
|
+
|
|
5930
|
+
- Si tu as termin\xE9 avec succ\xE8s (chemin par d\xE9faut) : \`<pass />\`
|
|
5931
|
+
- 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)
|
|
5932
|
+
- Si tu as rencontr\xE9 une erreur bloquante : \`<error>description courte</error>\`
|
|
5933
|
+
- 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 ?")
|
|
5934
|
+
|
|
5935
|
+
Ne jamais omettre ce tag. Il doit \xEAtre la toute derni\xE8re ligne de ta r\xE9ponse.
|
|
5936
|
+
`.trim();
|
|
5937
|
+
|
|
5938
|
+
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5939
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5940
|
+
|
|
5941
|
+
// ../server/src/services/ai-runners/claude-pty-tail.ts
|
|
5942
|
+
import { open } from "fs/promises";
|
|
5943
|
+
import { watch as watch2 } from "fs";
|
|
5944
|
+
async function waitForFile(sessionId, signal, timeoutMs) {
|
|
5945
|
+
const start = Date.now();
|
|
5946
|
+
while (Date.now() - start < timeoutMs) {
|
|
5947
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
5948
|
+
const path = await findClaudeSessionFile(sessionId);
|
|
5949
|
+
if (path) return path;
|
|
5950
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
5951
|
+
}
|
|
5952
|
+
throw new Error(`Transcript file for session ${sessionId} never appeared (timeout ${timeoutMs}ms)`);
|
|
5953
|
+
}
|
|
5954
|
+
function tailTranscript(opts) {
|
|
5955
|
+
const { sessionId, signal, onEntry, fileAppearTimeoutMs = 3e4 } = opts;
|
|
5956
|
+
let stopped = false;
|
|
5957
|
+
let watcher = null;
|
|
5958
|
+
let fh = null;
|
|
5959
|
+
let offset = 0;
|
|
5960
|
+
let pendingBuf = "";
|
|
5961
|
+
let reading = false;
|
|
5962
|
+
let queuedRead = false;
|
|
5963
|
+
const cleanup = async () => {
|
|
5964
|
+
stopped = true;
|
|
5965
|
+
if (watcher) {
|
|
5966
|
+
try {
|
|
5967
|
+
watcher.close();
|
|
5968
|
+
} catch {
|
|
5969
|
+
}
|
|
5970
|
+
watcher = null;
|
|
5971
|
+
}
|
|
5972
|
+
if (fh) {
|
|
5973
|
+
try {
|
|
5974
|
+
await fh.close();
|
|
5975
|
+
} catch {
|
|
5976
|
+
}
|
|
5977
|
+
fh = null;
|
|
5978
|
+
}
|
|
5979
|
+
};
|
|
5980
|
+
const drainBuffer = () => {
|
|
5981
|
+
let idx;
|
|
5982
|
+
while ((idx = pendingBuf.indexOf("\n")) >= 0) {
|
|
5983
|
+
const line = pendingBuf.slice(0, idx).trim();
|
|
5984
|
+
pendingBuf = pendingBuf.slice(idx + 1);
|
|
5985
|
+
if (!line) continue;
|
|
5986
|
+
try {
|
|
5987
|
+
const entry = JSON.parse(line);
|
|
5988
|
+
onEntry(entry);
|
|
5989
|
+
} catch {
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
};
|
|
5993
|
+
const readMore = async () => {
|
|
5994
|
+
if (!fh || stopped) return;
|
|
5995
|
+
if (reading) {
|
|
5996
|
+
queuedRead = true;
|
|
5997
|
+
return;
|
|
5998
|
+
}
|
|
5999
|
+
reading = true;
|
|
6000
|
+
try {
|
|
6001
|
+
while (!stopped) {
|
|
6002
|
+
const st = await fh.stat();
|
|
6003
|
+
if (st.size <= offset) break;
|
|
6004
|
+
const len = st.size - offset;
|
|
6005
|
+
const buf = Buffer.alloc(len);
|
|
6006
|
+
const { bytesRead } = await fh.read(buf, 0, len, offset);
|
|
5308
6007
|
if (bytesRead <= 0) break;
|
|
5309
6008
|
offset += bytesRead;
|
|
5310
6009
|
pendingBuf += buf.slice(0, bytesRead).toString("utf-8");
|
|
@@ -5337,12 +6036,12 @@ function tailTranscript(opts) {
|
|
|
5337
6036
|
const poll = setInterval(() => {
|
|
5338
6037
|
void readMore();
|
|
5339
6038
|
}, 500);
|
|
5340
|
-
await new Promise((
|
|
6039
|
+
await new Promise((resolve6) => {
|
|
5341
6040
|
const tick = setInterval(() => {
|
|
5342
6041
|
if (stopped) {
|
|
5343
6042
|
clearInterval(tick);
|
|
5344
6043
|
clearInterval(poll);
|
|
5345
|
-
|
|
6044
|
+
resolve6();
|
|
5346
6045
|
}
|
|
5347
6046
|
}, 100);
|
|
5348
6047
|
});
|
|
@@ -5361,7 +6060,7 @@ function tailTranscript(opts) {
|
|
|
5361
6060
|
|
|
5362
6061
|
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
5363
6062
|
async function getAgentManager() {
|
|
5364
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
6063
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-2P4THUVA.js");
|
|
5365
6064
|
return agentManager2;
|
|
5366
6065
|
}
|
|
5367
6066
|
function buildPtyArgs(params, sessionId, opts) {
|
|
@@ -5431,7 +6130,7 @@ function dispatchEntry(entry, state, cbs) {
|
|
|
5431
6130
|
async function runClaudeViaPty(params) {
|
|
5432
6131
|
const { agentId, signal, skipTokenInstructions } = params;
|
|
5433
6132
|
if (!agentId) throw new Error("runClaudeViaPty requires an agentId");
|
|
5434
|
-
const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId :
|
|
6133
|
+
const sessionId = params.sessionId && params.sessionId.length > 0 ? params.sessionId : randomUUID6();
|
|
5435
6134
|
const args = buildPtyArgs(params, sessionId, { excludeMcp: true });
|
|
5436
6135
|
const agentMgr = await getAgentManager();
|
|
5437
6136
|
const displayArgs = args.map((a) => {
|
|
@@ -5440,7 +6139,7 @@ async function runClaudeViaPty(params) {
|
|
|
5440
6139
|
return a;
|
|
5441
6140
|
});
|
|
5442
6141
|
params.onCommand?.(`claude ${displayArgs.join(" ")}`);
|
|
5443
|
-
return new Promise((
|
|
6142
|
+
return new Promise((resolve6, reject) => {
|
|
5444
6143
|
let settled = false;
|
|
5445
6144
|
let spawnId = null;
|
|
5446
6145
|
const state = { assistantText: "", tools: /* @__PURE__ */ new Map() };
|
|
@@ -5463,7 +6162,7 @@ async function runClaudeViaPty(params) {
|
|
|
5463
6162
|
settled = true;
|
|
5464
6163
|
cleanup();
|
|
5465
6164
|
void killSpawn();
|
|
5466
|
-
|
|
6165
|
+
resolve6(result);
|
|
5467
6166
|
};
|
|
5468
6167
|
const fail = (err) => {
|
|
5469
6168
|
if (settled) return;
|
|
@@ -5536,10 +6235,6 @@ async function runClaudeViaPty(params) {
|
|
|
5536
6235
|
}
|
|
5537
6236
|
|
|
5538
6237
|
// ../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
6238
|
function createClaudeStreamState() {
|
|
5544
6239
|
return {
|
|
5545
6240
|
fullContent: "",
|
|
@@ -5762,116 +6457,16 @@ function processClaudeStdoutLines(lines, streamState, params, ctx) {
|
|
|
5762
6457
|
}
|
|
5763
6458
|
var ClaudeCodeRunner = class {
|
|
5764
6459
|
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
6460
|
if (params.agentId) {
|
|
5770
|
-
return
|
|
6461
|
+
return runClaudeViaPty(params);
|
|
5771
6462
|
}
|
|
5772
6463
|
return this.runLocal(params);
|
|
5773
6464
|
}
|
|
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
6465
|
// ── Run locally (original implementation) ───────────────────────────────
|
|
5871
6466
|
runLocal(params) {
|
|
5872
6467
|
const { signal, skipTokenInstructions } = params;
|
|
5873
6468
|
const { args, env: env2, stdinData, sessionArg, hasImages: _hasImages } = buildClaudeSpawnConfig(params);
|
|
5874
|
-
return new Promise((
|
|
6469
|
+
return new Promise((resolve6, reject) => {
|
|
5875
6470
|
const totalArgSize = args.reduce((s, a) => s + a.length + 1, 0);
|
|
5876
6471
|
console.log(`[claude-code-runner] arg size: ${totalArgSize} bytes (${(totalArgSize / 1024 / 1024).toFixed(2)} MB), session: ${sessionArg.join(" ")}, images: ${params.images?.length ?? 0}`);
|
|
5877
6472
|
const displayArgs = args.map((a) => {
|
|
@@ -5913,7 +6508,7 @@ var ClaudeCodeRunner = class {
|
|
|
5913
6508
|
});
|
|
5914
6509
|
proc.on("close", (code) => {
|
|
5915
6510
|
if (ctx.earlyResult) {
|
|
5916
|
-
|
|
6511
|
+
resolve6({ ...ctx.earlyResult, actualSessionId: streamState.actualSessionId });
|
|
5917
6512
|
return;
|
|
5918
6513
|
}
|
|
5919
6514
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -5930,7 +6525,7 @@ var ClaudeCodeRunner = class {
|
|
|
5930
6525
|
return;
|
|
5931
6526
|
}
|
|
5932
6527
|
const { status, message, branchChoice } = skipTokenInstructions ? { status: "none", message: "", branchChoice: void 0 } : parseEndToken(streamState.fullContent);
|
|
5933
|
-
|
|
6528
|
+
resolve6({ output: streamState.fullContent, tokenStatus: status, tokenMessage: message, branchChoice, actualSessionId: streamState.actualSessionId });
|
|
5934
6529
|
});
|
|
5935
6530
|
proc.on("error", (err) => {
|
|
5936
6531
|
if (ctx.settled) return;
|
|
@@ -5986,12 +6581,12 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
5986
6581
|
if (forceId !== void 0 && forceId >= this.nextId) {
|
|
5987
6582
|
this.nextId = forceId + 1;
|
|
5988
6583
|
}
|
|
5989
|
-
return new Promise((
|
|
6584
|
+
return new Promise((resolve6, reject) => {
|
|
5990
6585
|
const timer = setTimeout(() => {
|
|
5991
6586
|
this.pending.delete(id);
|
|
5992
6587
|
reject(new Error(`Request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`));
|
|
5993
6588
|
}, this.requestTimeoutMs);
|
|
5994
|
-
this.pending.set(id, { resolve:
|
|
6589
|
+
this.pending.set(id, { resolve: resolve6, reject, timer });
|
|
5995
6590
|
this.writeLine({ jsonrpc: "2.0", method, id, params: params ?? {} });
|
|
5996
6591
|
});
|
|
5997
6592
|
}
|
|
@@ -6023,8 +6618,8 @@ var CodexAppServerClient = class extends EventEmitter3 {
|
|
|
6023
6618
|
this.rl = null;
|
|
6024
6619
|
const proc = this.proc;
|
|
6025
6620
|
this.proc = null;
|
|
6026
|
-
return new Promise((
|
|
6027
|
-
proc.on("exit", () =>
|
|
6621
|
+
return new Promise((resolve6) => {
|
|
6622
|
+
proc.on("exit", () => resolve6());
|
|
6028
6623
|
proc.kill("SIGTERM");
|
|
6029
6624
|
const forceTimer = setTimeout(() => {
|
|
6030
6625
|
proc.kill("SIGKILL");
|
|
@@ -6117,8 +6712,8 @@ async function shutdownCodexAppServer() {
|
|
|
6117
6712
|
await client.shutdown();
|
|
6118
6713
|
}
|
|
6119
6714
|
}
|
|
6120
|
-
async function
|
|
6121
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
6715
|
+
async function getAgentManager2() {
|
|
6716
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-2P4THUVA.js");
|
|
6122
6717
|
return agentManager2;
|
|
6123
6718
|
}
|
|
6124
6719
|
function parseRuneyaCurlCommand(command) {
|
|
@@ -6187,7 +6782,7 @@ var CodexRunner = class {
|
|
|
6187
6782
|
// ── Run via agent (app-server JSON-RPC via agent tRPC routes) ───────────
|
|
6188
6783
|
async runViaAgent(params) {
|
|
6189
6784
|
const { agentId, prompt, systemContext, signal, model, effort, isResume, images, skipTokenInstructions, apiBaseUrl, apiToken, injectedEnv } = params;
|
|
6190
|
-
const agentMgr = await
|
|
6785
|
+
const agentMgr = await getAgentManager2();
|
|
6191
6786
|
if (isResume && params.sessionId && !this.lastThreadId) {
|
|
6192
6787
|
this.lastThreadId = params.sessionId;
|
|
6193
6788
|
}
|
|
@@ -6233,7 +6828,7 @@ ${prompt}`;
|
|
|
6233
6828
|
}
|
|
6234
6829
|
input.push({ type: "text", text: fullPrompt });
|
|
6235
6830
|
const turnRef = { id: null };
|
|
6236
|
-
return new Promise((
|
|
6831
|
+
return new Promise((resolve6, reject) => {
|
|
6237
6832
|
let settled = false;
|
|
6238
6833
|
let fullContent = "";
|
|
6239
6834
|
let thinkingEmitted = false;
|
|
@@ -6282,7 +6877,7 @@ ${prompt}`;
|
|
|
6282
6877
|
params.onDelta(delta);
|
|
6283
6878
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6284
6879
|
if (status !== "none") {
|
|
6285
|
-
settle(() =>
|
|
6880
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6286
6881
|
}
|
|
6287
6882
|
}
|
|
6288
6883
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -6356,7 +6951,7 @@ ${prompt}`;
|
|
|
6356
6951
|
return;
|
|
6357
6952
|
}
|
|
6358
6953
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6359
|
-
settle(() =>
|
|
6954
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6360
6955
|
} else if (method === "turn/errored") {
|
|
6361
6956
|
const error = p["error"];
|
|
6362
6957
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -6431,7 +7026,7 @@ ${prompt}`;
|
|
|
6431
7026
|
sandboxPolicy: { type: "dangerFullAccess" }
|
|
6432
7027
|
});
|
|
6433
7028
|
const turnId = turnRes.turn.id;
|
|
6434
|
-
return new Promise((
|
|
7029
|
+
return new Promise((resolve6, reject) => {
|
|
6435
7030
|
let settled = false;
|
|
6436
7031
|
let fullContent = "";
|
|
6437
7032
|
let thinkingEmitted = false;
|
|
@@ -6457,7 +7052,7 @@ ${prompt}`;
|
|
|
6457
7052
|
params.onDelta(delta);
|
|
6458
7053
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6459
7054
|
if (status !== "none") {
|
|
6460
|
-
settle(() =>
|
|
7055
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6461
7056
|
}
|
|
6462
7057
|
}
|
|
6463
7058
|
} else if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
@@ -6531,7 +7126,7 @@ ${prompt}`;
|
|
|
6531
7126
|
return;
|
|
6532
7127
|
}
|
|
6533
7128
|
const { status, message, branchChoice } = parseEndToken(fullContent);
|
|
6534
|
-
settle(() =>
|
|
7129
|
+
settle(() => resolve6({ output: fullContent, tokenStatus: status, tokenMessage: message, branchChoice }));
|
|
6535
7130
|
} else if (method === "turn/errored") {
|
|
6536
7131
|
const error = p["error"];
|
|
6537
7132
|
const msg = typeof error?.["message"] === "string" ? error["message"] : "Codex turn errored";
|
|
@@ -6552,18 +7147,18 @@ ${prompt}`;
|
|
|
6552
7147
|
|
|
6553
7148
|
// ../server/src/trpc/routers/chat.ts
|
|
6554
7149
|
var execFileAsync2 = promisify2(execFile3);
|
|
6555
|
-
var MessageSchema =
|
|
6556
|
-
role:
|
|
6557
|
-
content:
|
|
7150
|
+
var MessageSchema = z15.object({
|
|
7151
|
+
role: z15.enum(["user", "assistant"]),
|
|
7152
|
+
content: z15.string().max(1e5)
|
|
6558
7153
|
});
|
|
6559
|
-
var ImagePartSchema =
|
|
6560
|
-
type:
|
|
6561
|
-
imageId:
|
|
6562
|
-
url:
|
|
6563
|
-
mimeType:
|
|
6564
|
-
altText:
|
|
7154
|
+
var ImagePartSchema = z15.object({
|
|
7155
|
+
type: z15.literal("image"),
|
|
7156
|
+
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),
|
|
7157
|
+
url: z15.string().max(500),
|
|
7158
|
+
mimeType: z15.string().max(100),
|
|
7159
|
+
altText: z15.string().max(500).optional()
|
|
6565
7160
|
});
|
|
6566
|
-
var ImagePartsSchema =
|
|
7161
|
+
var ImagePartsSchema = z15.array(ImagePartSchema).max(5).optional();
|
|
6567
7162
|
var LANGUAGE_INSTRUCTION = "Always respond in the same language the user writes in.";
|
|
6568
7163
|
async function buildSystemPrompt(apiKey) {
|
|
6569
7164
|
const allServices = await serviceStore.list();
|
|
@@ -6720,7 +7315,7 @@ async function* runOpenAI(client, model, initialMessages, apiKey, authToken, sig
|
|
|
6720
7315
|
yield { type: "done", text: "" };
|
|
6721
7316
|
}
|
|
6722
7317
|
async function* drainSession(session, signal, isReconnect) {
|
|
6723
|
-
if (process.env.
|
|
7318
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: drainSession");
|
|
6724
7319
|
if (isReconnect) {
|
|
6725
7320
|
for (const part of session.parts) {
|
|
6726
7321
|
if (part.type === "thinking") {
|
|
@@ -6873,7 +7468,7 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
|
|
|
6873
7468
|
if (result) capturedFinalResult = result;
|
|
6874
7469
|
}
|
|
6875
7470
|
}).then(() => {
|
|
6876
|
-
if (process.env.
|
|
7471
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: claude end...");
|
|
6877
7472
|
if (activeSessionRegistry.get(convId) !== session) return;
|
|
6878
7473
|
if (capturedFinalResult && capturedFinalResult.length > session.fullContent.length) {
|
|
6879
7474
|
const missing = capturedFinalResult.slice(session.fullContent.length);
|
|
@@ -6882,7 +7477,7 @@ async function startClaudeCodeSession(convId, prompt, opts = {}) {
|
|
|
6882
7477
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
6883
7478
|
activeSessionRegistry.markDone(convId);
|
|
6884
7479
|
}).catch((err) => {
|
|
6885
|
-
if (process.env.
|
|
7480
|
+
if (process.env.RUNEYA_DEBUG) console.error("[chat]: error...", err?.message, err);
|
|
6886
7481
|
if (activeSessionRegistry.get(convId) !== session) return;
|
|
6887
7482
|
activeSessionRegistry.pushEvent(convId, { type: "error", message: `Claude Code error: ${err.message}` });
|
|
6888
7483
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
@@ -6944,24 +7539,24 @@ async function startCodexSession(convId, prompt, opts = {}) {
|
|
|
6944
7539
|
});
|
|
6945
7540
|
return { session, done };
|
|
6946
7541
|
}
|
|
6947
|
-
var MessagePartSchema =
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
type:
|
|
7542
|
+
var MessagePartSchema = z15.discriminatedUnion("type", [
|
|
7543
|
+
z15.object({ type: z15.literal("thinking"), text: z15.string().max(1e5) }),
|
|
7544
|
+
z15.object({ type: z15.literal("text"), text: z15.string().max(1e5) }),
|
|
7545
|
+
z15.object({
|
|
7546
|
+
type: z15.literal("tool"),
|
|
6952
7547
|
id: Id128Schema,
|
|
6953
7548
|
name: Id128Schema,
|
|
6954
|
-
input:
|
|
6955
|
-
rawInput:
|
|
6956
|
-
result:
|
|
6957
|
-
isError:
|
|
6958
|
-
pending:
|
|
7549
|
+
input: z15.record(z15.string(), z15.unknown()).optional(),
|
|
7550
|
+
rawInput: z15.string().max(1e4).optional(),
|
|
7551
|
+
result: z15.unknown().optional(),
|
|
7552
|
+
isError: z15.boolean().optional(),
|
|
7553
|
+
pending: z15.boolean()
|
|
6959
7554
|
})
|
|
6960
7555
|
]);
|
|
6961
|
-
var StoredMessageSchema =
|
|
6962
|
-
role:
|
|
6963
|
-
content:
|
|
6964
|
-
parts:
|
|
7556
|
+
var StoredMessageSchema = z15.object({
|
|
7557
|
+
role: z15.enum(["user", "assistant"]),
|
|
7558
|
+
content: z15.string().max(1e5),
|
|
7559
|
+
parts: z15.array(MessagePartSchema).max(200).optional()
|
|
6965
7560
|
});
|
|
6966
7561
|
var chatRouter = router({
|
|
6967
7562
|
conversations: router({
|
|
@@ -6969,13 +7564,13 @@ var chatRouter = router({
|
|
|
6969
7564
|
const initial = await conversationStore.list();
|
|
6970
7565
|
yield { type: "initial", conversations: initial };
|
|
6971
7566
|
const queue = [];
|
|
6972
|
-
let
|
|
7567
|
+
let resolve6 = null;
|
|
6973
7568
|
const handler = (event) => {
|
|
6974
7569
|
if (queue.length >= 500) queue.shift();
|
|
6975
7570
|
queue.push(event);
|
|
6976
|
-
if (
|
|
6977
|
-
|
|
6978
|
-
|
|
7571
|
+
if (resolve6) {
|
|
7572
|
+
resolve6();
|
|
7573
|
+
resolve6 = null;
|
|
6979
7574
|
}
|
|
6980
7575
|
};
|
|
6981
7576
|
conversationStore.onChanged(handler);
|
|
@@ -6985,7 +7580,7 @@ var chatRouter = router({
|
|
|
6985
7580
|
yield queue.shift();
|
|
6986
7581
|
} else {
|
|
6987
7582
|
await new Promise((r) => {
|
|
6988
|
-
|
|
7583
|
+
resolve6 = r;
|
|
6989
7584
|
});
|
|
6990
7585
|
}
|
|
6991
7586
|
}
|
|
@@ -6994,25 +7589,25 @@ var chatRouter = router({
|
|
|
6994
7589
|
}
|
|
6995
7590
|
}),
|
|
6996
7591
|
list: protectedProcedure.query(() => conversationStore.list()),
|
|
6997
|
-
get: protectedProcedure.input(
|
|
7592
|
+
get: protectedProcedure.input(z15.object({ id: Id128Schema })).query(async ({ input }) => {
|
|
6998
7593
|
const conv = await conversationStore.get(input.id);
|
|
6999
|
-
if (!conv) throw new
|
|
7594
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7000
7595
|
return conv;
|
|
7001
7596
|
}),
|
|
7002
|
-
create: protectedProcedure.input(
|
|
7597
|
+
create: protectedProcedure.input(z15.object({
|
|
7003
7598
|
title: ShortString200Schema.optional(),
|
|
7004
|
-
isCli:
|
|
7005
|
-
runner:
|
|
7599
|
+
isCli: z15.boolean().optional(),
|
|
7600
|
+
runner: z15.enum(["claude-code", "codex"]).optional(),
|
|
7006
7601
|
providerId: Id128Schema.optional(),
|
|
7007
7602
|
scenarioId: Id128Schema.optional(),
|
|
7008
|
-
scenarioInput:
|
|
7603
|
+
scenarioInput: z15.string().max(1e5).optional(),
|
|
7009
7604
|
kanbanBoardId: Id128Schema.optional(),
|
|
7010
7605
|
kanbanCardId: Id128Schema.optional()
|
|
7011
7606
|
})).mutation(async ({ input }) => {
|
|
7012
7607
|
let scenario = null;
|
|
7013
7608
|
if (input.scenarioId) {
|
|
7014
7609
|
scenario = await scenarioStore.get(input.scenarioId);
|
|
7015
|
-
if (!scenario) throw new
|
|
7610
|
+
if (!scenario) throw new TRPCError11({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
7016
7611
|
}
|
|
7017
7612
|
const conv = await conversationStore.create(
|
|
7018
7613
|
input.title,
|
|
@@ -7033,22 +7628,22 @@ var chatRouter = router({
|
|
|
7033
7628
|
}
|
|
7034
7629
|
return conv;
|
|
7035
7630
|
}),
|
|
7036
|
-
update: protectedProcedure.input(
|
|
7631
|
+
update: protectedProcedure.input(z15.object({
|
|
7037
7632
|
id: Id128Schema,
|
|
7038
7633
|
title: ShortString200Schema.optional(),
|
|
7039
|
-
messages:
|
|
7040
|
-
contextUsage:
|
|
7041
|
-
inputTokens:
|
|
7042
|
-
contextWindow:
|
|
7634
|
+
messages: z15.array(StoredMessageSchema).max(500).optional(),
|
|
7635
|
+
contextUsage: z15.object({
|
|
7636
|
+
inputTokens: z15.number().int().nonnegative(),
|
|
7637
|
+
contextWindow: z15.number().int().positive()
|
|
7043
7638
|
}).optional()
|
|
7044
7639
|
})).mutation(async ({ input }) => {
|
|
7045
7640
|
const conv = await conversationStore.update(input.id, { title: input.title, messages: input.messages, contextUsage: input.contextUsage });
|
|
7046
|
-
if (!conv) throw new
|
|
7641
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7047
7642
|
return conv;
|
|
7048
7643
|
}),
|
|
7049
|
-
delete: protectedProcedure.input(
|
|
7644
|
+
delete: protectedProcedure.input(z15.object({ id: Id128Schema })).mutation(async ({ input }) => {
|
|
7050
7645
|
const existing = await conversationStore.get(input.id);
|
|
7051
|
-
if (!existing) throw new
|
|
7646
|
+
if (!existing) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7052
7647
|
for (const agent of agentManager.listAgents()) {
|
|
7053
7648
|
const agentId = agent.config.id;
|
|
7054
7649
|
agentManager.agentQuery(
|
|
@@ -7068,13 +7663,13 @@ var chatRouter = router({
|
|
|
7068
7663
|
(err) => console.error("[chat.delete] Image cleanup error:", err)
|
|
7069
7664
|
);
|
|
7070
7665
|
}),
|
|
7071
|
-
archive: protectedProcedure.input(
|
|
7666
|
+
archive: protectedProcedure.input(z15.object({ id: Id128Schema, archived: z15.boolean() })).mutation(async ({ input }) => {
|
|
7072
7667
|
const conv = await conversationStore.update(input.id, { archived: input.archived });
|
|
7073
|
-
if (!conv) throw new
|
|
7668
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7074
7669
|
return conv;
|
|
7075
7670
|
})
|
|
7076
7671
|
}),
|
|
7077
|
-
listModels: protectedProcedure.input(
|
|
7672
|
+
listModels: protectedProcedure.input(z15.object({ providerId: Id128Schema.optional() }).optional()).query(async ({ input }) => {
|
|
7078
7673
|
const active = await getActiveProvider(input?.providerId);
|
|
7079
7674
|
if (!active) return [];
|
|
7080
7675
|
try {
|
|
@@ -7083,7 +7678,7 @@ var chatRouter = router({
|
|
|
7083
7678
|
return [];
|
|
7084
7679
|
}
|
|
7085
7680
|
}),
|
|
7086
|
-
checkCliAvailability: protectedProcedure.input(
|
|
7681
|
+
checkCliAvailability: protectedProcedure.input(z15.object({ agentId: Id128Schema.optional() }).optional()).query(async ({ input }) => {
|
|
7087
7682
|
if (input?.agentId) {
|
|
7088
7683
|
try {
|
|
7089
7684
|
const res = await agentManager.agentQuery(
|
|
@@ -7101,9 +7696,13 @@ var chatRouter = router({
|
|
|
7101
7696
|
]);
|
|
7102
7697
|
return { available: claudeOk, codexAvailable: codexOk };
|
|
7103
7698
|
}),
|
|
7104
|
-
enhanceScenarioPrompt: protectedProcedure.input(
|
|
7699
|
+
enhanceScenarioPrompt: protectedProcedure.input(z15.object({
|
|
7700
|
+
text: z15.string().max(1e4),
|
|
7701
|
+
providerId: Id128Schema.optional(),
|
|
7702
|
+
model: z15.string().max(200).optional()
|
|
7703
|
+
})).mutation(async ({ input }) => {
|
|
7105
7704
|
const active = await getActiveProvider(input.providerId);
|
|
7106
|
-
if (!active) throw new
|
|
7705
|
+
if (!active) throw new TRPCError11({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
7107
7706
|
const systemPrompt = `You are an expert at writing clear, concise user request briefs for AI-powered workflows.
|
|
7108
7707
|
When given a rough user request, rewrite it to be:
|
|
7109
7708
|
- Clear and well-structured
|
|
@@ -7112,7 +7711,7 @@ When given a rough user request, rewrite it to be:
|
|
|
7112
7711
|
- Concise but complete (no fluff)
|
|
7113
7712
|
Return ONLY the improved text, no explanation, no preamble.`;
|
|
7114
7713
|
const { provider, plaintextKey } = active;
|
|
7115
|
-
const model = provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7714
|
+
const model = input.model || provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7116
7715
|
if (provider.provider === "anthropic") {
|
|
7117
7716
|
const client = new Anthropic2({ apiKey: plaintextKey });
|
|
7118
7717
|
const res = await client.messages.create({
|
|
@@ -7154,39 +7753,39 @@ Return ONLY the improved text, no explanation, no preamble.`;
|
|
|
7154
7753
|
}
|
|
7155
7754
|
}),
|
|
7156
7755
|
getStreamingStatus: protectedProcedure.query(() => activeSessionRegistry.listActive()),
|
|
7157
|
-
stopSession: protectedProcedure.input(
|
|
7158
|
-
if (process.env.
|
|
7756
|
+
stopSession: protectedProcedure.input(z15.object({ conversationId: ConversationIdSchema })).mutation(({ input }) => {
|
|
7757
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: stopSession");
|
|
7159
7758
|
const killed = activeSessionRegistry.kill(input.conversationId);
|
|
7160
|
-
if (!killed) throw new
|
|
7759
|
+
if (!killed) throw new TRPCError11({ code: "NOT_FOUND", message: "No active session for this conversation" });
|
|
7161
7760
|
return { ok: true };
|
|
7162
7761
|
}),
|
|
7163
|
-
streamViaClaudeCode: protectedProcedure.input(
|
|
7762
|
+
streamViaClaudeCode: protectedProcedure.input(z15.object({
|
|
7164
7763
|
conversationId: ConversationIdSchema,
|
|
7165
|
-
prompt:
|
|
7166
|
-
contextNote:
|
|
7764
|
+
prompt: z15.string().max(1e5),
|
|
7765
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7167
7766
|
model: ShortString200Schema.optional(),
|
|
7168
|
-
effort:
|
|
7169
|
-
reconnect:
|
|
7767
|
+
effort: z15.enum(["low", "medium", "high"]).optional(),
|
|
7768
|
+
reconnect: z15.boolean().optional(),
|
|
7170
7769
|
agentId: Id128Schema.optional(),
|
|
7171
|
-
environmentId:
|
|
7172
|
-
imageIds:
|
|
7770
|
+
environmentId: z15.string().max(128).optional(),
|
|
7771
|
+
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
7772
|
imageParts: ImagePartsSchema
|
|
7174
7773
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7175
|
-
if (process.env.
|
|
7774
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: Reconnect");
|
|
7176
7775
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
7177
7776
|
if (input.reconnect && existingSession) {
|
|
7178
7777
|
yield* drainSession(existingSession, signal, true);
|
|
7179
7778
|
return;
|
|
7180
7779
|
}
|
|
7181
|
-
if (!input.prompt) throw new
|
|
7780
|
+
if (!input.prompt) throw new TRPCError11({ code: "BAD_REQUEST", message: "Prompt is required for new messages" });
|
|
7182
7781
|
const conv = await conversationStore.get(input.conversationId);
|
|
7183
|
-
if (!conv) throw new
|
|
7782
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7184
7783
|
console.log(`[kanban-debug] streamViaClaudeCode start \u2014 convId=${conv.id} kanbanBoardId=${conv.kanbanBoardId ?? "null"}`);
|
|
7185
7784
|
const isResume = conv.messages.length > 0;
|
|
7186
7785
|
const userParts = input.imageParts && input.imageParts.length > 0 ? [...input.prompt.trim() ? [{ type: "text", text: input.prompt }] : [], ...input.imageParts] : void 0;
|
|
7187
7786
|
conv.messages.push({ role: "user", content: input.prompt, parts: userParts, contextNote: input.contextNote });
|
|
7188
7787
|
conv.messages.push({ role: "assistant", content: "" });
|
|
7189
|
-
if (process.env.
|
|
7788
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: update");
|
|
7190
7789
|
conversationStore.update(conv.id, { messages: conv.messages }, { silent: true }).catch(() => {
|
|
7191
7790
|
});
|
|
7192
7791
|
const cliPrompt = input.contextNote ? `${input.contextNote}
|
|
@@ -7215,7 +7814,7 @@ ${input.prompt}` : input.prompt;
|
|
|
7215
7814
|
const imagePaths = [];
|
|
7216
7815
|
if (input.imageIds && input.imageIds.length > 0) {
|
|
7217
7816
|
for (const id of input.imageIds) {
|
|
7218
|
-
const result = await imageStorageService.getImagePath(id);
|
|
7817
|
+
const result = await imageStorageService.getImagePath(id, input.conversationId);
|
|
7219
7818
|
if (result) imagePaths.push(result.filePath);
|
|
7220
7819
|
}
|
|
7221
7820
|
}
|
|
@@ -7228,14 +7827,14 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7228
7827
|
try {
|
|
7229
7828
|
session = activeSessionRegistry.registerProviderApiSession(convId, ac);
|
|
7230
7829
|
} catch {
|
|
7231
|
-
throw new
|
|
7830
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7232
7831
|
}
|
|
7233
7832
|
let capturedFinalResult = "";
|
|
7234
7833
|
const resolvedAgentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7235
7834
|
if (resolvedAgentId) {
|
|
7236
7835
|
session.agentId = resolvedAgentId;
|
|
7237
7836
|
}
|
|
7238
|
-
if (process.env.
|
|
7837
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: claude run...");
|
|
7239
7838
|
const injectedEnv = await resolveInjectedEnv(input.environmentId);
|
|
7240
7839
|
new ClaudeCodeRunner().run({
|
|
7241
7840
|
agentId: resolvedAgentId ?? void 0,
|
|
@@ -7276,18 +7875,18 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7276
7875
|
activeSessionRegistry.pushEvent(convId, { type: "done", text: "" });
|
|
7277
7876
|
activeSessionRegistry.markDone(convId);
|
|
7278
7877
|
});
|
|
7279
|
-
if (process.env.
|
|
7878
|
+
if (process.env.RUNEYA_DEBUG) console.log("[chat]: drainSession...");
|
|
7280
7879
|
yield* drainSession(session, signal, false);
|
|
7281
7880
|
}),
|
|
7282
|
-
streamViaCodex: protectedProcedure.input(
|
|
7881
|
+
streamViaCodex: protectedProcedure.input(z15.object({
|
|
7283
7882
|
conversationId: ConversationIdSchema,
|
|
7284
|
-
prompt:
|
|
7285
|
-
contextNote:
|
|
7883
|
+
prompt: z15.string().max(1e5),
|
|
7884
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7286
7885
|
model: ShortString200Schema.optional(),
|
|
7287
|
-
reconnect:
|
|
7886
|
+
reconnect: z15.boolean().optional(),
|
|
7288
7887
|
agentId: Id128Schema.optional(),
|
|
7289
|
-
environmentId:
|
|
7290
|
-
imageIds:
|
|
7888
|
+
environmentId: z15.string().max(128).optional(),
|
|
7889
|
+
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
7890
|
imageParts: ImagePartsSchema
|
|
7292
7891
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7293
7892
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7295,9 +7894,9 @@ ${imagePaths.map((p) => `Image: ${p}`).join("\n")}` : cliPrompt;
|
|
|
7295
7894
|
yield* drainSession(existingSession, signal, true);
|
|
7296
7895
|
return;
|
|
7297
7896
|
}
|
|
7298
|
-
if (!input.prompt) throw new
|
|
7897
|
+
if (!input.prompt) throw new TRPCError11({ code: "BAD_REQUEST", message: "Prompt is required" });
|
|
7299
7898
|
const conv = await conversationStore.get(input.conversationId);
|
|
7300
|
-
if (!conv) throw new
|
|
7899
|
+
if (!conv) throw new TRPCError11({ code: "NOT_FOUND", message: "Conversation not found" });
|
|
7301
7900
|
const isResume = !!conv.codexThreadId;
|
|
7302
7901
|
const codexUserParts = input.imageParts && input.imageParts.length > 0 ? [...input.prompt.trim() ? [{ type: "text", text: input.prompt }] : [], ...input.imageParts] : void 0;
|
|
7303
7902
|
conv.messages.push({ role: "user", content: input.prompt, parts: codexUserParts, contextNote: input.contextNote });
|
|
@@ -7317,14 +7916,14 @@ ${input.prompt}` : input.prompt;
|
|
|
7317
7916
|
systemContext += "\n\n" + (conv.kanbanCardId ? generateKanbanSystemPrompt(kanbanBoard, conv.kanbanCardId) : generateKanbanPopulatePrompt(kanbanBoard));
|
|
7318
7917
|
}
|
|
7319
7918
|
}
|
|
7320
|
-
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" }))) : [];
|
|
7919
|
+
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" })), input.conversationId) : [];
|
|
7321
7920
|
const convId = input.conversationId;
|
|
7322
7921
|
const ac = new AbortController();
|
|
7323
7922
|
let session;
|
|
7324
7923
|
try {
|
|
7325
7924
|
session = activeSessionRegistry.registerProviderApiSession(convId, ac);
|
|
7326
7925
|
} catch {
|
|
7327
|
-
throw new
|
|
7926
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7328
7927
|
}
|
|
7329
7928
|
const resolvedAgentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7330
7929
|
if (resolvedAgentId) {
|
|
@@ -7364,13 +7963,13 @@ ${input.prompt}` : input.prompt;
|
|
|
7364
7963
|
});
|
|
7365
7964
|
yield* drainSession(session, signal, false);
|
|
7366
7965
|
}),
|
|
7367
|
-
send: protectedProcedure.input(
|
|
7966
|
+
send: protectedProcedure.input(z15.object({
|
|
7368
7967
|
conversationId: ConversationIdSchema,
|
|
7369
|
-
messages:
|
|
7968
|
+
messages: z15.array(MessageSchema).min(0).max(100),
|
|
7370
7969
|
model: ShortString200Schema.optional(),
|
|
7371
|
-
contextNote:
|
|
7372
|
-
reconnect:
|
|
7373
|
-
imageIds:
|
|
7970
|
+
contextNote: z15.string().max(2e5).optional(),
|
|
7971
|
+
reconnect: z15.boolean().optional(),
|
|
7972
|
+
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
7973
|
imageParts: ImagePartsSchema
|
|
7375
7974
|
})).subscription(async function* ({ input, ctx, signal }) {
|
|
7376
7975
|
const existingSession = activeSessionRegistry.get(input.conversationId);
|
|
@@ -7379,12 +7978,12 @@ ${input.prompt}` : input.prompt;
|
|
|
7379
7978
|
return;
|
|
7380
7979
|
}
|
|
7381
7980
|
if (input.messages.length === 0) {
|
|
7382
|
-
throw new
|
|
7981
|
+
throw new TRPCError11({ code: "BAD_REQUEST", message: "Messages are required for new messages" });
|
|
7383
7982
|
}
|
|
7384
7983
|
const conv = await conversationStore.get(input.conversationId);
|
|
7385
7984
|
const active = await getActiveProvider(conv?.providerId);
|
|
7386
7985
|
if (!active) {
|
|
7387
|
-
throw new
|
|
7986
|
+
throw new TRPCError11({
|
|
7388
7987
|
code: "PRECONDITION_FAILED",
|
|
7389
7988
|
message: "No AI provider configured for this conversation. Please select one when starting a new chat."
|
|
7390
7989
|
});
|
|
@@ -7410,12 +8009,12 @@ ${input.prompt}` : input.prompt;
|
|
|
7410
8009
|
system += "\n\n" + (conv.kanbanCardId ? generateKanbanSystemPrompt(kanbanBoard, conv.kanbanCardId) : generateKanbanPopulatePrompt(kanbanBoard));
|
|
7411
8010
|
}
|
|
7412
8011
|
}
|
|
7413
|
-
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" }))) : [];
|
|
8012
|
+
const resolvedImages = input.imageIds && input.imageIds.length > 0 ? await resolveImagesToBase64(input.imageIds.map((id) => ({ imageId: id, mimeType: "" })), input.conversationId) : [];
|
|
7414
8013
|
let session;
|
|
7415
8014
|
try {
|
|
7416
8015
|
session = activeSessionRegistry.registerDirectProviderApiSession(input.conversationId);
|
|
7417
8016
|
} catch {
|
|
7418
|
-
throw new
|
|
8017
|
+
throw new TRPCError11({ code: "CONFLICT", message: "A session is already running for this conversation. Stop it first." });
|
|
7419
8018
|
}
|
|
7420
8019
|
const apiSignal = session.abortController.signal;
|
|
7421
8020
|
const runStream = async () => {
|
|
@@ -7487,25 +8086,25 @@ ${input.prompt}` : input.prompt;
|
|
|
7487
8086
|
// ── PTY passthrough (interactive Claude Code session) ────────────────────
|
|
7488
8087
|
pty: router({
|
|
7489
8088
|
/** 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:
|
|
8089
|
+
start: protectedProcedure.input(z15.object({
|
|
8090
|
+
agentId: z15.string().max(128).optional(),
|
|
8091
|
+
sessionId: z15.string().max(128).optional(),
|
|
8092
|
+
runner: z15.enum(["claude-code", "codex"]).default("claude-code"),
|
|
8093
|
+
resume: z15.boolean().default(false),
|
|
8094
|
+
model: z15.string().max(128).optional(),
|
|
8095
|
+
cwd: z15.string().max(4096).optional(),
|
|
8096
|
+
cols: z15.number().int().min(1).max(1e3).default(120),
|
|
8097
|
+
rows: z15.number().int().min(1).max(1e3).default(30),
|
|
8098
|
+
enableMcp: z15.boolean().default(false),
|
|
8099
|
+
apiBaseUrl: z15.string().max(512).optional(),
|
|
8100
|
+
environmentId: z15.string().max(128).optional(),
|
|
8101
|
+
attachedContexts: z15.array(z15.object({
|
|
8102
|
+
label: z15.string().max(200),
|
|
8103
|
+
text: z15.string().max(1e6)
|
|
7505
8104
|
})).max(20).optional()
|
|
7506
8105
|
})).mutation(async ({ input, ctx }) => {
|
|
7507
8106
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7508
|
-
if (!agentId) throw new
|
|
8107
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7509
8108
|
if (input.sessionId) {
|
|
7510
8109
|
try {
|
|
7511
8110
|
const existing = await agentManager.agentQuery(
|
|
@@ -7641,15 +8240,15 @@ Context:
|
|
|
7641
8240
|
return { spawnId: res.spawnId, agentId, reattached: false };
|
|
7642
8241
|
}),
|
|
7643
8242
|
/** Subscribe to PTY output stream (raw text with ANSI). */
|
|
7644
|
-
stream: protectedProcedure.input(
|
|
7645
|
-
agentId:
|
|
7646
|
-
spawnId:
|
|
7647
|
-
fromOffset:
|
|
8243
|
+
stream: protectedProcedure.input(z15.object({
|
|
8244
|
+
agentId: z15.string().max(128).optional(),
|
|
8245
|
+
spawnId: z15.string().max(128),
|
|
8246
|
+
fromOffset: z15.number().int().min(0).default(0)
|
|
7648
8247
|
})).subscription(async function* ({ input, signal }) {
|
|
7649
8248
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7650
|
-
if (!agentId) throw new
|
|
8249
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7651
8250
|
const queue = [];
|
|
7652
|
-
let
|
|
8251
|
+
let resolve6 = null;
|
|
7653
8252
|
let stopped = false;
|
|
7654
8253
|
const unsub = agentManager.agentSubscribe(
|
|
7655
8254
|
agentId,
|
|
@@ -7657,16 +8256,16 @@ Context:
|
|
|
7657
8256
|
{ spawnId: input.spawnId, fromOffset: input.fromOffset },
|
|
7658
8257
|
(data) => {
|
|
7659
8258
|
queue.push(data);
|
|
7660
|
-
if (
|
|
7661
|
-
|
|
7662
|
-
|
|
8259
|
+
if (resolve6) {
|
|
8260
|
+
resolve6();
|
|
8261
|
+
resolve6 = null;
|
|
7663
8262
|
}
|
|
7664
8263
|
},
|
|
7665
8264
|
() => {
|
|
7666
8265
|
stopped = true;
|
|
7667
|
-
if (
|
|
7668
|
-
|
|
7669
|
-
|
|
8266
|
+
if (resolve6) {
|
|
8267
|
+
resolve6();
|
|
8268
|
+
resolve6 = null;
|
|
7670
8269
|
}
|
|
7671
8270
|
}
|
|
7672
8271
|
);
|
|
@@ -7682,7 +8281,7 @@ Context:
|
|
|
7682
8281
|
}
|
|
7683
8282
|
if (stopped) break;
|
|
7684
8283
|
await new Promise((r) => {
|
|
7685
|
-
|
|
8284
|
+
resolve6 = r;
|
|
7686
8285
|
});
|
|
7687
8286
|
}
|
|
7688
8287
|
} finally {
|
|
@@ -7690,27 +8289,27 @@ Context:
|
|
|
7690
8289
|
}
|
|
7691
8290
|
}),
|
|
7692
8291
|
/** Write input to a PTY session (keystrokes from xterm). */
|
|
7693
|
-
write: protectedProcedure.input(
|
|
7694
|
-
agentId:
|
|
7695
|
-
spawnId:
|
|
7696
|
-
data:
|
|
8292
|
+
write: protectedProcedure.input(z15.object({
|
|
8293
|
+
agentId: z15.string().max(128).optional(),
|
|
8294
|
+
spawnId: z15.string().max(128),
|
|
8295
|
+
data: z15.string().max(1e6)
|
|
7697
8296
|
})).mutation(async ({ input }) => {
|
|
7698
8297
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7699
|
-
if (!agentId) throw new
|
|
8298
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7700
8299
|
return agentManager.agentMutation(agentId, "ai.writePty", {
|
|
7701
8300
|
spawnId: input.spawnId,
|
|
7702
8301
|
data: input.data
|
|
7703
8302
|
});
|
|
7704
8303
|
}),
|
|
7705
8304
|
/** Resize a PTY session. */
|
|
7706
|
-
resize: protectedProcedure.input(
|
|
7707
|
-
agentId:
|
|
7708
|
-
spawnId:
|
|
7709
|
-
cols:
|
|
7710
|
-
rows:
|
|
8305
|
+
resize: protectedProcedure.input(z15.object({
|
|
8306
|
+
agentId: z15.string().max(128).optional(),
|
|
8307
|
+
spawnId: z15.string().max(128),
|
|
8308
|
+
cols: z15.number().int().min(1).max(1e3),
|
|
8309
|
+
rows: z15.number().int().min(1).max(1e3)
|
|
7711
8310
|
})).mutation(async ({ input }) => {
|
|
7712
8311
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7713
|
-
if (!agentId) throw new
|
|
8312
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7714
8313
|
return agentManager.agentMutation(agentId, "ai.resizePty", {
|
|
7715
8314
|
spawnId: input.spawnId,
|
|
7716
8315
|
cols: input.cols,
|
|
@@ -7723,14 +8322,14 @@ Context:
|
|
|
7723
8322
|
* picks it up. Returns the tmp path and which clipboard method succeeded
|
|
7724
8323
|
* (null if none — caller should inject `@<path>` instead of Ctrl+V).
|
|
7725
8324
|
*/
|
|
7726
|
-
pasteImage: protectedProcedure.input(
|
|
7727
|
-
agentId:
|
|
7728
|
-
sessionId:
|
|
7729
|
-
mimeType:
|
|
7730
|
-
base64:
|
|
8325
|
+
pasteImage: protectedProcedure.input(z15.object({
|
|
8326
|
+
agentId: z15.string().max(128).optional(),
|
|
8327
|
+
sessionId: z15.string().max(128).optional(),
|
|
8328
|
+
mimeType: z15.string().max(64),
|
|
8329
|
+
base64: z15.string().max(15e6)
|
|
7731
8330
|
})).mutation(async ({ input }) => {
|
|
7732
8331
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7733
|
-
if (!agentId) throw new
|
|
8332
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7734
8333
|
const res = await agentManager.agentMutation(agentId, "ai.pasteImage", {
|
|
7735
8334
|
sessionId: input.sessionId,
|
|
7736
8335
|
mimeType: input.mimeType,
|
|
@@ -7745,7 +8344,7 @@ Context:
|
|
|
7745
8344
|
* Read the transcript Claude Code stores on disk for a given session.
|
|
7746
8345
|
* Returns parsed JSONL entries (user / assistant messages, tool uses, etc.).
|
|
7747
8346
|
*/
|
|
7748
|
-
transcript: protectedProcedure.input(
|
|
8347
|
+
transcript: protectedProcedure.input(z15.object({ sessionId: z15.string().max(128) })).query(async ({ input }) => {
|
|
7749
8348
|
return readClaudeTranscript(input.sessionId);
|
|
7750
8349
|
}),
|
|
7751
8350
|
/**
|
|
@@ -7753,9 +8352,9 @@ Context:
|
|
|
7753
8352
|
* when was the transcript JSONL last touched? Used by the non-PTY view
|
|
7754
8353
|
* to show a "Claude is thinking…" indicator while output streams.
|
|
7755
8354
|
*/
|
|
7756
|
-
activity: protectedProcedure.input(
|
|
7757
|
-
agentId:
|
|
7758
|
-
sessionId:
|
|
8355
|
+
activity: protectedProcedure.input(z15.object({
|
|
8356
|
+
agentId: z15.string().max(128).optional(),
|
|
8357
|
+
sessionId: z15.string().max(128)
|
|
7759
8358
|
})).query(async ({ input }) => {
|
|
7760
8359
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7761
8360
|
let running = false;
|
|
@@ -7781,10 +8380,10 @@ Context:
|
|
|
7781
8380
|
* them on the conversation, so the non-PTY chat view stays in sync.
|
|
7782
8381
|
* Uses the conversation id as session id (PTY sessions are spawned that way).
|
|
7783
8382
|
*/
|
|
7784
|
-
syncTranscript: protectedProcedure.input(
|
|
8383
|
+
syncTranscript: protectedProcedure.input(z15.object({ conversationId: ConversationIdSchema })).mutation(async ({ input }) => {
|
|
7785
8384
|
const conv = await conversationStore.get(input.conversationId);
|
|
7786
8385
|
if (!conv) {
|
|
7787
|
-
throw new
|
|
8386
|
+
throw new TRPCError11({ code: "NOT_FOUND", message: `Conversation ${input.conversationId} not found` });
|
|
7788
8387
|
}
|
|
7789
8388
|
const synced = await syncConversationFromClaudeTranscript(conv.id);
|
|
7790
8389
|
const after = await conversationStore.get(conv.id);
|
|
@@ -7795,17 +8394,17 @@ Context:
|
|
|
7795
8394
|
* with the given sessionId. Used to benchmark the PTY → tRPC → xterm.js
|
|
7796
8395
|
* pipeline. Open the conversation in PTY mode to attach to it.
|
|
7797
8396
|
*/
|
|
7798
|
-
debugStress: protectedProcedure.input(
|
|
7799
|
-
sessionId:
|
|
7800
|
-
agentId:
|
|
7801
|
-
bytesPerSec:
|
|
7802
|
-
chunkSize:
|
|
7803
|
-
durationMs:
|
|
7804
|
-
cols:
|
|
7805
|
-
rows:
|
|
8397
|
+
debugStress: protectedProcedure.input(z15.object({
|
|
8398
|
+
sessionId: z15.string().max(128),
|
|
8399
|
+
agentId: z15.string().max(128).optional(),
|
|
8400
|
+
bytesPerSec: z15.number().int().min(100).max(1e7).default(5e4),
|
|
8401
|
+
chunkSize: z15.number().int().min(1).max(65536).default(256),
|
|
8402
|
+
durationMs: z15.number().int().min(0).max(36e5).default(0),
|
|
8403
|
+
cols: z15.number().int().min(1).max(1e3).default(120),
|
|
8404
|
+
rows: z15.number().int().min(1).max(1e3).default(30)
|
|
7806
8405
|
})).mutation(async ({ input }) => {
|
|
7807
8406
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7808
|
-
if (!agentId) throw new
|
|
8407
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7809
8408
|
const stressSessionId = `${input.sessionId}-stress-${Date.now().toString(36)}`;
|
|
7810
8409
|
const { existsSync: existsSync3 } = await import("fs");
|
|
7811
8410
|
const candidates = [
|
|
@@ -7822,7 +8421,7 @@ Context:
|
|
|
7822
8421
|
}
|
|
7823
8422
|
}
|
|
7824
8423
|
if (!scriptPath) {
|
|
7825
|
-
throw new
|
|
8424
|
+
throw new TRPCError11({
|
|
7826
8425
|
code: "INTERNAL_SERVER_ERROR",
|
|
7827
8426
|
message: `pty-stress-gen.mjs not found near ${fileURLToPath(import.meta.url)}`
|
|
7828
8427
|
});
|
|
@@ -7847,12 +8446,12 @@ Context:
|
|
|
7847
8446
|
return { spawnId: res.spawnId, agentId, stressSessionId };
|
|
7848
8447
|
}),
|
|
7849
8448
|
/** Kill a PTY session. */
|
|
7850
|
-
kill: protectedProcedure.input(
|
|
7851
|
-
agentId:
|
|
7852
|
-
spawnId:
|
|
8449
|
+
kill: protectedProcedure.input(z15.object({
|
|
8450
|
+
agentId: z15.string().max(128).optional(),
|
|
8451
|
+
spawnId: z15.string().max(128)
|
|
7853
8452
|
})).mutation(async ({ input }) => {
|
|
7854
8453
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7855
|
-
if (!agentId) throw new
|
|
8454
|
+
if (!agentId) throw new TRPCError11({ code: "PRECONDITION_FAILED", message: "No agent available" });
|
|
7856
8455
|
return agentManager.agentMutation(agentId, "ai.kill", {
|
|
7857
8456
|
spawnId: input.spawnId
|
|
7858
8457
|
});
|
|
@@ -7861,125 +8460,125 @@ Context:
|
|
|
7861
8460
|
});
|
|
7862
8461
|
|
|
7863
8462
|
// ../server/src/trpc/routers/git.ts
|
|
7864
|
-
import { z as
|
|
7865
|
-
import { TRPCError as
|
|
8463
|
+
import { z as z16 } from "zod";
|
|
8464
|
+
import { TRPCError as TRPCError12 } from "@trpc/server";
|
|
7866
8465
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
7867
8466
|
import OpenAI3 from "openai";
|
|
7868
|
-
var SERVICE_ID_SCHEMA =
|
|
7869
|
-
var SAFE_PATH_SCHEMA =
|
|
8467
|
+
var SERVICE_ID_SCHEMA = z16.string().max(128);
|
|
8468
|
+
var SAFE_PATH_SCHEMA = z16.string().min(1).max(4096).refine((p) => !p.startsWith("/") && !p.includes("../"), {
|
|
7870
8469
|
message: "Path must be relative and not traverse up"
|
|
7871
8470
|
});
|
|
7872
|
-
var REMOTE_SCHEMA =
|
|
7873
|
-
var BRANCH_SCHEMA =
|
|
8471
|
+
var REMOTE_SCHEMA = z16.string().min(1).max(128).regex(/^[a-zA-Z0-9_\-.]+$/);
|
|
8472
|
+
var BRANCH_SCHEMA = z16.string().min(1).max(256).regex(/^[a-zA-Z0-9._\-/]+$/);
|
|
7874
8473
|
async function resolveServiceContext(serviceId) {
|
|
7875
8474
|
const service = await serviceStore.get(serviceId);
|
|
7876
8475
|
if (!service) {
|
|
7877
|
-
throw new
|
|
8476
|
+
throw new TRPCError12({ code: "NOT_FOUND", message: "Service not found" });
|
|
7878
8477
|
}
|
|
7879
8478
|
const agentId = await resolveEffectiveAgentId(service);
|
|
7880
8479
|
if (!agentId) {
|
|
7881
|
-
throw new
|
|
8480
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent configured for this service" });
|
|
7882
8481
|
}
|
|
7883
8482
|
return { agentId, cwd: service.cwd || "." };
|
|
7884
8483
|
}
|
|
7885
8484
|
var gitTargetShape = {
|
|
7886
8485
|
serviceId: SERVICE_ID_SCHEMA.optional(),
|
|
7887
|
-
agentId:
|
|
8486
|
+
agentId: z16.string().max(128).optional()
|
|
7888
8487
|
};
|
|
7889
8488
|
async function resolveGitContext(input) {
|
|
7890
8489
|
if (input.serviceId) return resolveServiceContext(input.serviceId);
|
|
7891
8490
|
const agentId = input.agentId ?? agentManager.getDefaultAgentId();
|
|
7892
8491
|
if (!agentId) {
|
|
7893
|
-
throw new
|
|
8492
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No agent available" });
|
|
7894
8493
|
}
|
|
7895
8494
|
return { agentId, cwd: "." };
|
|
7896
8495
|
}
|
|
7897
8496
|
var gitRouter = router({
|
|
7898
8497
|
/** Get full git status */
|
|
7899
|
-
status: protectedProcedure.input(
|
|
8498
|
+
status: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
7900
8499
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7901
8500
|
return agentManager.agentQuery(agentId, "git.status", { cwd });
|
|
7902
8501
|
}),
|
|
7903
8502
|
/** Get commit log */
|
|
7904
|
-
log: protectedProcedure.input(
|
|
8503
|
+
log: protectedProcedure.input(z16.object({ ...gitTargetShape, limit: z16.number().int().min(1).max(200).default(50) })).query(async ({ input }) => {
|
|
7905
8504
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7906
8505
|
return agentManager.agentQuery(agentId, "git.log", { cwd, limit: input.limit });
|
|
7907
8506
|
}),
|
|
7908
8507
|
/** List local and remote branches */
|
|
7909
|
-
branches: protectedProcedure.input(
|
|
8508
|
+
branches: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
7910
8509
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7911
8510
|
return agentManager.agentQuery(agentId, "git.branches", { cwd });
|
|
7912
8511
|
}),
|
|
7913
8512
|
/** Checkout a branch */
|
|
7914
|
-
checkout: protectedProcedure.input(
|
|
8513
|
+
checkout: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA, create: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
7915
8514
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7916
8515
|
return agentManager.agentMutation(agentId, "git.checkout", { cwd, branch: input.branch, create: input.create });
|
|
7917
8516
|
}),
|
|
7918
8517
|
/** Stage files */
|
|
7919
|
-
stage: protectedProcedure.input(
|
|
8518
|
+
stage: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7920
8519
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7921
8520
|
return agentManager.agentMutation(agentId, "git.stage", { cwd, paths: input.paths });
|
|
7922
8521
|
}),
|
|
7923
8522
|
/** Stage all files */
|
|
7924
|
-
stageAll: protectedProcedure.input(
|
|
8523
|
+
stageAll: protectedProcedure.input(z16.object({ ...gitTargetShape })).mutation(async ({ input }) => {
|
|
7925
8524
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7926
8525
|
return agentManager.agentMutation(agentId, "git.stageAll", { cwd });
|
|
7927
8526
|
}),
|
|
7928
8527
|
/** Unstage files */
|
|
7929
|
-
unstage: protectedProcedure.input(
|
|
8528
|
+
unstage: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7930
8529
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7931
8530
|
return agentManager.agentMutation(agentId, "git.unstage", { cwd, paths: input.paths });
|
|
7932
8531
|
}),
|
|
7933
8532
|
/** Unstage all files */
|
|
7934
|
-
unstageAll: protectedProcedure.input(
|
|
8533
|
+
unstageAll: protectedProcedure.input(z16.object({ ...gitTargetShape })).mutation(async ({ input }) => {
|
|
7935
8534
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7936
8535
|
return agentManager.agentMutation(agentId, "git.unstageAll", { cwd });
|
|
7937
8536
|
}),
|
|
7938
8537
|
/** Commit staged changes */
|
|
7939
|
-
commit: protectedProcedure.input(
|
|
8538
|
+
commit: protectedProcedure.input(z16.object({ ...gitTargetShape, message: z16.string().min(1).max(4096) })).mutation(async ({ input }) => {
|
|
7940
8539
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7941
8540
|
return agentManager.agentMutation(agentId, "git.commit", { cwd, message: input.message });
|
|
7942
8541
|
}),
|
|
7943
8542
|
/** Push to remote */
|
|
7944
|
-
push: protectedProcedure.input(
|
|
8543
|
+
push: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin"), setUpstream: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
7945
8544
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7946
8545
|
return agentManager.agentMutation(agentId, "git.push", { cwd, remote: input.remote, setUpstream: input.setUpstream });
|
|
7947
8546
|
}),
|
|
7948
8547
|
/** Pull from remote */
|
|
7949
|
-
pull: protectedProcedure.input(
|
|
8548
|
+
pull: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin") })).mutation(async ({ input }) => {
|
|
7950
8549
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7951
8550
|
return agentManager.agentMutation(agentId, "git.pull", { cwd, remote: input.remote });
|
|
7952
8551
|
}),
|
|
7953
8552
|
/** Fetch from remote */
|
|
7954
|
-
fetch: protectedProcedure.input(
|
|
8553
|
+
fetch: protectedProcedure.input(z16.object({ ...gitTargetShape, remote: REMOTE_SCHEMA.default("origin") })).mutation(async ({ input }) => {
|
|
7955
8554
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7956
8555
|
return agentManager.agentMutation(agentId, "git.fetch", { cwd, remote: input.remote });
|
|
7957
8556
|
}),
|
|
7958
8557
|
/** Get diff for a file or the whole index */
|
|
7959
|
-
diff: protectedProcedure.input(
|
|
8558
|
+
diff: protectedProcedure.input(z16.object({
|
|
7960
8559
|
...gitTargetShape,
|
|
7961
|
-
path:
|
|
7962
|
-
staged:
|
|
7963
|
-
untracked:
|
|
8560
|
+
path: z16.string().max(4096).optional(),
|
|
8561
|
+
staged: z16.boolean().default(false),
|
|
8562
|
+
untracked: z16.boolean().default(false)
|
|
7964
8563
|
})).query(async ({ input }) => {
|
|
7965
8564
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7966
8565
|
return agentManager.agentQuery(agentId, "git.diff", { cwd, path: input.path, staged: input.staged, untracked: input.untracked });
|
|
7967
8566
|
}),
|
|
7968
8567
|
/** Discard changes to working tree files */
|
|
7969
|
-
discard: protectedProcedure.input(
|
|
8568
|
+
discard: protectedProcedure.input(z16.object({ ...gitTargetShape, paths: z16.array(SAFE_PATH_SCHEMA).min(1).max(500) })).mutation(async ({ input }) => {
|
|
7970
8569
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7971
8570
|
return agentManager.agentMutation(agentId, "git.discard", { cwd, paths: input.paths });
|
|
7972
8571
|
}),
|
|
7973
8572
|
/** Generate a commit message from staged diffs using AI (diff fetched from agent, AI runs on server) */
|
|
7974
|
-
generateCommitMessage: protectedProcedure.input(
|
|
8573
|
+
generateCommitMessage: protectedProcedure.input(z16.object({ ...gitTargetShape, providerId: OptionalProviderIdSchema, model: z16.string().max(200).optional() })).mutation(async ({ input }) => {
|
|
7975
8574
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
7976
8575
|
const { diff } = await agentManager.agentQuery(agentId, "git.stagedDiff", { cwd });
|
|
7977
8576
|
if (!diff.trim()) {
|
|
7978
|
-
throw new
|
|
8577
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No staged changes to generate a commit message from" });
|
|
7979
8578
|
}
|
|
7980
|
-
const active = await getActiveProvider();
|
|
8579
|
+
const active = await getActiveProvider(input.providerId);
|
|
7981
8580
|
if (!active) {
|
|
7982
|
-
throw new
|
|
8581
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
7983
8582
|
}
|
|
7984
8583
|
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
8584
|
Rules:
|
|
@@ -7988,7 +8587,7 @@ Rules:
|
|
|
7988
8587
|
- Write in English
|
|
7989
8588
|
- Return ONLY the commit message, nothing else`;
|
|
7990
8589
|
const { provider, plaintextKey } = active;
|
|
7991
|
-
const model = provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
8590
|
+
const model = input.model || provider.model || DEFAULT_MODELS[provider.provider] || "claude-opus-4-6";
|
|
7992
8591
|
if (provider.provider === "anthropic") {
|
|
7993
8592
|
const client = new Anthropic3({ apiKey: plaintextKey });
|
|
7994
8593
|
const res = await client.messages.create({
|
|
@@ -8012,48 +8611,48 @@ Rules:
|
|
|
8012
8611
|
});
|
|
8013
8612
|
return { message: res.choices[0]?.message?.content?.trim() ?? "" };
|
|
8014
8613
|
}
|
|
8015
|
-
throw new
|
|
8614
|
+
throw new TRPCError12({ code: "BAD_REQUEST", message: "Unsupported AI provider" });
|
|
8016
8615
|
}),
|
|
8017
8616
|
/** Merge a branch into the current branch */
|
|
8018
|
-
merge: protectedProcedure.input(
|
|
8617
|
+
merge: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA, noFf: z16.boolean().default(false) })).mutation(async ({ input }) => {
|
|
8019
8618
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8020
8619
|
return agentManager.agentMutation(agentId, "git.merge", { cwd, branch: input.branch, noFf: input.noFf });
|
|
8021
8620
|
}),
|
|
8022
8621
|
/** Rebase current branch onto another branch */
|
|
8023
|
-
rebase: protectedProcedure.input(
|
|
8622
|
+
rebase: protectedProcedure.input(z16.object({ ...gitTargetShape, branch: BRANCH_SCHEMA })).mutation(async ({ input }) => {
|
|
8024
8623
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8025
8624
|
return agentManager.agentMutation(agentId, "git.rebase", { cwd, branch: input.branch });
|
|
8026
8625
|
}),
|
|
8027
8626
|
/** List stash entries */
|
|
8028
|
-
stashList: protectedProcedure.input(
|
|
8627
|
+
stashList: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
8029
8628
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8030
8629
|
return agentManager.agentQuery(agentId, "git.stashList", { cwd });
|
|
8031
8630
|
}),
|
|
8032
8631
|
/** Stash current changes */
|
|
8033
|
-
stashPush: protectedProcedure.input(
|
|
8632
|
+
stashPush: protectedProcedure.input(z16.object({ ...gitTargetShape, message: z16.string().max(4096).optional(), includeUntracked: z16.boolean().default(true) })).mutation(async ({ input }) => {
|
|
8034
8633
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8035
8634
|
return agentManager.agentMutation(agentId, "git.stashPush", { cwd, message: input.message, includeUntracked: input.includeUntracked });
|
|
8036
8635
|
}),
|
|
8037
8636
|
/** Pop a stash entry */
|
|
8038
|
-
stashPop: protectedProcedure.input(
|
|
8637
|
+
stashPop: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8039
8638
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8040
8639
|
return agentManager.agentMutation(agentId, "git.stashPop", { cwd, index: input.index });
|
|
8041
8640
|
}),
|
|
8042
8641
|
/** Apply a stash entry without dropping it */
|
|
8043
|
-
stashApply: protectedProcedure.input(
|
|
8642
|
+
stashApply: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8044
8643
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8045
8644
|
return agentManager.agentMutation(agentId, "git.stashApply", { cwd, index: input.index });
|
|
8046
8645
|
}),
|
|
8047
8646
|
/** Drop a stash entry */
|
|
8048
|
-
stashDrop: protectedProcedure.input(
|
|
8647
|
+
stashDrop: protectedProcedure.input(z16.object({ ...gitTargetShape, index: z16.number().int().min(0).max(1e3).default(0) })).mutation(async ({ input }) => {
|
|
8049
8648
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8050
8649
|
return agentManager.agentMutation(agentId, "git.stashDrop", { cwd, index: input.index });
|
|
8051
8650
|
}),
|
|
8052
8651
|
/** Watch .git/ for changes and stream updated status */
|
|
8053
|
-
onStatusChange: protectedProcedure.input(
|
|
8652
|
+
onStatusChange: protectedProcedure.input(z16.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
|
|
8054
8653
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8055
8654
|
const queue = [];
|
|
8056
|
-
let
|
|
8655
|
+
let resolve6 = null;
|
|
8057
8656
|
let done = false;
|
|
8058
8657
|
let unsubscribe;
|
|
8059
8658
|
try {
|
|
@@ -8063,21 +8662,21 @@ Rules:
|
|
|
8063
8662
|
{ cwd },
|
|
8064
8663
|
(data) => {
|
|
8065
8664
|
queue.push(data);
|
|
8066
|
-
if (
|
|
8067
|
-
|
|
8068
|
-
|
|
8665
|
+
if (resolve6) {
|
|
8666
|
+
resolve6();
|
|
8667
|
+
resolve6 = null;
|
|
8069
8668
|
}
|
|
8070
8669
|
},
|
|
8071
8670
|
() => {
|
|
8072
8671
|
done = true;
|
|
8073
|
-
if (
|
|
8074
|
-
|
|
8075
|
-
|
|
8672
|
+
if (resolve6) {
|
|
8673
|
+
resolve6();
|
|
8674
|
+
resolve6 = null;
|
|
8076
8675
|
}
|
|
8077
8676
|
}
|
|
8078
8677
|
);
|
|
8079
8678
|
} catch (err) {
|
|
8080
|
-
throw new
|
|
8679
|
+
throw new TRPCError12({
|
|
8081
8680
|
code: "BAD_REQUEST",
|
|
8082
8681
|
message: `Cannot subscribe to git status: ${err.message}`
|
|
8083
8682
|
});
|
|
@@ -8088,7 +8687,7 @@ Rules:
|
|
|
8088
8687
|
yield queue.shift();
|
|
8089
8688
|
} else {
|
|
8090
8689
|
await new Promise((r) => {
|
|
8091
|
-
|
|
8690
|
+
resolve6 = r;
|
|
8092
8691
|
});
|
|
8093
8692
|
}
|
|
8094
8693
|
}
|
|
@@ -8097,49 +8696,49 @@ Rules:
|
|
|
8097
8696
|
}
|
|
8098
8697
|
}),
|
|
8099
8698
|
/** List remotes */
|
|
8100
|
-
remotes: protectedProcedure.input(
|
|
8699
|
+
remotes: protectedProcedure.input(z16.object({ ...gitTargetShape })).query(async ({ input }) => {
|
|
8101
8700
|
const { agentId, cwd } = await resolveGitContext(input);
|
|
8102
8701
|
return agentManager.agentQuery(agentId, "git.remotes", { cwd });
|
|
8103
8702
|
})
|
|
8104
8703
|
});
|
|
8105
8704
|
|
|
8106
8705
|
// ../server/src/trpc/routers/scenario.ts
|
|
8107
|
-
import { randomUUID as
|
|
8108
|
-
import { TRPCError as
|
|
8109
|
-
import { z as
|
|
8706
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
8707
|
+
import { TRPCError as TRPCError15 } from "@trpc/server";
|
|
8708
|
+
import { z as z17 } from "zod";
|
|
8110
8709
|
|
|
8111
8710
|
// ../server/src/trpc/crud-procedure-utils.ts
|
|
8112
|
-
import { TRPCError as
|
|
8711
|
+
import { TRPCError as TRPCError13 } from "@trpc/server";
|
|
8113
8712
|
async function getOrNotFound(getter, notFoundMessage) {
|
|
8114
8713
|
const entity = await getter();
|
|
8115
|
-
if (!entity) throw new
|
|
8714
|
+
if (!entity) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8116
8715
|
return entity;
|
|
8117
8716
|
}
|
|
8118
8717
|
async function createOrBadRequest(creator) {
|
|
8119
8718
|
try {
|
|
8120
8719
|
return await creator();
|
|
8121
8720
|
} catch (err) {
|
|
8122
|
-
throw new
|
|
8721
|
+
throw new TRPCError13({ code: "BAD_REQUEST", message: err.message });
|
|
8123
8722
|
}
|
|
8124
8723
|
}
|
|
8125
8724
|
async function updateOrThrow(updater, notFoundMessage) {
|
|
8126
8725
|
try {
|
|
8127
8726
|
const entity = await updater();
|
|
8128
|
-
if (!entity) throw new
|
|
8727
|
+
if (!entity) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8129
8728
|
return entity;
|
|
8130
8729
|
} catch (err) {
|
|
8131
|
-
if (err instanceof
|
|
8132
|
-
throw new
|
|
8730
|
+
if (err instanceof TRPCError13) throw err;
|
|
8731
|
+
throw new TRPCError13({ code: "BAD_REQUEST", message: err.message });
|
|
8133
8732
|
}
|
|
8134
8733
|
}
|
|
8135
8734
|
async function deleteOrNotFound(deleter, notFoundMessage) {
|
|
8136
8735
|
const deleted = await deleter();
|
|
8137
|
-
if (!deleted) throw new
|
|
8736
|
+
if (!deleted) throw new TRPCError13({ code: "NOT_FOUND", message: notFoundMessage });
|
|
8138
8737
|
return { ok: true };
|
|
8139
8738
|
}
|
|
8140
8739
|
|
|
8141
8740
|
// ../server/src/trpc/ai-generation-procedure-utils.ts
|
|
8142
|
-
import { TRPCError as
|
|
8741
|
+
import { TRPCError as TRPCError14 } from "@trpc/server";
|
|
8143
8742
|
|
|
8144
8743
|
// ../server/src/services/ai-generation.ts
|
|
8145
8744
|
import Anthropic4 from "@anthropic-ai/sdk";
|
|
@@ -8195,9 +8794,9 @@ async function* streamProviderText(input) {
|
|
|
8195
8794
|
// ../server/src/trpc/ai-generation-procedure-utils.ts
|
|
8196
8795
|
async function* streamWithActiveProvider(opts) {
|
|
8197
8796
|
const active = await getActiveProvider(opts.providerId);
|
|
8198
|
-
if (!active) throw new
|
|
8797
|
+
if (!active) throw new TRPCError14({ code: "BAD_REQUEST", message: "Aucun fournisseur IA configur\xE9" });
|
|
8199
8798
|
const { provider, plaintextKey } = active;
|
|
8200
|
-
const model = provider.model
|
|
8799
|
+
const model = opts.model || provider.model || DEFAULT_MODELS[provider.provider] || "";
|
|
8201
8800
|
try {
|
|
8202
8801
|
for await (const text of streamProviderText({
|
|
8203
8802
|
provider,
|
|
@@ -8213,16 +8812,16 @@ async function* streamWithActiveProvider(opts) {
|
|
|
8213
8812
|
yield { type: "done", text: "" };
|
|
8214
8813
|
} catch (err) {
|
|
8215
8814
|
if (String(err?.message ?? "").startsWith("Unsupported provider:")) {
|
|
8216
|
-
throw new
|
|
8815
|
+
throw new TRPCError14({ code: "BAD_REQUEST", message: `Fournisseur non support\xE9: ${provider.provider}` });
|
|
8217
8816
|
}
|
|
8218
8817
|
throw err;
|
|
8219
8818
|
}
|
|
8220
8819
|
}
|
|
8221
8820
|
|
|
8222
8821
|
// ../server/src/services/scenario-runner.ts
|
|
8223
|
-
import { mkdir as
|
|
8224
|
-
import { randomUUID as
|
|
8225
|
-
import { join as
|
|
8822
|
+
import { mkdir as mkdir13 } from "fs/promises";
|
|
8823
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8824
|
+
import { join as join15 } from "path";
|
|
8226
8825
|
|
|
8227
8826
|
// ../server/src/services/scenario-presets.ts
|
|
8228
8827
|
var BUILT_IN_PRESETS = [
|
|
@@ -8436,21 +9035,21 @@ async function* tapBus(conversationId, signal) {
|
|
|
8436
9035
|
}
|
|
8437
9036
|
if (bus.done) return;
|
|
8438
9037
|
if (signal.aborted) return;
|
|
8439
|
-
await new Promise((
|
|
9038
|
+
await new Promise((resolve6) => {
|
|
8440
9039
|
const onAbort = () => {
|
|
8441
9040
|
bus.listeners.delete(notify);
|
|
8442
|
-
|
|
9041
|
+
resolve6();
|
|
8443
9042
|
};
|
|
8444
9043
|
const notify = () => {
|
|
8445
9044
|
signal.removeEventListener("abort", onAbort);
|
|
8446
|
-
|
|
9045
|
+
resolve6();
|
|
8447
9046
|
};
|
|
8448
9047
|
bus.listeners.add(notify);
|
|
8449
9048
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
8450
9049
|
if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
|
|
8451
9050
|
bus.listeners.delete(notify);
|
|
8452
9051
|
signal.removeEventListener("abort", onAbort);
|
|
8453
|
-
|
|
9052
|
+
resolve6();
|
|
8454
9053
|
}
|
|
8455
9054
|
});
|
|
8456
9055
|
}
|
|
@@ -8563,7 +9162,7 @@ function applyHireDirectivesFromOutput(manager, scenario, output) {
|
|
|
8563
9162
|
}
|
|
8564
9163
|
const hired = [];
|
|
8565
9164
|
for (const payload of hireDirectives) {
|
|
8566
|
-
const id = `hire-${
|
|
9165
|
+
const id = `hire-${randomUUID7().slice(0, 8)}`;
|
|
8567
9166
|
const position = computeHirePosition(manager, scenario.nodes);
|
|
8568
9167
|
ensureGroupCanContainNode(scenario, manager.groupId ?? null, position);
|
|
8569
9168
|
const node = {
|
|
@@ -8927,9 +9526,6 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
8927
9526
|
model,
|
|
8928
9527
|
effort: "normal",
|
|
8929
9528
|
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
9529
|
onDelta: (text) => {
|
|
8934
9530
|
accumulated += text;
|
|
8935
9531
|
onDelta(text);
|
|
@@ -8957,9 +9553,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
|
|
|
8957
9553
|
publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
|
|
8958
9554
|
await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
|
|
8959
9555
|
`);
|
|
8960
|
-
const answer = await new Promise((
|
|
9556
|
+
const answer = await new Promise((resolve6, reject) => {
|
|
8961
9557
|
const key = pendingNodeKey(conversationId, node.id);
|
|
8962
|
-
pendingNodeInputs.set(key,
|
|
9558
|
+
pendingNodeInputs.set(key, resolve6);
|
|
8963
9559
|
signal.addEventListener("abort", () => {
|
|
8964
9560
|
pendingNodeInputs.delete(key);
|
|
8965
9561
|
reject(new Error("Aborted"));
|
|
@@ -8999,7 +9595,7 @@ async function appendNodeOutput(conversationId, node, output) {
|
|
|
8999
9595
|
if (!dir) return;
|
|
9000
9596
|
const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
|
|
9001
9597
|
const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
|
|
9002
|
-
const filePath =
|
|
9598
|
+
const filePath = join15(dir, `${slug}_${safeId}.md`);
|
|
9003
9599
|
const content = `## Output
|
|
9004
9600
|
|
|
9005
9601
|
${output}
|
|
@@ -9012,9 +9608,9 @@ async function appendToReadme(conversationId, bus, text) {
|
|
|
9012
9608
|
const line = `${text}
|
|
9013
9609
|
`;
|
|
9014
9610
|
try {
|
|
9015
|
-
await
|
|
9611
|
+
await mkdir13(dir, { recursive: true });
|
|
9016
9612
|
const { appendFile: appendFile2 } = await import("fs/promises");
|
|
9017
|
-
await appendFile2(
|
|
9613
|
+
await appendFile2(join15(dir, "README.md"), line, "utf-8");
|
|
9018
9614
|
} catch {
|
|
9019
9615
|
}
|
|
9020
9616
|
publishToBus(bus, { type: "readme_append", text: line });
|
|
@@ -9034,7 +9630,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
|
|
|
9034
9630
|
|
|
9035
9631
|
${content}
|
|
9036
9632
|
`;
|
|
9037
|
-
const filePath =
|
|
9633
|
+
const filePath = join15(dir, "FINAL.md");
|
|
9038
9634
|
await atomicWriteSecure(filePath, fullContent);
|
|
9039
9635
|
await appendToReadme(
|
|
9040
9636
|
conversationId,
|
|
@@ -9364,9 +9960,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
|
|
|
9364
9960
|
publish2({ type: "node_waiting_input", nodeId: node.id, question });
|
|
9365
9961
|
await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
|
|
9366
9962
|
`);
|
|
9367
|
-
const answer = await new Promise((
|
|
9963
|
+
const answer = await new Promise((resolve6, reject) => {
|
|
9368
9964
|
const key = pendingNodeKey(conversationId, node.id);
|
|
9369
|
-
pendingNodeInputs.set(key,
|
|
9965
|
+
pendingNodeInputs.set(key, resolve6);
|
|
9370
9966
|
signal.addEventListener("abort", () => {
|
|
9371
9967
|
pendingNodeInputs.delete(key);
|
|
9372
9968
|
reject(new Error("Aborted"));
|
|
@@ -9703,21 +10299,21 @@ Modify the scenario. Keep existing ids when possible.`;
|
|
|
9703
10299
|
}
|
|
9704
10300
|
var scenarioRouter = router({
|
|
9705
10301
|
list: protectedProcedure.query(() => scenarioStore.list()),
|
|
9706
|
-
get: protectedProcedure.input(
|
|
9707
|
-
create: protectedProcedure.input(
|
|
10302
|
+
get: protectedProcedure.input(z17.object({ id: Id128Schema })).query(({ input }) => getOrNotFound(() => scenarioStore.get(input.id), "Scenario not found")),
|
|
10303
|
+
create: protectedProcedure.input(z17.object({
|
|
9708
10304
|
name: NameSchema,
|
|
9709
|
-
icon:
|
|
10305
|
+
icon: z17.string().max(64).optional(),
|
|
9710
10306
|
description: DescriptionSchema.optional(),
|
|
9711
10307
|
leaderId: Id128Schema,
|
|
9712
|
-
groups:
|
|
9713
|
-
nodes:
|
|
9714
|
-
traversalMode:
|
|
9715
|
-
presetId:
|
|
9716
|
-
isPublished:
|
|
10308
|
+
groups: z17.array(ScenarioGroupSchema).max(100).optional(),
|
|
10309
|
+
nodes: z17.array(ScenarioNodeSchema).min(1).max(500),
|
|
10310
|
+
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
10311
|
+
presetId: z17.string().max(128).optional(),
|
|
10312
|
+
isPublished: z17.boolean().optional()
|
|
9717
10313
|
})).mutation(({ input }) => {
|
|
9718
10314
|
const now = Date.now();
|
|
9719
10315
|
return createOrBadRequest(() => scenarioStore.create({
|
|
9720
|
-
id:
|
|
10316
|
+
id: randomUUID8(),
|
|
9721
10317
|
name: input.name,
|
|
9722
10318
|
icon: input.icon,
|
|
9723
10319
|
description: input.description,
|
|
@@ -9731,17 +10327,17 @@ var scenarioRouter = router({
|
|
|
9731
10327
|
updatedAt: now
|
|
9732
10328
|
}));
|
|
9733
10329
|
}),
|
|
9734
|
-
update: protectedProcedure.input(
|
|
10330
|
+
update: protectedProcedure.input(z17.object({
|
|
9735
10331
|
id: Id128Schema,
|
|
9736
10332
|
name: NameSchema.optional(),
|
|
9737
|
-
icon:
|
|
10333
|
+
icon: z17.string().max(64).optional(),
|
|
9738
10334
|
description: DescriptionSchema.optional(),
|
|
9739
10335
|
leaderId: Id128Schema.optional(),
|
|
9740
|
-
groups:
|
|
9741
|
-
nodes:
|
|
9742
|
-
traversalMode:
|
|
9743
|
-
presetId:
|
|
9744
|
-
isPublished:
|
|
10336
|
+
groups: z17.array(ScenarioGroupSchema).max(100).optional(),
|
|
10337
|
+
nodes: z17.array(ScenarioNodeSchema).min(1).max(500).optional(),
|
|
10338
|
+
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
10339
|
+
presetId: z17.string().max(128).optional(),
|
|
10340
|
+
isPublished: z17.boolean().optional()
|
|
9745
10341
|
})).mutation(({ input }) => updateOrThrow(
|
|
9746
10342
|
() => scenarioStore.update(input.id, {
|
|
9747
10343
|
name: input.name,
|
|
@@ -9757,18 +10353,18 @@ var scenarioRouter = router({
|
|
|
9757
10353
|
}),
|
|
9758
10354
|
"Scenario not found"
|
|
9759
10355
|
)),
|
|
9760
|
-
delete: protectedProcedure.input(
|
|
9761
|
-
run: protectedProcedure.input(
|
|
10356
|
+
delete: protectedProcedure.input(z17.object({ id: Id128Schema })).mutation(({ input }) => deleteOrNotFound(() => scenarioStore.delete(input.id), "Scenario not found")),
|
|
10357
|
+
run: protectedProcedure.input(z17.object({
|
|
9762
10358
|
conversationId: ConversationIdSchema,
|
|
9763
10359
|
scenarioId: Id128Schema,
|
|
9764
|
-
userRequest:
|
|
9765
|
-
runnerOverride:
|
|
10360
|
+
userRequest: z17.string().max(1e5),
|
|
10361
|
+
runnerOverride: z17.enum(["claude-code", "codex"]).optional(),
|
|
9766
10362
|
modelOverride: Id128Schema.optional(),
|
|
9767
10363
|
agentId: Id128Schema.optional(),
|
|
9768
|
-
resume:
|
|
10364
|
+
resume: z17.boolean().optional()
|
|
9769
10365
|
})).subscription(async function* ({ input, signal }) {
|
|
9770
10366
|
const scenario = await scenarioStore.get(input.scenarioId);
|
|
9771
|
-
if (!scenario) throw new
|
|
10367
|
+
if (!scenario) throw new TRPCError15({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
9772
10368
|
const canStart = input.userRequest.trim().length > 0 || !!input.resume;
|
|
9773
10369
|
if (!isScenarioRunning(input.conversationId) && canStart) {
|
|
9774
10370
|
let userRequest = input.userRequest;
|
|
@@ -9784,52 +10380,54 @@ var scenarioRouter = router({
|
|
|
9784
10380
|
}
|
|
9785
10381
|
yield* tapBus(input.conversationId, signal ?? new AbortController().signal);
|
|
9786
10382
|
}),
|
|
9787
|
-
stop: protectedProcedure.input(
|
|
10383
|
+
stop: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).mutation(({ input }) => {
|
|
9788
10384
|
abortScenario(input.conversationId);
|
|
9789
10385
|
return { ok: true };
|
|
9790
10386
|
}),
|
|
9791
|
-
sendNodeInput: protectedProcedure.input(
|
|
10387
|
+
sendNodeInput: protectedProcedure.input(z17.object({
|
|
9792
10388
|
conversationId: ConversationIdSchema,
|
|
9793
10389
|
nodeId: Id128Schema,
|
|
9794
|
-
text:
|
|
10390
|
+
text: z17.string().min(1).max(1e5)
|
|
9795
10391
|
})).mutation(({ input }) => {
|
|
9796
10392
|
const resolved = resolveNodeInput(input.conversationId, input.nodeId, input.text);
|
|
9797
|
-
if (!resolved) throw new
|
|
10393
|
+
if (!resolved) throw new TRPCError15({ code: "NOT_FOUND", message: "No pending input request for this node" });
|
|
9798
10394
|
return { ok: true };
|
|
9799
10395
|
}),
|
|
9800
|
-
isRunning: protectedProcedure.input(
|
|
9801
|
-
runState: protectedProcedure.input(
|
|
10396
|
+
isRunning: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(({ input }) => ({ running: isScenarioRunning(input.conversationId) })),
|
|
10397
|
+
runState: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(({ input }) => {
|
|
9802
10398
|
const runState = scenarioRunStore.getRunState(input.conversationId);
|
|
9803
10399
|
return runState ?? { nodeStates: {}, nodeOutputs: {}, activeNodeId: void 0 };
|
|
9804
10400
|
}),
|
|
9805
|
-
getPersistedRunState: protectedProcedure.input(
|
|
10401
|
+
getPersistedRunState: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9806
10402
|
return await scenarioRunStore.loadPersistedRunState(input.conversationId);
|
|
9807
10403
|
}),
|
|
9808
|
-
getReadme: protectedProcedure.input(
|
|
10404
|
+
getReadme: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9809
10405
|
return await scenarioRunStore.readReadme(input.conversationId);
|
|
9810
10406
|
}),
|
|
9811
|
-
getSnapshot: protectedProcedure.input(
|
|
10407
|
+
getSnapshot: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).query(async ({ input }) => {
|
|
9812
10408
|
return await scenarioRunStore.loadScenarioSnapshot(input.conversationId);
|
|
9813
10409
|
}),
|
|
9814
|
-
streamGenerateWithAI: protectedProcedure.input(
|
|
9815
|
-
prompt:
|
|
10410
|
+
streamGenerateWithAI: protectedProcedure.input(z17.object({
|
|
10411
|
+
prompt: z17.string().min(1).max(1e4),
|
|
9816
10412
|
providerId: OptionalProviderIdSchema,
|
|
9817
|
-
|
|
10413
|
+
model: z17.string().max(200).optional(),
|
|
10414
|
+
current: z17.object({
|
|
9818
10415
|
leaderId: Id128Schema,
|
|
9819
|
-
groups:
|
|
9820
|
-
nodes:
|
|
10416
|
+
groups: z17.array(ScenarioGroupSchema).max(100),
|
|
10417
|
+
nodes: z17.array(ScenarioNodeSchema).max(500)
|
|
9821
10418
|
}).optional()
|
|
9822
10419
|
})).subscription(async function* ({ input, signal }) {
|
|
9823
10420
|
const userMessage = buildScenarioUserMessage(input.prompt, input.current);
|
|
9824
10421
|
yield* streamWithActiveProvider({
|
|
9825
10422
|
providerId: input.providerId,
|
|
10423
|
+
model: input.model,
|
|
9826
10424
|
systemPrompt: SCENARIO_SYSTEM_PROMPT,
|
|
9827
10425
|
userMessage,
|
|
9828
10426
|
maxTokens: 8192,
|
|
9829
10427
|
signal: signal ?? void 0
|
|
9830
10428
|
});
|
|
9831
10429
|
}),
|
|
9832
|
-
streamRunEvents: protectedProcedure.input(
|
|
10430
|
+
streamRunEvents: protectedProcedure.input(z17.object({ conversationId: ConversationIdSchema })).subscription(async function* ({ input }) {
|
|
9833
10431
|
const convId = input.conversationId;
|
|
9834
10432
|
const runState = scenarioRunStore.getRunState(convId);
|
|
9835
10433
|
if (runState) {
|
|
@@ -9843,24 +10441,24 @@ var scenarioRouter = router({
|
|
|
9843
10441
|
* open a debug PTY with `claude --resume <sessionId>` on the right agent.
|
|
9844
10442
|
* Falls back to the persisted run-state for past nodes.
|
|
9845
10443
|
*/
|
|
9846
|
-
getNodeSession: protectedProcedure.input(
|
|
10444
|
+
getNodeSession: protectedProcedure.input(z17.object({
|
|
9847
10445
|
conversationId: ConversationIdSchema,
|
|
9848
10446
|
nodeId: Id128Schema
|
|
9849
10447
|
})).query(async ({ input }) => {
|
|
9850
10448
|
return await scenarioRunStore.getNodeSessionPersistent(input.conversationId, input.nodeId);
|
|
9851
10449
|
}),
|
|
9852
10450
|
listPresets: protectedProcedure.query(() => listPresets()),
|
|
9853
|
-
getPreset: protectedProcedure.input(
|
|
10451
|
+
getPreset: protectedProcedure.input(z17.object({ id: Id128Schema })).query(({ input }) => {
|
|
9854
10452
|
const preset = getPreset(input.id);
|
|
9855
|
-
if (!preset) throw new
|
|
10453
|
+
if (!preset) throw new TRPCError15({ code: "NOT_FOUND", message: "Preset not found" });
|
|
9856
10454
|
return preset;
|
|
9857
10455
|
})
|
|
9858
10456
|
});
|
|
9859
10457
|
|
|
9860
10458
|
// ../server/src/trpc/routers/debug.ts
|
|
9861
|
-
import { randomUUID as
|
|
9862
|
-
import { TRPCError as
|
|
9863
|
-
import { z as
|
|
10459
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
10460
|
+
import { TRPCError as TRPCError16 } from "@trpc/server";
|
|
10461
|
+
import { z as z18 } from "zod";
|
|
9864
10462
|
var MAX_BUFFER3 = 300;
|
|
9865
10463
|
var jobs = /* @__PURE__ */ new Map();
|
|
9866
10464
|
function publish(job, event) {
|
|
@@ -9897,31 +10495,31 @@ async function* tapJob(job, signal) {
|
|
|
9897
10495
|
yield evt;
|
|
9898
10496
|
}
|
|
9899
10497
|
if (job.done || signal.aborted) return;
|
|
9900
|
-
await new Promise((
|
|
10498
|
+
await new Promise((resolve6) => {
|
|
9901
10499
|
const onAbort = () => {
|
|
9902
10500
|
job.listeners.delete(notify);
|
|
9903
|
-
|
|
10501
|
+
resolve6();
|
|
9904
10502
|
};
|
|
9905
10503
|
const notify = () => {
|
|
9906
10504
|
signal.removeEventListener("abort", onAbort);
|
|
9907
|
-
|
|
10505
|
+
resolve6();
|
|
9908
10506
|
};
|
|
9909
10507
|
job.listeners.add(notify);
|
|
9910
10508
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9911
10509
|
if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
|
|
9912
10510
|
job.listeners.delete(notify);
|
|
9913
10511
|
signal.removeEventListener("abort", onAbort);
|
|
9914
|
-
|
|
10512
|
+
resolve6();
|
|
9915
10513
|
}
|
|
9916
10514
|
});
|
|
9917
10515
|
}
|
|
9918
10516
|
}
|
|
9919
10517
|
var debugRouter = router({
|
|
9920
|
-
startRandomJob: protectedProcedure.input(
|
|
9921
|
-
intervalMs:
|
|
9922
|
-
maxTicks:
|
|
10518
|
+
startRandomJob: protectedProcedure.input(z18.object({
|
|
10519
|
+
intervalMs: z18.number().int().min(100).max(5e3).optional(),
|
|
10520
|
+
maxTicks: z18.number().int().min(1).max(1e4).optional()
|
|
9923
10521
|
}).optional()).mutation(({ input }) => {
|
|
9924
|
-
const jobId =
|
|
10522
|
+
const jobId = randomUUID9();
|
|
9925
10523
|
const intervalMs = input?.intervalMs ?? 400;
|
|
9926
10524
|
const maxTicks = input?.maxTicks ?? 500;
|
|
9927
10525
|
const job = {
|
|
@@ -9953,15 +10551,15 @@ var debugRouter = router({
|
|
|
9953
10551
|
jobs.set(jobId, job);
|
|
9954
10552
|
return { jobId, intervalMs, maxTicks, startedAt: job.startedAt };
|
|
9955
10553
|
}),
|
|
9956
|
-
streamRandomJob: protectedProcedure.input(
|
|
10554
|
+
streamRandomJob: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).subscription(async function* ({ input, signal }) {
|
|
9957
10555
|
const job = jobs.get(input.jobId);
|
|
9958
10556
|
if (!job) {
|
|
9959
|
-
throw new
|
|
10557
|
+
throw new TRPCError16({ code: "NOT_FOUND", message: "Random job not found" });
|
|
9960
10558
|
}
|
|
9961
10559
|
yield* tapJob(job, signal ?? new AbortController().signal);
|
|
9962
10560
|
}),
|
|
9963
|
-
stopRandomJob: protectedProcedure.input(
|
|
9964
|
-
randomJobStatus: protectedProcedure.input(
|
|
10561
|
+
stopRandomJob: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).mutation(({ input }) => ({ ok: stopJob(input.jobId, "stopped") })),
|
|
10562
|
+
randomJobStatus: protectedProcedure.input(z18.object({ jobId: z18.string().max(128) })).query(({ input }) => {
|
|
9965
10563
|
const job = jobs.get(input.jobId);
|
|
9966
10564
|
return {
|
|
9967
10565
|
running: !!job && !job.done,
|
|
@@ -9970,132 +10568,11 @@ var debugRouter = router({
|
|
|
9970
10568
|
})
|
|
9971
10569
|
});
|
|
9972
10570
|
|
|
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
10571
|
// ../server/src/trpc/routers/kanban-queue.ts
|
|
10095
10572
|
import { z as z19 } from "zod";
|
|
10096
10573
|
|
|
10097
10574
|
// ../server/src/services/kanban-queue.ts
|
|
10098
|
-
import { randomUUID as
|
|
10575
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
10099
10576
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
10100
10577
|
var KanbanQueueService = class extends EventEmitter4 {
|
|
10101
10578
|
items = [];
|
|
@@ -10118,7 +10595,7 @@ var KanbanQueueService = class extends EventEmitter4 {
|
|
|
10118
10595
|
return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
|
|
10119
10596
|
}
|
|
10120
10597
|
const queueItem = {
|
|
10121
|
-
id:
|
|
10598
|
+
id: randomUUID10(),
|
|
10122
10599
|
...item,
|
|
10123
10600
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10124
10601
|
};
|
|
@@ -10310,13 +10787,13 @@ var kanbanQueueRouter = router({
|
|
|
10310
10787
|
}),
|
|
10311
10788
|
onChanged: protectedProcedure.subscription(async function* () {
|
|
10312
10789
|
const queue = [];
|
|
10313
|
-
let
|
|
10790
|
+
let resolve6 = null;
|
|
10314
10791
|
const handler = () => {
|
|
10315
10792
|
if (queue.length >= 50) queue.shift();
|
|
10316
10793
|
queue.push(kanbanQueue.getState());
|
|
10317
|
-
if (
|
|
10318
|
-
|
|
10319
|
-
|
|
10794
|
+
if (resolve6) {
|
|
10795
|
+
resolve6();
|
|
10796
|
+
resolve6 = null;
|
|
10320
10797
|
}
|
|
10321
10798
|
};
|
|
10322
10799
|
kanbanQueue.on("changed", handler);
|
|
@@ -10327,7 +10804,7 @@ var kanbanQueueRouter = router({
|
|
|
10327
10804
|
yield queue.shift();
|
|
10328
10805
|
} else {
|
|
10329
10806
|
await new Promise((r) => {
|
|
10330
|
-
|
|
10807
|
+
resolve6 = r;
|
|
10331
10808
|
});
|
|
10332
10809
|
}
|
|
10333
10810
|
}
|
|
@@ -10337,16 +10814,52 @@ var kanbanQueueRouter = router({
|
|
|
10337
10814
|
})
|
|
10338
10815
|
});
|
|
10339
10816
|
|
|
10340
|
-
// ../server/src/trpc/routers/
|
|
10817
|
+
// ../server/src/trpc/routers/kanban.ts
|
|
10341
10818
|
import { z as z20 } from "zod";
|
|
10819
|
+
var kanbanRouter = router({
|
|
10820
|
+
moveCard: protectedProcedure.input(z20.object({
|
|
10821
|
+
boardId: z20.string().max(128),
|
|
10822
|
+
cardId: z20.string().max(128),
|
|
10823
|
+
targetColumn: z20.string().max(256)
|
|
10824
|
+
})).mutation(async ({ input }) => {
|
|
10825
|
+
await executeKanbanCommands(input.boardId, input.cardId, [
|
|
10826
|
+
{ type: "move_card", columnTitle: input.targetColumn }
|
|
10827
|
+
]);
|
|
10828
|
+
return { ok: true };
|
|
10829
|
+
}),
|
|
10830
|
+
addComment: protectedProcedure.input(z20.object({
|
|
10831
|
+
boardId: z20.string().max(128),
|
|
10832
|
+
cardId: z20.string().max(128),
|
|
10833
|
+
comment: z20.string().max(2e4)
|
|
10834
|
+
})).mutation(async ({ input }) => {
|
|
10835
|
+
await executeKanbanCommands(input.boardId, input.cardId, [
|
|
10836
|
+
{ type: "add_comment", comment: input.comment }
|
|
10837
|
+
]);
|
|
10838
|
+
return { ok: true };
|
|
10839
|
+
}),
|
|
10840
|
+
addCard: protectedProcedure.input(z20.object({
|
|
10841
|
+
boardId: z20.string().max(128),
|
|
10842
|
+
column: z20.string().max(256),
|
|
10843
|
+
title: z20.string().max(512),
|
|
10844
|
+
content: z20.string().max(2e4).optional()
|
|
10845
|
+
})).mutation(async ({ input }) => {
|
|
10846
|
+
await executeKanbanBoardCommands(input.boardId, [
|
|
10847
|
+
{ type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }
|
|
10848
|
+
]);
|
|
10849
|
+
return { ok: true };
|
|
10850
|
+
})
|
|
10851
|
+
});
|
|
10852
|
+
|
|
10853
|
+
// ../server/src/trpc/routers/terminal.ts
|
|
10854
|
+
import { z as z21 } from "zod";
|
|
10342
10855
|
import { TRPCError as TRPCError17 } from "@trpc/server";
|
|
10343
10856
|
var terminalRouter = router({
|
|
10344
10857
|
/** Create a PTY shell session on the target agent */
|
|
10345
|
-
create: protectedProcedure.input(
|
|
10346
|
-
agentId:
|
|
10347
|
-
cwd:
|
|
10348
|
-
cols:
|
|
10349
|
-
rows:
|
|
10858
|
+
create: protectedProcedure.input(z21.object({
|
|
10859
|
+
agentId: z21.string().max(128),
|
|
10860
|
+
cwd: z21.string().max(512).optional(),
|
|
10861
|
+
cols: z21.number().int().min(1).max(512).optional(),
|
|
10862
|
+
rows: z21.number().int().min(1).max(256).optional()
|
|
10350
10863
|
})).mutation(async ({ input }) => {
|
|
10351
10864
|
const result = await agentManager.agentMutation(
|
|
10352
10865
|
input.agentId,
|
|
@@ -10356,10 +10869,10 @@ var terminalRouter = router({
|
|
|
10356
10869
|
return result;
|
|
10357
10870
|
}),
|
|
10358
10871
|
/** Send keystrokes / data to a PTY session */
|
|
10359
|
-
write: protectedProcedure.input(
|
|
10360
|
-
agentId:
|
|
10361
|
-
sessionId:
|
|
10362
|
-
data:
|
|
10872
|
+
write: protectedProcedure.input(z21.object({
|
|
10873
|
+
agentId: z21.string().max(128),
|
|
10874
|
+
sessionId: z21.string().max(128),
|
|
10875
|
+
data: z21.string().max(65536)
|
|
10363
10876
|
})).mutation(async ({ input }) => {
|
|
10364
10877
|
await agentManager.agentMutation(
|
|
10365
10878
|
input.agentId,
|
|
@@ -10369,11 +10882,11 @@ var terminalRouter = router({
|
|
|
10369
10882
|
return { ok: true };
|
|
10370
10883
|
}),
|
|
10371
10884
|
/** Resize the PTY */
|
|
10372
|
-
resize: protectedProcedure.input(
|
|
10373
|
-
agentId:
|
|
10374
|
-
sessionId:
|
|
10375
|
-
cols:
|
|
10376
|
-
rows:
|
|
10885
|
+
resize: protectedProcedure.input(z21.object({
|
|
10886
|
+
agentId: z21.string().max(128),
|
|
10887
|
+
sessionId: z21.string().max(128),
|
|
10888
|
+
cols: z21.number().int().min(1).max(512),
|
|
10889
|
+
rows: z21.number().int().min(1).max(256)
|
|
10377
10890
|
})).mutation(async ({ input }) => {
|
|
10378
10891
|
await agentManager.agentMutation(
|
|
10379
10892
|
input.agentId,
|
|
@@ -10383,9 +10896,9 @@ var terminalRouter = router({
|
|
|
10383
10896
|
return { ok: true };
|
|
10384
10897
|
}),
|
|
10385
10898
|
/** Destroy a PTY session */
|
|
10386
|
-
destroy: protectedProcedure.input(
|
|
10387
|
-
agentId:
|
|
10388
|
-
sessionId:
|
|
10899
|
+
destroy: protectedProcedure.input(z21.object({
|
|
10900
|
+
agentId: z21.string().max(128),
|
|
10901
|
+
sessionId: z21.string().max(128)
|
|
10389
10902
|
})).mutation(async ({ input }) => {
|
|
10390
10903
|
await agentManager.agentMutation(
|
|
10391
10904
|
input.agentId,
|
|
@@ -10395,12 +10908,12 @@ var terminalRouter = router({
|
|
|
10395
10908
|
return { ok: true };
|
|
10396
10909
|
}),
|
|
10397
10910
|
/** Stream PTY output — proxied via AgentWSBridge dynamic subscription */
|
|
10398
|
-
onData: protectedProcedure.input(
|
|
10399
|
-
agentId:
|
|
10400
|
-
sessionId:
|
|
10911
|
+
onData: protectedProcedure.input(z21.object({
|
|
10912
|
+
agentId: z21.string().max(128),
|
|
10913
|
+
sessionId: z21.string().max(128)
|
|
10401
10914
|
})).subscription(async function* ({ input }) {
|
|
10402
10915
|
const queue = [];
|
|
10403
|
-
let
|
|
10916
|
+
let resolve6 = null;
|
|
10404
10917
|
let done = false;
|
|
10405
10918
|
let unsubscribe;
|
|
10406
10919
|
try {
|
|
@@ -10410,16 +10923,16 @@ var terminalRouter = router({
|
|
|
10410
10923
|
{ sessionId: input.sessionId },
|
|
10411
10924
|
(data) => {
|
|
10412
10925
|
if (typeof data === "string") queue.push(data);
|
|
10413
|
-
if (
|
|
10414
|
-
|
|
10415
|
-
|
|
10926
|
+
if (resolve6) {
|
|
10927
|
+
resolve6();
|
|
10928
|
+
resolve6 = null;
|
|
10416
10929
|
}
|
|
10417
10930
|
},
|
|
10418
10931
|
() => {
|
|
10419
10932
|
done = true;
|
|
10420
|
-
if (
|
|
10421
|
-
|
|
10422
|
-
|
|
10933
|
+
if (resolve6) {
|
|
10934
|
+
resolve6();
|
|
10935
|
+
resolve6 = null;
|
|
10423
10936
|
}
|
|
10424
10937
|
}
|
|
10425
10938
|
);
|
|
@@ -10435,7 +10948,7 @@ var terminalRouter = router({
|
|
|
10435
10948
|
yield queue.shift();
|
|
10436
10949
|
} else {
|
|
10437
10950
|
await new Promise((r) => {
|
|
10438
|
-
|
|
10951
|
+
resolve6 = r;
|
|
10439
10952
|
});
|
|
10440
10953
|
}
|
|
10441
10954
|
}
|
|
@@ -10446,27 +10959,27 @@ var terminalRouter = router({
|
|
|
10446
10959
|
});
|
|
10447
10960
|
|
|
10448
10961
|
// ../server/src/trpc/routers/utility.ts
|
|
10449
|
-
import { z as
|
|
10962
|
+
import { z as z22 } from "zod";
|
|
10450
10963
|
var MAX_WAIT_MS = 5 * 60 * 1e3;
|
|
10451
10964
|
var utilityRouter = router({
|
|
10452
|
-
wait: protectedProcedure.input(
|
|
10453
|
-
ms:
|
|
10965
|
+
wait: protectedProcedure.input(z22.object({
|
|
10966
|
+
ms: z22.number().int().min(0).max(MAX_WAIT_MS)
|
|
10454
10967
|
})).mutation(async ({ input }) => {
|
|
10455
|
-
await new Promise((
|
|
10968
|
+
await new Promise((resolve6) => setTimeout(resolve6, input.ms));
|
|
10456
10969
|
return { waited: input.ms };
|
|
10457
10970
|
})
|
|
10458
10971
|
});
|
|
10459
10972
|
|
|
10460
10973
|
// ../server/src/trpc/routers/wiki.ts
|
|
10461
|
-
import { z as
|
|
10974
|
+
import { z as z23 } from "zod";
|
|
10462
10975
|
import { TRPCError as TRPCError18 } from "@trpc/server";
|
|
10463
|
-
import { readFile as
|
|
10464
|
-
import { resolve as resolvePath, join as
|
|
10465
|
-
var SCOPE_SCHEMA =
|
|
10466
|
-
|
|
10467
|
-
|
|
10976
|
+
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";
|
|
10977
|
+
import { resolve as resolvePath, join as join16, relative, sep as sep3, dirname as dirname3 } from "path";
|
|
10978
|
+
var SCOPE_SCHEMA = z23.union([
|
|
10979
|
+
z23.literal("global"),
|
|
10980
|
+
z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
|
|
10468
10981
|
]);
|
|
10469
|
-
var REL_PATH_SCHEMA =
|
|
10982
|
+
var REL_PATH_SCHEMA = z23.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
|
|
10470
10983
|
message: "Path must be a relative path without traversal"
|
|
10471
10984
|
});
|
|
10472
10985
|
var MAX_FILES = 500;
|
|
@@ -10486,9 +10999,9 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
10486
10999
|
"target",
|
|
10487
11000
|
"out"
|
|
10488
11001
|
]);
|
|
10489
|
-
var DocsEntrySchema =
|
|
10490
|
-
|
|
10491
|
-
|
|
11002
|
+
var DocsEntrySchema = z23.lazy(() => z23.union([
|
|
11003
|
+
z23.object({ type: z23.literal("file"), name: z23.string(), path: z23.string() }),
|
|
11004
|
+
z23.object({ type: z23.literal("dir"), name: z23.string(), path: z23.string(), children: z23.array(DocsEntrySchema) })
|
|
10492
11005
|
]));
|
|
10493
11006
|
async function resolveScope(scope) {
|
|
10494
11007
|
if (scope === "global") {
|
|
@@ -10498,13 +11011,13 @@ async function resolveScope(scope) {
|
|
|
10498
11011
|
throw new TRPCError18({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
|
|
10499
11012
|
}
|
|
10500
11013
|
const dataDirAbs = resolvePath(env.DATA_DIR);
|
|
10501
|
-
await
|
|
11014
|
+
await mkdir14(dataDirAbs, { recursive: true });
|
|
10502
11015
|
const base = dirname3(await realpath(dataDirAbs));
|
|
10503
11016
|
const target = resolvePath(base, sub);
|
|
10504
11017
|
if (target !== base && !(target + sep3).startsWith(base + sep3)) {
|
|
10505
11018
|
throw new TRPCError18({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
|
|
10506
11019
|
}
|
|
10507
|
-
await
|
|
11020
|
+
await mkdir14(target, { recursive: true });
|
|
10508
11021
|
const root = await realpath(target);
|
|
10509
11022
|
if (root !== base && !(root + sep3).startsWith(base + sep3)) {
|
|
10510
11023
|
throw new TRPCError18({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
|
|
@@ -10531,7 +11044,7 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
10531
11044
|
for (const e of entries) {
|
|
10532
11045
|
if (counter.n >= MAX_FILES) break;
|
|
10533
11046
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
10534
|
-
const abs =
|
|
11047
|
+
const abs = join16(dir, e.name);
|
|
10535
11048
|
const rel = relative(root, abs).split(sep3).join("/");
|
|
10536
11049
|
if (e.isDirectory()) {
|
|
10537
11050
|
if (IGNORED_DIRS.has(e.name)) continue;
|
|
@@ -10578,7 +11091,7 @@ async function resolveSafeLocal(root, relPath, requireExist) {
|
|
|
10578
11091
|
return resolvePath(realParent, target.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
10579
11092
|
}
|
|
10580
11093
|
var wikiRouter = router({
|
|
10581
|
-
list: protectedProcedure.input(
|
|
11094
|
+
list: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).output(z23.object({ root: z23.string(), entries: z23.array(DocsEntrySchema), truncated: z23.boolean() })).query(async ({ input }) => {
|
|
10582
11095
|
const target = await resolveScope(input.scope);
|
|
10583
11096
|
if (target.mode === "agent") {
|
|
10584
11097
|
return agentManager.agentQuery(target.agentId, "wiki.listRepoMarkdown", { cwd: target.cwd });
|
|
@@ -10587,7 +11100,7 @@ var wikiRouter = router({
|
|
|
10587
11100
|
const entries = await walkLocal(target.root, target.root, 0, counter);
|
|
10588
11101
|
return { root: target.root, entries, truncated: counter.n >= MAX_FILES };
|
|
10589
11102
|
}),
|
|
10590
|
-
read: protectedProcedure.input(
|
|
11103
|
+
read: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z23.object({ path: z23.string(), content: z23.string() })).query(async ({ input }) => {
|
|
10591
11104
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
10592
11105
|
throw new TRPCError18({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
10593
11106
|
}
|
|
@@ -10599,10 +11112,10 @@ var wikiRouter = router({
|
|
|
10599
11112
|
const s = await stat3(file);
|
|
10600
11113
|
if (!s.isFile()) throw new TRPCError18({ code: "BAD_REQUEST", message: "Pas un fichier" });
|
|
10601
11114
|
if (s.size > MAX_FILE_BYTES) throw new TRPCError18({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
|
|
10602
|
-
const content = await
|
|
11115
|
+
const content = await readFile13(file, "utf-8");
|
|
10603
11116
|
return { path: input.path, content };
|
|
10604
11117
|
}),
|
|
10605
|
-
write: protectedProcedure.input(
|
|
11118
|
+
write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
|
|
10606
11119
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
10607
11120
|
throw new TRPCError18({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
10608
11121
|
}
|
|
@@ -10611,11 +11124,11 @@ var wikiRouter = router({
|
|
|
10611
11124
|
return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
|
|
10612
11125
|
}
|
|
10613
11126
|
const file = await resolveSafeLocal(target.root, input.path, false);
|
|
10614
|
-
await
|
|
10615
|
-
await
|
|
11127
|
+
await mkdir14(dirname3(file), { recursive: true });
|
|
11128
|
+
await writeFile11(file, input.content, "utf-8");
|
|
10616
11129
|
return { path: input.path, ok: true };
|
|
10617
11130
|
}),
|
|
10618
|
-
createFile: protectedProcedure.input(
|
|
11131
|
+
createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
10619
11132
|
if (!/\.(md|markdown)$/i.test(input.path)) {
|
|
10620
11133
|
throw new TRPCError18({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
|
|
10621
11134
|
}
|
|
@@ -10630,20 +11143,20 @@ var wikiRouter = router({
|
|
|
10630
11143
|
} catch (err) {
|
|
10631
11144
|
if (err instanceof TRPCError18) throw err;
|
|
10632
11145
|
}
|
|
10633
|
-
await
|
|
10634
|
-
await
|
|
11146
|
+
await mkdir14(dirname3(file), { recursive: true });
|
|
11147
|
+
await writeFile11(file, "", "utf-8");
|
|
10635
11148
|
return { path: input.path, ok: true };
|
|
10636
11149
|
}),
|
|
10637
|
-
createDir: protectedProcedure.input(
|
|
11150
|
+
createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
10638
11151
|
const target = await resolveScope(input.scope);
|
|
10639
11152
|
if (target.mode === "agent") {
|
|
10640
11153
|
return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
|
|
10641
11154
|
}
|
|
10642
11155
|
const dir = await resolveSafeLocal(target.root, input.path, false);
|
|
10643
|
-
await
|
|
11156
|
+
await mkdir14(dir, { recursive: true });
|
|
10644
11157
|
return { path: input.path, ok: true };
|
|
10645
11158
|
}),
|
|
10646
|
-
rename: protectedProcedure.input(
|
|
11159
|
+
rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
10647
11160
|
const target = await resolveScope(input.scope);
|
|
10648
11161
|
if (target.mode === "agent") {
|
|
10649
11162
|
return agentManager.agentMutation(target.agentId, "wiki.renamePath", { cwd: target.cwd, fromPath: input.fromPath, toPath: input.toPath });
|
|
@@ -10656,11 +11169,11 @@ var wikiRouter = router({
|
|
|
10656
11169
|
} catch (err) {
|
|
10657
11170
|
if (err instanceof TRPCError18) throw err;
|
|
10658
11171
|
}
|
|
10659
|
-
await
|
|
10660
|
-
await
|
|
11172
|
+
await mkdir14(dirname3(to), { recursive: true });
|
|
11173
|
+
await rename10(from, to);
|
|
10661
11174
|
return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
|
|
10662
11175
|
}),
|
|
10663
|
-
ensureRoot: protectedProcedure.input(
|
|
11176
|
+
ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
|
|
10664
11177
|
if (input.scope === "global") {
|
|
10665
11178
|
await resolveScope("global");
|
|
10666
11179
|
return { ok: true };
|
|
@@ -10673,7 +11186,7 @@ var wikiRouter = router({
|
|
|
10673
11186
|
const cwd = (service.docsPath?.trim() || service.cwd || ".").toString();
|
|
10674
11187
|
return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
|
|
10675
11188
|
}),
|
|
10676
|
-
delete: protectedProcedure.input(
|
|
11189
|
+
delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
|
|
10677
11190
|
const target = await resolveScope(input.scope);
|
|
10678
11191
|
if (target.mode === "agent") {
|
|
10679
11192
|
return agentManager.agentMutation(target.agentId, "wiki.deletePath", { cwd: target.cwd, path: input.path });
|
|
@@ -10686,83 +11199,24 @@ var wikiRouter = router({
|
|
|
10686
11199
|
});
|
|
10687
11200
|
|
|
10688
11201
|
// ../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();
|
|
11202
|
+
var configRouter = router({
|
|
11203
|
+
cloud: publicProcedure.query(() => ({
|
|
11204
|
+
apiUrl: env.CLOUD_URL,
|
|
11205
|
+
appUrl: env.CLOUD_APP_URL
|
|
11206
|
+
}))
|
|
11207
|
+
});
|
|
10750
11208
|
|
|
10751
|
-
// ../server/src/
|
|
10752
|
-
import {
|
|
10753
|
-
import
|
|
11209
|
+
// ../server/src/trpc/routers/cloud-sync.ts
|
|
11210
|
+
import { z as z24 } from "zod";
|
|
11211
|
+
import { TRPCError as TRPCError19 } from "@trpc/server";
|
|
10754
11212
|
|
|
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";
|
|
11213
|
+
// ../server/src/services/cloud-sync-store.ts
|
|
11214
|
+
import { readFile as readFile14, writeFile as writeFile12, rename as rename11, mkdir as mkdir15, chmod as chmod9 } from "fs/promises";
|
|
10758
11215
|
import { join as join17 } from "path";
|
|
10759
|
-
var FILENAME5 = "cloud-
|
|
10760
|
-
var
|
|
10761
|
-
|
|
10762
|
-
session = null;
|
|
11216
|
+
var FILENAME5 = "cloud-sync.json";
|
|
11217
|
+
var CloudSyncStore = class {
|
|
11218
|
+
links = /* @__PURE__ */ new Map();
|
|
10763
11219
|
loaded = false;
|
|
10764
|
-
/** Pending login states (CSRF binding between `start` and the callback). */
|
|
10765
|
-
pending = /* @__PURE__ */ new Map();
|
|
10766
11220
|
getFilePath() {
|
|
10767
11221
|
return join17(env.DATA_DIR, FILENAME5);
|
|
10768
11222
|
}
|
|
@@ -10770,86 +11224,46 @@ var CloudSessionStore = class {
|
|
|
10770
11224
|
if (this.loaded) return;
|
|
10771
11225
|
try {
|
|
10772
11226
|
const raw = await readFile14(this.getFilePath(), "utf-8");
|
|
10773
|
-
|
|
11227
|
+
const data = JSON.parse(raw);
|
|
11228
|
+
for (const l of data) this.links.set(l.orgId, { orgId: l.orgId, cloudUrl: l.cloudUrl });
|
|
10774
11229
|
} catch {
|
|
10775
11230
|
}
|
|
10776
11231
|
this.loaded = true;
|
|
10777
11232
|
}
|
|
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
11233
|
async save() {
|
|
10813
11234
|
await mkdir15(env.DATA_DIR, { recursive: true });
|
|
10814
11235
|
const filePath = this.getFilePath();
|
|
10815
11236
|
const tmpPath = filePath + ".tmp";
|
|
10816
|
-
|
|
11237
|
+
const data = JSON.stringify(Array.from(this.links.values()), null, 2);
|
|
11238
|
+
await writeFile12(tmpPath, data, "utf-8");
|
|
10817
11239
|
await rename11(tmpPath, filePath);
|
|
10818
11240
|
await chmod9(filePath, 384);
|
|
10819
11241
|
}
|
|
10820
|
-
/** Store
|
|
10821
|
-
async
|
|
11242
|
+
/** Store (or replace) the sync link for an organization. */
|
|
11243
|
+
async upsert(orgId, cloudUrl) {
|
|
10822
11244
|
await this.load();
|
|
10823
|
-
this.
|
|
11245
|
+
this.links.set(orgId, { orgId, cloudUrl });
|
|
10824
11246
|
await this.save();
|
|
10825
11247
|
}
|
|
10826
|
-
|
|
10827
|
-
async getToken() {
|
|
11248
|
+
async remove(orgId) {
|
|
10828
11249
|
await this.load();
|
|
10829
|
-
if (
|
|
10830
|
-
try {
|
|
10831
|
-
return decryptValue(this.session.encryptedToken);
|
|
10832
|
-
} catch {
|
|
10833
|
-
return null;
|
|
10834
|
-
}
|
|
11250
|
+
if (this.links.delete(orgId)) await this.save();
|
|
10835
11251
|
}
|
|
10836
|
-
|
|
10837
|
-
|
|
11252
|
+
/** All sync links (for the bridge on startup). */
|
|
11253
|
+
async list() {
|
|
11254
|
+
await this.load();
|
|
11255
|
+
return Array.from(this.links.values());
|
|
10838
11256
|
}
|
|
10839
|
-
|
|
10840
|
-
async clear() {
|
|
11257
|
+
async get(orgId) {
|
|
10841
11258
|
await this.load();
|
|
10842
|
-
this.
|
|
10843
|
-
this.pending.clear();
|
|
10844
|
-
try {
|
|
10845
|
-
await unlink3(this.getFilePath());
|
|
10846
|
-
} catch {
|
|
10847
|
-
}
|
|
11259
|
+
return this.links.get(orgId) ?? null;
|
|
10848
11260
|
}
|
|
10849
11261
|
};
|
|
10850
|
-
var
|
|
11262
|
+
var cloudSyncStore = new CloudSyncStore();
|
|
10851
11263
|
|
|
10852
11264
|
// ../server/src/services/cloud-sync-bridge.ts
|
|
11265
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
11266
|
+
import * as Y from "yjs";
|
|
10853
11267
|
var LOCAL_ORIGIN = "local-bridge";
|
|
10854
11268
|
var REMOTE_ORIGIN = "remote";
|
|
10855
11269
|
var RECONNECT_DELAY_MS = 3e3;
|
|
@@ -10865,7 +11279,7 @@ var OrgSyncConnection = class {
|
|
|
10865
11279
|
projects = getCollection(this.doc, "projects");
|
|
10866
11280
|
services = getCollection(this.doc, "services");
|
|
10867
11281
|
environments = getCollection(this.doc, "environments");
|
|
10868
|
-
conn =
|
|
11282
|
+
conn = randomUUID11();
|
|
10869
11283
|
link;
|
|
10870
11284
|
stopped = false;
|
|
10871
11285
|
initializing = true;
|
|
@@ -10965,7 +11379,7 @@ var OrgSyncConnection = class {
|
|
|
10965
11379
|
await this.pushUpdate(diff);
|
|
10966
11380
|
} catch (err) {
|
|
10967
11381
|
if (err instanceof SyncAuthError) throw err;
|
|
10968
|
-
console.error(
|
|
11382
|
+
console.error("[cloud-sync] initial push failed for org %s:", this.link.orgId, err.message);
|
|
10969
11383
|
}
|
|
10970
11384
|
}
|
|
10971
11385
|
async pushUpdate(update) {
|
|
@@ -10983,7 +11397,7 @@ var OrgSyncConnection = class {
|
|
|
10983
11397
|
this.abort?.abort();
|
|
10984
11398
|
return;
|
|
10985
11399
|
}
|
|
10986
|
-
if (!this.stopped) console.error(
|
|
11400
|
+
if (!this.stopped) console.error("[cloud-sync] push failed for org %s:", this.link.orgId, err.message);
|
|
10987
11401
|
}
|
|
10988
11402
|
}
|
|
10989
11403
|
/**
|
|
@@ -11005,7 +11419,7 @@ var OrgSyncConnection = class {
|
|
|
11005
11419
|
await this.delay(UNAUTHENTICATED_RETRY_MS);
|
|
11006
11420
|
continue;
|
|
11007
11421
|
}
|
|
11008
|
-
if (!this.stopped) console.error(
|
|
11422
|
+
if (!this.stopped) console.error("[cloud-sync] stream error for org %s:", this.link.orgId, err.message);
|
|
11009
11423
|
}
|
|
11010
11424
|
await this.delay(RECONNECT_DELAY_MS);
|
|
11011
11425
|
}
|
|
@@ -11095,7 +11509,7 @@ var OrgSyncConnection = class {
|
|
|
11095
11509
|
});
|
|
11096
11510
|
await projectStore.addEnvironment(projectId, localEnv.id);
|
|
11097
11511
|
} catch (err) {
|
|
11098
|
-
console.error(
|
|
11512
|
+
console.error("[cloud-sync] env bootstrap failed for project %s:", projectId, err.message);
|
|
11099
11513
|
}
|
|
11100
11514
|
}
|
|
11101
11515
|
/**
|
|
@@ -11198,7 +11612,7 @@ var CloudSyncBridge = class {
|
|
|
11198
11612
|
try {
|
|
11199
11613
|
await conn.start();
|
|
11200
11614
|
} catch (err) {
|
|
11201
|
-
console.error(
|
|
11615
|
+
console.error("[cloud-sync] failed to start org %s:", link.orgId, err.message);
|
|
11202
11616
|
}
|
|
11203
11617
|
}
|
|
11204
11618
|
disconnect(orgId) {
|
|
@@ -11215,113 +11629,6 @@ var CloudSyncBridge = class {
|
|
|
11215
11629
|
};
|
|
11216
11630
|
var cloudSyncBridge = new CloudSyncBridge();
|
|
11217
11631
|
|
|
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
11632
|
// ../server/src/services/master-password.ts
|
|
11326
11633
|
import { randomBytes as randomBytes4, createCipheriv, createDecipheriv } from "crypto";
|
|
11327
11634
|
import { readFile as readFile15, writeFile as writeFile13, chmod as chmod10, stat as stat4, rename as rename12 } from "fs/promises";
|
|
@@ -11492,25 +11799,25 @@ async function hasEncryptedKeyFile(dataDir) {
|
|
|
11492
11799
|
async function reconcileOrgCanary(orgId) {
|
|
11493
11800
|
const key = getEncryptionKey();
|
|
11494
11801
|
if (!key) {
|
|
11495
|
-
throw new
|
|
11802
|
+
throw new TRPCError19({ code: "PRECONDITION_FAILED", message: "Encryption key not loaded." });
|
|
11496
11803
|
}
|
|
11497
11804
|
const local = await readVerifyFile(env.DATA_DIR);
|
|
11498
11805
|
if (!local) {
|
|
11499
|
-
throw new
|
|
11806
|
+
throw new TRPCError19({ code: "PRECONDITION_FAILED", message: "This machine has no key canary yet." });
|
|
11500
11807
|
}
|
|
11501
11808
|
const res = await cloudFetch("/api/auth/organization/canary", {
|
|
11502
11809
|
method: "POST",
|
|
11503
11810
|
body: { organizationId: orgId, canary: local }
|
|
11504
11811
|
});
|
|
11505
11812
|
if (!res.ok) {
|
|
11506
|
-
throw new
|
|
11813
|
+
throw new TRPCError19({ code: "BAD_GATEWAY", message: "Could not reach the organization on the cloud." });
|
|
11507
11814
|
}
|
|
11508
11815
|
const { canary } = await res.json();
|
|
11509
11816
|
if (typeof canary !== "string") {
|
|
11510
|
-
throw new
|
|
11817
|
+
throw new TRPCError19({ code: "BAD_GATEWAY", message: "The cloud returned no key canary." });
|
|
11511
11818
|
}
|
|
11512
11819
|
if (!matchesCanary(canary, key)) {
|
|
11513
|
-
throw new
|
|
11820
|
+
throw new TRPCError19({
|
|
11514
11821
|
code: "FORBIDDEN",
|
|
11515
11822
|
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
11823
|
});
|
|
@@ -11572,8 +11879,8 @@ var appRouter = router({
|
|
|
11572
11879
|
scenario: scenarioRouter,
|
|
11573
11880
|
git: gitRouter,
|
|
11574
11881
|
debug: debugRouter,
|
|
11575
|
-
kanban: kanbanRouter,
|
|
11576
11882
|
kanbanQueue: kanbanQueueRouter,
|
|
11883
|
+
kanban: kanbanRouter,
|
|
11577
11884
|
terminal: terminalRouter,
|
|
11578
11885
|
utility: utilityRouter,
|
|
11579
11886
|
wiki: wikiRouter,
|
|
@@ -11689,7 +11996,7 @@ async function ensureDataGitignore(dataDir) {
|
|
|
11689
11996
|
// ../server/src/migrations/migrate-from-legacy.ts
|
|
11690
11997
|
import { createRequire } from "module";
|
|
11691
11998
|
import { randomBytes as randomBytes5, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "crypto";
|
|
11692
|
-
import { randomUUID as
|
|
11999
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
11693
12000
|
import {
|
|
11694
12001
|
readFile as readFile16,
|
|
11695
12002
|
writeFile as writeFile15,
|
|
@@ -11831,8 +12138,8 @@ async function atomicWrite(path, data) {
|
|
|
11831
12138
|
await rename13(tmp, path);
|
|
11832
12139
|
}
|
|
11833
12140
|
async function runLegacyMigration(dataDir) {
|
|
11834
|
-
const { resolve:
|
|
11835
|
-
const abCwd =
|
|
12141
|
+
const { resolve: resolve6 } = await import("path");
|
|
12142
|
+
const abCwd = resolve6(process.cwd());
|
|
11836
12143
|
const legacyRoot = join21(abCwd, env.DATA_DIR);
|
|
11837
12144
|
const legacyKeyPath = join21(legacyRoot, "dbs", "encryption-key.json");
|
|
11838
12145
|
try {
|
|
@@ -11901,11 +12208,11 @@ async function runLegacyMigration(dataDir) {
|
|
|
11901
12208
|
const data = await readEncrypted(join21(parsersDir, file));
|
|
11902
12209
|
if (data?.id) legacyParsers.push(data);
|
|
11903
12210
|
}
|
|
11904
|
-
const projectMonoId =
|
|
12211
|
+
const projectMonoId = randomUUID12();
|
|
11905
12212
|
const envIdMap = {};
|
|
11906
12213
|
const labelToKey = {};
|
|
11907
12214
|
for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
|
|
11908
|
-
envIdMap[name] =
|
|
12215
|
+
envIdMap[name] = randomUUID12();
|
|
11909
12216
|
if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
|
|
11910
12217
|
}
|
|
11911
12218
|
const runeEnvs = [];
|
|
@@ -11930,7 +12237,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11930
12237
|
if (hasInvalidInterpolation(value)) continue;
|
|
11931
12238
|
const cleaned = cleanVarKey(key);
|
|
11932
12239
|
if (inheritedVars[cleaned] === value) continue;
|
|
11933
|
-
const varId =
|
|
12240
|
+
const varId = randomUUID12();
|
|
11934
12241
|
variables[varId] = makeVariable(cleaned, value);
|
|
11935
12242
|
varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
|
|
11936
12243
|
}
|
|
@@ -11951,7 +12258,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11951
12258
|
const serviceIdMap = {};
|
|
11952
12259
|
const runeServices = [];
|
|
11953
12260
|
for (const [name, legacy] of Object.entries(legacyServices)) {
|
|
11954
|
-
const id =
|
|
12261
|
+
const id = randomUUID12();
|
|
11955
12262
|
serviceIdMap[name] = id;
|
|
11956
12263
|
const rootPath = legacy.rootPath || "";
|
|
11957
12264
|
const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
|
|
@@ -11960,7 +12267,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
11960
12267
|
const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
|
|
11961
12268
|
if (!parts.length) continue;
|
|
11962
12269
|
commands.push({
|
|
11963
|
-
id: cmd.id ||
|
|
12270
|
+
id: cmd.id || randomUUID12(),
|
|
11964
12271
|
label: "Launch",
|
|
11965
12272
|
command: parts.join(" "),
|
|
11966
12273
|
cwd: cmd.spawnOptions?.cwd || "",
|
|
@@ -12021,7 +12328,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12021
12328
|
const legacyBootstrapCmds = container.bootstrap?.commands || [];
|
|
12022
12329
|
if (legacyBootstrapCmds.length > 0) {
|
|
12023
12330
|
dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
|
|
12024
|
-
id: bs.id ||
|
|
12331
|
+
id: bs.id || randomUUID12(),
|
|
12025
12332
|
label: bs.label || `Bootstrap ${idx + 1}`,
|
|
12026
12333
|
command: bs.cmd || "",
|
|
12027
12334
|
...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
|
|
@@ -12069,7 +12376,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12069
12376
|
cwd: serviceCwd,
|
|
12070
12377
|
runner: dockerConfig ? "docker" : "native",
|
|
12071
12378
|
shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
|
|
12072
|
-
id: s.id ||
|
|
12379
|
+
id: s.id || randomUUID12(),
|
|
12073
12380
|
label: s.label || s.spawnCmd || "",
|
|
12074
12381
|
command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
|
|
12075
12382
|
...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
|
|
@@ -12109,7 +12416,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12109
12416
|
);
|
|
12110
12417
|
if (!svcScopedEnv) {
|
|
12111
12418
|
svcScopedEnv = {
|
|
12112
|
-
id:
|
|
12419
|
+
id: randomUUID12(),
|
|
12113
12420
|
projectId: projectMonoId,
|
|
12114
12421
|
name: `${service?.name || name} (${globalEnv.name})`,
|
|
12115
12422
|
scope: "service",
|
|
@@ -12133,7 +12440,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12133
12440
|
const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
|
|
12134
12441
|
if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
|
|
12135
12442
|
const cleaned = cleanVarKey(varKey);
|
|
12136
|
-
const varId =
|
|
12443
|
+
const varId = randomUUID12();
|
|
12137
12444
|
svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
|
|
12138
12445
|
existingKeys.add(cleaned);
|
|
12139
12446
|
}
|
|
@@ -12153,7 +12460,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12153
12460
|
);
|
|
12154
12461
|
if (!svcScopedEnv) {
|
|
12155
12462
|
svcScopedEnv = {
|
|
12156
|
-
id:
|
|
12463
|
+
id: randomUUID12(),
|
|
12157
12464
|
projectId: projectMonoId,
|
|
12158
12465
|
name: `${service?.name || serviceName} (${globalEnv.name})`,
|
|
12159
12466
|
scope: "service",
|
|
@@ -12174,7 +12481,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
12174
12481
|
const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
|
|
12175
12482
|
let varId = baseEntry?.[0];
|
|
12176
12483
|
if (!varId) {
|
|
12177
|
-
varId =
|
|
12484
|
+
varId = randomUUID12();
|
|
12178
12485
|
svcScopedEnv.variables[varId] = {
|
|
12179
12486
|
key: cleaned,
|
|
12180
12487
|
value: { value: "", pre: "", post: "", isSecret: false, injectIntoAi: false }
|
|
@@ -12204,13 +12511,13 @@ async function runLegacyMigration(dataDir) {
|
|
|
12204
12511
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
12205
12512
|
}
|
|
12206
12513
|
} else {
|
|
12207
|
-
flatOverrides[
|
|
12514
|
+
flatOverrides[randomUUID12()] = makeVariable(cleaned, value);
|
|
12208
12515
|
}
|
|
12209
12516
|
} else {
|
|
12210
12517
|
const cleaned = cleanVarKey(key);
|
|
12211
12518
|
const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
|
|
12212
12519
|
if (!existingVarId) {
|
|
12213
|
-
const varId =
|
|
12520
|
+
const varId = randomUUID12();
|
|
12214
12521
|
env2.variables[varId] = makeVariable(cleaned, value);
|
|
12215
12522
|
varIdLookup.set(`${envId}:${cleaned}`, varId);
|
|
12216
12523
|
}
|
|
@@ -12298,9 +12605,9 @@ async function findLegacyKeyInBackup(dataDir) {
|
|
|
12298
12605
|
return null;
|
|
12299
12606
|
}
|
|
12300
12607
|
async function runOverrideOnlyMigration(dataDir) {
|
|
12301
|
-
const { resolve:
|
|
12302
|
-
const abDataDir =
|
|
12303
|
-
const abCwd =
|
|
12608
|
+
const { resolve: resolve6 } = await import("path");
|
|
12609
|
+
const abDataDir = resolve6(dataDir);
|
|
12610
|
+
const abCwd = resolve6(process.cwd());
|
|
12304
12611
|
const candidateDirs = [
|
|
12305
12612
|
join21(abCwd, ".runeya", "dbs", "overrides"),
|
|
12306
12613
|
join21(abDataDir, "dbs", "overrides")
|
|
@@ -12395,7 +12702,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12395
12702
|
if (isSameAsBase(value, baseVar)) continue;
|
|
12396
12703
|
flatOverrides[varId] = makeVariable(cleaned, value);
|
|
12397
12704
|
} else {
|
|
12398
|
-
flatOverrides[
|
|
12705
|
+
flatOverrides[randomUUID12()] = makeVariable(cleaned, value);
|
|
12399
12706
|
}
|
|
12400
12707
|
}
|
|
12401
12708
|
}
|
|
@@ -12432,7 +12739,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12432
12739
|
);
|
|
12433
12740
|
if (!svcScopedEnv) {
|
|
12434
12741
|
svcScopedEnv = {
|
|
12435
|
-
id:
|
|
12742
|
+
id: randomUUID12(),
|
|
12436
12743
|
projectId: globalEnv["projectId"],
|
|
12437
12744
|
name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
|
|
12438
12745
|
scope: "service",
|
|
@@ -12454,7 +12761,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
12454
12761
|
const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
|
|
12455
12762
|
let varId = baseEntry?.[0];
|
|
12456
12763
|
if (!varId) {
|
|
12457
|
-
varId =
|
|
12764
|
+
varId = randomUUID12();
|
|
12458
12765
|
svcScopedEnv["variables"][varId] = {
|
|
12459
12766
|
key: cleaned,
|
|
12460
12767
|
value: { value: "", pre: "", post: "", isSecret: false, injectIntoAi: false }
|
|
@@ -12903,7 +13210,7 @@ async function createLocalServer() {
|
|
|
12903
13210
|
}
|
|
12904
13211
|
}
|
|
12905
13212
|
if (!existsSync2(env.DATA_DIR)) await mkdir17(env.DATA_DIR);
|
|
12906
|
-
const absoluteDataDir =
|
|
13213
|
+
const absoluteDataDir = resolve5(env.DATA_DIR);
|
|
12907
13214
|
console.log(`[server] Data directory: ${absoluteDataDir}`);
|
|
12908
13215
|
await ensureDataGitignore(env.DATA_DIR);
|
|
12909
13216
|
await settingsManager.getSettings();
|
|
@@ -13012,7 +13319,102 @@ async function createLocalServer() {
|
|
|
13012
13319
|
res.status(502).send("Could not reach the Runeya cloud.");
|
|
13013
13320
|
}
|
|
13014
13321
|
});
|
|
13015
|
-
|
|
13322
|
+
function proxyCloudStream(localPath, upstreamPath) {
|
|
13323
|
+
app.get(localPath, async (req, res) => {
|
|
13324
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13325
|
+
try {
|
|
13326
|
+
await verifyJwt(jwt);
|
|
13327
|
+
} catch {
|
|
13328
|
+
res.status(401).end();
|
|
13329
|
+
return;
|
|
13330
|
+
}
|
|
13331
|
+
const organizationId = typeof req.query["organizationId"] === "string" ? req.query["organizationId"] : "";
|
|
13332
|
+
if (!organizationId) {
|
|
13333
|
+
res.status(400).end();
|
|
13334
|
+
return;
|
|
13335
|
+
}
|
|
13336
|
+
const token = await cloudSessionStore.getToken();
|
|
13337
|
+
if (!token) {
|
|
13338
|
+
res.status(401).end();
|
|
13339
|
+
return;
|
|
13340
|
+
}
|
|
13341
|
+
const upstreamAbort = new AbortController();
|
|
13342
|
+
req.on("close", () => upstreamAbort.abort());
|
|
13343
|
+
try {
|
|
13344
|
+
const upstream = await fetch(
|
|
13345
|
+
`${getCloudApiUrl()}${upstreamPath}?organizationId=${encodeURIComponent(organizationId)}`,
|
|
13346
|
+
{
|
|
13347
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
13348
|
+
signal: upstreamAbort.signal
|
|
13349
|
+
}
|
|
13350
|
+
);
|
|
13351
|
+
if (!upstream.ok || !upstream.body) {
|
|
13352
|
+
res.status(upstream.status === 401 ? 401 : 502).end();
|
|
13353
|
+
return;
|
|
13354
|
+
}
|
|
13355
|
+
res.writeHead(200, {
|
|
13356
|
+
"Content-Type": "text/event-stream",
|
|
13357
|
+
"Cache-Control": "no-cache",
|
|
13358
|
+
Connection: "keep-alive",
|
|
13359
|
+
"X-Accel-Buffering": "no"
|
|
13360
|
+
});
|
|
13361
|
+
const reader = upstream.body.getReader();
|
|
13362
|
+
for (; ; ) {
|
|
13363
|
+
const { done, value } = await reader.read();
|
|
13364
|
+
if (done) break;
|
|
13365
|
+
res.write(value);
|
|
13366
|
+
}
|
|
13367
|
+
res.end();
|
|
13368
|
+
} catch {
|
|
13369
|
+
if (!res.headersSent) res.status(502).end();
|
|
13370
|
+
else res.end();
|
|
13371
|
+
}
|
|
13372
|
+
});
|
|
13373
|
+
}
|
|
13374
|
+
app.post(
|
|
13375
|
+
"/api/cloud/kanban/image",
|
|
13376
|
+
express.raw({ type: () => true, limit: "64mb" }),
|
|
13377
|
+
async (req, res) => {
|
|
13378
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13379
|
+
try {
|
|
13380
|
+
await verifyJwt(jwt);
|
|
13381
|
+
} catch {
|
|
13382
|
+
res.status(401).end();
|
|
13383
|
+
return;
|
|
13384
|
+
}
|
|
13385
|
+
const organizationId = typeof req.query["organizationId"] === "string" ? req.query["organizationId"] : "";
|
|
13386
|
+
if (!organizationId) {
|
|
13387
|
+
res.status(400).end();
|
|
13388
|
+
return;
|
|
13389
|
+
}
|
|
13390
|
+
const token = await cloudSessionStore.getToken();
|
|
13391
|
+
if (!token) {
|
|
13392
|
+
res.status(401).end();
|
|
13393
|
+
return;
|
|
13394
|
+
}
|
|
13395
|
+
const upstreamQuery = new URLSearchParams({ organizationId });
|
|
13396
|
+
if (typeof req.query["name"] === "string") upstreamQuery.set("name", req.query["name"]);
|
|
13397
|
+
try {
|
|
13398
|
+
const upstream = await fetch(
|
|
13399
|
+
`${getCloudApiUrl()}/api/kanban/image?${upstreamQuery.toString()}`,
|
|
13400
|
+
{
|
|
13401
|
+
method: "POST",
|
|
13402
|
+
headers: {
|
|
13403
|
+
Authorization: `Bearer ${token}`,
|
|
13404
|
+
"Content-Type": String(req.headers["content-type"] ?? "application/octet-stream")
|
|
13405
|
+
},
|
|
13406
|
+
body: req.body,
|
|
13407
|
+
// A 55 MB body over a home upstream link takes minutes, not seconds.
|
|
13408
|
+
signal: AbortSignal.timeout(3e5)
|
|
13409
|
+
}
|
|
13410
|
+
);
|
|
13411
|
+
res.status(upstream.status).json(await upstream.json().catch(() => ({})));
|
|
13412
|
+
} catch {
|
|
13413
|
+
res.status(502).json({ error: "upload-failed" });
|
|
13414
|
+
}
|
|
13415
|
+
}
|
|
13416
|
+
);
|
|
13417
|
+
app.delete("/api/cloud/kanban/file/:id", async (req, res) => {
|
|
13016
13418
|
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13017
13419
|
try {
|
|
13018
13420
|
await verifyJwt(jwt);
|
|
@@ -13020,9 +13422,27 @@ async function createLocalServer() {
|
|
|
13020
13422
|
res.status(401).end();
|
|
13021
13423
|
return;
|
|
13022
13424
|
}
|
|
13023
|
-
const
|
|
13024
|
-
if (!
|
|
13025
|
-
res.status(
|
|
13425
|
+
const token = await cloudSessionStore.getToken();
|
|
13426
|
+
if (!token) {
|
|
13427
|
+
res.status(401).end();
|
|
13428
|
+
return;
|
|
13429
|
+
}
|
|
13430
|
+
try {
|
|
13431
|
+
const upstream = await fetch(
|
|
13432
|
+
`${getCloudApiUrl()}/api/kanban/file/${encodeURIComponent(req.params.id)}`,
|
|
13433
|
+
{ method: "DELETE", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(15e3) }
|
|
13434
|
+
);
|
|
13435
|
+
res.status(upstream.status).end();
|
|
13436
|
+
} catch {
|
|
13437
|
+
res.status(502).end();
|
|
13438
|
+
}
|
|
13439
|
+
});
|
|
13440
|
+
app.get("/api/cloud/kanban/file/:id", async (req, res) => {
|
|
13441
|
+
const jwt = typeof req.query["token"] === "string" ? req.query["token"] : "";
|
|
13442
|
+
try {
|
|
13443
|
+
await verifyJwt(jwt);
|
|
13444
|
+
} catch {
|
|
13445
|
+
res.status(401).end();
|
|
13026
13446
|
return;
|
|
13027
13447
|
}
|
|
13028
13448
|
const token = await cloudSessionStore.getToken();
|
|
@@ -13030,38 +13450,27 @@ async function createLocalServer() {
|
|
|
13030
13450
|
res.status(401).end();
|
|
13031
13451
|
return;
|
|
13032
13452
|
}
|
|
13033
|
-
const upstreamAbort = new AbortController();
|
|
13034
|
-
req.on("close", () => upstreamAbort.abort());
|
|
13035
13453
|
try {
|
|
13036
13454
|
const upstream = await fetch(
|
|
13037
|
-
`${getCloudApiUrl()}/api/
|
|
13038
|
-
{
|
|
13039
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
13040
|
-
signal: upstreamAbort.signal
|
|
13041
|
-
}
|
|
13455
|
+
`${getCloudApiUrl()}/api/kanban/file/${encodeURIComponent(req.params.id)}`,
|
|
13456
|
+
{ headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(3e5) }
|
|
13042
13457
|
);
|
|
13043
|
-
if (!upstream.ok
|
|
13044
|
-
res.status(upstream.status
|
|
13458
|
+
if (!upstream.ok) {
|
|
13459
|
+
res.status(upstream.status).end();
|
|
13045
13460
|
return;
|
|
13046
13461
|
}
|
|
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();
|
|
13462
|
+
res.set("Content-Type", upstream.headers.get("content-type") ?? "application/octet-stream");
|
|
13463
|
+
const disposition = upstream.headers.get("content-disposition");
|
|
13464
|
+
if (disposition) res.set("Content-Disposition", disposition);
|
|
13465
|
+
res.set("X-Content-Type-Options", "nosniff");
|
|
13466
|
+
res.set("Content-Security-Policy", "default-src 'none'; sandbox");
|
|
13467
|
+
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
13060
13468
|
} catch {
|
|
13061
|
-
|
|
13062
|
-
else res.end();
|
|
13469
|
+
res.status(502).end();
|
|
13063
13470
|
}
|
|
13064
13471
|
});
|
|
13472
|
+
proxyCloudStream("/api/cloud/chat/stream", "/api/chat/stream");
|
|
13473
|
+
proxyCloudStream("/api/cloud/kanban/stream", "/api/kanban/stream");
|
|
13065
13474
|
app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
|
|
13066
13475
|
try {
|
|
13067
13476
|
const body = req.body;
|
|
@@ -13242,7 +13651,18 @@ async function createLocalServer() {
|
|
|
13242
13651
|
});
|
|
13243
13652
|
const CONV_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
13244
13653
|
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
|
-
|
|
13654
|
+
const uploadSingleImage = (req, res, next) => {
|
|
13655
|
+
upload.single("image")(req, res, (err) => {
|
|
13656
|
+
if (err) {
|
|
13657
|
+
const isTooLarge = err.code === "LIMIT_FILE_SIZE";
|
|
13658
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
13659
|
+
res.status(isTooLarge ? 413 : 400).json({ error: message });
|
|
13660
|
+
return;
|
|
13661
|
+
}
|
|
13662
|
+
next();
|
|
13663
|
+
});
|
|
13664
|
+
};
|
|
13665
|
+
app.post("/api/images/upload", uploadSingleImage, async (req, res) => {
|
|
13246
13666
|
try {
|
|
13247
13667
|
if (!req.file) {
|
|
13248
13668
|
res.status(400).json({ error: "No image file provided" });
|
|
@@ -13280,7 +13700,7 @@ async function createLocalServer() {
|
|
|
13280
13700
|
res.status(400).json({ error: "Invalid imageId" });
|
|
13281
13701
|
return;
|
|
13282
13702
|
}
|
|
13283
|
-
const result = await imageStorageService.getImagePath(imageId);
|
|
13703
|
+
const result = await imageStorageService.getImagePath(imageId, conversationId);
|
|
13284
13704
|
if (!result) {
|
|
13285
13705
|
res.status(404).json({ error: "Image not found" });
|
|
13286
13706
|
return;
|
|
@@ -13362,6 +13782,7 @@ async function createLocalServer() {
|
|
|
13362
13782
|
const shutdown = async () => {
|
|
13363
13783
|
console.log("[server] Shutting down...");
|
|
13364
13784
|
clearInterval(wsTickInterval);
|
|
13785
|
+
stopClaudeTranscriptWatcher();
|
|
13365
13786
|
imageCleanupService.stop();
|
|
13366
13787
|
cloudSyncBridge.stopAll();
|
|
13367
13788
|
await Promise.all([
|
|
@@ -13370,12 +13791,12 @@ async function createLocalServer() {
|
|
|
13370
13791
|
]);
|
|
13371
13792
|
server.closeAllConnections();
|
|
13372
13793
|
await Promise.race([
|
|
13373
|
-
new Promise((
|
|
13374
|
-
new Promise((
|
|
13794
|
+
new Promise((resolve6) => wss.close(() => resolve6())),
|
|
13795
|
+
new Promise((resolve6) => setTimeout(resolve6, 2e3))
|
|
13375
13796
|
]);
|
|
13376
13797
|
await Promise.race([
|
|
13377
|
-
new Promise((
|
|
13378
|
-
new Promise((
|
|
13798
|
+
new Promise((resolve6) => server.close(() => resolve6())),
|
|
13799
|
+
new Promise((resolve6) => setTimeout(resolve6, 2e3))
|
|
13379
13800
|
]);
|
|
13380
13801
|
console.log("[server] Shutdown complete");
|
|
13381
13802
|
};
|
|
@@ -13396,7 +13817,7 @@ var PullEnvError = class extends Error {
|
|
|
13396
13817
|
};
|
|
13397
13818
|
async function promptPassword() {
|
|
13398
13819
|
process.stderr.write("Master password: ");
|
|
13399
|
-
return new Promise((
|
|
13820
|
+
return new Promise((resolve6) => {
|
|
13400
13821
|
const stdin = process.stdin;
|
|
13401
13822
|
const wasRaw = stdin.isRaw;
|
|
13402
13823
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
@@ -13409,7 +13830,7 @@ async function promptPassword() {
|
|
|
13409
13830
|
stdin.pause();
|
|
13410
13831
|
stdin.removeListener("data", onData);
|
|
13411
13832
|
process.stderr.write("\n");
|
|
13412
|
-
|
|
13833
|
+
resolve6(input);
|
|
13413
13834
|
} else if (ch === "") {
|
|
13414
13835
|
process.exit(1);
|
|
13415
13836
|
} else if (ch === "\x7F" || ch === "\b") {
|
|
@@ -13466,13 +13887,13 @@ async function pullEnv(opts) {
|
|
|
13466
13887
|
|
|
13467
13888
|
// ../server/src/index.ts
|
|
13468
13889
|
dotenv.config({ quiet: true });
|
|
13469
|
-
if (process.env["
|
|
13470
|
-
const resolved = resolvePath2(process.env["
|
|
13890
|
+
if (process.env["RUNEYA_DATA_DIR"]) {
|
|
13891
|
+
const resolved = resolvePath2(process.env["RUNEYA_DATA_DIR"], "..");
|
|
13471
13892
|
process.chdir(resolved);
|
|
13472
|
-
console.log(`[server] Working directory: ${resolved} (from
|
|
13893
|
+
console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
|
|
13473
13894
|
}
|
|
13474
13895
|
function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
|
|
13475
|
-
return new Promise((
|
|
13896
|
+
return new Promise((resolve6, reject) => {
|
|
13476
13897
|
let attempts = 0;
|
|
13477
13898
|
const wssErrorHandler = () => {
|
|
13478
13899
|
};
|
|
@@ -13493,7 +13914,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
|
|
|
13493
13914
|
server.listen(port, host, () => {
|
|
13494
13915
|
server.removeListener("error", onError);
|
|
13495
13916
|
wss?.removeListener("error", wssErrorHandler);
|
|
13496
|
-
|
|
13917
|
+
resolve6();
|
|
13497
13918
|
});
|
|
13498
13919
|
};
|
|
13499
13920
|
tryListen();
|
|
@@ -13540,4 +13961,4 @@ export {
|
|
|
13540
13961
|
createLocalServer,
|
|
13541
13962
|
pullEnv
|
|
13542
13963
|
};
|
|
13543
|
-
//# sourceMappingURL=src-
|
|
13964
|
+
//# sourceMappingURL=src-EGBDYHL5.js.map
|