@prismshadow/penguin-server 0.2.11 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api/types.d.ts +3650 -85
- package/dist/api/types.js +9 -0
- package/dist/auth-token.d.ts +22 -1
- package/dist/auth-token.js +7 -5
- package/dist/auth-token.js.map +1 -1
- package/dist/benchmark-package.d.ts +39 -0
- package/dist/benchmark-package.js +97 -0
- package/dist/benchmark-package.js.map +1 -0
- package/dist/chunk-54E64LWB.js +63 -0
- package/dist/chunk-54E64LWB.js.map +1 -0
- package/dist/{chunk-Q65VPAQS.js → chunk-5Z4UCHDB.js} +172 -108
- package/dist/chunk-5Z4UCHDB.js.map +1 -0
- package/dist/chunk-B5IP37ZQ.js +56 -0
- package/dist/chunk-HX3WBUYX.js +18 -0
- package/dist/chunk-HX3WBUYX.js.map +1 -0
- package/dist/chunk-JKJXQTHH.js +19 -0
- package/dist/chunk-JKJXQTHH.js.map +1 -0
- package/dist/chunk-LPPVGHYD.js +83 -0
- package/dist/chunk-LPPVGHYD.js.map +1 -0
- package/dist/chunk-NVWAYYRX.js +981 -0
- package/dist/chunk-NVWAYYRX.js.map +1 -0
- package/dist/chunk-Q4ON2L6E.js +52 -0
- package/dist/chunk-Q4ON2L6E.js.map +1 -0
- package/dist/chunk-QFV3ERQO.js +20 -0
- package/dist/chunk-QFV3ERQO.js.map +1 -0
- package/dist/{chunk-HOS3W3UP.js → chunk-TOOTEEM4.js} +50 -8
- package/dist/chunk-TOOTEEM4.js.map +1 -0
- package/dist/hmr/manifest.d.ts +1 -77
- package/dist/hmr/manifest.js +6 -1
- package/dist/index.js +86336 -22980
- package/dist/index.js.map +1 -1
- package/dist/initial-password.js +1 -0
- package/dist/install.ps1 +16 -6
- package/dist/install.sh +24 -10
- package/dist/lock.js +1 -0
- package/dist/machine-status.js +2 -1
- package/dist/machine-status.js.map +1 -1
- package/dist/plugin/index.d.ts +3384 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/reset-admin-password.js +8 -5
- package/dist/reset-admin-password.js.map +1 -1
- package/dist/secret-file.js +4 -13
- package/dist/secret-file.js.map +1 -1
- package/dist/version-report.d.ts +3 -0
- package/dist/version-report.js +3 -2
- package/package.json +19 -11
- package/web-dist/assets/KaTeX_Size3-Regular-gV2CO0n9.woff2 +0 -0
- package/web-dist/assets/abnfDiagram-VCTEODGH-CKWh4hoA.js +1 -0
- package/web-dist/assets/{addon-clipboard-1cARCaK9.js → addon-clipboard-DxLqat7q.js} +1 -1
- package/web-dist/assets/{addon-fit-BWdzl8Er.js → addon-fit-B8oPGPkW.js} +1 -1
- package/web-dist/assets/{addon-web-links-DjXuvn4Z.js → addon-web-links-C0W5TcK0.js} +1 -1
- package/web-dist/assets/arc-Dp9EkaAH.js +1 -0
- package/web-dist/assets/architectureDiagram-5GKGNRK7-BQ3LUcle.js +36 -0
- package/web-dist/assets/blockDiagram-I7D4REHJ-CZzyU0Jj.js +129 -0
- package/web-dist/assets/c-BG-tZi7o.js +1 -0
- package/web-dist/assets/c4Diagram-7LVT6UL2-Qps3jeAu.js +38 -0
- package/web-dist/assets/channel--PblgLlC.js +1 -0
- package/web-dist/assets/chunk-2Q5K7J3B-CQMntzuX.js +1 -0
- package/web-dist/assets/chunk-5VM5RSS4-2H2ZJAte.js +15 -0
- package/web-dist/assets/chunk-F27PBJKO-B30xCNBv.js +1 -0
- package/web-dist/assets/chunk-IMKFNOWR-Cyfx4zDs.js +231 -0
- package/web-dist/assets/chunk-JWPE2WC7-DADUytTh.js +1 -0
- package/web-dist/assets/chunk-POPQ4Y6H-9bjsAxLS.js +1 -0
- package/web-dist/assets/chunk-SVP7TREG-B9xO1VpN.js +88 -0
- package/web-dist/assets/chunk-TICWLB2K-B6oDkrep.js +206 -0
- package/web-dist/assets/chunk-XXDRQBXY-BbU8DR4B.js +1 -0
- package/web-dist/assets/classDiagram-ZZMXUADV-mn8tbuqa.js +1 -0
- package/web-dist/assets/classDiagram-v2-VYDZK3BY-mn8tbuqa.js +1 -0
- package/web-dist/assets/cose-bilkent-JH36ORCC-pkbuyec-.js +1 -0
- package/web-dist/assets/cpp-CaeG3uDQ.js +1 -0
- package/web-dist/assets/css-Br67jR8j.js +1 -0
- package/web-dist/assets/cynefin-OW5HDTMX-DU9YQQnc.js +166 -0
- package/web-dist/assets/cynefinDiagram-5FMLGOSQ-3a3XuhQq.js +62 -0
- package/web-dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/web-dist/assets/dagre-GXQ25YYZ-BfZ9C5CZ.js +4 -0
- package/web-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web-dist/assets/diagram-S7CK7UJ4-BoGhVBMn.js +30 -0
- package/web-dist/assets/diagram-UQ7AKVKN-zE67quty.js +41 -0
- package/web-dist/assets/diagram-VSXAHHWV-NihoXeXm.js +3 -0
- package/web-dist/assets/diagram-VX7I27RA-1627np0S.js +24 -0
- package/web-dist/assets/diagram-Z3DM3KII-Ddg_bUan.js +24 -0
- package/web-dist/assets/diff-jW5kmiru.js +1 -0
- package/web-dist/assets/ebnfDiagram-PWID7BFC-CmPUOM4j.js +1 -0
- package/web-dist/assets/erDiagram-RLTQ6QDP-DiTnEK72.js +99 -0
- package/web-dist/assets/flowDiagram-HODETNUW-mGQ38tRM.js +168 -0
- package/web-dist/assets/ganttDiagram-EL5Y4UJY-DU7MlzpW.js +292 -0
- package/web-dist/assets/gitGraphDiagram-WWUBYQGX-CRE_3vjl.js +106 -0
- package/web-dist/assets/github-dark-BQV5ugrc.js +1 -0
- package/web-dist/assets/github-light-Rcb2q3L5.js +1 -0
- package/web-dist/assets/go-B7VTfpnm.js +1 -0
- package/web-dist/assets/highlighter-CzfXQ6yd.js +2 -0
- package/web-dist/assets/{highlighter-Cu43iYr8.js → highlighter-core-B5CvXcEK.js} +7 -7
- package/web-dist/assets/highlighter.worker-D6D9fPma.js +152 -0
- package/web-dist/assets/html-BW_6VhB7.js +1 -0
- package/web-dist/assets/ibm-plex-sans-condensed-latin-600-normal-CRd5VyFf.woff2 +0 -0
- package/web-dist/assets/ibm-plex-sans-condensed-latin-ext-600-normal-CBdNZ9Ci.woff2 +0 -0
- package/web-dist/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
- package/web-dist/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
- package/web-dist/assets/index-BdG4qRyI.js +1275 -0
- package/web-dist/assets/index-W-xYFoIm.css +1 -0
- package/web-dist/assets/infoDiagram-27XIBGKW-Cdo6Ar-a.js +2 -0
- package/web-dist/assets/ini-Dix3pTmg.js +1 -0
- package/web-dist/assets/init-Gi6I4Gst.js +1 -0
- package/web-dist/assets/ishikawaDiagram-5VMMS53U-C27pgect.js +70 -0
- package/web-dist/assets/java-BTDmaRNO.js +1 -0
- package/web-dist/assets/javascript-WyGyM1wV.js +1 -0
- package/web-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web-dist/assets/journeyDiagram-3NMN7TZE-CVxtKpLG.js +139 -0
- package/web-dist/assets/json-B8JryHcM.js +1 -0
- package/web-dist/assets/jsx-8cLv2Six.js +1 -0
- package/web-dist/assets/kanban-definition-UXKFOSKX-DWA31Da7.js +89 -0
- package/web-dist/assets/layout-DWKCU9PO.js +1 -0
- package/web-dist/assets/linear-BwCthj_k.js +1 -0
- package/web-dist/assets/log-CcaF_xNv.js +1 -0
- package/web-dist/assets/markdown-DX-TNHnc.js +1 -0
- package/web-dist/assets/mermaid.core-BcTrmPOv.js +310 -0
- package/web-dist/assets/mindmap-definition-YA3MSWOX-BLEDhbn1.js +96 -0
- package/web-dist/assets/misans-400-100-DHkMCix2.woff2 +0 -0
- package/web-dist/assets/misans-400-101-CwgSLsmc.woff2 +0 -0
- package/web-dist/assets/misans-400-102-BQgefHPQ.woff2 +0 -0
- package/web-dist/assets/misans-400-103-CP58Mc_T.woff2 +0 -0
- package/web-dist/assets/misans-400-104-Ta_Xu1gd.woff2 +0 -0
- package/web-dist/assets/misans-400-105-CDop11AR.woff2 +0 -0
- package/web-dist/assets/misans-400-106-Dv8y0ubk.woff2 +0 -0
- package/web-dist/assets/misans-400-107-CjnHtp9E.woff2 +0 -0
- package/web-dist/assets/misans-400-108-BA8nQUBw.woff2 +0 -0
- package/web-dist/assets/misans-400-109-B6uEN0pG.woff2 +0 -0
- package/web-dist/assets/misans-400-110-BQqnShrY.woff2 +0 -0
- package/web-dist/assets/misans-400-111-CHB-Jj5X.woff2 +0 -0
- package/web-dist/assets/misans-400-112-D1dh4Cjx.woff2 +0 -0
- package/web-dist/assets/misans-400-113-Bw_wNCFp.woff2 +0 -0
- package/web-dist/assets/misans-400-114-CUb-5IzE.woff2 +0 -0
- package/web-dist/assets/misans-400-115-BICOatsO.woff2 +0 -0
- package/web-dist/assets/misans-400-116-B3_gmYW6.woff2 +0 -0
- package/web-dist/assets/misans-400-117-B8k_y3cv.woff2 +0 -0
- package/web-dist/assets/misans-400-118-C-2Cf5C6.woff2 +0 -0
- package/web-dist/assets/misans-400-119-CmLP7xnX.woff2 +0 -0
- package/web-dist/assets/misans-400-21-DsSSD_Zj.woff2 +0 -0
- package/web-dist/assets/misans-400-22-rtkxWOv4.woff2 +0 -0
- package/web-dist/assets/misans-400-23-CDDB4M1H.woff2 +0 -0
- package/web-dist/assets/misans-400-24-Dqf_XeOL.woff2 +0 -0
- package/web-dist/assets/misans-400-25-C4U1tJ5J.woff2 +0 -0
- package/web-dist/assets/misans-400-26-CEXOK-nT.woff2 +0 -0
- package/web-dist/assets/misans-400-27-BrNOAScF.woff2 +0 -0
- package/web-dist/assets/misans-400-28-CPpEnCkG.woff2 +0 -0
- package/web-dist/assets/misans-400-29-C9JMVw8g.woff2 +0 -0
- package/web-dist/assets/misans-400-30-CD-QimXa.woff2 +0 -0
- package/web-dist/assets/misans-400-31-S4k06NbS.woff2 +0 -0
- package/web-dist/assets/misans-400-32-zRMBDkz6.woff2 +0 -0
- package/web-dist/assets/misans-400-33-sQ_tNZSM.woff2 +0 -0
- package/web-dist/assets/misans-400-34-8LEGljja.woff2 +0 -0
- package/web-dist/assets/misans-400-35-CxSPyuiU.woff2 +0 -0
- package/web-dist/assets/misans-400-36-BY1i4WtO.woff2 +0 -0
- package/web-dist/assets/misans-400-37-C0oF42pu.woff2 +0 -0
- package/web-dist/assets/misans-400-38-y7xcB90w.woff2 +0 -0
- package/web-dist/assets/misans-400-39-C307YgZx.woff2 +0 -0
- package/web-dist/assets/misans-400-40-ByZr736f.woff2 +0 -0
- package/web-dist/assets/misans-400-41-ChKEw5zV.woff2 +0 -0
- package/web-dist/assets/misans-400-42-CD__zJu5.woff2 +0 -0
- package/web-dist/assets/misans-400-43-CqNV6_TG.woff2 +0 -0
- package/web-dist/assets/misans-400-44-zE-akmW8.woff2 +0 -0
- package/web-dist/assets/misans-400-45-Du98otwc.woff2 +0 -0
- package/web-dist/assets/misans-400-46-B6JXbEy2.woff2 +0 -0
- package/web-dist/assets/misans-400-47-DpqW0kSq.woff2 +0 -0
- package/web-dist/assets/misans-400-48-BprON5Q_.woff2 +0 -0
- package/web-dist/assets/misans-400-49-N-ct3sN6.woff2 +0 -0
- package/web-dist/assets/misans-400-5-ton_4jCN.woff2 +0 -0
- package/web-dist/assets/misans-400-50-JgU49P0U.woff2 +0 -0
- package/web-dist/assets/misans-400-51-DSS3Moge.woff2 +0 -0
- package/web-dist/assets/misans-400-52-BMoXFmwE.woff2 +0 -0
- package/web-dist/assets/misans-400-53-BaLMGl4z.woff2 +0 -0
- package/web-dist/assets/misans-400-54-CaeSn3OR.woff2 +0 -0
- package/web-dist/assets/misans-400-55-BBqrtLxN.woff2 +0 -0
- package/web-dist/assets/misans-400-56-DAZ5BnbO.woff2 +0 -0
- package/web-dist/assets/misans-400-57-F-n3b0gI.woff2 +0 -0
- package/web-dist/assets/misans-400-58-DEUuPQAq.woff2 +0 -0
- package/web-dist/assets/misans-400-59-DXP5rhCL.woff2 +0 -0
- package/web-dist/assets/misans-400-6-DWCCUO_c.woff2 +0 -0
- package/web-dist/assets/misans-400-60-BE6IO6zJ.woff2 +0 -0
- package/web-dist/assets/misans-400-61-5Qb1gaxK.woff2 +0 -0
- package/web-dist/assets/misans-400-62-D_tJKNAj.woff2 +0 -0
- package/web-dist/assets/misans-400-63-hwmne8yB.woff2 +0 -0
- package/web-dist/assets/misans-400-64-BGedxReM.woff2 +0 -0
- package/web-dist/assets/misans-400-65-BaKPsugb.woff2 +0 -0
- package/web-dist/assets/misans-400-66-IdaOMzCz.woff2 +0 -0
- package/web-dist/assets/misans-400-67-BdWyS83H.woff2 +0 -0
- package/web-dist/assets/misans-400-68-DrZ_1swq.woff2 +0 -0
- package/web-dist/assets/misans-400-69-B3jsSD92.woff2 +0 -0
- package/web-dist/assets/misans-400-70-D6UZI2Ua.woff2 +0 -0
- package/web-dist/assets/misans-400-71-HFxyMCYg.woff2 +0 -0
- package/web-dist/assets/misans-400-72-CP5A-yQP.woff2 +0 -0
- package/web-dist/assets/misans-400-73-BoD_bu6s.woff2 +0 -0
- package/web-dist/assets/misans-400-74-CbJlhZlZ.woff2 +0 -0
- package/web-dist/assets/misans-400-75-BsbWuELX.woff2 +0 -0
- package/web-dist/assets/misans-400-76-DkwiQhof.woff2 +0 -0
- package/web-dist/assets/misans-400-77-6pVHXHnF.woff2 +0 -0
- package/web-dist/assets/misans-400-78-CfUF6pih.woff2 +0 -0
- package/web-dist/assets/misans-400-79-BCS3Dx4e.woff2 +0 -0
- package/web-dist/assets/misans-400-80-BEYSdQ_N.woff2 +0 -0
- package/web-dist/assets/misans-400-81-BzL-Gncx.woff2 +0 -0
- package/web-dist/assets/misans-400-82-CaENmdUU.woff2 +0 -0
- package/web-dist/assets/misans-400-83-Bvq1eBcA.woff2 +0 -0
- package/web-dist/assets/misans-400-84-Ctd90hiP.woff2 +0 -0
- package/web-dist/assets/misans-400-85-DWuOxfL1.woff2 +0 -0
- package/web-dist/assets/misans-400-86-D5niZS5c.woff2 +0 -0
- package/web-dist/assets/misans-400-87-DkOIADQJ.woff2 +0 -0
- package/web-dist/assets/misans-400-88-CGY4WTcC.woff2 +0 -0
- package/web-dist/assets/misans-400-89-CA3eoMTt.woff2 +0 -0
- package/web-dist/assets/misans-400-90-CzxR9MYb.woff2 +0 -0
- package/web-dist/assets/misans-400-91-Dyr85txc.woff2 +0 -0
- package/web-dist/assets/misans-400-97-qEZrCUNe.woff2 +0 -0
- package/web-dist/assets/misans-400-cyrillic--hFcG0Xp.woff2 +0 -0
- package/web-dist/assets/misans-400-extra-ZRI9n2Hu.woff2 +0 -0
- package/web-dist/assets/misans-400-latin-BRDR1WaL.woff2 +0 -0
- package/web-dist/assets/misans-400-latin-ext-6xS-MvHW.woff2 +0 -0
- package/web-dist/assets/misans-400-vietnamese-D7xD7kBB.woff2 +0 -0
- package/web-dist/assets/misans-500-100-BHuGA96E.woff2 +0 -0
- package/web-dist/assets/misans-500-101-CDMYUz0c.woff2 +0 -0
- package/web-dist/assets/misans-500-102-CWcqYYCV.woff2 +0 -0
- package/web-dist/assets/misans-500-103-BE1QSEzY.woff2 +0 -0
- package/web-dist/assets/misans-500-104-CDp743BV.woff2 +0 -0
- package/web-dist/assets/misans-500-105-y7dY6Foi.woff2 +0 -0
- package/web-dist/assets/misans-500-106-CP-kGZ6j.woff2 +0 -0
- package/web-dist/assets/misans-500-107-CUc8Hvle.woff2 +0 -0
- package/web-dist/assets/misans-500-108-ItN_x8jX.woff2 +0 -0
- package/web-dist/assets/misans-500-109-CEtf3R4D.woff2 +0 -0
- package/web-dist/assets/misans-500-110-BSZSHYRL.woff2 +0 -0
- package/web-dist/assets/misans-500-111-DfdArrjn.woff2 +0 -0
- package/web-dist/assets/misans-500-112-DRtNyrQw.woff2 +0 -0
- package/web-dist/assets/misans-500-113-CDT7tKuB.woff2 +0 -0
- package/web-dist/assets/misans-500-114-CCh4jwL8.woff2 +0 -0
- package/web-dist/assets/misans-500-115-DplUgPp0.woff2 +0 -0
- package/web-dist/assets/misans-500-116-C7eCklyK.woff2 +0 -0
- package/web-dist/assets/misans-500-117-BA0KabyT.woff2 +0 -0
- package/web-dist/assets/misans-500-118-V_M0eu2q.woff2 +0 -0
- package/web-dist/assets/misans-500-119-Cgf3Hszt.woff2 +0 -0
- package/web-dist/assets/misans-500-21-Di6DyR6O.woff2 +0 -0
- package/web-dist/assets/misans-500-22-CSnXvtL-.woff2 +0 -0
- package/web-dist/assets/misans-500-23-A06Hm9a6.woff2 +0 -0
- package/web-dist/assets/misans-500-24-Df97RCik.woff2 +0 -0
- package/web-dist/assets/misans-500-25-bOSJ6YXH.woff2 +0 -0
- package/web-dist/assets/misans-500-26-C65icgBb.woff2 +0 -0
- package/web-dist/assets/misans-500-27-C_GRm0va.woff2 +0 -0
- package/web-dist/assets/misans-500-28-CzZkqO0l.woff2 +0 -0
- package/web-dist/assets/misans-500-29-BReyeRyX.woff2 +0 -0
- package/web-dist/assets/misans-500-30-DzfjlROz.woff2 +0 -0
- package/web-dist/assets/misans-500-31-B2nLpgdU.woff2 +0 -0
- package/web-dist/assets/misans-500-32-Bz3rHm8X.woff2 +0 -0
- package/web-dist/assets/misans-500-33-AU3lZ1vY.woff2 +0 -0
- package/web-dist/assets/misans-500-34-CBAWkH-I.woff2 +0 -0
- package/web-dist/assets/misans-500-35-VgnGd14P.woff2 +0 -0
- package/web-dist/assets/misans-500-36-B2kfXv6A.woff2 +0 -0
- package/web-dist/assets/misans-500-37-DhKo8ykb.woff2 +0 -0
- package/web-dist/assets/misans-500-38-D8tTxFuc.woff2 +0 -0
- package/web-dist/assets/misans-500-39-DTm3aqJQ.woff2 +0 -0
- package/web-dist/assets/misans-500-40-C_akwj8_.woff2 +0 -0
- package/web-dist/assets/misans-500-41-RVM1JHQW.woff2 +0 -0
- package/web-dist/assets/misans-500-42-MVWIN_il.woff2 +0 -0
- package/web-dist/assets/misans-500-43-BtLZYNyD.woff2 +0 -0
- package/web-dist/assets/misans-500-44-D0QHMl6G.woff2 +0 -0
- package/web-dist/assets/misans-500-45-DRlclX7-.woff2 +0 -0
- package/web-dist/assets/misans-500-46-BEWswL4N.woff2 +0 -0
- package/web-dist/assets/misans-500-47-COSoxWaW.woff2 +0 -0
- package/web-dist/assets/misans-500-48-DZAISq3n.woff2 +0 -0
- package/web-dist/assets/misans-500-49-KSfi7MW0.woff2 +0 -0
- package/web-dist/assets/misans-500-5-h1SBcwuW.woff2 +0 -0
- package/web-dist/assets/misans-500-50-CIVMfG11.woff2 +0 -0
- package/web-dist/assets/misans-500-51-BdeNBhBL.woff2 +0 -0
- package/web-dist/assets/misans-500-52-CEW4e7ex.woff2 +0 -0
- package/web-dist/assets/misans-500-53-lmPCsEnt.woff2 +0 -0
- package/web-dist/assets/misans-500-54-B_7AqKQW.woff2 +0 -0
- package/web-dist/assets/misans-500-55-B-3RtUnH.woff2 +0 -0
- package/web-dist/assets/misans-500-56-g6D0e0cW.woff2 +0 -0
- package/web-dist/assets/misans-500-57-D5hp5Yqr.woff2 +0 -0
- package/web-dist/assets/misans-500-58-ilnDgEKt.woff2 +0 -0
- package/web-dist/assets/misans-500-59-DJYXz8tr.woff2 +0 -0
- package/web-dist/assets/misans-500-6-uCdpdDQy.woff2 +0 -0
- package/web-dist/assets/misans-500-60-B3Iaqx0p.woff2 +0 -0
- package/web-dist/assets/misans-500-61-CMxqg5hc.woff2 +0 -0
- package/web-dist/assets/misans-500-62-7FY8O2NQ.woff2 +0 -0
- package/web-dist/assets/misans-500-63-JP6vEuJf.woff2 +0 -0
- package/web-dist/assets/misans-500-64-Cdub62jh.woff2 +0 -0
- package/web-dist/assets/misans-500-65-DVHk_mlC.woff2 +0 -0
- package/web-dist/assets/misans-500-66-BX39wRtF.woff2 +0 -0
- package/web-dist/assets/misans-500-67-BcsepI3X.woff2 +0 -0
- package/web-dist/assets/misans-500-68-etUIUjrh.woff2 +0 -0
- package/web-dist/assets/misans-500-69-DfhVmnuA.woff2 +0 -0
- package/web-dist/assets/misans-500-70-D6dgy6-B.woff2 +0 -0
- package/web-dist/assets/misans-500-71-PkeKjcVn.woff2 +0 -0
- package/web-dist/assets/misans-500-72-D66Fnb30.woff2 +0 -0
- package/web-dist/assets/misans-500-73-jr9z9870.woff2 +0 -0
- package/web-dist/assets/misans-500-74-DsCkPKwe.woff2 +0 -0
- package/web-dist/assets/misans-500-75-Dkooq6KM.woff2 +0 -0
- package/web-dist/assets/misans-500-76-B0sfieHl.woff2 +0 -0
- package/web-dist/assets/misans-500-77-8tflq1ji.woff2 +0 -0
- package/web-dist/assets/misans-500-78-COKdMbdp.woff2 +0 -0
- package/web-dist/assets/misans-500-79-Bm0rBjSF.woff2 +0 -0
- package/web-dist/assets/misans-500-80-B9OoAnyy.woff2 +0 -0
- package/web-dist/assets/misans-500-81-DsQ9tpfX.woff2 +0 -0
- package/web-dist/assets/misans-500-82-VKsL2Tk2.woff2 +0 -0
- package/web-dist/assets/misans-500-83-Ce6mfHYD.woff2 +0 -0
- package/web-dist/assets/misans-500-84-D7kPSZLm.woff2 +0 -0
- package/web-dist/assets/misans-500-85-W5L0cEfd.woff2 +0 -0
- package/web-dist/assets/misans-500-86-DukGOEET.woff2 +0 -0
- package/web-dist/assets/misans-500-87-BJ_Lok6j.woff2 +0 -0
- package/web-dist/assets/misans-500-88-DbJ11zcA.woff2 +0 -0
- package/web-dist/assets/misans-500-89-LNKcEq6M.woff2 +0 -0
- package/web-dist/assets/misans-500-90-DNDDgSCd.woff2 +0 -0
- package/web-dist/assets/misans-500-91-rT8Q0evu.woff2 +0 -0
- package/web-dist/assets/misans-500-97-c8zW_WRq.woff2 +0 -0
- package/web-dist/assets/misans-500-cyrillic-CLHOGwgx.woff2 +0 -0
- package/web-dist/assets/misans-500-extra-CTAMEQ59.woff2 +0 -0
- package/web-dist/assets/misans-500-latin-Be606ott.woff2 +0 -0
- package/web-dist/assets/misans-500-latin-ext-BzDyL4sU.woff2 +0 -0
- package/web-dist/assets/misans-500-vietnamese-DBOLOqZH.woff2 +0 -0
- package/web-dist/assets/mona-sans-latin-ext-wght-normal-BqSk0Z3x.woff2 +0 -0
- package/web-dist/assets/mona-sans-latin-wght-normal-Pz49MTQZ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-100-wght-normal-DrqXJETY.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-101-wght-normal-c94LAl5p.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-102-wght-normal-C1RtbCZr.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-103-wght-normal-DGHo20nu.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-104-wght-normal-CJT2ioDJ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-105-wght-normal-BFoiJwz2.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-106-wght-normal-D6uUHw3w.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-107-wght-normal-kFQzJDLH.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-108-wght-normal-7aXvqIa2.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-109-wght-normal-CnjNbNmw.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-110-wght-normal-D2JBr045.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-111-wght-normal-ClFr5QXM.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-112-wght-normal-CiZxJMyY.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-113-wght-normal-OgQXOX6x.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-114-wght-normal-CRH9SDZu.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-115-wght-normal-BN__2iBG.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-116-wght-normal-vdFVzbO2.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-117-wght-normal-Dsz3vkzU.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-118-wght-normal-BPEb0gM9.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-119-wght-normal-BfzSEbFz.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-21-wght-normal-DIu_1ZBI.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-22-wght-normal-VXjdYcT-.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-23-wght-normal-4HrHKhpL.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-24-wght-normal-B58DWgHS.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-25-wght-normal-C3Jm6l7O.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-26-wght-normal-YNOygvr_.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-27-wght-normal-BP2pEGQH.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-28-wght-normal-BWPKDbtH.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-29-wght-normal-45aVmHn_.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-30-wght-normal-B8_s30jZ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-31-wght-normal-DW0t445S.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-32-wght-normal-ShbWPcei.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-33-wght-normal-BOTZYTjM.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-34-wght-normal-BF1rqDSt.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-35-wght-normal-Bosspiyr.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-36-wght-normal-CtU9CyKw.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-37-wght-normal-ghDiyiuv.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-38-wght-normal-4Nz8k_dt.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-39-wght-normal-Ddtm1XVC.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-4-wght-normal-BRbPMj-C.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-40-wght-normal-BVn4-kgX.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-41-wght-normal-CZqajZnl.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-42-wght-normal-CQ4lY28u.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-43-wght-normal-psppu0d8.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-44-wght-normal-cbKJlTUe.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-45-wght-normal-MKIEVRIC.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-46-wght-normal-CErGc6Mt.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-47-wght-normal-C5Kgg1_m.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-48-wght-normal-DFMjDDY6.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-49-wght-normal-B7l_3BZr.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-5-wght-normal-DI846rD9.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-50-wght-normal-C5KfAmFp.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-51-wght-normal-C2bq6hC8.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-52-wght-normal-P18e_Y0j.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-53-wght-normal-Bgkuo2tF.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-54-wght-normal-DL5lmlUd.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-55-wght-normal-BV_Oy6IF.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-56-wght-normal-B4L7Hj8f.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-57-wght-normal-DU8YBsDj.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-58-wght-normal-CE0BKS9u.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-59-wght-normal-Azv2Ku9_.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-6-wght-normal-ozUCEY9O.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-60-wght-normal-C1lngC61.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-61-wght-normal-BxWkGF5M.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-62-wght-normal-CXXMAq-5.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-63-wght-normal-4La5uKvS.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-64-wght-normal-CbIuD2Eg.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-65-wght-normal-BGqvKda2.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-66-wght-normal-BvNozWOY.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-67-wght-normal-CC-WRBV-.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-68-wght-normal-c0FjfUaj.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-69-wght-normal-D596Jjl2.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-70-wght-normal-ColEcmtJ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-71-wght-normal-ChZfXpdg.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-72-wght-normal-D1VyfEG3.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-73-wght-normal-DwSJRYPJ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-74-wght-normal-Cte7o1QG.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-75-wght-normal-B0qL41rF.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-76-wght-normal-BPLMO_2x.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-77-wght-normal-Dkbi3pye.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-78-wght-normal-BFUXeSw6.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-79-wght-normal-DoJG4UTB.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-80-wght-normal-BEFypWOO.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-81-wght-normal-ed_TZlfo.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-82-wght-normal-DcxfR5OJ.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-83-wght-normal-C9UjAcMM.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-84-wght-normal-C7NxT478.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-85-wght-normal-DBcDBu1n.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-86-wght-normal-CLJIoPpl.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-87-wght-normal-DYfJ-vGE.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-88-wght-normal-ClNDOCGW.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-89-wght-normal-IQU_KQA8.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-90-wght-normal-BWcrRNoG.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-91-wght-normal-CGekvDdH.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-97-wght-normal-BsBHkXi3.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-98-wght-normal-BZqSA1r_.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-99-wght-normal-Q_IptJM5.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-cyrillic-wght-normal-CRA5_Pf1.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-latin-ext-wght-normal-CqPqLCfe.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-latin-wght-normal-DYAKOyvl.woff2 +0 -0
- package/web-dist/assets/noto-sans-sc-vietnamese-wght-normal-y5SU35Rl.woff2 +0 -0
- package/web-dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web-dist/assets/pegDiagram-XKGWAZYB-CLEM1SDU.js +1 -0
- package/web-dist/assets/php-CNeTOyEV.js +1 -0
- package/web-dist/assets/pieDiagram-E7YTZNPT-B__g6eF0.js +39 -0
- package/web-dist/assets/python-B6Ihcp-E.js +1 -0
- package/web-dist/assets/quadrantDiagram-AXDQQJYC-C7VQ2LMY.js +7 -0
- package/web-dist/assets/railroadDiagram-O6MQD6OU-eWNnzjwS.js +1 -0
- package/web-dist/assets/requirementDiagram-BXWQKSXE-Bi5qeD4b.js +84 -0
- package/web-dist/assets/ruby-CYiNE6ps.js +1 -0
- package/web-dist/assets/rust-CxuBE0J8.js +1 -0
- package/web-dist/assets/sankeyDiagram-P5KCCOFB-DG1VOpXX.js +40 -0
- package/web-dist/assets/sequenceDiagram-WJ2MYXX4-Fhkw73Th.js +162 -0
- package/web-dist/assets/shellscript-DgJyDXW6.js +1 -0
- package/web-dist/assets/sizeCapture-INFHLROL-D9E_17SF.js +1 -0
- package/web-dist/assets/sql-Dq2MiB0T.js +1 -0
- package/web-dist/assets/stateDiagram-D77RDMKH-Cs5Y0xhW.js +1 -0
- package/web-dist/assets/stateDiagram-v2-MP3YSRHH-CEM51fAf.js +1 -0
- package/web-dist/assets/swimlanes-42K2YHIH-t-EocvKZ.js +1 -0
- package/web-dist/assets/swimlanesDiagram-VR7AAH4N-BrR66vrK.js +8 -0
- package/web-dist/assets/timeline-definition-24CTP7MA-CozGCxuA.js +120 -0
- package/web-dist/assets/toml-Ms459DBw.js +1 -0
- package/web-dist/assets/tsx-D6116Ycy.js +1 -0
- package/web-dist/assets/typescript-BqvgUolh.js +1 -0
- package/web-dist/assets/vennDiagram-4TSXK5OY-ewU8ycSa.js +34 -0
- package/web-dist/assets/wardleyDiagram-VM6X3IG4-DuAhDu-0.js +78 -0
- package/web-dist/assets/xml-BkAJuBCc.js +1 -0
- package/web-dist/assets/{xterm-DKcY6q-I.js → xterm-v5e56GC9.js} +1 -1
- package/web-dist/assets/xychartDiagram-S5SC5T6Z-BxGuZO23.js +7 -0
- package/web-dist/assets/yaml-Bopit2L3.js +1 -0
- package/web-dist/fonts-licenses/ibm-plex-sans-condensed.txt +93 -0
- package/web-dist/fonts-licenses/ibm-plex-sans.txt +93 -0
- package/web-dist/fonts-licenses/jetbrains-mono.txt +93 -0
- package/web-dist/fonts-licenses/misans.txt +119 -0
- package/web-dist/fonts-licenses/mona-sans.txt +93 -0
- package/web-dist/fonts-licenses/noto-sans-sc.txt +93 -0
- package/web-dist/index.html +18 -4
- package/web-dist/workflow-ui.css +261 -0
- package/dist/chunk-4SXGU7RO.js +0 -50
- package/dist/chunk-4SXGU7RO.js.map +0 -1
- package/dist/chunk-ADZJYDNC.js +0 -421
- package/dist/chunk-ADZJYDNC.js.map +0 -1
- package/dist/chunk-HOS3W3UP.js.map +0 -1
- package/dist/chunk-Q65VPAQS.js.map +0 -1
- package/dist/chunk-TTGGJDNA.js +0 -14
- package/dist/chunk-TTGGJDNA.js.map +0 -1
- package/dist/extension/index.d.ts +0 -212
- package/web-dist/assets/index-Bm9n7ZV-.js +0 -457
- package/web-dist/assets/index-DR4PrRzS.css +0 -1
- /package/dist/{extension/index.js.map → chunk-B5IP37ZQ.js.map} +0 -0
- /package/dist/{extension → plugin}/index.js +0 -0
|
@@ -0,0 +1,3384 @@
|
|
|
1
|
+
import { SpawnConfiner, OmniMessage, ThinkingLevelName, ToolCallPayload, ApprovalDecision, ProjectConfig, ModelRef, PluginTables, ListEndpointModelsOptions, ModelRequestContext, ProxyEnvPolicy, ControlEnvContext, AgentAssembly, ApproveFn, StopReason, CompactAvailability, SessionTitleResult, BackgroundCommandInfo, ToolDetachResult, BackgroundSubagentInfo, SubagentMessageOptions, SubagentMessageOutcome } from '@prismshadow/penguin-core';
|
|
2
|
+
import { SandboxProviderSource, SandboxSettings, SandboxDimension, SandboxLimit } from '@prismshadow/penguin-core/plugin';
|
|
3
|
+
import { Slot, Opaque } from '@prismshadow/penguin-core/kernel';
|
|
4
|
+
import { ApprovalMode, ModelRefDto, ChatDefaultsDto, CommandPolicyDto, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelTestRequest, ModelTestResponse, ModelProtocolDetectRequest, ModelProtocolDetectResponse, EndpointModelListRequest, EndpointModelListResponse, ModelsResponse, ModelsUpdateRequest, ProviderConnectionUpdate, PresetSyncMode, PresetSyncResponse, ServerEvent, MessagingChannel, UnavailableSandboxBackend, SessionSandboxPreset, SessionSandbox, SessionInfo, SessionCategory, SessionCategoryCounts, ModelScopeAuthFlowStatusResponse, SessionStatus, PendingSteeringInfo, SessionBackgroundTasks, PendingFollowUpInfo, AgentConfigDto, AgentConfigUpdateRequest, AgentKernelUpdateResponse, VaultResponse, VaultUpdateRequest, PluginUpdateRef, MessagingRuntimeStatus, PluginConfiguration, ContributionsResponse, RendererRef } from '../api/types.js';
|
|
5
|
+
export { PluginConfigField } from '../api/types.js';
|
|
6
|
+
import { ThinkingLevelName as ThinkingLevelName$1 } from '@prismshadow/penguin-core/interfaces';
|
|
7
|
+
import { SessionSource } from '@prismshadow/penguin-core/omnimessage';
|
|
8
|
+
import { Hono } from 'hono';
|
|
9
|
+
import '@prismshadow/penguin-hmr';
|
|
10
|
+
|
|
11
|
+
interface UserRow {
|
|
12
|
+
userId: string;
|
|
13
|
+
passwordHash: string;
|
|
14
|
+
isAdmin: boolean;
|
|
15
|
+
/** Still using the initial password (seeded / set by an admin); cleared to 0 once the user changes it. */
|
|
16
|
+
passwordIsInitial: boolean;
|
|
17
|
+
/** Nickname shown in place of the id; null = never set. */
|
|
18
|
+
displayName: string | null;
|
|
19
|
+
/** Avatar as a data URL; null = never set. */
|
|
20
|
+
avatar: string | null;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* "desktop" and "setup" may set a password without the old one (theirs is random and was never
|
|
26
|
+
* shown); desktop-only routes open to "desktop" alone. "token" is the local API token's Bearer
|
|
27
|
+
* header — per request, never a stored row. Anything else reads as "password".
|
|
28
|
+
*/
|
|
29
|
+
type SessionVia = "password" | "desktop" | "setup" | "token";
|
|
30
|
+
|
|
31
|
+
declare class SandboxService {
|
|
32
|
+
private mounted;
|
|
33
|
+
/**
|
|
34
|
+
* name → why it FAILED: it could not load, or failed its check on a host it is meant for —
|
|
35
|
+
* never silently absent. Surfaced in the fail-closed message and on the settings card.
|
|
36
|
+
*/
|
|
37
|
+
private loadErrors;
|
|
38
|
+
/**
|
|
39
|
+
* The backends that declined: this host is not theirs (a Linux backend on Windows). Nothing
|
|
40
|
+
* is wrong with a deployment that installs one backend per platform, so a decline is not a
|
|
41
|
+
* failure — it is listed only when NO backend serves, where it is the explanation.
|
|
42
|
+
*/
|
|
43
|
+
private declinedNames;
|
|
44
|
+
/** Every registration in routing order, with how its latest load settled. */
|
|
45
|
+
private readonly registrations;
|
|
46
|
+
/**
|
|
47
|
+
* Ships with confinement OFF (`danger-full-access`): the default flips to
|
|
48
|
+
* workspace-write together with the deployment-facing config surface, so a
|
|
49
|
+
* deployment gains the switch before the enforcement — otherwise every host without
|
|
50
|
+
* a usable backend would fail every command the moment this code arrives.
|
|
51
|
+
*/
|
|
52
|
+
private settings;
|
|
53
|
+
private readonly ready;
|
|
54
|
+
/**
|
|
55
|
+
* @param registrations - the backends plugins registered (see the sandbox registry on
|
|
56
|
+
* PenguinInterface). Loading is async, but create() and the confiner are sync: a
|
|
57
|
+
* confining policy resolved before loading settles fails closed rather than
|
|
58
|
+
* waiting. The default settings never consult a backend, so that window only
|
|
59
|
+
* exists for deployments flipping the mode in the first milliseconds after boot.
|
|
60
|
+
*/
|
|
61
|
+
constructor(registrations?: Iterable<[string, SandboxProviderSource]>);
|
|
62
|
+
/** Rebuilds the mounted / failed / declined views from the registrations, in routing order. */
|
|
63
|
+
private record;
|
|
64
|
+
/**
|
|
65
|
+
* Loads again every backend whose load failed and that registered a loader (a bare promise
|
|
66
|
+
* cannot be re-run). Called after the sandbox's settings are saved: a backend whose check
|
|
67
|
+
* failed on its own setting mounts once that setting is fixed. Resolves when those settle.
|
|
68
|
+
*/
|
|
69
|
+
retryFailed(): Promise<void>;
|
|
70
|
+
/** Resolves when every registered backend has loaded (or failed to). */
|
|
71
|
+
whenReady(): Promise<void>;
|
|
72
|
+
/** Replaces the active settings (the config surface's write path). */
|
|
73
|
+
configure(settings: SandboxSettings): void;
|
|
74
|
+
/** The active settings as a plain copy (the config surface's read side). */
|
|
75
|
+
currentSettings(): SandboxSettings;
|
|
76
|
+
/**
|
|
77
|
+
* The active settings as parked state, or undefined for the pristine default. The
|
|
78
|
+
* omission is load-bearing compatibility: a default deployment keeps parking
|
|
79
|
+
* `{ motd }`, which any platform bundle's context schema accepts. Once confinement
|
|
80
|
+
* is configured the field enters the document, and from then on pushing a
|
|
81
|
+
* sandbox-ignorant bundle is BLOCKED by schema validation — the right failure:
|
|
82
|
+
* refusing the swap beats a swap that silently un-confines the deployment.
|
|
83
|
+
*/
|
|
84
|
+
parkedSettings(): SandboxSettings | undefined;
|
|
85
|
+
/** The backends that failed to load, each with why (diagnostics / the config surface). */
|
|
86
|
+
failures(): Array<{
|
|
87
|
+
name: string;
|
|
88
|
+
reason: string;
|
|
89
|
+
}>;
|
|
90
|
+
/** The backends that declined because this host is not theirs (the config surface). */
|
|
91
|
+
declined(): string[];
|
|
92
|
+
/**
|
|
93
|
+
* The mounted backends and what each implements, in routing preference: the one a policy
|
|
94
|
+
* goes to first (diagnostics / the config surface).
|
|
95
|
+
*/
|
|
96
|
+
backends(): Array<{
|
|
97
|
+
name: string;
|
|
98
|
+
dimensions: readonly SandboxDimension[];
|
|
99
|
+
mechanism?: string;
|
|
100
|
+
limits?: readonly SandboxLimit[];
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* The closure handed to the runtime: rewrites each spawn's argv under the active
|
|
104
|
+
* settings. Pure and self-contained, so the previous platform's confiner keeps
|
|
105
|
+
* serving during a hot-swap freeze window.
|
|
106
|
+
*/
|
|
107
|
+
confiner(): SpawnConfiner;
|
|
108
|
+
/**
|
|
109
|
+
* The same closure under a policy the caller keeps — a Session's own snapshot. The
|
|
110
|
+
* backends are still this service's, so a backend change reaches every Session.
|
|
111
|
+
*/
|
|
112
|
+
confinerFor(policyOf: () => SandboxSettings): SpawnConfiner;
|
|
113
|
+
/**
|
|
114
|
+
* The preferred mounted backend implementing every required dimension (byPreference), or a
|
|
115
|
+
* fail-closed throw.
|
|
116
|
+
*/
|
|
117
|
+
private pick;
|
|
118
|
+
private failedSuffix;
|
|
119
|
+
}
|
|
120
|
+
declare const Sandbox_base: (abstract new () => Pick<SandboxService, "failures" | "configure" | "currentSettings" | "parkedSettings" | "backends" | "declined" | "retryFailed" | "confiner" | "confinerFor" | "whenReady">) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
121
|
+
/** Confinement: settings, the active confiner, and which backends are mounted. */
|
|
122
|
+
declare abstract class Sandbox extends Sandbox_base {
|
|
123
|
+
}
|
|
124
|
+
interface SandboxSlots {
|
|
125
|
+
/**
|
|
126
|
+
* A backend: its static half here (name, the dimensions it implements), its code half
|
|
127
|
+
* bound by the contributor — a provider, or a promise of one for backends that probe.
|
|
128
|
+
*/
|
|
129
|
+
providers: Slot<{
|
|
130
|
+
name: string;
|
|
131
|
+
dimensions: SandboxDimension[];
|
|
132
|
+
}, SandboxProviderSource>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** What a terminal puts first on PATH, where the startup files that do it live, and its first line. */
|
|
136
|
+
interface TerminalPathFirst {
|
|
137
|
+
/** The directories that must lead PATH once the shell is ready, in order: the CLI shim dir. */
|
|
138
|
+
dirs: readonly string[];
|
|
139
|
+
/** The directory holding the startup files, see {@link shellStartupDir}. */
|
|
140
|
+
startupDir: string;
|
|
141
|
+
/** A development instance's first line, for the shell to print at its first prompt. Absent or null: none. */
|
|
142
|
+
notice?: ShellNotice | null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The first line of a development instance's terminal, printed by the shell itself at its first
|
|
146
|
+
* prompt, after its own startup files: `text` while `penguin` there still targets this instance,
|
|
147
|
+
* else `elsewhere`'s two halves around `VAR=value` — when those files set PENGUIN_API_URL, which
|
|
148
|
+
* names a server outright, or a PENGUIN_HOME that is not `root` (a trailing `/` aside), which the
|
|
149
|
+
* shim then leaves to the CLI.
|
|
150
|
+
*/
|
|
151
|
+
interface ShellNotice {
|
|
152
|
+
text: string;
|
|
153
|
+
/** This instance's data root. */
|
|
154
|
+
root: string;
|
|
155
|
+
elsewhere: readonly [before: string, after: string];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Screen snapshot -> ANSI restore stream, plus plain-text capture.
|
|
160
|
+
*
|
|
161
|
+
* This is what makes a browser refresh (or a reconnect from a second device) show the
|
|
162
|
+
* terminal exactly as it was: the daemon keeps an authoritative headless xterm per pty, and
|
|
163
|
+
* on attach it *re-renders* that grid as an escape-sequence stream the client's real xterm
|
|
164
|
+
* replays into an identical screen. That beats the two obvious alternatives:
|
|
165
|
+
*
|
|
166
|
+
* - Replaying the raw pty byte log: unbounded, and full-screen programs (vim, top) replay
|
|
167
|
+
* as a flicker of stale frames.
|
|
168
|
+
* - Sending a JSON cell grid: a 200k-object payload per attach, parsed on the UI thread.
|
|
169
|
+
*
|
|
170
|
+
* Cursor alignment is the subtle part. The restore writes `scrollback + rows` lines, so the
|
|
171
|
+
* client's viewport ends up holding exactly the server's viewport, and only then is the
|
|
172
|
+
* cursor placed with a viewport-relative CUP. Trailing blank rows are therefore written on
|
|
173
|
+
* purpose — dropping them would shift every subsequent row and land the cursor in the wrong
|
|
174
|
+
* place.
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
interface CaptureLinesOptions {
|
|
178
|
+
/** Line index into scrollback+viewport; negative counts from the end (-1 = last line). */
|
|
179
|
+
start?: number;
|
|
180
|
+
end?: number;
|
|
181
|
+
}
|
|
182
|
+
interface CaptureLinesResult {
|
|
183
|
+
lines: string[];
|
|
184
|
+
totalLines: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface TerminalExitInfo {
|
|
188
|
+
exitCode: number;
|
|
189
|
+
signal: number | null;
|
|
190
|
+
lastOutputLines: string[];
|
|
191
|
+
}
|
|
192
|
+
interface CreateTerminalSessionOptions {
|
|
193
|
+
id?: string;
|
|
194
|
+
/** Absolute working directory (already resolved and validated by the manager). */
|
|
195
|
+
cwd: string;
|
|
196
|
+
/** Owning user id — a shell is the most privileged thing this server hands out. */
|
|
197
|
+
ownerUserId: string;
|
|
198
|
+
/**
|
|
199
|
+
* Stable per-user display number (tmux-style), assigned by the manager at creation. It
|
|
200
|
+
* travels with the terminal for its whole life — UI numbering must never depend on list
|
|
201
|
+
* position, which changes under user-reordered tabs.
|
|
202
|
+
*/
|
|
203
|
+
seq?: number;
|
|
204
|
+
name?: string;
|
|
205
|
+
shell?: string;
|
|
206
|
+
cols?: number;
|
|
207
|
+
rows?: number;
|
|
208
|
+
env?: Record<string, string>;
|
|
209
|
+
/**
|
|
210
|
+
* Where a pushed bundle's node-pty assets live (the hmr capability's assetsDir);
|
|
211
|
+
* absent falls back to the packaged require (see pty-module.ts).
|
|
212
|
+
*/
|
|
213
|
+
assets?: () => string | null;
|
|
214
|
+
/**
|
|
215
|
+
* What leads PATH once the shell's own startup files have run: the server's CLI shim
|
|
216
|
+
* directory, so `penguin` here is the one the Agent's commands get (see shell-startup.ts).
|
|
217
|
+
* Absent or null, the shell starts with the inherited PATH as it is.
|
|
218
|
+
*/
|
|
219
|
+
pathFirst?: TerminalPathFirst | null;
|
|
220
|
+
/**
|
|
221
|
+
* One line shown above everything the shell prints — on a development instance, which
|
|
222
|
+
* `penguin` this terminal runs, or why it is not this instance's (cli-notice.ts). Written to
|
|
223
|
+
* the screen, never to the shell: nothing runs, and the user's prompt follows it. A line with
|
|
224
|
+
* a `target` the shell prints itself instead, at its first prompt, where it can
|
|
225
|
+
* (shell-startup.ts ShellNotice): only then is it known what the user's own startup files left
|
|
226
|
+
* `penguin` pointing at.
|
|
227
|
+
*/
|
|
228
|
+
notice?: TerminalNotice | null;
|
|
229
|
+
/**
|
|
230
|
+
* Whether the shell keeps the server process's `PENGUIN_HOME` and `PENGUIN_WEB_DB`. Only
|
|
231
|
+
* where no shim names this server's data root for `penguin` — a server with no CLI to offer,
|
|
232
|
+
* or a runtime older than the shim's root default — so that `penguin` there still finds this
|
|
233
|
+
* server the way it did before. Default false: every `PENGUIN_*` stays out.
|
|
234
|
+
*/
|
|
235
|
+
inheritDataRoot?: boolean;
|
|
236
|
+
}
|
|
237
|
+
/** A line the server shows at the top of a new terminal; `warning` when something is wrong. */
|
|
238
|
+
interface TerminalNotice {
|
|
239
|
+
text: string;
|
|
240
|
+
tone: "info" | "warning";
|
|
241
|
+
/**
|
|
242
|
+
* What `text` takes for granted: that `penguin` here targets this data root. On a development
|
|
243
|
+
* instance's line, which then says otherwise — `elsewhere`'s two halves around `VAR=value` —
|
|
244
|
+
* when the shell's own startup files set PENGUIN_API_URL, or a PENGUIN_HOME that is not `root`.
|
|
245
|
+
*/
|
|
246
|
+
target?: {
|
|
247
|
+
root: string;
|
|
248
|
+
elsewhere: readonly [before: string, after: string];
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
interface TerminalSessionInfo {
|
|
252
|
+
id: string;
|
|
253
|
+
seq: number;
|
|
254
|
+
name: string;
|
|
255
|
+
cwd: string;
|
|
256
|
+
ownerUserId: string;
|
|
257
|
+
title: string | null;
|
|
258
|
+
cols: number;
|
|
259
|
+
rows: number;
|
|
260
|
+
pid: number;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
alive: boolean;
|
|
263
|
+
exit: TerminalExitInfo | null;
|
|
264
|
+
}
|
|
265
|
+
type TerminalOutputListener = (data: string) => void;
|
|
266
|
+
type TerminalExitListener = (info: TerminalExitInfo) => void;
|
|
267
|
+
type TerminalTitleListener = (title: string | null) => void;
|
|
268
|
+
declare class TerminalSession {
|
|
269
|
+
readonly id: string;
|
|
270
|
+
readonly seq: number;
|
|
271
|
+
readonly cwd: string;
|
|
272
|
+
readonly ownerUserId: string;
|
|
273
|
+
readonly createdAt: Date;
|
|
274
|
+
private name;
|
|
275
|
+
private readonly terminal;
|
|
276
|
+
private readonly ptyProcess;
|
|
277
|
+
private readonly inputMode;
|
|
278
|
+
private readonly outputListeners;
|
|
279
|
+
private readonly exitListeners;
|
|
280
|
+
private readonly titleListeners;
|
|
281
|
+
private title;
|
|
282
|
+
private pendingTitle;
|
|
283
|
+
private titleTimer;
|
|
284
|
+
private sizeState;
|
|
285
|
+
private exitInfo;
|
|
286
|
+
private disposed;
|
|
287
|
+
constructor(options: CreateTerminalSessionOptions);
|
|
288
|
+
/** DA1 / DSR / OSC colour queries — see OSC_COLOR_QUERY_RESPONSES. */
|
|
289
|
+
private registerCapabilityResponders;
|
|
290
|
+
get alive(): boolean;
|
|
291
|
+
get exit(): TerminalExitInfo | null;
|
|
292
|
+
info(): TerminalSessionInfo;
|
|
293
|
+
rename(name: string): void;
|
|
294
|
+
write(data: string): void;
|
|
295
|
+
/**
|
|
296
|
+
* Applies a size request under the claim/update ownership rule, resizing both the pty and
|
|
297
|
+
* the daemon's emulator so snapshots keep matching what the shell laid out.
|
|
298
|
+
*/
|
|
299
|
+
resize(request: {
|
|
300
|
+
connectionId: string;
|
|
301
|
+
cols: number;
|
|
302
|
+
rows: number;
|
|
303
|
+
intent: "claim" | "update";
|
|
304
|
+
}): boolean;
|
|
305
|
+
releaseSize(connectionId: string): void;
|
|
306
|
+
get size(): {
|
|
307
|
+
cols: number;
|
|
308
|
+
rows: number;
|
|
309
|
+
};
|
|
310
|
+
/** The attach payload: the whole screen as an ANSI stream (see snapshot.ts). */
|
|
311
|
+
restoreStream(options?: {
|
|
312
|
+
scrollbackLines?: number;
|
|
313
|
+
}): string;
|
|
314
|
+
capture(options?: CaptureLinesOptions): CaptureLinesResult;
|
|
315
|
+
onOutput(listener: TerminalOutputListener): () => void;
|
|
316
|
+
onExit(listener: TerminalExitListener): () => void;
|
|
317
|
+
onTitleChange(listener: TerminalTitleListener): () => void;
|
|
318
|
+
kill(signal?: string): void;
|
|
319
|
+
dispose(): void;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface CreateTerminalRequest {
|
|
323
|
+
cwd: string;
|
|
324
|
+
ownerUserId: string;
|
|
325
|
+
name?: string;
|
|
326
|
+
cols?: number;
|
|
327
|
+
rows?: number;
|
|
328
|
+
shell?: string;
|
|
329
|
+
}
|
|
330
|
+
/** A live pty: a host-like object (it owns a process), compared by name. */
|
|
331
|
+
type Terminal = Opaque<"TerminalSession", TerminalSession>;
|
|
332
|
+
/** Live ptys: the spawn primitive behind /api/terminals and the terminal WebSocket. */
|
|
333
|
+
declare abstract class Terminals {
|
|
334
|
+
abstract create(request: CreateTerminalRequest): Promise<Terminal>;
|
|
335
|
+
abstract adopt(ids: readonly string[]): void;
|
|
336
|
+
abstract quiesce(): void;
|
|
337
|
+
abstract handleIds(): string[];
|
|
338
|
+
abstract require(id: string, userId: string): Terminal;
|
|
339
|
+
abstract get(id: string): Terminal | undefined;
|
|
340
|
+
abstract list(userId: string): Terminal[];
|
|
341
|
+
abstract listInfo(userId: string): TerminalSessionInfo[];
|
|
342
|
+
abstract kill(id: string, userId: string): void;
|
|
343
|
+
abstract disposeAll(): void;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The resolved, in-force limits for one request, in bytes. Read fresh per request from the
|
|
348
|
+
* settings repo rather than snapshotted at boot, so an admin's change applies to the very next
|
|
349
|
+
* upload without a restart.
|
|
350
|
+
*/
|
|
351
|
+
interface AttachmentLimits {
|
|
352
|
+
/** Per-file cap. */
|
|
353
|
+
maxBytes: number;
|
|
354
|
+
/** Per-request total of decoded attachment bytes. */
|
|
355
|
+
totalBytes: number;
|
|
356
|
+
/** Per-request file count. */
|
|
357
|
+
maxCount: number;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* A queued message's file attachment as held for recall: the scratchpad path it was written
|
|
362
|
+
* to, plus what is needed to rebuild the composer-shaped part — the submitted name and media
|
|
363
|
+
* type (the bytes stay on disk; queued entries never hold file payloads in memory).
|
|
364
|
+
*/
|
|
365
|
+
interface RecallableFile {
|
|
366
|
+
fileName: string;
|
|
367
|
+
path: string;
|
|
368
|
+
mime: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** The open streaming fragments of running sessions, as their holder uses them — what {@link LiveTailTracker} implements. */
|
|
372
|
+
interface LiveTail {
|
|
373
|
+
observe(sessionId: string, msg: OmniMessage): void;
|
|
374
|
+
fragments(sessionId: string): OmniMessage[];
|
|
375
|
+
clear(sessionId: string): void;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** SessionIndex: the mechanism SessionsRepo implements. */
|
|
379
|
+
declare abstract class SessionIndex {
|
|
380
|
+
abstract insert(row: SessionRow): void;
|
|
381
|
+
abstract insertOrIgnore(row: SessionRow): void;
|
|
382
|
+
abstract insertFork(sourceSessionId: string, row: SessionRow): SessionRow;
|
|
383
|
+
abstract markOrgClient(sessionIds: readonly string[]): void;
|
|
384
|
+
abstract markHasTrace(sessionId: string): void;
|
|
385
|
+
abstract markDriven(sessionId: string, at: string): void;
|
|
386
|
+
abstract touchLastActive(sessionId: string, at: string): void;
|
|
387
|
+
abstract findById(sessionId: string): SessionRow | null;
|
|
388
|
+
abstract listByAgent(projectId: string, agentId: string): SessionRow[];
|
|
389
|
+
abstract listByProject(projectId: string): SessionRow[];
|
|
390
|
+
abstract updateApprovalMode(sessionId: string, mode: ApprovalMode): void;
|
|
391
|
+
abstract updateSandbox(sessionId: string, sandbox: SandboxSettings): void;
|
|
392
|
+
abstract updateThinkingLevel(sessionId: string, level: ThinkingLevelName): void;
|
|
393
|
+
abstract updateTitle(sessionId: string, title: string): void;
|
|
394
|
+
abstract updateTitleIfNull(sessionId: string, title: string): void;
|
|
395
|
+
abstract updateModel(sessionId: string, provider: string, modelId: string): void;
|
|
396
|
+
abstract setListing(sessionIds: readonly string[], listing: "archived" | "kept", at: string): void;
|
|
397
|
+
abstract replaceId(oldSessionId: string, newSessionId: string): void;
|
|
398
|
+
abstract deleteByAgent(projectId: string, agentId: string): void;
|
|
399
|
+
abstract deleteByProject(projectId: string): void;
|
|
400
|
+
abstract deleteById(sessionId: string): void;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* What a run that has just ended asks of the CURRENT generation: start the next queued
|
|
404
|
+
* follow-up, or deliver background notices that raced the run's exit. A run outlives the
|
|
405
|
+
* generation that launched it (see AgentState.current), and what it starts next is the
|
|
406
|
+
* current logic's to start.
|
|
407
|
+
*/
|
|
408
|
+
interface RunStarter {
|
|
409
|
+
startQueuedFollowUp(sessionId: string): Promise<void>;
|
|
410
|
+
startBackgroundNoticeTask(sessionId: string): Promise<void>;
|
|
411
|
+
}
|
|
412
|
+
declare const AgentState_base: (abstract new () => {
|
|
413
|
+
readonly entries: Map<string, RuntimeEntry>;
|
|
414
|
+
readonly childRoots: Map<string, string>;
|
|
415
|
+
readonly locks: Map<string, Promise<unknown>>;
|
|
416
|
+
readonly deletingAgents: Set<string>;
|
|
417
|
+
readonly deletingSessions: Set<string>;
|
|
418
|
+
readonly agentGenerations: Map<string, number>;
|
|
419
|
+
readonly liveTail: LiveTail;
|
|
420
|
+
/** The generation working on this state right now; every generation points it at itself when it takes the state over. */
|
|
421
|
+
current: RunStarter | null;
|
|
422
|
+
}) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
423
|
+
/** AgentState: the mechanism AgentStateStore implements — the Session runtime's in-memory state, data only. */
|
|
424
|
+
declare abstract class AgentState extends AgentState_base {
|
|
425
|
+
}
|
|
426
|
+
/** SessionOrigins: the mechanism SessionSources implements. */
|
|
427
|
+
declare abstract class SessionOrigins {
|
|
428
|
+
/** `null`: the Session's Trace head records no source (written before it was required). */
|
|
429
|
+
abstract set(sessionId: string, source: SessionSource | null): void;
|
|
430
|
+
abstract get(sessionId: string): SessionSource | null | undefined;
|
|
431
|
+
abstract delete(sessionId: string): void;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* sessions table repo:
|
|
436
|
+
* Session index, approval mode, and auto-generated title; Session-level routes use this to look up project ownership.
|
|
437
|
+
*/
|
|
438
|
+
|
|
439
|
+
interface SessionRow {
|
|
440
|
+
sessionId: string;
|
|
441
|
+
projectId: string;
|
|
442
|
+
agentId: string;
|
|
443
|
+
/**
|
|
444
|
+
* Provider group of the session's **current** model (pairs with `modelId` to form the model
|
|
445
|
+
* reference). Moves with each in-session switch (`updateModel`); the Trace's latest
|
|
446
|
+
* `session_meta` is the durable truth, and the runtime reconciles a row it disagrees with.
|
|
447
|
+
*/
|
|
448
|
+
provider: string;
|
|
449
|
+
/** Upstream model_id of the session's current model (sent as-is to MMSP; never concatenated). */
|
|
450
|
+
modelId: string;
|
|
451
|
+
workspace: string;
|
|
452
|
+
approvalMode: ApprovalMode;
|
|
453
|
+
/**
|
|
454
|
+
* Thinking level pinned for THIS session (PATCH /api/sessions/:id); NULL = not pinned,
|
|
455
|
+
* so runs that carry no level of their own follow the Agent config as before. Sessions
|
|
456
|
+
* are never inserted with one — pinning is an explicit act inside a live conversation.
|
|
457
|
+
*/
|
|
458
|
+
thinkingLevel?: ThinkingLevelName$1 | null;
|
|
459
|
+
/** Auto-generated session title; NULL = not yet generated (frontend shows "New Conversation"). */
|
|
460
|
+
title: string | null;
|
|
461
|
+
/**
|
|
462
|
+
* When the user archived it, ISO; NULL = not archived by hand (omitting on insert defaults to
|
|
463
|
+
* NULL). Archived or not is the listing rule's answer (runtime/session-sources.ts listCategory):
|
|
464
|
+
* a background Session without `keptAt` is filed away too.
|
|
465
|
+
*/
|
|
466
|
+
archivedAt?: string | null;
|
|
467
|
+
/**
|
|
468
|
+
* The "keep in sidebar" mark, ISO: written when the user unarchives the Session, cleared when
|
|
469
|
+
* they archive it, never written by a run. It is what keeps a background Session (api /
|
|
470
|
+
* schedule / cli) listed among the conversations; NULL = the listing rule decides.
|
|
471
|
+
*/
|
|
472
|
+
keptAt?: string | null;
|
|
473
|
+
/**
|
|
474
|
+
* Creating client: "web" (created via the Web App), "cli" (created through the API by
|
|
475
|
+
* the CLI, or adopted from a Trace a legacy CLI-direct run left behind), "org" (opened by
|
|
476
|
+
* the organization runtime — a desk or a ticket session — or spawned as a sub-session of
|
|
477
|
+
* one, which inherits the stamp at registration), "api" (created by an Agent API run — the
|
|
478
|
+
* only rows the API may continue); NULL = legacy row from before the column existed,
|
|
479
|
+
* treated as web. Free text in SQLite, so a new value needs no migration.
|
|
480
|
+
* Provenance that outlives its organization: development mode's list is the one reader
|
|
481
|
+
* that filters on it (through the list's `excludeOrg`), and it hides "org" rows whether or
|
|
482
|
+
* not the organization still exists. The schedule/subagent SOURCE is deliberately NOT a row field — core
|
|
483
|
+
* session_meta in the Trace stays the single source of truth for it
|
|
484
|
+
* (runtime/session-sources.ts); `client` is a separate, DB-only axis that meta never
|
|
485
|
+
* records.
|
|
486
|
+
*/
|
|
487
|
+
client?: "web" | "cli" | "org" | "api" | null;
|
|
488
|
+
/** Cache: a Trace record exists (set at task start / adoption / subagent registration; backfilled by list hydration). */
|
|
489
|
+
hasTrace?: boolean;
|
|
490
|
+
/**
|
|
491
|
+
* Last activity this server drove for the session, ISO: stamped once when a run starts
|
|
492
|
+
* and once when it ends (see SessionManager.drive — a goal run is ONE drive over all its
|
|
493
|
+
* rounds, so it stamps twice, not per round). Set to createdAt at insert, and only ever
|
|
494
|
+
* moves forward (markDriven/touchLastActive clamp with MAX, so a backwards clock step
|
|
495
|
+
* cannot regress it). Rows this server never drives — CLI-adopted Sessions, subagent
|
|
496
|
+
* rows (their activity is driven through the parent's entry) — keep createdAt.
|
|
497
|
+
*/
|
|
498
|
+
lastActiveAt: string;
|
|
499
|
+
createdAt: string;
|
|
500
|
+
/**
|
|
501
|
+
* The Session's own sandbox policy, snapshotted from the server's settings at creation.
|
|
502
|
+
* NULL = a row from before the column: it takes the settings at its next command.
|
|
503
|
+
*/
|
|
504
|
+
sandbox?: SandboxSettings | null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Tool call approval: ApproveFn factory + pending
|
|
509
|
+
* approval registry.
|
|
510
|
+
*
|
|
511
|
+
* Approval mode semantics match the CLI (packages/cli/src/approval.ts):
|
|
512
|
+
* allow-all auto-approves; deny-all auto-denies; read-only allows read-only tools
|
|
513
|
+
* (permission==="r") and routes the rest to manual approval; always-ask routes
|
|
514
|
+
* everything to manual approval. Routing to manual approval registers a pending entry
|
|
515
|
+
* and pushes an `approval_request` via SSE, suspending until the frontend decides via
|
|
516
|
+
* `POST /approvals/:toolCallId`; no timeout — pending approvals are resolved to deny
|
|
517
|
+
* when the Task is interrupted (then proceeds through the abort flow).
|
|
518
|
+
*
|
|
519
|
+
* An unattended Session takes that one route differently: there is no frontend to ask, so
|
|
520
|
+
* the call is denied on the spot instead of suspending forever (see `unattended` below).
|
|
521
|
+
*
|
|
522
|
+
* Every approval decision re-reads the current approval_mode (`getMode` reads the DB),
|
|
523
|
+
* so mode changes take effect immediately.
|
|
524
|
+
* Docs: /docs/tools § "Approval".
|
|
525
|
+
*/
|
|
526
|
+
|
|
527
|
+
interface PendingApproval {
|
|
528
|
+
toolCall: OmniMessage<ToolCallPayload>;
|
|
529
|
+
origin?: string[];
|
|
530
|
+
}
|
|
531
|
+
/** A Session runtime's pending approvals, as its holder uses them — what {@link ApprovalRegistry} implements. */
|
|
532
|
+
interface Approvals {
|
|
533
|
+
readonly size: number;
|
|
534
|
+
list(): PendingApproval[];
|
|
535
|
+
wait(toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision>;
|
|
536
|
+
decide(toolCallId: string, decision: ApprovalDecision): boolean;
|
|
537
|
+
denyAll(): void;
|
|
538
|
+
denyMain(): void;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** A numbered channel event; `id` is `<epoch>-<seq>`, `data` is serialized single-line JSON. */
|
|
542
|
+
interface ChannelEvent {
|
|
543
|
+
id: string;
|
|
544
|
+
/** SSE event name; omitted (OmniMessage) means no `event:` line. */
|
|
545
|
+
event?: string;
|
|
546
|
+
data: string;
|
|
547
|
+
}
|
|
548
|
+
type ChannelListener = (evt: ChannelEvent) => void;
|
|
549
|
+
interface ChannelOptions {
|
|
550
|
+
maxBufferCount?: number;
|
|
551
|
+
maxBufferBytes?: number;
|
|
552
|
+
}
|
|
553
|
+
declare class Channel {
|
|
554
|
+
/** Channel epoch: generated at instance creation, prefixed onto event ids (necessarily changes after recycle/recreate or restart). */
|
|
555
|
+
readonly epoch: string;
|
|
556
|
+
private nextSeq;
|
|
557
|
+
private buffer;
|
|
558
|
+
private bufferBytes;
|
|
559
|
+
/** Max seq among evicted events (0 means never evicted): lower bound for hit checks. */
|
|
560
|
+
private lastEvictedSeq;
|
|
561
|
+
private readonly listeners;
|
|
562
|
+
private readonly maxCount;
|
|
563
|
+
private readonly maxBytes;
|
|
564
|
+
/** Timestamp of last activity (publish/subscription change), used for idle-reclaim checks. */
|
|
565
|
+
lastActivityMs: number;
|
|
566
|
+
constructor(opts?: ChannelOptions);
|
|
567
|
+
get subscriberCount(): number;
|
|
568
|
+
/**
|
|
569
|
+
* Id of the most recently assigned event (`<epoch>-<seq>`; seq 0 when none was assigned
|
|
570
|
+
* yet). Unicast (sendTo) seqs count too — the value is a position marker, not a buffer
|
|
571
|
+
* lookup key. Used as the live-tail cursor on GET /messages: captured in the same
|
|
572
|
+
* synchronous tick as the fragment snapshot, it tells the client which buffered events
|
|
573
|
+
* the snapshot already covers.
|
|
574
|
+
*/
|
|
575
|
+
get lastEventId(): string;
|
|
576
|
+
/** Broadcast an event: number it, buffer it (evicting the oldest), notify all subscribers. */
|
|
577
|
+
publish(data: unknown, event?: string): ChannelEvent;
|
|
578
|
+
/** Unicast an event to a single subscriber: consumes a seq but doesn't buffer or broadcast (used for replay at subscribe time). */
|
|
579
|
+
sendTo(listener: ChannelListener, data: unknown, event?: string): ChannelEvent;
|
|
580
|
+
subscribe(listener: ChannelListener): () => void;
|
|
581
|
+
/**
|
|
582
|
+
* Compute replay from a Last-Event-ID (`<epoch>-<seq>`): a mismatched epoch (channel
|
|
583
|
+
* recycled/recreated, process restarted, or malformed id) always misses; a matching
|
|
584
|
+
* epoch hits the buffer (if no events after that seq have been evicted and the seq was
|
|
585
|
+
* indeed assigned by this channel) and returns the buffered events after it; otherwise
|
|
586
|
+
* miss (the caller should send `resync_required` first).
|
|
587
|
+
*/
|
|
588
|
+
replayAfter(lastEventId: string): {
|
|
589
|
+
hit: boolean;
|
|
590
|
+
events: ChannelEvent[];
|
|
591
|
+
};
|
|
592
|
+
private makeEvent;
|
|
593
|
+
}
|
|
594
|
+
interface ChannelHubOptions {
|
|
595
|
+
idleMs?: number;
|
|
596
|
+
/**
|
|
597
|
+
* Active check: keys for which this returns true are excluded from idle reclaim (app
|
|
598
|
+
* assembly injects `manager.statusOf(key) !== "idle"`, so a running/compacting
|
|
599
|
+
* Session channel is never reclaimed no matter how long since its last publish; a
|
|
600
|
+
* user channel key looks like `user:<id>` and is always considered active).
|
|
601
|
+
*/
|
|
602
|
+
isActive?: (key: string) => boolean;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Channel collection: lazily created by key (Session id or `user:<user_id>`);
|
|
606
|
+
* a channel whose Session is idle and has had no subscribers for over 30 minutes is
|
|
607
|
+
* reclaimed, releasing its buffer as well.
|
|
608
|
+
*/
|
|
609
|
+
declare class ChannelHub {
|
|
610
|
+
private readonly channels;
|
|
611
|
+
private readonly timer;
|
|
612
|
+
private readonly idleMs;
|
|
613
|
+
private isActive;
|
|
614
|
+
constructor(opts?: ChannelHubOptions);
|
|
615
|
+
/**
|
|
616
|
+
* Replaces the activity probe. "Is this session busy" is a business question the hub
|
|
617
|
+
* (runtime mechanism) cannot answer itself; the App installs its answer at create over
|
|
618
|
+
* the claimed hub — ordinary capability use, not a registry entry. Overwrite-only, the
|
|
619
|
+
* cross-generation discipline: a successor installs its own probe and a dead
|
|
620
|
+
* generation's is simply left behind (its manager has drained, so it reports idle —
|
|
621
|
+
* never wrong, only replaced).
|
|
622
|
+
*/
|
|
623
|
+
setActivityProbe(probe: (key: string) => boolean): void;
|
|
624
|
+
get(key: string): Channel;
|
|
625
|
+
peek(key: string): Channel | undefined;
|
|
626
|
+
/**
|
|
627
|
+
* Publishes to every live channel whose key starts with `prefix` — global
|
|
628
|
+
* notices (e.g. `user:` for "the web assets were hot-swapped, reload").
|
|
629
|
+
* Only reaches channels that exist (i.e. someone connected at least once);
|
|
630
|
+
* that is the right scope for a notice that only matters to live clients.
|
|
631
|
+
*/
|
|
632
|
+
broadcast(prefix: string, data: unknown, event?: string): void;
|
|
633
|
+
/** Reclaim idle channels (skips active Sessions: no reclaim even without a publish while awaiting approval); `now` is injectable for tests. */
|
|
634
|
+
sweep(now?: number): void;
|
|
635
|
+
dispose(): void;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Attribution context for one record (top-level Session scope). */
|
|
639
|
+
interface UsageContext {
|
|
640
|
+
projectId: string;
|
|
641
|
+
agentId: string;
|
|
642
|
+
/** Top-level Session id (the current actual id after self-heal). */
|
|
643
|
+
sessionId: string;
|
|
644
|
+
/** Vendor grouping for the top-level Session's model (paired with modelId; the fallback attribution when the origin mapping has no hit). */
|
|
645
|
+
provider: string;
|
|
646
|
+
/** Upstream model_id of the top-level Session (paired with provider). */
|
|
647
|
+
modelId: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Usage statistics query.
|
|
652
|
+
*
|
|
653
|
+
* Cost is **computed in real time**: usage_records only stores Tokens (pricing may
|
|
654
|
+
* be added later), so at query time each Model's cost is converted using the
|
|
655
|
+
* current Project's configured pricing — the repo returns raw Token totals broken
|
|
656
|
+
* down by `(provider, model_id)` paired reference, and this service looks up each
|
|
657
|
+
* reference's price once and folds it into cost / hasUncosted (if a Model has no
|
|
658
|
+
* pricing, its consumption is excluded from cost and hasUncosted is flagged).
|
|
659
|
+
* Summary cards (today / last 7 days / cumulative), grouped aggregation (date /
|
|
660
|
+
* agent / model / session, with the session dimension supporting agentId drill-down
|
|
661
|
+
* filtering), and the zero-filled time series the cost center's charts draw.
|
|
662
|
+
* Server-side error statistics (error_records) ride along on the same response:
|
|
663
|
+
* the statistics center fetches everything in one request, and filters are
|
|
664
|
+
* naturally shared; unattributed errors (login failures, process crashes, and other
|
|
665
|
+
* errors with no Project context) are visible only to admins, see the ErrorsRepo
|
|
666
|
+
* file header.
|
|
667
|
+
*/
|
|
668
|
+
|
|
669
|
+
/** The three pricing buckets (usd_per_mtok convention), returned by the pricing lookup callback. */
|
|
670
|
+
interface PricingRates {
|
|
671
|
+
cacheRead: number;
|
|
672
|
+
cacheWrite: number;
|
|
673
|
+
output: number;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* What one Model costs, in the tiers it can be billed in.
|
|
677
|
+
*
|
|
678
|
+
* `offPeak` differs from `peak` only for a catalog row on a time-based schedule whose stored
|
|
679
|
+
* price is still the catalog's own. Both are returned together because a query spans time: a
|
|
680
|
+
* week that straddles the boundary contains Tokens of both kinds, and each half is priced at
|
|
681
|
+
* the rate it actually ran at rather than at whichever tier is in force when it is read.
|
|
682
|
+
*/
|
|
683
|
+
interface TieredRates {
|
|
684
|
+
peak: PricingRates;
|
|
685
|
+
offPeak: PricingRates;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** Errors: the mechanism ErrorRecorder implements. */
|
|
689
|
+
declare abstract class Errors {
|
|
690
|
+
abstract record(args: ErrorRecordArgs): void;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/** Capture-site source (maps one-to-one to error_records.source). */
|
|
694
|
+
type ErrorSource = "http" | "organization" | "session" | "llm" | "environment" | "compaction" | "usage" | "title" | "subagent" | "process" | "schedule" | "messaging" | "id_suggest" | "plugin";
|
|
695
|
+
/** Error classification: see file header — the criterion is "does a human need to step in". */
|
|
696
|
+
type ErrorKind = "expected" | "unexpected";
|
|
697
|
+
/** Attribution context (all optional: the login endpoint has no Project, and process-level fallback has no request at all). */
|
|
698
|
+
interface ErrorContext {
|
|
699
|
+
projectId?: string;
|
|
700
|
+
agentId?: string;
|
|
701
|
+
sessionId?: string;
|
|
702
|
+
}
|
|
703
|
+
interface ErrorRecordArgs {
|
|
704
|
+
source: ErrorSource;
|
|
705
|
+
/** The caught error (unknown: the value caught may not be an Error; failures from the message stream pass the reason text directly). */
|
|
706
|
+
err: unknown;
|
|
707
|
+
ctx?: ErrorContext;
|
|
708
|
+
/** Semantic code (required for non-HTTP sources, e.g. session_run_failed); defaults to HttpError.code. */
|
|
709
|
+
code?: string;
|
|
710
|
+
/** HTTP status code; leave empty for non-HTTP sources. */
|
|
711
|
+
status?: number;
|
|
712
|
+
/** Explicit classification (see file header); defaults to inferring from `HttpError` — HTTP sources rely on this, other sources should pass it explicitly. */
|
|
713
|
+
kind?: ErrorKind;
|
|
714
|
+
}
|
|
715
|
+
declare class ErrorRecorder implements Errors {
|
|
716
|
+
/** Dedup table: `source \0 code \0 projectId` → timestamp of the last **persist** (see file header). */
|
|
717
|
+
private readonly lastSeen;
|
|
718
|
+
private readonly errors;
|
|
719
|
+
private readonly clock;
|
|
720
|
+
/** Record an error (synchronous, fails silently; same-window duplicates are dropped outright, see file header). */
|
|
721
|
+
record(args: ErrorRecordArgs): void;
|
|
722
|
+
/** true if a same-kind error was already recorded within the window (drop it); otherwise register this persist timestamp and keep the dedup table bounded. */
|
|
723
|
+
private deduped;
|
|
724
|
+
/** Keep the dedup table bounded (see file header): clear expired entries first; if still over the limit (hundreds/thousands of distinct error codes erupting at once), clear it entirely. */
|
|
725
|
+
private evict;
|
|
726
|
+
}
|
|
727
|
+
/** Minimal dependency a capture site needs on the recorder (tests inject a fake; structurally matches SessionManager's UsageRecorderLike). */
|
|
728
|
+
type ErrorSink = Pick<ErrorRecorder, "record">;
|
|
729
|
+
|
|
730
|
+
/** Host-side parameters for one title-generation request. */
|
|
731
|
+
interface TitleRequest {
|
|
732
|
+
/** The user input this run started from: its first words become the immediate fallback title. */
|
|
733
|
+
fallbackText: string;
|
|
734
|
+
/** Material override (the main session passes its input text, a subagent the prompt that spawned it — assistant material stays empty either way, so the request never waits on model output); defaults to the material self-collected by the core Session. */
|
|
735
|
+
material?: {
|
|
736
|
+
userText: string;
|
|
737
|
+
assistantText: string;
|
|
738
|
+
};
|
|
739
|
+
/** The channel to push the `session_title` event to; defaults to `ctx.sessionId`. A
|
|
740
|
+
* subagent has no SSE channel of its own, so its title must reach the frontend via
|
|
741
|
+
* the **parent Session's** channel (the list updates in place by sessionId). */
|
|
742
|
+
notifyOn?: string;
|
|
743
|
+
}
|
|
744
|
+
/** session-manager's minimal dependency on the title generator (tests inject a fake implementation). */
|
|
745
|
+
interface TitleNotifier {
|
|
746
|
+
maybeGenerate(ctx: UsageContext, session: Pick<RuntimeSession, "generateTitle">, req: TitleRequest): void;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* In-process registry of Session sources, derived from core `session_meta` — the single
|
|
751
|
+
* source of truth for what kind of conversation a Session is (the DB stores no `source`
|
|
752
|
+
* column).
|
|
753
|
+
*
|
|
754
|
+
* Populated wherever the server actually has the meta in hand: Session creation
|
|
755
|
+
* (SessionService reads the just-created core Session's meta), subagent registration
|
|
756
|
+
* (SessionManager reads the forwarded child meta), forks (always `user`), and Trace adoption /
|
|
757
|
+
* lazy list resolution (the trace index's head read). A value read back from a Trace is
|
|
758
|
+
* narrowed through core's `normalizeSessionSource` (the retired `benchmark` reads as `cli`),
|
|
759
|
+
* except that a head recording no source is kept as `null`: what it reads as depends on the
|
|
760
|
+
* Session's index row (`readRecordedSource`), which a head read does not have. An absent entry
|
|
761
|
+
* means "unknown": the list path resolves it from the Trace once per process lifetime.
|
|
762
|
+
*/
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* What a Session's Trace head records as its source: one of the sources, or `null` for a head
|
|
766
|
+
* written before the source was required, which records none.
|
|
767
|
+
*/
|
|
768
|
+
type RecordedSource = SessionSource | null;
|
|
769
|
+
|
|
770
|
+
/** One indexed Trace shard. */
|
|
771
|
+
interface TraceFileRow {
|
|
772
|
+
projectId: string;
|
|
773
|
+
agentId: string;
|
|
774
|
+
sessionId: string;
|
|
775
|
+
fileIndex: number;
|
|
776
|
+
date: string;
|
|
777
|
+
sizeBytes: number;
|
|
778
|
+
}
|
|
779
|
+
/** One Session's registration-time facts (earliest shard's head). */
|
|
780
|
+
interface TraceSessionRow {
|
|
781
|
+
sessionId: string;
|
|
782
|
+
projectId: string;
|
|
783
|
+
agentId: string;
|
|
784
|
+
/**
|
|
785
|
+
* The head's session_meta source, narrowed; `null` when the head records none (written before
|
|
786
|
+
* the source was required; read it with the row's client, `readRecordedSource`). Meaningful
|
|
787
|
+
* only once `metaRead`.
|
|
788
|
+
*/
|
|
789
|
+
source: RecordedSource;
|
|
790
|
+
workspace: string;
|
|
791
|
+
title: string | null;
|
|
792
|
+
provider: string | null;
|
|
793
|
+
modelId: string | null;
|
|
794
|
+
firstTs: string | null;
|
|
795
|
+
metaRead: boolean;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** TraceIndexStore: the mechanism TraceIndexRepo implements. */
|
|
799
|
+
declare abstract class TraceIndexStore {
|
|
800
|
+
abstract upsertFile(row: TraceFileRow): void;
|
|
801
|
+
abstract updateFileSize(projectId: string, agentId: string, sessionId: string, fileIndex: number, sizeBytes: number): void;
|
|
802
|
+
abstract getPageStats(projectId: string, agentId: string, sessionId: string, fileIndex: number): string | null;
|
|
803
|
+
abstract setPageStats(projectId: string, agentId: string, sessionId: string, fileIndex: number, sizeBytes: number, pageStats: string): void;
|
|
804
|
+
abstract deleteFile(projectId: string, agentId: string, sessionId: string, fileIndex: number): void;
|
|
805
|
+
abstract listFilesByAgent(projectId: string, agentId: string): TraceFileRow[];
|
|
806
|
+
abstract listFilesBySession(projectId: string, agentId: string, sessionId: string): TraceFileRow[];
|
|
807
|
+
abstract findAgentBySession(projectId: string, sessionId: string): string | null;
|
|
808
|
+
abstract upsertSession(row: TraceSessionRow): void;
|
|
809
|
+
abstract getSession(sessionId: string): TraceSessionRow | null;
|
|
810
|
+
abstract listSessionsByAgent(projectId: string, agentId: string): TraceSessionRow[];
|
|
811
|
+
abstract deleteBySession(sessionId: string): void;
|
|
812
|
+
abstract deleteByAgent(projectId: string, agentId: string): void;
|
|
813
|
+
abstract deleteByProject(projectId: string): void;
|
|
814
|
+
}
|
|
815
|
+
/** TraceIndex: the mechanism TraceIndexService implements. */
|
|
816
|
+
declare abstract class TraceIndex {
|
|
817
|
+
abstract readonly counters: {
|
|
818
|
+
gateStats: number;
|
|
819
|
+
dirScans: number;
|
|
820
|
+
headReads: number;
|
|
821
|
+
};
|
|
822
|
+
abstract reconcileAgent(projectId: string, agentId: string, opts?: {
|
|
823
|
+
force?: boolean;
|
|
824
|
+
}): Promise<void>;
|
|
825
|
+
abstract reconcileProject(projectId: string, opts?: {
|
|
826
|
+
force?: boolean;
|
|
827
|
+
}): Promise<void>;
|
|
828
|
+
abstract registerImportedFile(args: {
|
|
829
|
+
projectId: string;
|
|
830
|
+
agentId: string;
|
|
831
|
+
sessionId: string;
|
|
832
|
+
fileIndex: number;
|
|
833
|
+
date: string;
|
|
834
|
+
sizeBytes: number;
|
|
835
|
+
records: OmniMessage[];
|
|
836
|
+
}): void;
|
|
837
|
+
abstract removeSession(projectId: string, agentId: string, sessionId: string): void;
|
|
838
|
+
abstract removeAgent(projectId: string, agentId: string): void;
|
|
839
|
+
abstract removeProject(projectId: string): void;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
interface PlatformCatalogPricing {
|
|
843
|
+
unit: "usd_per_mtok";
|
|
844
|
+
cacheRead: number;
|
|
845
|
+
cacheWrite: number;
|
|
846
|
+
output: number;
|
|
847
|
+
}
|
|
848
|
+
/** A platform model normalized into the fields Penguin persists for a newly discovered row. */
|
|
849
|
+
interface PlatformCatalogModel {
|
|
850
|
+
modelId: string;
|
|
851
|
+
displayName: string;
|
|
852
|
+
contextWindow: number;
|
|
853
|
+
supportsVision: boolean;
|
|
854
|
+
/**
|
|
855
|
+
* List price, which is what a Project stores: the platform's `listPricing` when it runs a
|
|
856
|
+
* promotion on the model, its billed `pricing` otherwise.
|
|
857
|
+
*/
|
|
858
|
+
pricing: PlatformCatalogPricing;
|
|
859
|
+
/** Fraction off `pricing` the platform is running (0.5 = half price); absent when none. */
|
|
860
|
+
discount?: number;
|
|
861
|
+
baseUrl: string;
|
|
862
|
+
clientType: "mmsp";
|
|
863
|
+
}
|
|
864
|
+
/** Validated snapshot returned by Penguin Go's client-model catalog. */
|
|
865
|
+
interface PlatformModelCatalog {
|
|
866
|
+
models: PlatformCatalogModel[];
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Catalog merge result. `updated` counts route migrations; existing metadata is preserved.
|
|
870
|
+
*/
|
|
871
|
+
interface PlatformModelApplyResult {
|
|
872
|
+
added: number;
|
|
873
|
+
updated: number;
|
|
874
|
+
applied: number;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
type RawTable = Record<string, unknown>;
|
|
878
|
+
/**
|
|
879
|
+
* What one utility completion produced: the model's text, or why there is none. A bare null
|
|
880
|
+
* would say "no answer" and nothing else — and every one of these failures (no model
|
|
881
|
+
* configured, a missing credential, a provider rejection, a timeout, an answer that was all
|
|
882
|
+
* thinking) is one the user can act on, so the reason travels to the caller, which records it.
|
|
883
|
+
*/
|
|
884
|
+
type UtilityCompletion = {
|
|
885
|
+
ok: true;
|
|
886
|
+
text: string;
|
|
887
|
+
} | {
|
|
888
|
+
ok: false;
|
|
889
|
+
/** `no_model`: the Project names no default model, or names one it has no entry for — nothing was asked. `failed`: the model was asked and produced no text. */
|
|
890
|
+
cause: "no_model" | "failed";
|
|
891
|
+
error: string;
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
/** ProjectConfigStore: the mechanism ProjectConfigService implements. */
|
|
895
|
+
declare abstract class ProjectConfigStore {
|
|
896
|
+
abstract readRaw(projectId: string): Promise<RawTable>;
|
|
897
|
+
abstract loadConfig(projectId: string): Promise<ProjectConfig>;
|
|
898
|
+
abstract writeRaw(projectId: string, data: RawTable): Promise<void>;
|
|
899
|
+
abstract writeInitialConfig(projectId: string, name: string): Promise<void>;
|
|
900
|
+
abstract ensurePresetModels(projectId: string): Promise<boolean>;
|
|
901
|
+
abstract seedPresetPromotions(projectId: string): Promise<void>;
|
|
902
|
+
abstract getName(projectId: string): Promise<string | undefined>;
|
|
903
|
+
abstract setName(projectId: string, name: string): Promise<void>;
|
|
904
|
+
abstract getDefaultModelRef(projectId: string): Promise<ModelRef | undefined>;
|
|
905
|
+
abstract setDefaultModelRef(projectId: string, ref: ModelRefDto): Promise<ModelRefDto>;
|
|
906
|
+
abstract getChatDefaults(projectId: string): Promise<ChatDefaultsDto>;
|
|
907
|
+
abstract setChatDefaults(projectId: string, req: ChatDefaultsDto): Promise<ChatDefaultsDto>;
|
|
908
|
+
/** The `[plugins]` key: the shared table and each machine's own, package name → requirement, in the file's order. */
|
|
909
|
+
abstract getPluginTables(projectId: string): Promise<PluginTables>;
|
|
910
|
+
/** Replaces the tables (a declarative PUT); answers what was written. */
|
|
911
|
+
abstract setPluginTables(projectId: string, tables: PluginTables): Promise<PluginTables>;
|
|
912
|
+
abstract getCommandPolicy(projectId: string): Promise<CommandPolicyDto>;
|
|
913
|
+
abstract setCommandPolicy(projectId: string, req: {
|
|
914
|
+
enabled?: boolean;
|
|
915
|
+
rules: {
|
|
916
|
+
name: string;
|
|
917
|
+
pattern: string;
|
|
918
|
+
description?: string;
|
|
919
|
+
enabled?: boolean;
|
|
920
|
+
}[];
|
|
921
|
+
}): Promise<CommandPolicyDto>;
|
|
922
|
+
abstract getPricing(projectId: string, provider: string, modelId: string): Promise<TieredRates | undefined>;
|
|
923
|
+
abstract detectVision(projectId: string, req: ModelVisionDetectRequest): Promise<ModelVisionDetectResponse>;
|
|
924
|
+
abstract testModel(projectId: string, req: ModelTestRequest): Promise<ModelTestResponse>;
|
|
925
|
+
abstract detectProtocol(projectId: string, req: ModelProtocolDetectRequest): Promise<ModelProtocolDetectResponse>;
|
|
926
|
+
abstract listEndpointModels(req: EndpointModelListRequest, listImpl?: (options: ListEndpointModelsOptions) => Promise<string[]>, timeoutMs?: number): Promise<EndpointModelListResponse>;
|
|
927
|
+
abstract getModels(projectId: string): Promise<ModelsResponse>;
|
|
928
|
+
abstract updateModels(projectId: string, req: ModelsUpdateRequest): Promise<ModelsResponse>;
|
|
929
|
+
/** One group's `[providers.<id>]` connection, merged per field; rows are never touched. */
|
|
930
|
+
abstract setProviderConnection(projectId: string, provider: string, patch: ProviderConnectionUpdate): Promise<ModelsResponse>;
|
|
931
|
+
/** "Add new models" (`add`) or "Restore defaults" (`restore`) against the built-in catalog. */
|
|
932
|
+
abstract syncPresets(projectId: string, mode: PresetSyncMode): Promise<PresetSyncResponse>;
|
|
933
|
+
/** Writes the group key; resolves to how many of the group's rows use it (none of their own). */
|
|
934
|
+
abstract setGroupApiKey(projectId: string, provider: string, apiKey: string): Promise<number>;
|
|
935
|
+
abstract setGroupApiKeyWithProviderAuthToken(projectId: string, provider: string, apiKey: string, token: Omit<ModelProviderAuthToken, "provider" | "updatedAt">, options?: {
|
|
936
|
+
expectedRefreshToken?: string;
|
|
937
|
+
}): Promise<number>;
|
|
938
|
+
/** The group key (`[providers.<id>].api_key`), never a row's own. Server-side only. */
|
|
939
|
+
abstract getGroupApiKey(projectId: string, provider: string): Promise<string | undefined>;
|
|
940
|
+
/** One row's own key (its override of the group's), or undefined. Server-side only. */
|
|
941
|
+
abstract getModelApiKey(projectId: string, provider: string, modelId: string): Promise<string | undefined>;
|
|
942
|
+
/**
|
|
943
|
+
* The key a group's account balance is read with: the group key (never a row's own), or
|
|
944
|
+
* else the environment key a Session on one of the group's rows would use on its effective
|
|
945
|
+
* endpoint, when that variable's official endpoint is the balance endpoint's own host.
|
|
946
|
+
* Server-side only.
|
|
947
|
+
*/
|
|
948
|
+
abstract getGroupBalanceKey(projectId: string, provider: string): Promise<string | undefined>;
|
|
949
|
+
abstract setModelApiKeyResolver(resolver: (context: ModelRequestContext) => Promise<string | undefined>): void;
|
|
950
|
+
abstract mergePlatformModels(projectId: string, provider: string, catalog: PlatformModelCatalog, apiKey: string, applyKeyToExisting: boolean): Promise<PlatformModelApplyResult>;
|
|
951
|
+
abstract completeOnce(projectId: string, prompt: string): Promise<UtilityCompletion>;
|
|
952
|
+
}
|
|
953
|
+
interface ModelProviderAuthToken {
|
|
954
|
+
provider: string;
|
|
955
|
+
refreshToken: string;
|
|
956
|
+
accessTokenExpiresAt?: string;
|
|
957
|
+
updatedAt: string;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* The order a Session list is served in: `created` (the default) is newest creation first;
|
|
962
|
+
* `activity` is most recent `lastActiveAt` first — the order the sidebar displays, and the only
|
|
963
|
+
* one an {@link ActivityCursor} pages.
|
|
964
|
+
*/
|
|
965
|
+
type SessionListOrder = "created" | "activity";
|
|
966
|
+
/** A row's place in the activity order; as a `before` cursor, the last row the client holds. */
|
|
967
|
+
interface ActivityCursor {
|
|
968
|
+
lastActiveAt: string;
|
|
969
|
+
sessionId: string;
|
|
970
|
+
}
|
|
971
|
+
/** One row the listing rule files as archived (SessionService.archivedRows). */
|
|
972
|
+
interface ArchivedRow {
|
|
973
|
+
row: SessionRow;
|
|
974
|
+
/** The source the row was classified by; absent while its Trace head is unread. */
|
|
975
|
+
source?: SessionSource;
|
|
976
|
+
/** Whether a Trace record exists: what toInfo takes for this row. */
|
|
977
|
+
hasTrace: boolean;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* A slice of a Session list: `offset` rows skipped (the `created` contract, also accepted under
|
|
981
|
+
* `activity`), or every row strictly below `before` in the activity order.
|
|
982
|
+
*/
|
|
983
|
+
type SessionListPaging = {
|
|
984
|
+
offset: number;
|
|
985
|
+
limit: number;
|
|
986
|
+
} | {
|
|
987
|
+
before: ActivityCursor;
|
|
988
|
+
limit: number;
|
|
989
|
+
};
|
|
990
|
+
interface SessionServiceDeps {
|
|
991
|
+
root: string;
|
|
992
|
+
sessions: SessionIndex;
|
|
993
|
+
manager: SessionManager;
|
|
994
|
+
projectConfig: ProjectConfigStore;
|
|
995
|
+
/** In-process origin registry derived from session_meta (the DB stores no source column). */
|
|
996
|
+
sources: SessionOrigins;
|
|
997
|
+
/** Trace-file index: discovery / adoption / stats serve from it (mtime-gated reconciler; no per-request walks). */
|
|
998
|
+
traceIndex: TraceIndex;
|
|
999
|
+
/** The index rows themselves (files and sessions), for the reads the service does directly. */
|
|
1000
|
+
traceStore: TraceIndexStore;
|
|
1001
|
+
/**
|
|
1002
|
+
* Admin proxy-settings threading (same getter the session loader passes, see
|
|
1003
|
+
* createCoreSessionLoader): the runtime created here is adopted by the manager and
|
|
1004
|
+
* runs the Session's first Task, so it needs the command-subprocess proxy policy too.
|
|
1005
|
+
*/
|
|
1006
|
+
proxyEnv?: () => ProxyEnvPolicy | null;
|
|
1007
|
+
/**
|
|
1008
|
+
* Harness-control env threading (same policy the session loader passes): the server's
|
|
1009
|
+
* API URL/token plus the Session coordinates, injected into command subprocesses so
|
|
1010
|
+
* agents can drive the harness back through the CLI/API.
|
|
1011
|
+
*/
|
|
1012
|
+
controlEnv?: (ctx: ControlEnvContext) => Record<string, string>;
|
|
1013
|
+
/**
|
|
1014
|
+
* PATH threading (same getter the session loader passes): the shim directory holding
|
|
1015
|
+
* this harness's own `penguin`, put in front of every command an Agent runs.
|
|
1016
|
+
*/
|
|
1017
|
+
pathPrepend?: () => string[];
|
|
1018
|
+
/**
|
|
1019
|
+
* Tells every user of a Project that a Session now exists. The list only ever learns
|
|
1020
|
+
* about rows it did not create itself from this: a Session started by the CLI, by
|
|
1021
|
+
* another tab, by a schedule, or by an agent spawning a child sat invisible until the
|
|
1022
|
+
* next full reload without it. Optional so the service keeps unit-testing without a
|
|
1023
|
+
* channel registry; absent means nobody is told.
|
|
1024
|
+
*/
|
|
1025
|
+
notifyProjectUsers?: (projectId: string, event: ServerEvent) => void;
|
|
1026
|
+
/**
|
|
1027
|
+
* The channel of the Session's ENABLED messaging binding, or null when none is enabled
|
|
1028
|
+
* (SessionInfo.messagingChannel, the sidebar row's per-channel indicator — saved-but-
|
|
1029
|
+
* disabled configs stay off the row). A lookup lambda rather than the repo, so the
|
|
1030
|
+
* service stays decoupled from the bindings table; absent (older assemblies/tests)
|
|
1031
|
+
* means the field is never set.
|
|
1032
|
+
*/
|
|
1033
|
+
messagingChannel?: (sessionId: string) => MessagingChannel | null;
|
|
1034
|
+
/**
|
|
1035
|
+
* Company mode: the organization owning a Session (a desk session, or a session
|
|
1036
|
+
* contributing to a ticket), for `SessionInfo.orgId` — development mode's list hides
|
|
1037
|
+
* those rows, the company sidebar groups them. Two shapes because the two flows cost
|
|
1038
|
+
* differently: the single-Session GET asks about one id, a list asks once for the whole
|
|
1039
|
+
* Project and looks its rows up in the returned map, so a long list never costs a query
|
|
1040
|
+
* per row. Lambdas rather than the repo, so the service stays decoupled from the
|
|
1041
|
+
* company-mode caches; absent (older assemblies/tests) means the field is never set.
|
|
1042
|
+
*/
|
|
1043
|
+
orgIdOfSession?: (sessionId: string) => string | undefined;
|
|
1044
|
+
orgIdsOfProject?: (projectId: string) => ReadonlyMap<string, string>;
|
|
1045
|
+
/** Spawn-confinement getter (the sandbox module's), forwarded into core beside proxyEnv. */
|
|
1046
|
+
confineSpawn?: (ctx: ControlEnvContext) => SpawnConfiner | null;
|
|
1047
|
+
/** The server's Sandbox settings: what a new Session's policy is snapshotted from. */
|
|
1048
|
+
sandboxDefaults?: () => SandboxSettings;
|
|
1049
|
+
/** Host-owned model-request hooks, forwarded to core for every Session LLM. */
|
|
1050
|
+
assembly?: AgentAssembly;
|
|
1051
|
+
/** The dimensions the mounted sandbox backends implement between them (none when absent). */
|
|
1052
|
+
sandboxDimensions?: () => readonly SandboxDimension[];
|
|
1053
|
+
/** The enabled sandbox backends that failed to load or failed their check, with why. */
|
|
1054
|
+
sandboxUnavailable?: () => readonly UnavailableSandboxBackend[];
|
|
1055
|
+
/** The names of the sandbox backends in use, in routing preference (none when absent). */
|
|
1056
|
+
sandboxBackends?: () => readonly string[];
|
|
1057
|
+
/** The Sandbox card's presets table, in table order (absent: the view carries none). */
|
|
1058
|
+
sandboxPresets?: () => readonly SessionSandboxPreset[];
|
|
1059
|
+
/** The Sandbox card's switch: whether new Sessions start confined (absent: not reported). */
|
|
1060
|
+
sandboxSwitchOn?: () => boolean;
|
|
1061
|
+
/** The approval mode a new Session starts with when its request names none (the default preset's). */
|
|
1062
|
+
sandboxDefaultApproval?: () => ApprovalMode | undefined;
|
|
1063
|
+
}
|
|
1064
|
+
declare class SessionService {
|
|
1065
|
+
private readonly deps;
|
|
1066
|
+
constructor(deps: SessionServiceDeps);
|
|
1067
|
+
/** The policy a new Session starts with: the server's Sandbox settings. */
|
|
1068
|
+
defaultSandbox(): SandboxSettings;
|
|
1069
|
+
/** Whether this server can enforce the `local` network level right now. */
|
|
1070
|
+
localNetworkSupported(): boolean;
|
|
1071
|
+
private sandboxDimensions;
|
|
1072
|
+
/** A policy as the composer sees it, with which of its levels this server can enforce. */
|
|
1073
|
+
sandboxView(policy: SandboxSettings): SessionSandbox;
|
|
1074
|
+
/**
|
|
1075
|
+
* What a new Session starts from, as the composer's draft reads it (the chat defaults): the
|
|
1076
|
+
* settings' policy, plus the approval mode the default preset gives a request naming none.
|
|
1077
|
+
*/
|
|
1078
|
+
defaultsView(): SessionSandbox;
|
|
1079
|
+
/**
|
|
1080
|
+
* The approval mode a Session created without one starts with. An organization's Session
|
|
1081
|
+
* keeps `allow-all`: its runtime names the mode it wants, and nobody is there to answer an
|
|
1082
|
+
* ask a preset might bring. The other unattended creators — a scheduled run, a workflow —
|
|
1083
|
+
* pass `allow-all` themselves (scheduler.ts, workflows/service.ts).
|
|
1084
|
+
*/
|
|
1085
|
+
private startApproval;
|
|
1086
|
+
/** A Session's policy: its snapshot, or — for a row from before snapshots — the settings. */
|
|
1087
|
+
sandboxOf(row: SessionRow): SandboxSettings;
|
|
1088
|
+
/**
|
|
1089
|
+
* The policy a pick would give one Session, checked — the non-admin ceiling and what this
|
|
1090
|
+
* server can enforce — and not written: a PATCH checks every field before it writes any.
|
|
1091
|
+
*/
|
|
1092
|
+
pickSandbox(row: SessionRow, pick: Partial<SessionSandbox>, isAdmin: boolean): SandboxSettings;
|
|
1093
|
+
/** Stores one Session's policy (`pickSandbox`'s): its next command runs under it. */
|
|
1094
|
+
updateSandbox(sessionId: string, policy: SandboxSettings): void;
|
|
1095
|
+
/**
|
|
1096
|
+
* DB row -> SessionInfo (run status and pending approval count come from session-manager).
|
|
1097
|
+
* Async because `source` is derived from session_meta: a registry miss (Session predating
|
|
1098
|
+
* this process) falls back to reading the Trace head once (see sourceOf). `traces` is the
|
|
1099
|
+
* list flow's one-walk discovery result; without it a miss locates the shard itself.
|
|
1100
|
+
* `archived` is the listing rule's answer (listCategory), not the bare mark: a background
|
|
1101
|
+
* Session the user never kept reads archived, and a subagent with a stray mark does not.
|
|
1102
|
+
*
|
|
1103
|
+
* `orgIds` is the list flow's one-query organization map (see listSessions); without it the
|
|
1104
|
+
* organization is a point lookup, which is what the single-Session paths want.
|
|
1105
|
+
*/
|
|
1106
|
+
toInfo(row: SessionRow, hasTrace: boolean, orgIds?: ReadonlyMap<string, string>): Promise<SessionInfo>;
|
|
1107
|
+
/**
|
|
1108
|
+
* A Session's source, with session_meta as the single source of truth: the in-process
|
|
1109
|
+
* registry answers first (populated at creation / subagent registration / forks / adoption /
|
|
1110
|
+
* index registration); on a miss (a Session created before this process started) the
|
|
1111
|
+
* trace index's registration-time facts answer — the reconciler head-read the earliest
|
|
1112
|
+
* shard once when the file first appeared, so no file is touched here. A head that records
|
|
1113
|
+
* no source is read with the row's client (readRecordedSource). A Session with no Trace yet
|
|
1114
|
+
* is NOT cached — its meta appears with the first run — and reads as its row says
|
|
1115
|
+
* (unrunSource): the organization runtime's as `company`, the Agent API's as `api`, any other
|
|
1116
|
+
* as unknown.
|
|
1117
|
+
*/
|
|
1118
|
+
private sourceOf;
|
|
1119
|
+
/** Whether this Session already has a Trace record (a Task has been run): answered by the index (reconciled first). */
|
|
1120
|
+
hasTrace(row: SessionRow): Promise<boolean>;
|
|
1121
|
+
/**
|
|
1122
|
+
* The list category of a row (see listCategory): none for a `subagent` or `company` Session,
|
|
1123
|
+
* whatever its marks, which the list never serves; otherwise archived when the user archived
|
|
1124
|
+
* it or when a program opened it and the user has not kept it, else active.
|
|
1125
|
+
*/
|
|
1126
|
+
private categoryOf;
|
|
1127
|
+
/**
|
|
1128
|
+
* Where one row is listed, for a caller holding just the row (a PATCH, the archive page's
|
|
1129
|
+
* batches). A row the registry already classified costs nothing; any other is looked up
|
|
1130
|
+
* through the trace index once (hasTrace reconciles the Agent), so a subagent Session is
|
|
1131
|
+
* known for one even before any list has walked it.
|
|
1132
|
+
*/
|
|
1133
|
+
listingOf(row: SessionRow): Promise<SessionCategory | null>;
|
|
1134
|
+
/**
|
|
1135
|
+
* List, sorted by createdAt descending — or, with `order: "activity"`, by lastActiveAt
|
|
1136
|
+
* descending (ties by sessionId, see {@link compareActivityDesc}). Every row is served
|
|
1137
|
+
* **straight from the DB**, whichever client created it, with no Trace directory scanning —
|
|
1138
|
+
* the answer to many-session sidebar reloads re-walking the filesystem on every request (#139).
|
|
1139
|
+
* Sessions living only in the Trace directory were adopted into the index by the
|
|
1140
|
+
* boot-time sweep (`adoptUnmanagedTraceSessions`), so listing never discovers. One
|
|
1141
|
+
* lazy discovery walk still runs for a list call that contains rows this process has
|
|
1142
|
+
* not classified yet (no in-process source entry): it supplies the Trace locations for
|
|
1143
|
+
* the one-time head reads and backfills the `has_trace` cache; once every row is
|
|
1144
|
+
* classified, list calls touch only the DB.
|
|
1145
|
+
*
|
|
1146
|
+
* Optional `paging` returns just that slice (the sidebar pages with limit+1 to detect
|
|
1147
|
+
* "has more"); slicing happens before toInfo, so per-request source derivation (lazy
|
|
1148
|
+
* Trace-head reads) stays bounded by the page size.
|
|
1149
|
+
*
|
|
1150
|
+
* The `before` form pages the activity order by cursor: only rows strictly below it are
|
|
1151
|
+
* walked and served. An offset cannot page that order, because a row below the offset that
|
|
1152
|
+
* becomes active moves above it and the next page would skip a row; a cursor stays put — the
|
|
1153
|
+
* moved row is simply not served again (the client learns of it from `session_state`). The
|
|
1154
|
+
* offset form keeps serving the `created` order exactly as before.
|
|
1155
|
+
*
|
|
1156
|
+
* `category` filters to one list (the sidebar's `active`, the archive page's `archived`)
|
|
1157
|
+
* **before** paging, so offset/limit page within the category. Filtering needs each walked
|
|
1158
|
+
* row's category (a possible Trace-head read per row, cached in the sources registry);
|
|
1159
|
+
* without `withCounts` the walk stops as soon as the requested page is complete.
|
|
1160
|
+
* `withCounts` classifies every row, a cursor or not, and returns per-category totals over
|
|
1161
|
+
* the whole list — plus the same totals broken down by Workspace path, and each path's
|
|
1162
|
+
* newest Session's `createdAt`, once over every category (`workspaceLatest`, the Workspace
|
|
1163
|
+
* picker's "recently used") and once over the active ones (`workspaceLatestActive`) — so the
|
|
1164
|
+
* sidebar can list every Workspace that holds active Sessions (not only the ones its loaded
|
|
1165
|
+
* pages happen to touch) and place the groups by the recency of what they show, all without
|
|
1166
|
+
* loading them.
|
|
1167
|
+
*
|
|
1168
|
+
* `workspaceGroup` filters the same way, to one Workspace group (see workspace-group.ts),
|
|
1169
|
+
* so a sidebar grouped by Workspace pages each group down its OWN stream instead of
|
|
1170
|
+
* sharing one per-Agent cursor — without it, one group's "load more" consumes the page
|
|
1171
|
+
* its siblings were about to read, and their rows move on screen untouched. The two
|
|
1172
|
+
* filters compose; the returned counts stay whole-Agent either way.
|
|
1173
|
+
*
|
|
1174
|
+
* A `subagent` or `company` Session — a child its parent's Agent panel opens, company mode's
|
|
1175
|
+
* desk and ticket Sessions — belongs to no category (listCategory), whatever its marks: every
|
|
1176
|
+
* classified form of the list (`category`, `workspaceGroup` or `withCounts`) leaves it out of
|
|
1177
|
+
* the page, the totals, the Workspace breakdown and its stamps alike. The plain form, which
|
|
1178
|
+
* classifies nothing, still serves every row.
|
|
1179
|
+
*
|
|
1180
|
+
* `excludeOrg` drops the rows an organization owns (its desk and ticket sessions, and the
|
|
1181
|
+
* sub-sessions they spawned) from the stream BEFORE anything else looks at it — the page,
|
|
1182
|
+
* `counts`, `workspaceCounts`, both Workspace stamps and the limit+1 "has more" all describe the
|
|
1183
|
+
* same own-rows stream. It is what development mode's list asks for: that list draws the
|
|
1184
|
+
* user's own conversations, and a total or a stamp that still counted a desk or a ticket
|
|
1185
|
+
* session would make its Workspace appear as a group the list can never fill. The rule above
|
|
1186
|
+
* does not replace it: a person's conversation attached to a ticket is stamped `org` but
|
|
1187
|
+
* stays `user`, and a row the organization caches name before the reconcile pass stamps it
|
|
1188
|
+
* reads as `user`. Without the flag every row is served, whichever client created it, but
|
|
1189
|
+
* for that rule.
|
|
1190
|
+
*/
|
|
1191
|
+
listSessions(projectId: string, agentId: string, opts?: {
|
|
1192
|
+
paging?: SessionListPaging;
|
|
1193
|
+
order?: SessionListOrder;
|
|
1194
|
+
category?: SessionCategory;
|
|
1195
|
+
workspaceGroup?: string;
|
|
1196
|
+
withCounts?: boolean;
|
|
1197
|
+
excludeOrg?: boolean;
|
|
1198
|
+
}): Promise<{
|
|
1199
|
+
sessions: SessionInfo[];
|
|
1200
|
+
counts?: SessionCategoryCounts;
|
|
1201
|
+
workspaceCounts?: Record<string, SessionCategoryCounts>;
|
|
1202
|
+
workspaceLatest?: Record<string, string>;
|
|
1203
|
+
workspaceLatestActive?: Record<string, string>;
|
|
1204
|
+
}>;
|
|
1205
|
+
/**
|
|
1206
|
+
* The archive page's walk (services/archived-sessions-service.ts): every row of the Agent the
|
|
1207
|
+
* listing rule files as archived — the rows `listSessions` serves with `category: "archived"`
|
|
1208
|
+
* and `excludeOrg`, classified the same way — with the source each was classified by and
|
|
1209
|
+
* whether it has a Trace. It builds no SessionInfo: the page makes one (toInfo) only for the
|
|
1210
|
+
* rows a response carries, so a request costs a classification per archived row, not a full
|
|
1211
|
+
* row view per archived row.
|
|
1212
|
+
*/
|
|
1213
|
+
archivedRows(projectId: string, agentId: string): Promise<ArchivedRow[]>;
|
|
1214
|
+
/**
|
|
1215
|
+
* The rows a list walk starts from: the Agent's index rows, an organization's left out when
|
|
1216
|
+
* `excludeOrg` asks (see listSessions), with the Project's organization map; and when any row
|
|
1217
|
+
* predates this process, the one reconciled Trace discovery (`traces`) that lets every row be
|
|
1218
|
+
* classified without per-row work and backfills the `has_trace` cache. Steady state —
|
|
1219
|
+
* every row classified — skips the discovery and leaves `traces` absent.
|
|
1220
|
+
*/
|
|
1221
|
+
private walkRows;
|
|
1222
|
+
/**
|
|
1223
|
+
* Session stats (Agents list card): total count = size of the union of DB index
|
|
1224
|
+
* ∪ Trace directory discovery; activity = number of active Sessions per day over
|
|
1225
|
+
* the last `days` days (deduplicated count of Sessions created that day or with a
|
|
1226
|
+
* Trace record that day; index 0 = earliest, last index = today). Counts only —
|
|
1227
|
+
* does not backfill index rows.
|
|
1228
|
+
*/
|
|
1229
|
+
sessionStats(projectId: string, agentId: string, days: number): Promise<{
|
|
1230
|
+
sessionCount: number;
|
|
1231
|
+
activity: number[];
|
|
1232
|
+
}>;
|
|
1233
|
+
/**
|
|
1234
|
+
* Create a Session: the model reference is a complete `(provider, modelId)` pair.
|
|
1235
|
+
* Half a reference is a client error, never something to resolve — the missing half
|
|
1236
|
+
* is never guessed, since a guessed provider would send an entry's credential to a
|
|
1237
|
+
* vendor nobody named. Omitting both falls back to the Project's default reference
|
|
1238
|
+
* (400 prompting to configure a model first if there is none). `workspace` is already
|
|
1239
|
+
* validated by the route guard. The new Session is added to the active table
|
|
1240
|
+
* (idle).
|
|
1241
|
+
*/
|
|
1242
|
+
createSession(args: {
|
|
1243
|
+
projectId: string;
|
|
1244
|
+
agentId: string;
|
|
1245
|
+
/** Upstream id of the session's model; always paired with provider. Omit both for the Project's default reference. */
|
|
1246
|
+
modelId?: string;
|
|
1247
|
+
/** The provider group for `modelId`; always paired with modelId, never inferred. */
|
|
1248
|
+
provider?: string;
|
|
1249
|
+
workspace?: string;
|
|
1250
|
+
approvalMode?: ApprovalMode;
|
|
1251
|
+
/** The composer's sandbox picks; omitted halves take the server's settings. */
|
|
1252
|
+
sandbox?: Partial<SessionSandbox>;
|
|
1253
|
+
/** Whether the creator is an administrator (may loosen past the settings). Default false. */
|
|
1254
|
+
isAdmin?: boolean;
|
|
1255
|
+
/**
|
|
1256
|
+
* What kind of conversation this is, recorded in the Session's session_meta: `schedule`
|
|
1257
|
+
* from the scheduler, `company` from the organization runtime, `cli` from `penguin run`
|
|
1258
|
+
* (the only source a request may name), `api` from an Agent API run; absent means `user`,
|
|
1259
|
+
* a person's conversation.
|
|
1260
|
+
*/
|
|
1261
|
+
source?: SessionSource;
|
|
1262
|
+
/**
|
|
1263
|
+
* Creating-client hint stored on the index row (`POST .../sessions` body `client`):
|
|
1264
|
+
* "cli" from the CLI, defaulting to "web". "org" and "api" are not accepted over HTTP —
|
|
1265
|
+
* the organization runtime and the Agent API's run handler call this method directly and
|
|
1266
|
+
* are the only callers that pass them, so no request can claim either provenance for
|
|
1267
|
+
* itself ("api" is what lets the Agent API continue a Session).
|
|
1268
|
+
*/
|
|
1269
|
+
client?: "web" | "cli" | "org" | "api";
|
|
1270
|
+
}): Promise<SessionInfo>;
|
|
1271
|
+
/** A new Session's snapshot: the settings, with the creator's picks (checked) on top. */
|
|
1272
|
+
private snapshotSandbox;
|
|
1273
|
+
/**
|
|
1274
|
+
* Absolute path of a Session's **latest** Trace file (the current context shard);
|
|
1275
|
+
* undefined when no Trace exists. Costs a directory walk, so only the single-session
|
|
1276
|
+
* GET includes it in the DTO (see SessionInfo.tracePath) — the web's `/model` switch
|
|
1277
|
+
* hands it to the new session's `[model_switch_from]` block so the model can read the
|
|
1278
|
+
* source history itself when it needs it.
|
|
1279
|
+
*/
|
|
1280
|
+
latestTracePath(row: SessionRow): Promise<string | undefined>;
|
|
1281
|
+
/**
|
|
1282
|
+
* One walk over the Trace directory: session_id → its **earliest** shard (the shard
|
|
1283
|
+
* whose head carries the original session_meta). Discovery (which Sessions have
|
|
1284
|
+
* records) and the meta-read location come out of a single pass, so classifying every
|
|
1285
|
+
* row (`counts=1`) costs one directory walk total instead of one per Session.
|
|
1286
|
+
*/
|
|
1287
|
+
private discoverTraces;
|
|
1288
|
+
/**
|
|
1289
|
+
* Startup adoption sweep: walks the whole trace tree once per boot and adopts every
|
|
1290
|
+
* unmanaged Session (a Trace with no index row — legacy CLI-direct runs) as a
|
|
1291
|
+
* `client: "cli"` row, so lists stay pure-SQLite afterwards. Enumerates
|
|
1292
|
+
* `<root>/<project>/agents/<agent>/` directories directly — legacy Traces can live
|
|
1293
|
+
* under Projects the DB has never seen — and reuses the existing discovery/adoption
|
|
1294
|
+
* path per Agent (mtime-gated TraceIndexService reconcile, then registration-time
|
|
1295
|
+
* facts; adoption reads no file itself). Idempotent (insertOrIgnore), so re-running —
|
|
1296
|
+
* e.g. after a hot swap re-assembles the business surface — only costs the gated
|
|
1297
|
+
* reconcile. Returns the number of rows adopted.
|
|
1298
|
+
*/
|
|
1299
|
+
adoptUnmanagedTraceSessions(): Promise<number>;
|
|
1300
|
+
/**
|
|
1301
|
+
* Adopts a Session that exists only in the Trace directory, from the index's
|
|
1302
|
+
* registration-time facts (the reconciler head-read the earliest shard's session_meta
|
|
1303
|
+
* once when the file first appeared — adoption itself reads no file).
|
|
1304
|
+
*/
|
|
1305
|
+
private adoptTraceSession;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
type ModelScopeAuthStatusResult = ModelScopeAuthFlowStatusResponse & {
|
|
1309
|
+
changed?: true;
|
|
1310
|
+
};
|
|
1311
|
+
interface ModelScopeCredential {
|
|
1312
|
+
accessToken: string;
|
|
1313
|
+
refreshToken?: string;
|
|
1314
|
+
expiresAt?: string;
|
|
1315
|
+
}
|
|
1316
|
+
interface ModelScopeRefreshResult {
|
|
1317
|
+
changed: boolean;
|
|
1318
|
+
}
|
|
1319
|
+
interface ModelScopeAuthDeps {
|
|
1320
|
+
/** Base URL of the bridge, without a trailing slash (config.modelscopeBridgeUrl). */
|
|
1321
|
+
bridgeUrl: string;
|
|
1322
|
+
/**
|
|
1323
|
+
* Writes the delivered credential as the group key; resolves to how many of the group's rows
|
|
1324
|
+
* use the access token (0 also when a refresh lost to a newer authorization and wrote nothing).
|
|
1325
|
+
*/
|
|
1326
|
+
applyCredential: (projectId: string, credential: ModelScopeCredential, options?: {
|
|
1327
|
+
expectedRefreshToken?: string;
|
|
1328
|
+
}) => Promise<number>;
|
|
1329
|
+
/** Reads the server-side refresh metadata for the group. */
|
|
1330
|
+
readStoredCredential?: (projectId: string) => ModelProviderAuthToken | undefined;
|
|
1331
|
+
fetchImpl?: typeof fetch;
|
|
1332
|
+
now?: () => number;
|
|
1333
|
+
}
|
|
1334
|
+
declare class ModelScopeAuthService {
|
|
1335
|
+
private readonly deps;
|
|
1336
|
+
private readonly flows;
|
|
1337
|
+
private readonly refreshes;
|
|
1338
|
+
private readonly refreshFailures;
|
|
1339
|
+
private readonly fetchImpl;
|
|
1340
|
+
private readonly now;
|
|
1341
|
+
constructor(deps: ModelScopeAuthDeps);
|
|
1342
|
+
private url;
|
|
1343
|
+
private isPending;
|
|
1344
|
+
private request;
|
|
1345
|
+
ensureFresh(input: {
|
|
1346
|
+
projectId: string;
|
|
1347
|
+
provider: string;
|
|
1348
|
+
}): Promise<ModelScopeRefreshResult>;
|
|
1349
|
+
start(input: {
|
|
1350
|
+
projectId: string;
|
|
1351
|
+
userId: string;
|
|
1352
|
+
}): Promise<{
|
|
1353
|
+
flowId: string;
|
|
1354
|
+
authorizeUrl: string;
|
|
1355
|
+
expiresAt: string;
|
|
1356
|
+
}>;
|
|
1357
|
+
status(input: {
|
|
1358
|
+
flowId: string;
|
|
1359
|
+
projectId: string;
|
|
1360
|
+
userId: string;
|
|
1361
|
+
}): Promise<ModelScopeAuthStatusResult>;
|
|
1362
|
+
retryApply(input: {
|
|
1363
|
+
flowId: string;
|
|
1364
|
+
projectId: string;
|
|
1365
|
+
userId: string;
|
|
1366
|
+
}): Promise<ModelScopeAuthStatusResult>;
|
|
1367
|
+
cancel(input: {
|
|
1368
|
+
flowId: string;
|
|
1369
|
+
projectId: string;
|
|
1370
|
+
userId: string;
|
|
1371
|
+
}): void;
|
|
1372
|
+
private refreshState;
|
|
1373
|
+
private refreshNow;
|
|
1374
|
+
private reauthorizationRequired;
|
|
1375
|
+
private pollBridge;
|
|
1376
|
+
private apply;
|
|
1377
|
+
private publicFlow;
|
|
1378
|
+
private requireFlow;
|
|
1379
|
+
private sweep;
|
|
1380
|
+
private evictOldest;
|
|
1381
|
+
}
|
|
1382
|
+
declare const ModelScopeAuth_base: (abstract new () => Pick<ModelScopeAuthService, "status" | "start" | "retryApply" | "cancel" | "ensureFresh">) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
1383
|
+
/** The per-App ModelScope key authorization capability consumed by Project model routes. */
|
|
1384
|
+
declare abstract class ModelScopeAuth extends ModelScopeAuth_base {
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/** Minimal interface for a runtime Session (satisfied by core Session; tests may inject a fake implementation). */
|
|
1388
|
+
/** What one run is started with: the approval callback and the interrupt signal. */
|
|
1389
|
+
type RunOpts = {
|
|
1390
|
+
approve: ApproveFn;
|
|
1391
|
+
signal: AbortSignal;
|
|
1392
|
+
};
|
|
1393
|
+
interface RuntimeSession {
|
|
1394
|
+
readonly sessionId: string;
|
|
1395
|
+
run(newMessages: OmniMessage[], opts: RunOpts): AsyncGenerator<OmniMessage>;
|
|
1396
|
+
compact(opts: {
|
|
1397
|
+
signal: AbortSignal;
|
|
1398
|
+
}): AsyncGenerator<OmniMessage>;
|
|
1399
|
+
/**
|
|
1400
|
+
* The running context's model (core `Session.provider` / `Session.modelId`, getters over
|
|
1401
|
+
* the context's session_meta): the row's pair at creation, moved by each in-session switch
|
|
1402
|
+
* — and, after a restart, whatever the Trace resumed on. Optional: test fakes may omit them,
|
|
1403
|
+
* in which case the entry keeps the row's pair.
|
|
1404
|
+
*/
|
|
1405
|
+
readonly provider?: string;
|
|
1406
|
+
readonly modelId?: string;
|
|
1407
|
+
/**
|
|
1408
|
+
* Switches the Session's model in place (core `Session.switchModel`): closes the running
|
|
1409
|
+
* context on the model it ran on, then opens the next one on the target. A completed switch
|
|
1410
|
+
* streams the new context's main-session `session_meta` last, and the getters above answer
|
|
1411
|
+
* the target by then; a refusal is thrown before the first event; a Session that never ran
|
|
1412
|
+
* switches without streaming anything. Optional: test fakes may omit it, reading as
|
|
1413
|
+
* "switching is not available".
|
|
1414
|
+
*/
|
|
1415
|
+
switchModel?(opts: {
|
|
1416
|
+
provider: string;
|
|
1417
|
+
modelId: string;
|
|
1418
|
+
signal: AbortSignal;
|
|
1419
|
+
}): AsyncGenerator<OmniMessage, StopReason>;
|
|
1420
|
+
/**
|
|
1421
|
+
* The Session's thinking level (core `Session.thinkingLevel`, plain state): soft-limited —
|
|
1422
|
+
* an assignment applies from the Session's very next LLM request. Optional: test fakes may
|
|
1423
|
+
* omit it.
|
|
1424
|
+
*/
|
|
1425
|
+
thinkingLevel?: ThinkingLevelName;
|
|
1426
|
+
/** Whether compaction is possible and why; when not ok, compact() yields no messages (see core ContextEngine.compactability). */
|
|
1427
|
+
compactability(): CompactAvailability;
|
|
1428
|
+
/** Queues a mid-run steering input (core `Session.steer`); false when no Task is running. */
|
|
1429
|
+
steer(input: OmniMessage[]): boolean;
|
|
1430
|
+
/**
|
|
1431
|
+
* Withdraws a steering input queued via `steer` before core delivers it (core
|
|
1432
|
+
* `Session.unsteer`, matched by input-list identity); false when it is no longer queued —
|
|
1433
|
+
* already delivered, or the run exited. Optional: test fakes may omit it, in which case
|
|
1434
|
+
* every recall reports "already delivered".
|
|
1435
|
+
*/
|
|
1436
|
+
unsteer?(input: OmniMessage[]): boolean;
|
|
1437
|
+
/** Skips the in-progress reconnect backoff, firing the next retry immediately (core `Session.skipReconnectWait`); false when no wait is in progress. */
|
|
1438
|
+
skipReconnectWait(): boolean;
|
|
1439
|
+
/**
|
|
1440
|
+
* Whether a run with empty input would send anything (core `Session.hasPendingInput`): the
|
|
1441
|
+
* failed turn's input held as carry-over — in process, or rebuilt from the Trace after a
|
|
1442
|
+
* restart — or a compaction summary waiting for the next run. Optional: test fakes may omit
|
|
1443
|
+
* it, reading as nothing held.
|
|
1444
|
+
*/
|
|
1445
|
+
hasPendingInput?(): boolean;
|
|
1446
|
+
/**
|
|
1447
|
+
* Runs the named package's `user_prompt` hook (core `Session.runUserPromptHook` — hooks
|
|
1448
|
+
* run in core and nowhere else); null = the package is not installed or names no such
|
|
1449
|
+
* command. Optional: test fakes may omit it, reading as not installed.
|
|
1450
|
+
*/
|
|
1451
|
+
runUserPromptHook?(name: string, prompt: string, extras?: Record<string, string | number | boolean>): Promise<{
|
|
1452
|
+
context?: string;
|
|
1453
|
+
} | null>;
|
|
1454
|
+
/** A tool's permission level from the running context's toolset (core `Session.toolPermission`; strict-tier — rebuilt at rotation). */
|
|
1455
|
+
toolPermission(name: string): "r" | "rw" | undefined;
|
|
1456
|
+
/**
|
|
1457
|
+
* Out-of-band one-shot request for title generation (core `Session.generateTitle`,
|
|
1458
|
+
* writes no history/Trace). Material defaults to what the Session collects itself
|
|
1459
|
+
* (the first Task's text gathered during run); `material` overrides this for
|
|
1460
|
+
* subagents.
|
|
1461
|
+
*/
|
|
1462
|
+
generateTitle(args?: {
|
|
1463
|
+
material?: {
|
|
1464
|
+
userText: string;
|
|
1465
|
+
assistantText: string;
|
|
1466
|
+
};
|
|
1467
|
+
signal?: AbortSignal;
|
|
1468
|
+
}): Promise<SessionTitleResult>;
|
|
1469
|
+
/**
|
|
1470
|
+
* Subscribes the idle-arrival signal for background-task completion notices (core
|
|
1471
|
+
* `Session.onBackgroundNotice`): fired when a `run_in_background` launch settles while no
|
|
1472
|
+
* Task is running — the manager then takes the queue and submits it as an ordinary task.
|
|
1473
|
+
* Optional: test fakes may omit it.
|
|
1474
|
+
*/
|
|
1475
|
+
onBackgroundNotice?(listener: () => void): void;
|
|
1476
|
+
/** Takes the queued background completion notices as task input (core `Session.takeBackgroundNotices`). Optional, like onBackgroundNotice. */
|
|
1477
|
+
takeBackgroundNotices?(): OmniMessage[];
|
|
1478
|
+
/** Whether completion notices are still queued (core `Session.hasPendingBackgroundNotices`); pins the entry against idle eviction. Optional, like onBackgroundNotice. */
|
|
1479
|
+
hasPendingBackgroundNotices?(): boolean;
|
|
1480
|
+
/** Refreshes the listen-port probes behind the process list's `serviceUrl` (core `Session.probeBackgroundCommandServices`). Optional: test fakes may omit it. */
|
|
1481
|
+
probeBackgroundCommandServices?(): Promise<void>;
|
|
1482
|
+
/** Subscribes live-forwarded background-subagent messages (core `Session.onBackgroundMessage`); the manager publishes them to the session channel. Optional: test fakes may omit it. */
|
|
1483
|
+
onBackgroundMessage?(listener: (msg: OmniMessage) => void): void;
|
|
1484
|
+
/** Background command processes owned by the Session's environment (core `Session.listBackgroundCommands`). Optional: test fakes may omit it. */
|
|
1485
|
+
listBackgroundCommands?(): BackgroundCommandInfo[];
|
|
1486
|
+
/** Kills one background command process (core `Session.killBackgroundCommand`); false when the id is unknown. Optional, like listBackgroundCommands. */
|
|
1487
|
+
killBackgroundCommand?(processId: string): boolean;
|
|
1488
|
+
/** Hands one EXECUTING tool call back as a background task (core `Session.detachToolCall`). Optional: test fakes may omit it, reading as "not_running". */
|
|
1489
|
+
detachToolCall?(toolCallId: string): ToolDetachResult;
|
|
1490
|
+
/** Whether a background subagent is mid-round (core `Session.hasRunningBackgroundSubagents`); pins the entry against idle eviction. Optional, like listBackgroundCommands. */
|
|
1491
|
+
hasRunningBackgroundSubagents?(): boolean;
|
|
1492
|
+
/** All live subagent child sessions of the Session's environment (core `Session.listBackgroundSubagents`). Optional, like listBackgroundCommands. */
|
|
1493
|
+
listBackgroundSubagents?(): BackgroundSubagentInfo[];
|
|
1494
|
+
/** Host message to one child session — steering mid-run, a follow-up run when idle, a revival when released (core `Session.sendToBackgroundSubagent`). Optional. */
|
|
1495
|
+
sendToBackgroundSubagent?(childSessionId: string, messages: OmniMessage[], opts?: SubagentMessageOptions): Promise<SubagentMessageOutcome>;
|
|
1496
|
+
/** Subscribes the host's session-lifetime fallback approval sink for child sessions (core `Session.setSubagentApprovalFallback`). Optional. */
|
|
1497
|
+
setSubagentApprovalFallback?(approve: ApproveFn): void;
|
|
1498
|
+
/** Aborts one child session's current run, keeping the session (core `Session.abortBackgroundSubagentRun`); false when unknown or idle. Optional. */
|
|
1499
|
+
abortBackgroundSubagentRun?(childSessionId: string): boolean;
|
|
1500
|
+
/** Pins one live child session's thinking level (core `Session.setBackgroundSubagentThinkingLevel`); false when the child is not live. Optional. */
|
|
1501
|
+
setBackgroundSubagentThinkingLevel?(childSessionId: string, level: ThinkingLevelName): boolean;
|
|
1502
|
+
/** Subscribes subagent run-state changes (core `Session.onSubagentState`): the manager republishes `task_state` with the fresh live listing. Optional. */
|
|
1503
|
+
onSubagentState?(listener: () => void): void;
|
|
1504
|
+
/** Subscribes background-task state changes (core `Session.onBackgroundState`): the manager re-counts the live registries and publishes `session_background` when the counts moved. Optional. */
|
|
1505
|
+
onBackgroundState?(listener: () => void): void;
|
|
1506
|
+
/** Releases environment resources — kills the remaining background processes (core `Session.dispose`). Optional, idempotent. */
|
|
1507
|
+
dispose?(): void;
|
|
1508
|
+
}
|
|
1509
|
+
/** The underlying loader behind get-or-resume-or-heal. */
|
|
1510
|
+
interface SessionLoader {
|
|
1511
|
+
/**
|
|
1512
|
+
* Load a runtime Session from an index row: recover (with a Trace) or self-heal
|
|
1513
|
+
* rebuild (no Trace, session_id will change). Throws HttpError(409) for unrecoverable
|
|
1514
|
+
* cases such as a missing Workspace.
|
|
1515
|
+
*/
|
|
1516
|
+
load(row: SessionRow): Promise<RuntimeSession>;
|
|
1517
|
+
}
|
|
1518
|
+
interface UsageRecorderLike {
|
|
1519
|
+
record(ctx: UsageContext, msg: OmniMessage): Promise<void>;
|
|
1520
|
+
}
|
|
1521
|
+
interface SessionManagerDeps {
|
|
1522
|
+
/** The in-memory state this manager works on (see agent-state.ts). Optional: a manager built without one — unit tests — gets an empty state of its own. */
|
|
1523
|
+
state?: AgentState;
|
|
1524
|
+
sessions: SessionIndex;
|
|
1525
|
+
channels: ChannelHub;
|
|
1526
|
+
loader: SessionLoader;
|
|
1527
|
+
/** Session-origin registry (session_meta is the single source of truth; subagent registration records the forwarded meta's source here). */
|
|
1528
|
+
sources: SessionOrigins;
|
|
1529
|
+
recorder: UsageRecorderLike;
|
|
1530
|
+
/** Automatic Session title generation (optional: not injected in tests or when disabled). */
|
|
1531
|
+
titles?: TitleNotifier;
|
|
1532
|
+
/** Error persistence (optional: without it, only logs — same as before this was wired up). */
|
|
1533
|
+
errors?: ErrorSink;
|
|
1534
|
+
/** Provider auth refresh (optional for tests that do not exercise external model credentials). */
|
|
1535
|
+
modelScopeAuth?: ModelScopeAuth;
|
|
1536
|
+
log?: (line: string) => void;
|
|
1537
|
+
/** Goal run-state persistence (optional like `titles`: without it, goals run but leave no restorable record). */
|
|
1538
|
+
/**
|
|
1539
|
+
* Publishes a Session-scoped event on the user-level channel of everyone who can see the
|
|
1540
|
+
* Project. The audience lookup (owner + members) and the `user:<id>` channel binding stay in
|
|
1541
|
+
* the app layer, the same split the scheduler's `notify` uses — this class holds no Project
|
|
1542
|
+
* membership repos. Optional: without it only the per-Session channel is served, which is
|
|
1543
|
+
* what unit tests that don't wire it get.
|
|
1544
|
+
*/
|
|
1545
|
+
notifyProjectUsers?: (projectId: string, event: ServerEvent) => void;
|
|
1546
|
+
/**
|
|
1547
|
+
* Clock for persisted timestamps (last_active_at). Injected like the other services' so a
|
|
1548
|
+
* stubbed clock moves this and `usage_records.ts` together — the pairing the legacy
|
|
1549
|
+
* backfill assumes when it reads MAX(ts) as a session's last activity.
|
|
1550
|
+
*/
|
|
1551
|
+
now?: () => Date;
|
|
1552
|
+
/**
|
|
1553
|
+
* Called when a Task starts (or is queued) from a PERSON's input — a message typed in the
|
|
1554
|
+
* Web App, the CLI or a bound messaging bot, as opposed to one the harness injected
|
|
1555
|
+
* (`sender: "server"` / `"harness"`). Company mode uses it to reset the desk's @-chain
|
|
1556
|
+
* accounting: a person's message opens a fresh chain (hop 0), whatever mention last woke
|
|
1557
|
+
* the desk. Optional; unit tests that do not wire it get nothing.
|
|
1558
|
+
*/
|
|
1559
|
+
onHumanInput?: (sessionId: string) => void;
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* A queued message's original content, held while it waits in a queue so a recall (DELETE
|
|
1563
|
+
* /steer/:id, DELETE /follow-ups/:id) can hand it back to the composer for editing (#287).
|
|
1564
|
+
* `text` and `images` are the same strings the queued input already references (no extra
|
|
1565
|
+
* copy); files stay on disk in the Session scratchpad and are read back only at recall.
|
|
1566
|
+
*/
|
|
1567
|
+
interface RecallStore {
|
|
1568
|
+
text: string;
|
|
1569
|
+
images: string[];
|
|
1570
|
+
files: RecallableFile[];
|
|
1571
|
+
}
|
|
1572
|
+
/** One queued follow-up task (`queueIfBusy`): the task input, held until the session is idle. */
|
|
1573
|
+
interface QueuedFollowUp {
|
|
1574
|
+
/** Recall handle (PendingFollowUpInfo.id), assigned at queue time. */
|
|
1575
|
+
id: string;
|
|
1576
|
+
input: OmniMessage[];
|
|
1577
|
+
/**
|
|
1578
|
+
* Original content for recall, and the content `task_state` shows on the queued line.
|
|
1579
|
+
* Always present: a caller that knows more than the input carries (the HTTP route, which
|
|
1580
|
+
* also knows where the file attachments landed on disk) supplies it, and every other
|
|
1581
|
+
* queueing path gets it derived from the input — what can be recalled must not depend on
|
|
1582
|
+
* which door the message came through.
|
|
1583
|
+
*/
|
|
1584
|
+
recall: RecallStore;
|
|
1585
|
+
}
|
|
1586
|
+
/** One undelivered steering entry: the display info broadcast on task_state, plus what a recall needs — the exact input list core queued (its unsteer handle) and the original content. */
|
|
1587
|
+
interface PendingSteeringEntry {
|
|
1588
|
+
info: PendingSteeringInfo;
|
|
1589
|
+
input: OmniMessage[];
|
|
1590
|
+
recall: RecallStore;
|
|
1591
|
+
}
|
|
1592
|
+
/** Active-table entry: a loaded runtime Session plus its running state. */
|
|
1593
|
+
interface RuntimeEntry {
|
|
1594
|
+
sessionId: string;
|
|
1595
|
+
projectId: string;
|
|
1596
|
+
agentId: string;
|
|
1597
|
+
/**
|
|
1598
|
+
* The Session's current model (provider group paired with modelId): what the next drive
|
|
1599
|
+
* attributes usage to. The row's pair, kept in step with the runtime's own answer (see
|
|
1600
|
+
* `syncEntryModel`) — at load, and whenever a model switch moves it.
|
|
1601
|
+
*/
|
|
1602
|
+
provider: string;
|
|
1603
|
+
modelId: string;
|
|
1604
|
+
session: RuntimeSession;
|
|
1605
|
+
status: SessionStatus;
|
|
1606
|
+
approvals: Approvals;
|
|
1607
|
+
abort: AbortController | null;
|
|
1608
|
+
/** The in-flight drive Promise (awaited during graceful shutdown). */
|
|
1609
|
+
running: Promise<void> | null;
|
|
1610
|
+
/**
|
|
1611
|
+
* Agent config generation this runtime was built under (see
|
|
1612
|
+
* invalidateAgentRuntimes): once it falls behind the Agent's current generation,
|
|
1613
|
+
* the entry is discarded on its next idle access and re-resumed via the loader.
|
|
1614
|
+
*/
|
|
1615
|
+
generation: number;
|
|
1616
|
+
/**
|
|
1617
|
+
* Queued follow-up tasks (`queueIfBusy`): task inputs accepted while a Task/compaction was
|
|
1618
|
+
* in progress, auto-started one at a time (in order) once the session returns to idle —
|
|
1619
|
+
* ordinary tasks with normal semantics, unlike the mid-run steering queue.
|
|
1620
|
+
* Deliberately NOT discarded on abort: they are future tasks the user explicitly queued.
|
|
1621
|
+
*/
|
|
1622
|
+
followUps: QueuedFollowUp[];
|
|
1623
|
+
/**
|
|
1624
|
+
* The running Task's input messages, held from launch until the run ends. The engine
|
|
1625
|
+
* writes these exact envelopes to the Trace only after the session bootstrap (MCP
|
|
1626
|
+
* connect + discovery on the first run), so a history read during that window would
|
|
1627
|
+
* miss the user's own message — and the draft flow subscribes to the stream only after
|
|
1628
|
+
* the input publish, so the live channel cannot backfill it either. GET /messages
|
|
1629
|
+
* appends the ones the Trace has not caught up to yet (exact-envelope dedup).
|
|
1630
|
+
*/
|
|
1631
|
+
pendingInputs: OmniMessage[];
|
|
1632
|
+
/**
|
|
1633
|
+
* The running Task's streamed bootstrap records (mcp_connect begin/end,
|
|
1634
|
+
* tool_list_ready), held until the run ends. Their Trace writes are deferred by the
|
|
1635
|
+
* engine until after the input lands (turn attribution), and the draft flow subscribes
|
|
1636
|
+
* only after they were published — so a history rebuild during the MCP connect would
|
|
1637
|
+
* otherwise show nothing at all (a silent blank while a slow server times out).
|
|
1638
|
+
* GET /messages appends whichever of them the Trace has not caught up to.
|
|
1639
|
+
*/
|
|
1640
|
+
pendingBootstrap: OmniMessage[];
|
|
1641
|
+
/**
|
|
1642
|
+
* Steering messages queued on core but not yet delivered to the model — a display mirror
|
|
1643
|
+
* of core's steering queue (same FIFO order), so the composer's "steering queued" hint and
|
|
1644
|
+
* its content survive reloads: pushed on `steer`, shifted as each `[user_steering]`
|
|
1645
|
+
* message appears on the drive stream, dropped wholesale when the run exits (core
|
|
1646
|
+
* discards undelivered steering on abort/completion). The `info` halves are broadcast on
|
|
1647
|
+
* every `task_state`; the rest is the recall handle (see recallSteering).
|
|
1648
|
+
*/
|
|
1649
|
+
pendingSteering: PendingSteeringEntry[];
|
|
1650
|
+
/**
|
|
1651
|
+
* Steering the run ended without ever delivering — handed back to the user instead of
|
|
1652
|
+
* discarded (#287 follow-up). An interrupt mid-tool-call is the ordinary way to get here:
|
|
1653
|
+
* core drops its queue when the run exits, so the message would otherwise vanish with the
|
|
1654
|
+
* text the user had already typed.
|
|
1655
|
+
*
|
|
1656
|
+
* Entries move here from `pendingSteering` when the run exits and stay recallable by the
|
|
1657
|
+
* same handle, so the composer pulls them back into its draft (auto-recall on observing
|
|
1658
|
+
* them, a reload included). They are deliberately NOT cleared when the next run starts:
|
|
1659
|
+
* the message was never delivered whatever happens later, and dropping it there would
|
|
1660
|
+
* reintroduce exactly the loss this list exists to prevent. Only a recall — or the entry's
|
|
1661
|
+
* idle eviction — removes one.
|
|
1662
|
+
*/
|
|
1663
|
+
returnedSteering: PendingSteeringEntry[];
|
|
1664
|
+
/** Timestamp of last activity (refreshed on load / status flip / drive completion / background-task change), used for idle-eviction checks. */
|
|
1665
|
+
lastActivityMs: number;
|
|
1666
|
+
/**
|
|
1667
|
+
* Background-task counts as last read from the runtime — at entry creation, then on every
|
|
1668
|
+
* background-state ping. The baseline `session_background` is published against: a ping
|
|
1669
|
+
* that leaves the counts where they were (a foreground-window child settling, an exited
|
|
1670
|
+
* process being removed from the list) publishes nothing.
|
|
1671
|
+
*/
|
|
1672
|
+
backgroundTasks: SessionBackgroundTasks;
|
|
1673
|
+
}
|
|
1674
|
+
declare class SessionManager {
|
|
1675
|
+
private readonly deps;
|
|
1676
|
+
private readonly state;
|
|
1677
|
+
private readonly entries;
|
|
1678
|
+
/**
|
|
1679
|
+
* Each registered subagent Session's ROOT Session: a subagent runs under its root's
|
|
1680
|
+
* sandbox policy, as it runs under its root's approval mode, so a change the person makes
|
|
1681
|
+
* in the composer reaches the children already running.
|
|
1682
|
+
*/
|
|
1683
|
+
private readonly childRoots;
|
|
1684
|
+
/** The Session whose policy governs `sessionId`: its root for a subagent, else itself. */
|
|
1685
|
+
rootSessionOf(sessionId: string): string;
|
|
1686
|
+
/** Per-Session mutex (serializes get-or-load and status flips); auto-cleaned once the chain drains. */
|
|
1687
|
+
private readonly locks;
|
|
1688
|
+
private readonly log;
|
|
1689
|
+
/** Graceful-shutdown flag: once set, new Tasks/compactions are rejected (503). */
|
|
1690
|
+
private closed;
|
|
1691
|
+
/** Agents currently being deleted (key = agentKey): new Tasks/compactions are always rejected with 409 during this window. */
|
|
1692
|
+
private readonly deletingAgents;
|
|
1693
|
+
/** Sessions currently being deleted (guards against the entry/Trace file being rebuilt and reviving it inside the deletion race window). */
|
|
1694
|
+
private readonly deletingSessions;
|
|
1695
|
+
/** Sessions whose runtime entry idle eviction leaves alone (see keepLoaded). */
|
|
1696
|
+
private readonly keptLoaded;
|
|
1697
|
+
/** Per-Agent config generation (key = agentKey), bumped by invalidateAgentRuntimes when a Project's credentials change. */
|
|
1698
|
+
private readonly agentGenerations;
|
|
1699
|
+
/** Open streaming fragments of running sessions (fed by drive, served to GET /messages; see live-tail.ts). */
|
|
1700
|
+
private readonly liveTail;
|
|
1701
|
+
private readonly sweepTimer;
|
|
1702
|
+
/** Clock for persisted timestamps (see SessionManagerDeps.now); wall clock unless injected. */
|
|
1703
|
+
private readonly now;
|
|
1704
|
+
constructor(deps: SessionManagerDeps);
|
|
1705
|
+
statusOf(sessionId: string): SessionStatus;
|
|
1706
|
+
pendingApprovalCount(sessionId: string): number;
|
|
1707
|
+
pendingApprovals(sessionId: string): PendingApproval[];
|
|
1708
|
+
/** Number of queued follow-up tasks (`queueIfBusy`) awaiting auto-start. */
|
|
1709
|
+
pendingFollowUpCount(sessionId: string): number;
|
|
1710
|
+
/**
|
|
1711
|
+
* Background-task counts of a LOADED session, read live (see SessionInfo.backgroundTasks);
|
|
1712
|
+
* undefined when the session is not in the active table or nothing is running — the list
|
|
1713
|
+
* row and the single GET omit the field in both cases.
|
|
1714
|
+
*/
|
|
1715
|
+
backgroundTasksOf(sessionId: string): SessionBackgroundTasks | undefined;
|
|
1716
|
+
/** Steering messages queued but not yet delivered to the model (display mirror; see RuntimeEntry.pendingSteering). */
|
|
1717
|
+
pendingSteeringOf(sessionId: string): PendingSteeringInfo[];
|
|
1718
|
+
/** Steering a finished run never delivered, waiting for the composer to take it back (see RuntimeEntry.returnedSteering). */
|
|
1719
|
+
returnedSteeringOf(sessionId: string): PendingSteeringInfo[];
|
|
1720
|
+
/**
|
|
1721
|
+
* Live subagent children of an ACTIVE runtime entry (empty when the session is not loaded
|
|
1722
|
+
* — after a restart there is no in-process child left to report, so empty is the truth).
|
|
1723
|
+
* Rides `task_state` events and the SSE subscribe snapshot; the panel renders child
|
|
1724
|
+
* running marks from this instead of parsing tool-output text.
|
|
1725
|
+
*/
|
|
1726
|
+
subagentsOf(sessionId: string): BackgroundSubagentInfo[];
|
|
1727
|
+
/**
|
|
1728
|
+
* Host message to one child session — a user input on the child, whatever its state:
|
|
1729
|
+
* steering while it runs, a follow-up run while it is idle, a revival (resume-session
|
|
1730
|
+
* semantics) when it is no longer live (core `Session.sendToBackgroundSubagent`). The
|
|
1731
|
+
* parent runtime itself is loaded on demand (ensureEntry — the same get-or-resume path a
|
|
1732
|
+
* task uses), so messaging a child of a long-idle conversation works after a restart too;
|
|
1733
|
+
* the resume fallback names the child's owning Agent from its own session row, which must
|
|
1734
|
+
* belong to the parent's project.
|
|
1735
|
+
*/
|
|
1736
|
+
sendToSubagent(sessionId: string, childSessionId: string, messages: OmniMessage[]): Promise<SubagentMessageOutcome>;
|
|
1737
|
+
/**
|
|
1738
|
+
* Sets a Session's thinking level on its loaded runtime (core `Session.thinkingLevel`,
|
|
1739
|
+
* plain state): the row's value is what the loader applies at load, so this only needs to
|
|
1740
|
+
* reach a runtime that is already in the active table — or a live child session, which
|
|
1741
|
+
* has an index row and a panel picker of its own but runs inside its parent's runtime.
|
|
1742
|
+
* The level is soft-limited: it applies from the Session's very next LLM request
|
|
1743
|
+
* (mid-context — the UI advises compacting first, since the change invalidates the
|
|
1744
|
+
* provider's cached context). No-op when nothing is loaded.
|
|
1745
|
+
*/
|
|
1746
|
+
setThinkingLevel(sessionId: string, level: ThinkingLevelName): void;
|
|
1747
|
+
/** Host abort of one child session's current run (core `Session.abortBackgroundSubagentRun`); false when the parent runtime is not loaded, the child is unknown, or it is idle. */
|
|
1748
|
+
abortSubagentRun(sessionId: string, childSessionId: string): boolean;
|
|
1749
|
+
/** Queued follow-up tasks awaiting auto-start, as their display/recall info (id + content summary). */
|
|
1750
|
+
pendingFollowUpsOf(sessionId: string): PendingFollowUpInfo[];
|
|
1751
|
+
/**
|
|
1752
|
+
* Live tail of a running session: one synthetic `partial_* start` OmniMessage per open
|
|
1753
|
+
* streaming fragment, carrying the full accumulated content so far (see live-tail.ts).
|
|
1754
|
+
* Empty when idle or when nothing is streaming. GET /messages attaches this (with a
|
|
1755
|
+
* channel cursor) so a client joining mid-stream can render the in-progress message.
|
|
1756
|
+
*/
|
|
1757
|
+
liveFragments(sessionId: string): OmniMessage[];
|
|
1758
|
+
/**
|
|
1759
|
+
* The running Task's input messages as published at launch; empty when idle. The engine
|
|
1760
|
+
* writes these exact envelopes to the Trace only after the first run's bootstrap (MCP
|
|
1761
|
+
* connect + discovery), so GET /messages appends whichever of them the Trace read has
|
|
1762
|
+
* not caught up to yet — without this, a client rebuilding history during the connect
|
|
1763
|
+
* (the draft flow subscribes only after the input publish) loses the user's own message.
|
|
1764
|
+
*/
|
|
1765
|
+
pendingInputs(sessionId: string): OmniMessage[];
|
|
1766
|
+
/** The running Task's streamed bootstrap records (see RuntimeEntry.pendingBootstrap); empty when idle. */
|
|
1767
|
+
pendingBootstrap(sessionId: string): OmniMessage[];
|
|
1768
|
+
/** Number of Sessions for this Agent that are currently running / compacting. */
|
|
1769
|
+
activeCountForAgent(projectId: string, agentId: string): number;
|
|
1770
|
+
/** Add a newly created Session to the active table (status idle), avoiding a redundant load on the next Task. */
|
|
1771
|
+
adopt(row: SessionRow, session: RuntimeSession): void;
|
|
1772
|
+
/**
|
|
1773
|
+
* Subscribes the background hooks on a runtime Session entering the active table — every
|
|
1774
|
+
* insertion path must call it (ensureEntry's loads, adopt's fresh creations):
|
|
1775
|
+
* - the idle-arrival signal for completion notices (mid-run arrivals are delivered inside
|
|
1776
|
+
* the run by core; this signal is the only trigger left when the session sits idle);
|
|
1777
|
+
* - live-forwarded background-subagent messages, published to the session channel (the
|
|
1778
|
+
* same feed SSE relays) and recorded for usage — a background child streams to the
|
|
1779
|
+
* frontend in real time past the launching turn's end, until its terminal state;
|
|
1780
|
+
* - subagent run-state changes, republished as `task_state` so the panel's running
|
|
1781
|
+
* marks track child rounds structurally instead of parsing tool-output text;
|
|
1782
|
+
* - background-task state changes (a command promoted or exited, a subagent promoted,
|
|
1783
|
+
* settling or released), re-counted and published as `session_background` on the user
|
|
1784
|
+
* channel so every Session list shows which rows still own background work.
|
|
1785
|
+
*/
|
|
1786
|
+
private registerNoticeListener;
|
|
1787
|
+
/**
|
|
1788
|
+
* The entry-lifetime approval callback: the registry, the per-decision approval-mode
|
|
1789
|
+
* re-read, and the SSE escalation are all entry-scoped, so one instance serves every run
|
|
1790
|
+
* of the entry and the children's session-lifetime fallback sink alike.
|
|
1791
|
+
*
|
|
1792
|
+
* This is also where the one cross-cutting approval override lives — wrapping the callback
|
|
1793
|
+
* handed to `Session.run` rather than teaching the engine anything: an organization's
|
|
1794
|
+
* sessions run with nobody watching, so a call their approval mode would hand to a person
|
|
1795
|
+
* is denied here instead of waiting for an answer that is never coming (see `unattended`
|
|
1796
|
+
* in approvals.ts). The marker is the row's durable `client = "org"` stamp — desk, ticket
|
|
1797
|
+
* and the sub-sessions that inherit it — read per decision, so a row the reconcile pass
|
|
1798
|
+
* stamps later is covered from its next decision on.
|
|
1799
|
+
*/
|
|
1800
|
+
private entryApprove;
|
|
1801
|
+
/** Publishes one live background-subagent message and records its usage (fire-and-forget; the child's own Trace is the durable record). */
|
|
1802
|
+
private forwardBackgroundMessage;
|
|
1803
|
+
/**
|
|
1804
|
+
* Bumps one Agent's config generation so every runtime built before it is discarded on
|
|
1805
|
+
* its next idle access and re-resumed via the loader (history is preserved through the
|
|
1806
|
+
* Trace). What warrants it is a Session-fixed fact changing under the runtime — the
|
|
1807
|
+
* Project's credentials (see invalidateProjectRuntimes). Agent State changes never do:
|
|
1808
|
+
* core reads them into the next model context itself. A Task already in flight is neither
|
|
1809
|
+
* aborted nor hot-swapped: it keeps the values it started with, and its entry is rebuilt
|
|
1810
|
+
* on the first access after it returns to idle (see ensureEntry).
|
|
1811
|
+
*/
|
|
1812
|
+
invalidateAgentRuntimes(projectId: string, agentId: string): void;
|
|
1813
|
+
/**
|
|
1814
|
+
* After a Project's models/credentials change: invalidate every cached runtime in this
|
|
1815
|
+
* Project, so the next Task re-resumes with the new api_key / base_url. Same
|
|
1816
|
+
* effective-value semantics as invalidateAgentRuntimes — no hot swap into a Task already
|
|
1817
|
+
* in flight. Iterating the active table is complete here, not a shortcut: the generation
|
|
1818
|
+
* map only matters for runtimes that are already cached, and an Agent with no cached
|
|
1819
|
+
* entry builds fresh through the loader anyway.
|
|
1820
|
+
*/
|
|
1821
|
+
invalidateProjectRuntimes(projectId: string): void;
|
|
1822
|
+
/**
|
|
1823
|
+
* Cheap, lock-free rehearsal of the 409/503 conditions startTask checks, throwing exactly the
|
|
1824
|
+
* same HttpErrors. **Advisory only**: it neither takes the Session lock nor loads an entry, so
|
|
1825
|
+
* a session that isn't in the active table reads as acceptable and a status change racing this
|
|
1826
|
+
* call is not caught — the authoritative check is still the one inside startTask.
|
|
1827
|
+
*
|
|
1828
|
+
* It exists so a caller that has irreversible work to do first (POST /tasks writes the
|
|
1829
|
+
* message's file attachments to disk) can find out about the ordinary "a Task is already
|
|
1830
|
+
* running" rejection before doing it, instead of undoing it afterwards.
|
|
1831
|
+
*/
|
|
1832
|
+
assertCanAcceptTask(sessionId: string, opts?: {
|
|
1833
|
+
queueIfBusy?: boolean;
|
|
1834
|
+
}): void;
|
|
1835
|
+
/**
|
|
1836
|
+
* Run a metadata operation at an idle Session boundary under the same mutex as Task and
|
|
1837
|
+
* compaction starts. Session fork uses this to snapshot an append-only Trace without a Task
|
|
1838
|
+
* beginning between its status check and final read. An uncached Session is idle by definition;
|
|
1839
|
+
* unlike startTask this path deliberately does not load a heavyweight runtime.
|
|
1840
|
+
*/
|
|
1841
|
+
atIdleBoundary<T>(sessionId: string, operation: () => Promise<T>): Promise<T>;
|
|
1842
|
+
/**
|
|
1843
|
+
* Start a Task: get-or-load → 409
|
|
1844
|
+
* mutual-exclusion check → publish the input messages first → drive run in the
|
|
1845
|
+
* background. Returns the current actual session_id (the new id after self-heal).
|
|
1846
|
+
* With `queueIfBusy`, a busy session (running/compacting) enqueues the input as a
|
|
1847
|
+
* follow-up instead of 409: it auto-starts as an ordinary next task once the session
|
|
1848
|
+
* returns to idle (`queued: true` in the result; see startQueuedFollowUp).
|
|
1849
|
+
* `opts.recall` is that queued message's original
|
|
1850
|
+
* content — optional, because only the HTTP route knows the parts of it the input does
|
|
1851
|
+
* not carry (the pre-attachment text and where each file landed on disk); every other
|
|
1852
|
+
* caller's store is read off the input, so a queued follow-up is recallable and shows
|
|
1853
|
+
* its content whichever path queued it.
|
|
1854
|
+
*/
|
|
1855
|
+
startTask(sessionId: string, input: OmniMessage[], opts?: {
|
|
1856
|
+
queueIfBusy?: boolean;
|
|
1857
|
+
recall?: RecallStore;
|
|
1858
|
+
}): Promise<{
|
|
1859
|
+
sessionId: string;
|
|
1860
|
+
queued: boolean;
|
|
1861
|
+
}>;
|
|
1862
|
+
/**
|
|
1863
|
+
* Retry a failed run (`POST /retry`): a Task started with EMPTY input, so core sends the
|
|
1864
|
+
* input of the turn that failed — held as carry-over since the run ended `fatal` or gave
|
|
1865
|
+
* up reconnecting — and nothing else. No user message is published or recorded, and with no
|
|
1866
|
+
* Prompt there is no user_prompt consult. Gated like startTask (busy is the same 409) but
|
|
1867
|
+
* never queued, and refused with 409 `nothing_to_retry` when nothing is held — the user's
|
|
1868
|
+
* next message already resent that input, or no run failed — since the run would end at
|
|
1869
|
+
* once and the client would watch a Task start and stop with nothing sent. Not a person's
|
|
1870
|
+
* input for company mode's hop accounting: the turn it resends was counted, or not, when
|
|
1871
|
+
* it was first sent.
|
|
1872
|
+
*/
|
|
1873
|
+
retryTask(sessionId: string): Promise<{
|
|
1874
|
+
sessionId: string;
|
|
1875
|
+
}>;
|
|
1876
|
+
/**
|
|
1877
|
+
* Start a goal run: like startTask, but the goal plugin composes the input and its stop
|
|
1878
|
+
* hook drives every later round inside this one `session.run` call — so the Session
|
|
1879
|
+
* stays `running` for the whole goal (every round), the existing abort endpoint
|
|
1880
|
+
* interrupts the entire loop and schedules queue behind it as usual. The plugin's
|
|
1881
|
+
* `user_prompt` hook (hooks run in core and nowhere else — `Session.runUserPromptHook`)
|
|
1882
|
+
* writes the Session's goal file and answers the round-1 protocol message; it runs under
|
|
1883
|
+
* the session lock AFTER the idle check, since it overwrites the goal file a running goal
|
|
1884
|
+
* is reading. Not installed (null) is the route's 409; an empty answer a 500. Round inputs
|
|
1885
|
+
* are yielded by core and published like any streamed message; progress additionally goes
|
|
1886
|
+
* out as goal_* server events (the run state itself is the goal file the plugin maintains).
|
|
1887
|
+
*/
|
|
1888
|
+
startGoal(sessionId: string, args: {
|
|
1889
|
+
/** The user's own message(s) exactly as submitted — text and images, no wrapping. */
|
|
1890
|
+
messages: OmniMessage[];
|
|
1891
|
+
/** The user's own objective text (leading marker blocks stripped): what the plugin records, the goal_started event and the title material. */
|
|
1892
|
+
objective: string;
|
|
1893
|
+
budget: number;
|
|
1894
|
+
}): Promise<{
|
|
1895
|
+
sessionId: string;
|
|
1896
|
+
}>;
|
|
1897
|
+
/**
|
|
1898
|
+
* Taps a goal run's stream for `drive`: round boundaries become goal_round events —
|
|
1899
|
+
* round 1 from the seeded input (core never yields a run's initial input, so it is
|
|
1900
|
+
* counted here, where startGoal published it), later rounds from the stop-hook continues
|
|
1901
|
+
* core yields, each announced by the hook's `continue` event; a harness-stamped input
|
|
1902
|
+
* nothing announced is another package's user_prompt context riding behind the
|
|
1903
|
+
* objective, not a round — and the goal hook's `stop` event becomes the
|
|
1904
|
+
* goal_finished server event. `used` is what the hook last recorded in its event's
|
|
1905
|
+
* `output` — the same number its budget check used — so the UI never shows a different
|
|
1906
|
+
* figure. A stream that ends without the hook's terminal event (a cut-off run, an
|
|
1907
|
+
* infrastructure failure) closes as `aborted`.
|
|
1908
|
+
*/
|
|
1909
|
+
private goalStream;
|
|
1910
|
+
/** Closes a goal as aborted for the UI (stream cut off / infrastructure failure). */
|
|
1911
|
+
private finishAborted;
|
|
1912
|
+
/**
|
|
1913
|
+
* Shared task launch (fresh tasks, auto-started follow-ups and retries): flips to running,
|
|
1914
|
+
* publishes the input, and drives the run. Caller holds the session lock and has verified
|
|
1915
|
+
* idle. A retry's input is empty: nothing is published or held, and drive gets no title
|
|
1916
|
+
* material — the turn it resends already offered its text when it was first sent.
|
|
1917
|
+
*/
|
|
1918
|
+
private launchTask;
|
|
1919
|
+
/**
|
|
1920
|
+
* Auto-start of queued follow-ups: called from drive's finally whenever a Task or
|
|
1921
|
+
* compaction finishes (abort included — follow-ups are future tasks the user queued, so
|
|
1922
|
+
* an abort of the current run does not discard them). Re-validates everything under the
|
|
1923
|
+
* session lock (another task may have snuck in, the server may be shutting down, the
|
|
1924
|
+
* session may be getting deleted) and launches exactly one queued input — the next
|
|
1925
|
+
* finish picks up the one after, keeping order and one-at-a-time semantics.
|
|
1926
|
+
*/
|
|
1927
|
+
private startQueuedFollowUp;
|
|
1928
|
+
/** Manually compact the context: 409 if already running; compaction output also flows into the SSE channel. */
|
|
1929
|
+
startCompact(sessionId: string): Promise<{
|
|
1930
|
+
sessionId: string;
|
|
1931
|
+
}>;
|
|
1932
|
+
/**
|
|
1933
|
+
* Switches the Session's model in place (`POST /switch-model`) — core `Session.switchModel`,
|
|
1934
|
+
* gated like a compaction (open, not deleting, idle). Core decides everything before its
|
|
1935
|
+
* first event, so the head of its stream is pulled here, under the lock, and tells the three
|
|
1936
|
+
* outcomes apart:
|
|
1937
|
+
*
|
|
1938
|
+
* - it throws — a refusal, answered 409 with the code of its reason (see `switchRefusal`);
|
|
1939
|
+
* - it is already done — a Session that never ran has no context to close: core re-assembled
|
|
1940
|
+
* its first context on the target, nothing streams, and the row carries the new pair by
|
|
1941
|
+
* the time this returns (`switched: true`; the route answers with the fresh DTO);
|
|
1942
|
+
* - it yields — a driven run like a compaction (`switched: false`): status `compacting`,
|
|
1943
|
+
* idle when it ends. The head is a compaction's begin, the first record of a bootstrap (a
|
|
1944
|
+
* Session loaded from its Trace opens its context first), or — for a context with no
|
|
1945
|
+
* completed turn, which streams no pair — the opener's first record.
|
|
1946
|
+
*
|
|
1947
|
+
* The switch is the entry's run from that pull on — it can be stopped, and a deletion or a
|
|
1948
|
+
* shutdown waits for it — while the entry only reads `compacting` once there is something
|
|
1949
|
+
* to stream, so a refusal never flickers a state.
|
|
1950
|
+
*
|
|
1951
|
+
* The entry and the row follow the runtime as the new context's `session_meta` passes
|
|
1952
|
+
* through the drive (see `drive`); a switch that does not complete streams no meta and
|
|
1953
|
+
* leaves them where they were. Usage: a drive bills to the model it started on, so the
|
|
1954
|
+
* switch's compaction request goes to the model that served it and the next run to the new
|
|
1955
|
+
* one.
|
|
1956
|
+
*/
|
|
1957
|
+
startSwitch(sessionId: string, ref: ModelRefDto): Promise<{
|
|
1958
|
+
sessionId: string;
|
|
1959
|
+
switched: boolean;
|
|
1960
|
+
}>;
|
|
1961
|
+
/** Who is told when a Session's model moves (see `onModelChanged`). */
|
|
1962
|
+
private readonly modelListeners;
|
|
1963
|
+
/**
|
|
1964
|
+
* Subscribes to a Session's model moving: an in-session switch completing, or a row brought
|
|
1965
|
+
* back in step with its Trace when the Session loads. The listener runs once the entry and
|
|
1966
|
+
* the row carry the new pair. Returns the unsubscribe. Company mode listens: an employee's
|
|
1967
|
+
* model is its desk Session's.
|
|
1968
|
+
*/
|
|
1969
|
+
onModelChanged(listener: (sessionId: string, model: ModelRefDto) => void): () => void;
|
|
1970
|
+
/**
|
|
1971
|
+
* Moves the entry — and the row it caches into — to the model the runtime reports itself on,
|
|
1972
|
+
* when that differs: the pair the next drive attributes usage to, and the one
|
|
1973
|
+
* `GET /sessions/:id` describes. A runtime that reports none (test fakes) leaves the entry
|
|
1974
|
+
* alone. The row write is guarded like the other bookkeeping writes (see `touchRow`): a
|
|
1975
|
+
* switch must not be stranded mid-drive by a closed DB handle, and the entry is right either
|
|
1976
|
+
* way. The same holds for whoever listens: a listener that throws is recorded, not rethrown.
|
|
1977
|
+
*/
|
|
1978
|
+
private syncEntryModel;
|
|
1979
|
+
/** Submit an approval decision; returns false if the pending approval doesn't exist (already decided/unknown). */
|
|
1980
|
+
decideApproval(sessionId: string, toolCallId: string, decision: "allow" | "deny"): boolean;
|
|
1981
|
+
/**
|
|
1982
|
+
* Mid-run steering: forward the message to the running Session (core delivers it between
|
|
1983
|
+
* turns as a standalone `[user_steering]` user message followed by its images — no SSE
|
|
1984
|
+
* event of its own; the messages arrive through the stream the drive loop already
|
|
1985
|
+
* publishes). 409 when the Session isn't running a Task (idle / compacting / not loaded)
|
|
1986
|
+
* or the run finished in the race window — the caller falls back to submitting a normal
|
|
1987
|
+
* task, which carries the same text and images.
|
|
1988
|
+
*
|
|
1989
|
+
* `recall` is the queued message's original content: its summary (with a fresh id) is
|
|
1990
|
+
* mirrored on the entry and broadcast via `task_state` (and the SSE subscribe snapshot)
|
|
1991
|
+
* until the delivered `[user_steering]` message is observed on the stream — that is what
|
|
1992
|
+
* keeps the composer's "steering queued" hint, content included, alive across reloads —
|
|
1993
|
+
* and the content itself is what a recall (recallSteering) hands back to the composer.
|
|
1994
|
+
*/
|
|
1995
|
+
steer(sessionId: string, input: OmniMessage[], recall: RecallStore): void;
|
|
1996
|
+
/**
|
|
1997
|
+
* Recall an undelivered steering message (#287): withdraw it from core's queue and the
|
|
1998
|
+
* display mirror, re-broadcast state, and hand back its original content for the composer
|
|
1999
|
+
* to restore. 409 `not_pending` when the entry is no longer queued — already delivered to
|
|
2000
|
+
* the model (core's `unsteer` says so authoritatively; the mirror can lag behind delivery
|
|
2001
|
+
* until the `[user_steering]` message is observed on the stream), the run exited, or the
|
|
2002
|
+
* id is unknown — all the same outcome for the caller: nothing left to take back.
|
|
2003
|
+
*/
|
|
2004
|
+
recallSteering(sessionId: string, steerId: string): RecallStore;
|
|
2005
|
+
/**
|
|
2006
|
+
* Recall a queued follow-up task (#287): remove it from the queue before it auto-starts,
|
|
2007
|
+
* re-broadcast state, and hand back its original content (plus the thinking level it was
|
|
2008
|
+
* queued with). Being in the queue is the whole condition — every queued entry carries a
|
|
2009
|
+
* recall store (see QueuedFollowUp.recall), so no queued message is ever refused. 409
|
|
2010
|
+
* `follow_up_started` when the id is not in the queue: it already auto-started, or the
|
|
2011
|
+
* runtime was released and took the queue with it. Its own code, distinct from steering's
|
|
2012
|
+
* `not_pending`, because the two mean different things to the user — a follow-up became a
|
|
2013
|
+
* task of its own, a steering message reached the model mid-run.
|
|
2014
|
+
*/
|
|
2015
|
+
recallFollowUp(sessionId: string, followUpId: string): {
|
|
2016
|
+
recall: RecallStore;
|
|
2017
|
+
};
|
|
2018
|
+
/**
|
|
2019
|
+
* "Retry now" on the reconnect countdown: skip the in-progress backoff wait and fire
|
|
2020
|
+
* the next retry immediately (the attempt counter is unchanged — the skipped wait does
|
|
2021
|
+
* not consume an extra attempt). Returns false as a benign no-op when the session has
|
|
2022
|
+
* no active runtime or no reconnect wait is in progress — timing races (the wait
|
|
2023
|
+
* elapsed right before the click) must not surface as errors. Mirrors the steer seam:
|
|
2024
|
+
* manager → RuntimeSession → core Session → engine.
|
|
2025
|
+
*/
|
|
2026
|
+
retryNow(sessionId: string): boolean;
|
|
2027
|
+
/**
|
|
2028
|
+
* Interrupt the current Task/compaction: pending approvals converge to deny first,
|
|
2029
|
+
* then the AbortSignal fires. Returns false if nothing is in progress (the route
|
|
2030
|
+
* treats this as a 204 no-op).
|
|
2031
|
+
*/
|
|
2032
|
+
abortTask(sessionId: string): boolean;
|
|
2033
|
+
/**
|
|
2034
|
+
* Background command processes of a LOADED session (empty when the entry is not in
|
|
2035
|
+
* the active table — a resumed entry starts with a fresh environment and can only
|
|
2036
|
+
* ever report an empty list, so nothing is resurrected just to answer a poll).
|
|
2037
|
+
*/
|
|
2038
|
+
listProcesses(sessionId: string): BackgroundCommandInfo[];
|
|
2039
|
+
/**
|
|
2040
|
+
* Refreshes the listen-port probes behind the process list's `serviceUrl` (core
|
|
2041
|
+
* `Session.probeBackgroundCommandServices`): called by the processes route before
|
|
2042
|
+
* listing, so the first fetch already carries probed URLs. Bounded by core's own probe
|
|
2043
|
+
* timeout and TTL; a session that isn't loaded has no processes to probe.
|
|
2044
|
+
*/
|
|
2045
|
+
probeProcessServices(sessionId: string): Promise<void>;
|
|
2046
|
+
/**
|
|
2047
|
+
* Hands one EXECUTING tool call of a loaded session back as a background task (core
|
|
2048
|
+
* `Session.detachToolCall`): the call closes with a registry handle, its work keeps
|
|
2049
|
+
* running, and the turn carries on. An unloaded session has nothing executing, so it
|
|
2050
|
+
* answers "not_running" like an unknown id.
|
|
2051
|
+
*/
|
|
2052
|
+
detachToolCall(sessionId: string, toolCallId: string): ToolDetachResult;
|
|
2053
|
+
/** Kills one background command process of a loaded session; false when the session isn't loaded or the id is unknown. */
|
|
2054
|
+
killProcess(sessionId: string, processId: string): boolean;
|
|
2055
|
+
/**
|
|
2056
|
+
* Removes one EXITED background command entry from a loaded session's process list.
|
|
2057
|
+
* A running process is refused ("running") — stopping is killProcess's job, and a
|
|
2058
|
+
* bare removal must never surprise-signal a live process group. "not_found" covers
|
|
2059
|
+
* an unloaded session and an unknown id alike: the entry is gone either way. The
|
|
2060
|
+
* removal itself reuses the kill path — core's registry removal signals the (dead)
|
|
2061
|
+
* group defensively and drops the row, exactly like input_command's post-exit reap.
|
|
2062
|
+
* The only race, an entry observed running that exits before a retry, is benign:
|
|
2063
|
+
* the caller gets "running", refreshes, and sees the row exited on the next look.
|
|
2064
|
+
*
|
|
2065
|
+
* Note what leaves with the row: the registry entry owns the ManagedSession holding
|
|
2066
|
+
* that process's captured output, so after a removal input_command on the same
|
|
2067
|
+
* process_id answers "unknown process_id". Removal is a deliberate discard, not just
|
|
2068
|
+
* a list tidy-up — the Web App says so at the button and in the docs.
|
|
2069
|
+
*/
|
|
2070
|
+
removeProcess(sessionId: string, processId: string): "removed" | "running" | "not_found";
|
|
2071
|
+
/**
|
|
2072
|
+
* Disposes a just-removed entry's runtime — after its in-flight drive (if any)
|
|
2073
|
+
* settles, so interrupt cleanup never races a dying environment. Deleting a Session /
|
|
2074
|
+
* Agent / Project is the one intent that must also end the background processes the
|
|
2075
|
+
* conversation started (a dev server surviving its deleted conversation is
|
|
2076
|
+
* unreachable from every UI, running forever).
|
|
2077
|
+
*/
|
|
2078
|
+
private disposeRemoved;
|
|
2079
|
+
/**
|
|
2080
|
+
* Before deleting a Project, converge all its active runs and clear them out of the
|
|
2081
|
+
* active table. Returns the in-flight drive Promises of the affected entries: the
|
|
2082
|
+
* caller (deleteProject) should await them before removing the directory, so that
|
|
2083
|
+
* interrupt-cleanup Trace writes don't recreate the directory after deletion.
|
|
2084
|
+
*/
|
|
2085
|
+
abortProject(projectId: string): Promise<void>[];
|
|
2086
|
+
/**
|
|
2087
|
+
* Before deleting an Agent, converge all its active runs and clear them out of the
|
|
2088
|
+
* active table (same semantics as abortProject). Also marks this Agent as "being
|
|
2089
|
+
* deleted": new Tasks/compactions entering during the deletion process are always
|
|
2090
|
+
* rejected with 409 (assertAgentNotDeleting), closing the race window where a new
|
|
2091
|
+
* task recreates the directory and revives an already-deleted Agent between the
|
|
2092
|
+
* abortAgent snapshot and the directory removal. The caller must call
|
|
2093
|
+
* endAgentDeletion once deletion finishes (success or failure).
|
|
2094
|
+
*/
|
|
2095
|
+
beginAgentDeletion(projectId: string, agentId: string): Promise<void>[];
|
|
2096
|
+
endAgentDeletion(projectId: string, agentId: string): void;
|
|
2097
|
+
/**
|
|
2098
|
+
* Before deleting a single Session, converge its active run and clear it out of the
|
|
2099
|
+
* active table (same semantics as beginAgentDeletion). Also marks this Session as
|
|
2100
|
+
* "being deleted": new Tasks/compactions entering during the deletion process are
|
|
2101
|
+
* always rejected with 409 (assertSessionNotDeleting), closing the race window where
|
|
2102
|
+
* a new task recreates the entry and Trace file, reviving an already-deleted Session
|
|
2103
|
+
* between the abort snapshot and the file removal. The caller must call
|
|
2104
|
+
* endSessionDeletion once deletion finishes (success or failure). Returns the
|
|
2105
|
+
* in-flight drive Promise: the caller should await it before deleting the Trace file,
|
|
2106
|
+
* so cleanup writes don't recreate the file.
|
|
2107
|
+
*/
|
|
2108
|
+
beginSessionDeletion(sessionId: string): Promise<void>[];
|
|
2109
|
+
endSessionDeletion(sessionId: string): void;
|
|
2110
|
+
/**
|
|
2111
|
+
* The hand-over's half of a swap: this generation stops working on the state and leaves
|
|
2112
|
+
* everything in it as it is — running Tasks keep running, pending approvals keep waiting —
|
|
2113
|
+
* for the next generation to take over (PRFC-0015). Only what is this instance's own stops:
|
|
2114
|
+
* the idle sweep. `shutdown` is the other ending, for a state nobody takes over.
|
|
2115
|
+
*/
|
|
2116
|
+
detach(): void;
|
|
2117
|
+
/** The Sessions with a Task in flight right now — what a swap parks, so that a successor that cannot take the state over knows which ones to start again. */
|
|
2118
|
+
runningSessionIds(): string[];
|
|
2119
|
+
/** Graceful shutdown: reject new tasks (503), interrupt all active runs, and wait for them to finish (default ≤5s). */
|
|
2120
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
2121
|
+
/**
|
|
2122
|
+
* Active-table idle eviction: removes entries that are idle (idle status, no pending
|
|
2123
|
+
* approvals, no in-flight drive) and have been inactive past the timeout, releasing
|
|
2124
|
+
* the core Session's full in-memory history. This is purely memory reclamation: the
|
|
2125
|
+
* next access re-resumes via the loader, so correctness is unaffected. Lock-table
|
|
2126
|
+
* entries are auto-cleaned by withLock once their chain drains (including leftover
|
|
2127
|
+
* entries under the old id after self-heal). `now` / `idleMs` are injectable for
|
|
2128
|
+
* tests and timers.
|
|
2129
|
+
*/
|
|
2130
|
+
sweepIdle(now?: number, idleMs?: number): void;
|
|
2131
|
+
/**
|
|
2132
|
+
* Pins (`on`) or releases a Session's runtime entry against idle eviction (see sweepIdle).
|
|
2133
|
+
* The messaging bridge holds the pin for as long as a binding's connection is enabled: that
|
|
2134
|
+
* Session is where the bot lives on this server, and evicting it after half an hour of quiet
|
|
2135
|
+
* makes the next chat message wait for a resume from the Trace before anything else happens.
|
|
2136
|
+
*
|
|
2137
|
+
* It pins only what is loaded and loads nothing itself (preload does). A stale id pins
|
|
2138
|
+
* nothing: an entry a delete removed is gone from the table whatever this set says, and the
|
|
2139
|
+
* bridge releases the id when its connection goes.
|
|
2140
|
+
*/
|
|
2141
|
+
keepLoaded(sessionId: string, on: boolean): void;
|
|
2142
|
+
/**
|
|
2143
|
+
* Loads a Session's runtime into the active table ahead of its first Task, through the same
|
|
2144
|
+
* get-or-resume path that Task would take, so the first message after a messaging connection
|
|
2145
|
+
* comes up — an enable, a server restart — does not pay for a resume from the Trace on top of
|
|
2146
|
+
* everything else. Only the load: no run starts, and the core bootstrap (session metadata,
|
|
2147
|
+
* MCP connections) still happens on the first run. Under the Session lock, like startTask, so
|
|
2148
|
+
* a Task arriving mid-load waits for this load instead of starting a second one.
|
|
2149
|
+
*
|
|
2150
|
+
* A Session that is gone or being deleted, alone or with its Agent, resolves quietly: there is
|
|
2151
|
+
* nothing to warm and the caller has nothing to do about it. So does one with no Trace yet,
|
|
2152
|
+
* because loading it is a self-heal REBUILD under a new session id — that would move the
|
|
2153
|
+
* Session out from under the binding that asked, and earlier than any Task would. The row's
|
|
2154
|
+
* `hasTrace` is a cache, so a row that has not caught up only misses the warm-up. Any other
|
|
2155
|
+
* load failure rejects; the first Task meets the same failure and reports it where a person
|
|
2156
|
+
* will see it.
|
|
2157
|
+
*/
|
|
2158
|
+
preload(sessionId: string): Promise<void>;
|
|
2159
|
+
private assertOpen;
|
|
2160
|
+
/** The Agent owning this Session is being deleted → 409 (guards against directory recreation inside the deletion race window). */
|
|
2161
|
+
private assertAgentNotDeleting;
|
|
2162
|
+
/** This Session is being deleted → 409 (guards against the entry/Trace being rebuilt and reviving it inside the deletion race window). */
|
|
2163
|
+
private assertSessionNotDeleting;
|
|
2164
|
+
private assertIdle;
|
|
2165
|
+
private generationOf;
|
|
2166
|
+
private refreshProviderCredentialIfNeeded;
|
|
2167
|
+
/** get-or-resume-or-heal: use directly on an active-table hit; otherwise load via the loader, updating the index's primary key on self-heal. */
|
|
2168
|
+
private ensureEntry;
|
|
2169
|
+
/**
|
|
2170
|
+
* Auto-start of queued background completion notices: called when core signals an
|
|
2171
|
+
* idle-session arrival, and from drive's finally for notices that raced a run's exit.
|
|
2172
|
+
* Revalidates under the session lock; only an idle entry launches, and only when the
|
|
2173
|
+
* queue is non-empty — a busy entry skips, because the running/queued task's engine
|
|
2174
|
+
* drains the same queue at its own boundaries (each notice is delivered exactly once).
|
|
2175
|
+
*/
|
|
2176
|
+
private startBackgroundNoticeTask;
|
|
2177
|
+
/**
|
|
2178
|
+
* Drive the output stream in the background: publish each message + persist usage +
|
|
2179
|
+
* persist LLM/tool errors; on completion (including errors) resets to idle and pushes
|
|
2180
|
+
* the status. `titleSource` is passed only for Task runs (compaction doesn't generate
|
|
2181
|
+
* a title): its user text drives automatic title generation at run start.
|
|
2182
|
+
*/
|
|
2183
|
+
private drive;
|
|
2184
|
+
/**
|
|
2185
|
+
* Register a subagent: persisted only when the origin message is session_meta
|
|
2186
|
+
* (agentId is derived from the agent_state path: `<…>/<agentId>/agent_state`).
|
|
2187
|
+
* **The title is left blank here** — the registration site in drive generates it
|
|
2188
|
+
* right away from the spawning prompt (fallback first words + LLM replacement), so
|
|
2189
|
+
* it never waits for the subagent's own output. Idempotent (children dedup +
|
|
2190
|
+
* insertOrIgnore); a subagent has its own Trace, so it's visible in both the list
|
|
2191
|
+
* and the trace view. On successful registration, the entry is put into `children`
|
|
2192
|
+
* and returned; a duplicate session_meta returns null.
|
|
2193
|
+
*/
|
|
2194
|
+
private registerChildSession;
|
|
2195
|
+
/**
|
|
2196
|
+
* Runs one `sessions` row write for a driven run's bookkeeping (has_trace /
|
|
2197
|
+
* last_active_at), swallowing failures the same way the per-message `recorder.record`
|
|
2198
|
+
* call does: bookkeeping must never take down a run's lifecycle. The two call sites both
|
|
2199
|
+
* sit outside a covering try — the run-start write precedes drive's try block, the
|
|
2200
|
+
* run-end one lives in its finally — and the realistic failure (the DatabaseSync handle
|
|
2201
|
+
* closed by shutdown while a run outlived its drain window) throws ERR_INVALID_STATE.
|
|
2202
|
+
*/
|
|
2203
|
+
private touchRow;
|
|
2204
|
+
private publishState;
|
|
2205
|
+
private publishEvent;
|
|
2206
|
+
/**
|
|
2207
|
+
* Re-counts a loaded session's background tasks and, when the counts moved, publishes
|
|
2208
|
+
* `session_background` on the user channel (the list's mark is a user-channel affair like
|
|
2209
|
+
* `session_state`; the open conversation reads the same row). A change is Session
|
|
2210
|
+
* activity for the idle sweep: the entry stays loaded for the usual idle window after its
|
|
2211
|
+
* last background task ends, so the finished process list can still be read. An entry
|
|
2212
|
+
* that already left the table (a delete disposing its runtime) publishes nothing — its
|
|
2213
|
+
* row is gone as well.
|
|
2214
|
+
*/
|
|
2215
|
+
private publishBackgroundTasks;
|
|
2216
|
+
/**
|
|
2217
|
+
* Publishes `session_approvals` on the user channel: how many tool calls of this Session wait
|
|
2218
|
+
* for a person now. The Session's own stream carries the calls; this is what lets a list, or
|
|
2219
|
+
* a host watching every Session of a Project, see which one waits without subscribing to each.
|
|
2220
|
+
* Sent for an entry that is being disposed too, so a mark it had is cleared.
|
|
2221
|
+
*/
|
|
2222
|
+
private publishApprovals;
|
|
2223
|
+
/** Serialize (mutually exclude) execution by sessionId; cleans up the lock-table entry once its chain drains (avoids unbounded growth). */
|
|
2224
|
+
private withLock;
|
|
2225
|
+
}
|
|
2226
|
+
declare const Sessions_base: (abstract new () => Pick<SessionManager, "steer" | "pendingApprovalCount" | "pendingFollowUpCount" | "statusOf" | "pendingApprovals" | "pendingSteeringOf" | "subagentsOf" | "sendToSubagent" | "abortSubagentRun" | "pendingFollowUpsOf" | "liveFragments" | "pendingInputs" | "pendingBootstrap" | "activeCountForAgent" | "invalidateAgentRuntimes" | "invalidateProjectRuntimes" | "assertCanAcceptTask" | "startTask" | "retryTask" | "startGoal" | "startCompact" | "startSwitch" | "onModelChanged" | "decideApproval" | "recallSteering" | "recallFollowUp" | "retryNow" | "abortTask" | "listProcesses" | "probeProcessServices" | "killProcess" | "removeProcess" | "abortProject" | "beginAgentDeletion" | "endAgentDeletion" | "beginSessionDeletion" | "endSessionDeletion" | "atIdleBoundary" | "shutdown" | "detach" | "runningSessionIds" | "sweepIdle" | "keepLoaded" | "preload">) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
2227
|
+
/** The session runtime: one entry per live Session, task mutex, approvals, streaming. */
|
|
2228
|
+
declare abstract class Sessions extends Sessions_base {
|
|
2229
|
+
}
|
|
2230
|
+
declare const SessionServiceIface_base: (abstract new () => Pick<SessionService, "hasTrace" | "toInfo" | "listingOf" | "listSessions" | "archivedRows" | "sessionStats" | "createSession" | "defaultSandbox" | "pickSandbox" | "updateSandbox" | "latestTracePath" | "adoptUnmanagedTraceSessions">) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
2231
|
+
declare abstract class SessionServiceIface extends SessionServiceIface_base {
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
interface AgentConfigView {
|
|
2235
|
+
agentsMd: string;
|
|
2236
|
+
systemConfigYaml: string;
|
|
2237
|
+
config: AgentConfigDto;
|
|
2238
|
+
stateDir: string;
|
|
2239
|
+
}
|
|
2240
|
+
declare class AgentConfigService implements AgentConfig {
|
|
2241
|
+
private readonly paths;
|
|
2242
|
+
private get root();
|
|
2243
|
+
/** Whether the Agent exists (determined by the presence of system_config.yaml, matching the CLI's convention). */
|
|
2244
|
+
exists(projectId: string, agentId: string): Promise<boolean>;
|
|
2245
|
+
requireExists(projectId: string, agentId: string): Promise<void>;
|
|
2246
|
+
/**
|
|
2247
|
+
* Read list-card metadata: name / description + tool count (sum of tools.builtin
|
|
2248
|
+
* and tools.mcpServers entries; MCP counted per server). Silently falls back to
|
|
2249
|
+
* empty / 0 if the file is corrupt.
|
|
2250
|
+
*/
|
|
2251
|
+
readCardMeta(projectId: string, agentId: string): Promise<{
|
|
2252
|
+
name?: string;
|
|
2253
|
+
description?: string;
|
|
2254
|
+
toolCount: number;
|
|
2255
|
+
version: number;
|
|
2256
|
+
kernelOutdated: boolean;
|
|
2257
|
+
}>;
|
|
2258
|
+
/** Structured config view (matching the edit form's shape) + raw text + AGENTS.md + State path. */
|
|
2259
|
+
getConfig(projectId: string, agentId: string): Promise<AgentConfigView>;
|
|
2260
|
+
/**
|
|
2261
|
+
* PUT accepts any subset: only the provided keys are updated (parseDocument
|
|
2262
|
+
* preserves comments and untouched content); agentsMd is overwritten in full.
|
|
2263
|
+
* Numeric validation: >0 or -1; thinkingLevel / mode are validated as enums.
|
|
2264
|
+
*/
|
|
2265
|
+
updateConfig(projectId: string, agentId: string, req: AgentConfigUpdateRequest): Promise<void>;
|
|
2266
|
+
/**
|
|
2267
|
+
* Overwrites system_config.yaml with the current code defaults (core's
|
|
2268
|
+
* resetSystemConfigToDefaults) — same semantics as updating an installed skill:
|
|
2269
|
+
* only the identity fields (name / description / version) survive; the system
|
|
2270
|
+
* prompt, max_turns, model/compaction settings and the tool list (incl. MCP
|
|
2271
|
+
* servers) become the current defaults. AGENTS.md, skills and the vault are
|
|
2272
|
+
* untouched.
|
|
2273
|
+
*/
|
|
2274
|
+
resetConfig(projectId: string, agentId: string): Promise<void>;
|
|
2275
|
+
/**
|
|
2276
|
+
* Smart-merges the config up to the current defaults generation (core's applyKernelUpdate):
|
|
2277
|
+
* a settings tab still carrying a recorded generation's default follows the new defaults,
|
|
2278
|
+
* a tab the user changed is kept whole and reported; the config is stamped with the new
|
|
2279
|
+
* kernel version.
|
|
2280
|
+
* The destructive full-refresh alternative stays resetConfig.
|
|
2281
|
+
*/
|
|
2282
|
+
kernelUpdate(projectId: string, agentId: string): Promise<AgentKernelUpdateResponse>;
|
|
2283
|
+
private applyConfigUpdate;
|
|
2284
|
+
/**
|
|
2285
|
+
* Inserts a feature's section placeholder into the Agent's prompt template — the explicit
|
|
2286
|
+
* adoption path mirroring the Memory tab's endpoint; nothing ever inserts automatically.
|
|
2287
|
+
* For skills/vault the insert is migration-first (core's helpers): a stored template still
|
|
2288
|
+
* carrying the legacy hardcoded section verbatim gets it replaced in place by the
|
|
2289
|
+
* placeholder, otherwise the placeholder is inserted before `# Environment`. Idempotent;
|
|
2290
|
+
* returns the refreshed config view (the route picks out the feature's DTO).
|
|
2291
|
+
*/
|
|
2292
|
+
insertTemplatePlaceholder(projectId: string, agentId: string, feature: "vault" | "skills" | "schedules"): Promise<AgentConfigView>;
|
|
2293
|
+
/** Read the Agent vault (agent_state/.vault.toml): values are always masked, plaintext is never sent to the client. */
|
|
2294
|
+
getVault(projectId: string, agentId: string): Promise<VaultResponse>;
|
|
2295
|
+
/**
|
|
2296
|
+
* PUT replaces the whole vault table (same semantics as models): keys absent from
|
|
2297
|
+
* the body are deleted; omitting value keeps the existing value (a new key must
|
|
2298
|
+
* provide a value). Key names are validated against shell environment variable
|
|
2299
|
+
* naming rules (same rule as core); deleting everything removes the whole
|
|
2300
|
+
* .vault.toml file.
|
|
2301
|
+
*/
|
|
2302
|
+
updateVault(projectId: string, agentId: string, req: VaultUpdateRequest): Promise<VaultResponse>;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
/** AgentConfig: the mechanism AgentConfigService implements. */
|
|
2306
|
+
declare abstract class AgentConfig {
|
|
2307
|
+
abstract exists(projectId: string, agentId: string): Promise<boolean>;
|
|
2308
|
+
abstract requireExists(projectId: string, agentId: string): Promise<void>;
|
|
2309
|
+
abstract readCardMeta(projectId: string, agentId: string): Promise<{
|
|
2310
|
+
name?: string;
|
|
2311
|
+
description?: string;
|
|
2312
|
+
toolCount: number;
|
|
2313
|
+
version: number;
|
|
2314
|
+
kernelOutdated: boolean;
|
|
2315
|
+
}>;
|
|
2316
|
+
abstract getConfig(projectId: string, agentId: string): Promise<AgentConfigView>;
|
|
2317
|
+
abstract updateConfig(projectId: string, agentId: string, req: AgentConfigUpdateRequest): Promise<void>;
|
|
2318
|
+
abstract resetConfig(projectId: string, agentId: string): Promise<void>;
|
|
2319
|
+
abstract kernelUpdate(projectId: string, agentId: string): Promise<AgentKernelUpdateResponse>;
|
|
2320
|
+
abstract insertTemplatePlaceholder(projectId: string, agentId: string, feature: "vault" | "skills" | "schedules"): Promise<AgentConfigView>;
|
|
2321
|
+
abstract getVault(projectId: string, agentId: string): Promise<VaultResponse>;
|
|
2322
|
+
abstract updateVault(projectId: string, agentId: string, req: VaultUpdateRequest): Promise<VaultResponse>;
|
|
2323
|
+
}
|
|
2324
|
+
/** AgentLifecycle: the mechanism AgentService implements. */
|
|
2325
|
+
declare abstract class AgentLifecycle {
|
|
2326
|
+
abstract listAgents(projectId: string): Promise<AgentListItem[]>;
|
|
2327
|
+
/** Every id `createAgent` refuses as taken, as names only, including a folder that does not list. */
|
|
2328
|
+
abstract takenAgentIds(projectId: string): Promise<string[]>;
|
|
2329
|
+
abstract deleteAgent(projectId: string, agentId: string): Promise<void>;
|
|
2330
|
+
abstract createAgent(projectId: string, agentId: string, name?: string, description?: string, skillNames?: readonly string[], directory?: {
|
|
2331
|
+
path: string;
|
|
2332
|
+
names: readonly string[];
|
|
2333
|
+
}, archive?: Buffer<ArrayBufferLike>): Promise<AgentListItem>;
|
|
2334
|
+
abstract pluginVersion(projectId: string, agentId: string, pluginName: string): Promise<{
|
|
2335
|
+
installed: string | null;
|
|
2336
|
+
library: string | null;
|
|
2337
|
+
}>;
|
|
2338
|
+
abstract updatePlugin(projectId: string, agentId: string, pluginName: string): Promise<void>;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
interface AgentListItem {
|
|
2342
|
+
agentId: string;
|
|
2343
|
+
name?: string;
|
|
2344
|
+
description?: string;
|
|
2345
|
+
createdAt?: string;
|
|
2346
|
+
/** Last config modification time: the later of system_config.yaml / AGENTS.md mtime. */
|
|
2347
|
+
updatedAt?: string;
|
|
2348
|
+
/** Tool count: number of tools.builtin + tools.mcpServers entries (MCP counted per server). */
|
|
2349
|
+
toolCount: number;
|
|
2350
|
+
/** Agent State version number (missing field treated as 1). */
|
|
2351
|
+
version: number;
|
|
2352
|
+
/** Whether the config's kernel stamp is behind the current defaults generation (missing stamp = outdated) — the list card's update hint. */
|
|
2353
|
+
kernelOutdated: boolean;
|
|
2354
|
+
/** Number of vault keys. */
|
|
2355
|
+
vaultKeyCount: number;
|
|
2356
|
+
/** Number of scheduled tasks (count of .toml files under schedule/, including invalid ones). */
|
|
2357
|
+
scheduleCount: number;
|
|
2358
|
+
/** Number of installed Skills (count of skills/<name>/ directories that contain a SKILL.md). */
|
|
2359
|
+
skillCount: number;
|
|
2360
|
+
/** Number of installed hook packages (hooks/<name>/ directories with a hooks.json). */
|
|
2361
|
+
hookCount: number;
|
|
2362
|
+
/** Installed skills and hook packages the library has a newer version of, by plugin, each carrying that library version. */
|
|
2363
|
+
pluginUpdates: PluginUpdateRef[];
|
|
2364
|
+
/** Number of memory topic files across every scope directory under memory/ (independent of the memory switch, like skillCount). */
|
|
2365
|
+
memoryCount: number;
|
|
2366
|
+
}
|
|
2367
|
+
declare class AgentService implements AgentLifecycle {
|
|
2368
|
+
private readonly paths;
|
|
2369
|
+
private get root();
|
|
2370
|
+
private readonly agents;
|
|
2371
|
+
private readonly agentConfig;
|
|
2372
|
+
private readonly snapshots;
|
|
2373
|
+
/** Union of DB index ∪ directory scan; unmanaged directory Agents are backfilled into the DB. */
|
|
2374
|
+
listAgents(projectId: string): Promise<AgentListItem[]>;
|
|
2375
|
+
/**
|
|
2376
|
+
* Every id `createAgent` refuses as taken, as names only: the index rows and every entry under
|
|
2377
|
+
* `agents/`. Unlike `listAgents`, this includes a folder with no `system_config.yaml`, and it
|
|
2378
|
+
* opens no Agent.
|
|
2379
|
+
*/
|
|
2380
|
+
takenAgentIds(projectId: string): Promise<string[]>;
|
|
2381
|
+
/** Number of vault keys (falls back to 0 on read failure). */
|
|
2382
|
+
private vaultKeyCount;
|
|
2383
|
+
/** Number of scheduled tasks: count of .toml files under schedule/ (0 if the directory doesn't exist). */
|
|
2384
|
+
private scheduleCount;
|
|
2385
|
+
/**
|
|
2386
|
+
* One pass over `skills/` and one over `hooks/`, answering the card's questions: how many
|
|
2387
|
+
* skills and hook packages are installed, and which of them the plugin library has moved
|
|
2388
|
+
* past — each part against the library's same part (a skill's dated version against that
|
|
2389
|
+
* skill's, a hook package's against the plugin's `hooks.version`), reported by plugin, since
|
|
2390
|
+
* a plugin is what gets reinstalled to catch up.
|
|
2391
|
+
*
|
|
2392
|
+
* Only the HEAD of each SKILL.md is read, and that bound is load-bearing rather than a
|
|
2393
|
+
* micro-optimization: a preinstalled library is ~180 KB of SKILL.md across seventeen files
|
|
2394
|
+
* per Agent, so a Project of ten Agents would move ~2 MB through every `GET /agents` — and
|
|
2395
|
+
* the plugin page reloads that list after each update. The frontmatter block is at the head
|
|
2396
|
+
* by definition (the parser's regex is anchored there) and the largest one in the library is
|
|
2397
|
+
* under 1 KB, so {@link SKILL_HEAD_BYTES} covers it many times over; a block that somehow
|
|
2398
|
+
* overran it would be truncated, fail to parse, and read as an empty version — the same
|
|
2399
|
+
* fallback an unparseable file already takes, and one that sorts before every real version.
|
|
2400
|
+
*
|
|
2401
|
+
* The directory name is the identity (core's listings say so, and install/uninstall address
|
|
2402
|
+
* by it), so that — not a frontmatter `name` — is what the library is looked up by: a skill
|
|
2403
|
+
* by the plugin that ships it, a hook package by the plugin of the same name. A skill the
|
|
2404
|
+
* library does not carry (installed from a zip or a picked directory) has no library version
|
|
2405
|
+
* to be behind and is never an update. An unreadable skill degrades to "not installed" / "no
|
|
2406
|
+
* update": a card hint must not be the thing that makes the Agent list fail. The library is
|
|
2407
|
+
* the caller's, read once per listing rather than once per installed skill.
|
|
2408
|
+
*/
|
|
2409
|
+
private installedPlugins;
|
|
2410
|
+
/**
|
|
2411
|
+
* Where one library plugin stands on one Agent, as one pair of dated versions of the same
|
|
2412
|
+
* part — so `library` newer than `installed` is exactly "some part is behind", the rule
|
|
2413
|
+
* {@link installedPlugins} draws the list cards' hints from. Each part the Agent carries (a
|
|
2414
|
+
* skill, the hook package) is paired with the library's same part; among the parts that are
|
|
2415
|
+
* behind — or, when none is, among all of them — the one with the OLDEST installed version
|
|
2416
|
+
* answers. `installed` is null when the Agent carries none of the plugin's parts, with
|
|
2417
|
+
* `library` then the plugin's content version; both are null when the library has no such
|
|
2418
|
+
* plugin. Only the head of each SKILL.md is read (see SKILL_HEAD_BYTES), so the answer costs
|
|
2419
|
+
* one small read per skill the plugin ships.
|
|
2420
|
+
*
|
|
2421
|
+
* The organization runtime asks this per employee per pass to keep company plugins current.
|
|
2422
|
+
* A skill the Agent does not carry — never installed, or removed by hand — counts for
|
|
2423
|
+
* nothing: a plugin whose carried skills are all current reads as current even where one of
|
|
2424
|
+
* them is missing. Restoring a deleted skill is the Agents page's business, not a pass's.
|
|
2425
|
+
*/
|
|
2426
|
+
pluginVersion(projectId: string, agentId: string, pluginName: string): Promise<{
|
|
2427
|
+
installed: string | null;
|
|
2428
|
+
library: string | null;
|
|
2429
|
+
}>;
|
|
2430
|
+
/**
|
|
2431
|
+
* Reinstall one library plugin over an Agent's copy: the same whole-plugin update the
|
|
2432
|
+
* Agents page performs, through the same `installPlugin` writer, which overwrites every
|
|
2433
|
+
* skill the plugin ships and its hook package with library content. Throws 404
|
|
2434
|
+
* `unknown_plugin` when the library does not carry the name. Callers that may hold a cached
|
|
2435
|
+
* runtime for the Agent invalidate it afterwards — hook packages are bound when a core
|
|
2436
|
+
* Session is built, unlike skills, which are read from disk on demand.
|
|
2437
|
+
*/
|
|
2438
|
+
updatePlugin(projectId: string, agentId: string, pluginName: string): Promise<void>;
|
|
2439
|
+
/** Number of memory topic files: regular `*.md` files (minus each scope's MEMORY.md index) summed over the scope directories under memory/ (0 if the directory doesn't exist). */
|
|
2440
|
+
private memoryCount;
|
|
2441
|
+
/** Last config modification time: the later of system_config.yaml and AGENTS.md mtime; omitted if neither is readable. */
|
|
2442
|
+
private configUpdatedAt;
|
|
2443
|
+
/**
|
|
2444
|
+
* Delete an Agent: the sole built-in Agent
|
|
2445
|
+
* default_agent (shared with the CLI, the default conversation Agent) cannot be
|
|
2446
|
+
* deleted; callers must first drain any active run via manager.abortAgent.
|
|
2447
|
+
* The directory is deleted recursively (including Trace), and the DB's
|
|
2448
|
+
* agents/sessions index rows are removed along with it; usage records are kept
|
|
2449
|
+
* (historical stats are unaffected).
|
|
2450
|
+
*/
|
|
2451
|
+
deleteAgent(projectId: string, agentId: string): Promise<void>;
|
|
2452
|
+
/**
|
|
2453
|
+
* Create an Agent: the id is chosen by the creator (a semantic id, checked for
|
|
2454
|
+
* duplicates against both the DB and the directory within the Project — a 409
|
|
2455
|
+
* if taken, which naturally also blocks built-in Agent ids) → initialize State →
|
|
2456
|
+
* write name/description (name defaults to the id) → install the seed plugins.
|
|
2457
|
+
*
|
|
2458
|
+
* `pluginNames` are library plugin names picked at creation; they go through the same
|
|
2459
|
+
* `installPlugin` writer the plugin routes use, inside the same cleanup window as the rest
|
|
2460
|
+
* of initialization, so an Agent never survives with only part of its picked set. They are
|
|
2461
|
+
* resolved before anything is created, so an unknown name creates no directory at all.
|
|
2462
|
+
*
|
|
2463
|
+
* `archive` is an exported Agent State snapshot package: the fresh Agent is seeded from
|
|
2464
|
+
* it instead of the default template (explicit name/description override the package's
|
|
2465
|
+
* values, absent ones keep them — no id fallback). Mutually exclusive with seeding: the
|
|
2466
|
+
* package carries its own skills and hooks. An invalid package fails the whole creation
|
|
2467
|
+
* inside the same cleanup window, leaving no empty Agent behind.
|
|
2468
|
+
*/
|
|
2469
|
+
createAgent(projectId: string, agentId: string, name?: string, description?: string, pluginNames?: readonly string[], directory?: {
|
|
2470
|
+
path: string;
|
|
2471
|
+
names: readonly string[];
|
|
2472
|
+
}, archive?: Buffer): Promise<AgentListItem>;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/** MessagingBindings: the mechanism MessagingBindingsRepo implements. */
|
|
2476
|
+
declare abstract class MessagingBindings {
|
|
2477
|
+
abstract find(sessionId: string, channel: string): MessagingBindingRow | null;
|
|
2478
|
+
abstract findEnabled(sessionId: string): MessagingBindingRow | null;
|
|
2479
|
+
abstract listForSession(sessionId: string): MessagingBindingRow[];
|
|
2480
|
+
abstract findEnabledByAccount(channel: string, accountId: string): MessagingBindingRow | null;
|
|
2481
|
+
abstract listAll(): MessagingBindingRow[];
|
|
2482
|
+
abstract upsert(args: {
|
|
2483
|
+
sessionId: string;
|
|
2484
|
+
channel: string;
|
|
2485
|
+
accountId: string;
|
|
2486
|
+
config: Record<string, unknown>;
|
|
2487
|
+
linePerMessage?: boolean;
|
|
2488
|
+
finalReplyOnly?: boolean;
|
|
2489
|
+
renderMarkdown?: boolean;
|
|
2490
|
+
}): MessagingBindingRow;
|
|
2491
|
+
abstract setEnabled(sessionId: string, channel: string, enabled: boolean): void;
|
|
2492
|
+
abstract recordChat(sessionId: string, channel: string, chatId: string, isDirect: boolean): void;
|
|
2493
|
+
abstract recordInboundWatermark(sessionId: string, channel: string, messageId: string | null): void;
|
|
2494
|
+
abstract delete(sessionId: string, channel: string): void;
|
|
2495
|
+
abstract deleteSession(sessionId: string): void;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
interface MessagingBindingRow {
|
|
2499
|
+
sessionId: string;
|
|
2500
|
+
/** Messaging channel discriminator (`feishu` | `telegram` | `qq` | `wechat`). */
|
|
2501
|
+
channel: string;
|
|
2502
|
+
/** Channel-scoped bot/app identity (feishu: the app_id; telegram: the bot token's numeric id; qq: the App ID; wechat: the scanned bot id); never secret. */
|
|
2503
|
+
accountId: string;
|
|
2504
|
+
/** Channel-specific config document (feishu: appId/appSecret/baseDomain; telegram: botToken; qq: appId/appSecret; wechat: botId/botToken/baseUrl/userId). */
|
|
2505
|
+
config: Record<string, unknown>;
|
|
2506
|
+
/**
|
|
2507
|
+
* INTENT state: whether the binding should hold a live connection (the connection's
|
|
2508
|
+
* runtime status stays in memory). Saving credentials never flips it — only the
|
|
2509
|
+
* explicit state toggle does — and new bindings start disabled. Flipping it on is what
|
|
2510
|
+
* binds the account to this Session: at most one of a Session's rows is enabled, and at
|
|
2511
|
+
* most one row per `(channel, accountId)` is enabled across all Sessions (both
|
|
2512
|
+
* route-enforced).
|
|
2513
|
+
*/
|
|
2514
|
+
enabled: boolean;
|
|
2515
|
+
/**
|
|
2516
|
+
* DELIVERY preference: send each non-blank line of a relayed assistant reply as its own
|
|
2517
|
+
* message instead of one message per reply. Off by default, and off reproduces the
|
|
2518
|
+
* original behaviour exactly. Ordinary saved state — the PUT owns it, like the config.
|
|
2519
|
+
*/
|
|
2520
|
+
linePerMessage: boolean;
|
|
2521
|
+
/**
|
|
2522
|
+
* DELIVERY preference: relay only the LAST completed assistant message of a run, sent when
|
|
2523
|
+
* the run ends, instead of mirroring each of them as it completes. Off by default, and off
|
|
2524
|
+
* reproduces the original behaviour exactly. Independent of `linePerMessage`: with both set
|
|
2525
|
+
* that one final message is what gets split per line. Ordinary saved state, like the field
|
|
2526
|
+
* above.
|
|
2527
|
+
*/
|
|
2528
|
+
finalReplyOnly: boolean;
|
|
2529
|
+
/**
|
|
2530
|
+
* DELIVERY preference: render a relayed assistant reply's Markdown in the channel's own
|
|
2531
|
+
* markup rather than sending its characters as written. ON by default — the model writes
|
|
2532
|
+
* Markdown, and relaying it raw is what this replaced. What survives differs per channel
|
|
2533
|
+
* (Telegram has no headings, lists or tables; QQ has no code or tables; Feishu has all of
|
|
2534
|
+
* them) and a rendering the channel refuses falls back to the plain source, so the setting
|
|
2535
|
+
* can cost formatting and never a message.
|
|
2536
|
+
*/
|
|
2537
|
+
renderMarkdown: boolean;
|
|
2538
|
+
/**
|
|
2539
|
+
* Most recent inbound chat (null until the bot is messaged once). The channel's connector
|
|
2540
|
+
* mints this string and is the only thing that may read it: Telegram encodes the forum
|
|
2541
|
+
* topic into it, so it is not always a bare chat id. Pass it to `MessagingClient.sendText`,
|
|
2542
|
+
* never to a channel API directly.
|
|
2543
|
+
*/
|
|
2544
|
+
lastChatId: string | null;
|
|
2545
|
+
/** Whether that chat is a direct chat; group chats prefer reply-to-message. */
|
|
2546
|
+
lastChatIsDirect: boolean;
|
|
2547
|
+
/**
|
|
2548
|
+
* Channel id of the most recently processed inbound message (null until one arrives).
|
|
2549
|
+
* The bridge's redelivery guard is in memory and dies with the process; this is the one
|
|
2550
|
+
* fact that survives it, so a channel replaying an already-processed event across a
|
|
2551
|
+
* restart is recognized rather than run again (see MessagingBridge's recentInbound).
|
|
2552
|
+
*/
|
|
2553
|
+
lastInboundMessageId: string | null;
|
|
2554
|
+
createdAt: string;
|
|
2555
|
+
updatedAt: string;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* The messaging-channel connector seam: what the MessagingBridge needs from one chat
|
|
2560
|
+
* platform (Feishu, Telegram, QQ and WeChat today; further channels implement the same interface
|
|
2561
|
+
* and register in app assembly). A connector owns everything channel-specific — credential
|
|
2562
|
+
* shape, wire protocol, event normalization — and hands the bridge a channel-neutral
|
|
2563
|
+
* view: a client for outbound sends and credential checks, and a long-lived event
|
|
2564
|
+
* connection delivering normalized inbound messages (text, images and files, each as far as
|
|
2565
|
+
* the channel actually carries them).
|
|
2566
|
+
*
|
|
2567
|
+
* Config documents are the repo's stored per-channel JSON (`MessagingBindingRow.config`);
|
|
2568
|
+
* every connector method validates its own shape and throws a readable error on a
|
|
2569
|
+
* malformed one — the bridge treats that like any other channel failure.
|
|
2570
|
+
*/
|
|
2571
|
+
|
|
2572
|
+
/** One inbound image's bytes, once fetched, with the MIME type the bridge needs for its data URL. */
|
|
2573
|
+
interface MessagingInboundImageData {
|
|
2574
|
+
data: Buffer;
|
|
2575
|
+
/** e.g. `image/png` — the connector's best answer, from the channel's own type or the bytes. */
|
|
2576
|
+
mimeType: string;
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* An image attached to an inbound message: a handle, not the bytes.
|
|
2580
|
+
*
|
|
2581
|
+
* The bytes are deliberately NOT eagerly downloaded by the connector. Most of what makes
|
|
2582
|
+
* an inbound message uninteresting is decided after the event is normalized — a
|
|
2583
|
+
* redelivery is dropped before anything else happens — and a channel that downloaded
|
|
2584
|
+
* first would pay a full image transfer for every replay. Fetching lazily also puts the
|
|
2585
|
+
* cap where the transfer is: `fetch` refuses anything over `maxBytes` rather than handing
|
|
2586
|
+
* the bridge something it would only discard, which is what keeps a 100MB attachment from
|
|
2587
|
+
* ever being resident in this process.
|
|
2588
|
+
*/
|
|
2589
|
+
interface MessagingInboundImage {
|
|
2590
|
+
/**
|
|
2591
|
+
* Downloads the image. Two distinct failures, and the bridge answers them differently:
|
|
2592
|
+
* `MessagingMediaTooLargeError` (see media.ts) for anything past `maxBytes`, which the
|
|
2593
|
+
* user fixes by sending something smaller, and any other Error — carrying the channel's
|
|
2594
|
+
* OWN reason, since a permission the bot lacks and a network blip are not the same
|
|
2595
|
+
* problem — for a transfer that could not be made at all. That reason reaches the chat,
|
|
2596
|
+
* so a connector must keep credentials out of it (see telegram-api's fetchErrorText).
|
|
2597
|
+
*/
|
|
2598
|
+
fetch(maxBytes: number): Promise<MessagingInboundImageData>;
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* A non-image file attached to an inbound message: a handle, not the bytes, for exactly the
|
|
2602
|
+
* reasons MessagingInboundImage is one — a redelivery is dropped before anything is
|
|
2603
|
+
* transferred, and the cap rides into `fetch` so an oversized attachment is refused at the
|
|
2604
|
+
* byte that crosses it rather than buffered whole and measured afterwards. A file's ceiling
|
|
2605
|
+
* is the server's per-file attachment limit, which is far larger than an image's, so the
|
|
2606
|
+
* difference between refusing during and after the transfer is correspondingly larger.
|
|
2607
|
+
*
|
|
2608
|
+
* There is no MIME type on this seam. An image needs one because its bytes become a `data:`
|
|
2609
|
+
* URL inside the conversation; a file's bytes go to the Session scratchpad and reach the
|
|
2610
|
+
* model as a PATH, so the name's extension is the whole of what says what it is — and a
|
|
2611
|
+
* channel's declared media type is the sender's claim about a file nothing here parses.
|
|
2612
|
+
*/
|
|
2613
|
+
interface MessagingInboundFile {
|
|
2614
|
+
/**
|
|
2615
|
+
* The sender's own file name, extension included — what the model sees at the end of the
|
|
2616
|
+
* `[attached file: …]` path, and what a refusal notice names. Channel text, so it is
|
|
2617
|
+
* neither trusted nor pre-sanitized here: the attachment writer maps it onto a name that
|
|
2618
|
+
* is safe on disk (see services/task-attachments.ts). A channel with no name for a file
|
|
2619
|
+
* says so with a plain fallback rather than inventing one, since a made-up extension
|
|
2620
|
+
* would tell the model the file is something it is not.
|
|
2621
|
+
*/
|
|
2622
|
+
fileName: string;
|
|
2623
|
+
/** Downloads the file; the same two failure shapes MessagingInboundImage.fetch documents. */
|
|
2624
|
+
fetch(maxBytes: number): Promise<Buffer>;
|
|
2625
|
+
}
|
|
2626
|
+
/** One inbound chat message, normalized across channels. */
|
|
2627
|
+
interface MessagingInboundMessage {
|
|
2628
|
+
/**
|
|
2629
|
+
* Channel-scoped chat id, and the reply target for direct chats. Opaque to the bridge, for
|
|
2630
|
+
* the same reason `messageId` is: the connector both mints it here and consumes it in
|
|
2631
|
+
* `sendText`, so a channel whose replies need more routing context than a chat identity
|
|
2632
|
+
* encodes it (Telegram appends the forum topic). It is stored verbatim as the binding's
|
|
2633
|
+
* last known chat, so whatever is encoded here is what survives a restart.
|
|
2634
|
+
*/
|
|
2635
|
+
chatId: string;
|
|
2636
|
+
/** Direct chat with the bot, or a group chat (groups prefer reply-to-message). */
|
|
2637
|
+
chatKind: "direct" | "group";
|
|
2638
|
+
/**
|
|
2639
|
+
* Channel-scoped id of the inbound message itself (the group reply target). Opaque to
|
|
2640
|
+
* the bridge: the connector both mints it here and consumes it in `replyText`, so a
|
|
2641
|
+
* channel whose native message ids are not globally unique encodes whatever context a
|
|
2642
|
+
* reply needs (Telegram packs `chatId:messageId`). It is also the bridge's inbound
|
|
2643
|
+
* dedupe key, so it must identify the MESSAGE and not the delivery: a channel that
|
|
2644
|
+
* redelivers one message must mint the same id both times. `""` opts the channel out of
|
|
2645
|
+
* deduplication entirely — the honest answer for a connector with no message identity,
|
|
2646
|
+
* and better than every message after the first reading as a duplicate.
|
|
2647
|
+
*/
|
|
2648
|
+
messageId: string;
|
|
2649
|
+
/**
|
|
2650
|
+
* The message's plain text; null for anything that carries none (stickers, voice, …) — a
|
|
2651
|
+
* message with no text, no image and no file gets the not-supported notice. An image or a
|
|
2652
|
+
* file sent with a caption puts the caption here: the caption IS that message's text, so
|
|
2653
|
+
* a channel needs no second field for it and the bridge no second rule. A channel that
|
|
2654
|
+
* carries a caption on media it does NOT deliver must still report null, or the model
|
|
2655
|
+
* answers a question about an attachment it never received.
|
|
2656
|
+
*/
|
|
2657
|
+
text: string | null;
|
|
2658
|
+
/**
|
|
2659
|
+
* Images attached to this message (absent or empty when there are none). Handles rather
|
|
2660
|
+
* than bytes — see MessagingInboundImage. A channel that carries several images in one
|
|
2661
|
+
* message lists them in the order the user sees them.
|
|
2662
|
+
*/
|
|
2663
|
+
images?: readonly MessagingInboundImage[];
|
|
2664
|
+
/**
|
|
2665
|
+
* Non-image files attached to this message, in the order the user sees them (absent or
|
|
2666
|
+
* empty when there are none) — see MessagingInboundFile.
|
|
2667
|
+
*
|
|
2668
|
+
* Optional, like `images`, so a channel that delivers no files simply never populates it
|
|
2669
|
+
* rather than carrying an empty list through its whole normalizer. A message may carry
|
|
2670
|
+
* text, images and files together; the bridge composes one composer input out of whatever
|
|
2671
|
+
* is there.
|
|
2672
|
+
*/
|
|
2673
|
+
files?: readonly MessagingInboundFile[];
|
|
2674
|
+
/** Sender display name when the channel's event carries one. */
|
|
2675
|
+
senderName?: string;
|
|
2676
|
+
}
|
|
2677
|
+
interface MessagingConnectorHandlers {
|
|
2678
|
+
onMessage(msg: MessagingInboundMessage): void | Promise<void>;
|
|
2679
|
+
/** The connection completed a handshake (may fire again after an automatic reconnect). */
|
|
2680
|
+
onReady?(): void;
|
|
2681
|
+
/**
|
|
2682
|
+
* The connection failed and the channel gave up (or the initial connect failed). A
|
|
2683
|
+
* {@link MessagingChannelError} says whether the outage clears without anyone acting.
|
|
2684
|
+
*/
|
|
2685
|
+
onError?(err: unknown): void;
|
|
2686
|
+
/**
|
|
2687
|
+
* The connection's account started or stopped holding replies it could not deliver yet
|
|
2688
|
+
* (see {@link MessagingReplyHeldError}): the ISO time since which it holds some, or null once
|
|
2689
|
+
* it holds none. Fired on every change, and once at connect for a backlog held from before.
|
|
2690
|
+
* Only a channel that holds replies (WeChat) ever calls it.
|
|
2691
|
+
*/
|
|
2692
|
+
onHeldChange?(since: string | null): void;
|
|
2693
|
+
/**
|
|
2694
|
+
* A send the connector made on its OWN — a withheld tail flushed after the quiet period, held
|
|
2695
|
+
* replies delivered on the next inbound message — failed for good. No caller is waiting on
|
|
2696
|
+
* those sends, so this is the only way their failure reaches the binding's status and the
|
|
2697
|
+
* error table.
|
|
2698
|
+
*/
|
|
2699
|
+
onSendFailed?(err: unknown): void;
|
|
2700
|
+
}
|
|
2701
|
+
/** A live inbound event stream; `close` ends it (idempotent). */
|
|
2702
|
+
interface MessagingConnection {
|
|
2703
|
+
close(): void;
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* A successful credential check's optional payload: whatever the probe learned that the test
|
|
2707
|
+
* endpoint's feedback can act on. Every member is independently optional — a channel reports
|
|
2708
|
+
* the ones its check happens to answer and omits the rest, so a check that learned nothing
|
|
2709
|
+
* beyond "these credentials work" returns an empty object.
|
|
2710
|
+
*/
|
|
2711
|
+
interface MessagingAccountInfo {
|
|
2712
|
+
/** A short human-readable label of the account the credentials sign in as (Telegram: the bot's `@username`). */
|
|
2713
|
+
accountLabel?: string;
|
|
2714
|
+
/**
|
|
2715
|
+
* Whether this ACCOUNT is set up to receive ordinary messages in the groups it belongs to
|
|
2716
|
+
* — as opposed to only the ones a platform hands a bot by default.
|
|
2717
|
+
*
|
|
2718
|
+
* Telegram's privacy mode is the case that needs saying: it is on for every bot whose
|
|
2719
|
+
* owner has not turned it off in @BotFather, and under it `getUpdates` simply omits
|
|
2720
|
+
* everything in a group that is not a command addressed to this bot or a reply to one of
|
|
2721
|
+
* its messages. Nothing errors — the messages are never delivered, so a binding that works
|
|
2722
|
+
* perfectly in a direct chat looks dead in a group. It is one account-wide setting and
|
|
2723
|
+
* Telegram overrides it in any group the bot administers, so `false` says the account is
|
|
2724
|
+
* muted where it is an ordinary member, never that some particular group is silent. Absent
|
|
2725
|
+
* when the channel has no such notion, or reports nothing about it: unknown must never be
|
|
2726
|
+
* reported as a problem.
|
|
2727
|
+
*/
|
|
2728
|
+
readsGroupMessages?: boolean;
|
|
2729
|
+
}
|
|
2730
|
+
/**
|
|
2731
|
+
* What a send had to give up in order to land, as a short readable phrase. The message DID
|
|
2732
|
+
* arrive — a send that did not throws — so this is not a failure: it says the message is
|
|
2733
|
+
* somewhere less right than it was addressed to, which is otherwise invisible from the
|
|
2734
|
+
* outside. A channel that always delivers as addressed returns nothing at all; Telegram is
|
|
2735
|
+
* the one that reports, a forum topic deleted under a live conversation sending the reply to
|
|
2736
|
+
* General instead.
|
|
2737
|
+
*/
|
|
2738
|
+
type MessagingSendNote = string;
|
|
2739
|
+
/** One file on its way out to a chat: the bytes, plus the name the chat should show. */
|
|
2740
|
+
interface MessagingOutboundFile {
|
|
2741
|
+
/** Display name — the base name of the Workspace-relative path the reply mentioned. */
|
|
2742
|
+
fileName: string;
|
|
2743
|
+
data: Buffer;
|
|
2744
|
+
}
|
|
2745
|
+
/** How one outbound text should be rendered. Absent throughout means plain text. */
|
|
2746
|
+
interface MessagingSendOptions {
|
|
2747
|
+
/**
|
|
2748
|
+
* Read the text as Markdown and render it in whatever this channel's markup is.
|
|
2749
|
+
*
|
|
2750
|
+
* A REQUEST, not an instruction: the connector owns both what its channel can show and
|
|
2751
|
+
* what to do when the channel refuses the rendered form, and it must fall back to sending
|
|
2752
|
+
* this same text plainly rather than let a formatting failure cost the message. What
|
|
2753
|
+
* survives the conversion differs per channel and is documented at each renderer
|
|
2754
|
+
* (telegram-html.ts, feishu-card.ts, qq-markdown.ts).
|
|
2755
|
+
*
|
|
2756
|
+
* Only a relayed assistant message ever sets it. The fixed notices and the test message
|
|
2757
|
+
* carry no markup and are sent as they are written, so a bug in a renderer can never
|
|
2758
|
+
* reach the one message a user sends to check whether the binding works at all.
|
|
2759
|
+
*/
|
|
2760
|
+
markdown?: boolean;
|
|
2761
|
+
/**
|
|
2762
|
+
* This send is a probe of the outbound leg ("send test message"), not a reply. A connector
|
|
2763
|
+
* that would HOLD a refused message for later delivery must throw
|
|
2764
|
+
* {@link MessagingNeedsRecentMessageError} instead: a held probe that arrives an hour later
|
|
2765
|
+
* proves nothing, and the person pressing the button needs to hear why it did not arrive now.
|
|
2766
|
+
*/
|
|
2767
|
+
probe?: boolean;
|
|
2768
|
+
}
|
|
2769
|
+
/** Outbound half of one bound account. Every method throws on failure with a readable reason. */
|
|
2770
|
+
interface MessagingClient {
|
|
2771
|
+
/** Credential check (used by the test endpoint); resolving means the config signs in. */
|
|
2772
|
+
checkCredentials(): Promise<MessagingAccountInfo | null>;
|
|
2773
|
+
/** Sends a text message into a chat by chat id; resolves a MessagingSendNote when it degraded. */
|
|
2774
|
+
sendText(chatId: string, text: string, opts?: MessagingSendOptions): Promise<MessagingSendNote | void>;
|
|
2775
|
+
/** Replies a text message to a specific inbound message (threads correctly in group chats). */
|
|
2776
|
+
replyText(messageId: string, text: string, opts?: MessagingSendOptions): Promise<MessagingSendNote | void>;
|
|
2777
|
+
/**
|
|
2778
|
+
* Sends a picture into a chat, so a chart the Agent drew arrives as something the reader
|
|
2779
|
+
* can see rather than as a download. Channels that need an upload step first do it here —
|
|
2780
|
+
* the bridge holds bytes and a name, never a channel's file handle.
|
|
2781
|
+
*/
|
|
2782
|
+
sendImage(chatId: string, file: MessagingOutboundFile): Promise<void>;
|
|
2783
|
+
/** Sends any other file into a chat as an attachment. */
|
|
2784
|
+
sendFile(chatId: string, file: MessagingOutboundFile): Promise<void>;
|
|
2785
|
+
}
|
|
2786
|
+
interface MessagingChannelConnector {
|
|
2787
|
+
readonly channel: MessagingChannel;
|
|
2788
|
+
/**
|
|
2789
|
+
* How many outbound messages this channel will accept in answer to ONE inbound message,
|
|
2790
|
+
* or undefined where no such limit exists (Feishu and Telegram both send freely).
|
|
2791
|
+
*
|
|
2792
|
+
* A channel that declares one enforces it itself — the connector is the only place that
|
|
2793
|
+
* knows what the platform rejects and how to combine messages so nothing is lost. What
|
|
2794
|
+
* the bridge does with the number is narrower: it caps the one-message-per-line split at
|
|
2795
|
+
* it, because that option's own ceiling is sized for a channel with no such budget and
|
|
2796
|
+
* would otherwise ask for more messages than the channel can ever deliver.
|
|
2797
|
+
*/
|
|
2798
|
+
readonly replyBudget?: number;
|
|
2799
|
+
/** Builds the outbound client for one stored config (throws on a malformed document). */
|
|
2800
|
+
createClient(config: Record<string, unknown>): Promise<MessagingClient>;
|
|
2801
|
+
/**
|
|
2802
|
+
* Opens the inbound event stream for one stored config. Resolves as soon as the
|
|
2803
|
+
* connection is constructed and connecting — lifecycle arrives via the handlers
|
|
2804
|
+
* (`onReady` / `onError`), because channels reconnect on their own and a single promise
|
|
2805
|
+
* cannot carry a lifecycle.
|
|
2806
|
+
*/
|
|
2807
|
+
connect(config: Record<string, unknown>, handlers: MessagingConnectorHandlers): Promise<MessagingConnection>;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
/**
|
|
2811
|
+
* Auth middleware: `Authorization: Bearer <local API token>` -> the built-in admin, or
|
|
2812
|
+
* session cookie -> auth_sessions row -> user; either way the user is injected into c.var.
|
|
2813
|
+
*
|
|
2814
|
+
* The Bearer path authenticates the boot's local API token (`<root>/api-token`, see
|
|
2815
|
+
* auth/api-token.ts) as the admin — the CLI's and agents' machine-local credential; it
|
|
2816
|
+
* applies to every route behind this middleware, SSE endpoints included (the CLI
|
|
2817
|
+
* consumes SSE via fetch with headers). A Bearer header that does not match fails the
|
|
2818
|
+
* request rather than falling back to the cookie: silently downgrading a wrong explicit
|
|
2819
|
+
* credential would mask misconfiguration. A few writes refuse the token all the same and take a
|
|
2820
|
+
* person's sign-in (requireHuman).
|
|
2821
|
+
*
|
|
2822
|
+
* Accessing a protected API while logged out -> 401 `{error:{code:"unauthorized"}}`.
|
|
2823
|
+
* CSRF: SameSite=Lax plus a Content-Type an HTML form cannot forge (see jsonOnlyWrites).
|
|
2824
|
+
* That guard stays for Bearer requests too — the CLI always sends application/json.
|
|
2825
|
+
*/
|
|
2826
|
+
|
|
2827
|
+
/** Hono env: variables injected by the auth middleware. */
|
|
2828
|
+
type AppEnv = {
|
|
2829
|
+
Variables: {
|
|
2830
|
+
user: UserRow;
|
|
2831
|
+
/** How the current session was established — see {@link SessionVia}. */
|
|
2832
|
+
sessionVia: SessionVia;
|
|
2833
|
+
};
|
|
2834
|
+
};
|
|
2835
|
+
|
|
2836
|
+
/** Minimal dependency on SessionManager (eases test doubles; mirrors ScheduleTaskRunner). */
|
|
2837
|
+
interface MessagingTaskRunnerShape {
|
|
2838
|
+
statusOf(sessionId: string): string;
|
|
2839
|
+
startTask(sessionId: string, input: OmniMessage[],
|
|
2840
|
+
/**
|
|
2841
|
+
* `recall` is what a queued follow-up hands back when the user withdraws it in the Web
|
|
2842
|
+
* App. It is optional because the manager derives one from the input for every caller
|
|
2843
|
+
* that has nothing to add; the inbound path supplies its own only when it wrote file
|
|
2844
|
+
* attachments, whose on-disk paths the input does not carry (see inboundRecallStore).
|
|
2845
|
+
*/
|
|
2846
|
+
opts: {
|
|
2847
|
+
queueIfBusy: boolean;
|
|
2848
|
+
recall?: RecallStore;
|
|
2849
|
+
}): Promise<{
|
|
2850
|
+
sessionId: string;
|
|
2851
|
+
queued: boolean;
|
|
2852
|
+
}>;
|
|
2853
|
+
/**
|
|
2854
|
+
* Pins the Session's runtime against idle eviction while a connection is enabled (`on`), or
|
|
2855
|
+
* releases the pin.
|
|
2856
|
+
*/
|
|
2857
|
+
keepLoaded(sessionId: string, on: boolean): void;
|
|
2858
|
+
/**
|
|
2859
|
+
* Loads the Session's runtime ahead of its first message, starting nothing; resolves quietly
|
|
2860
|
+
* for a Session that is gone or being deleted.
|
|
2861
|
+
*/
|
|
2862
|
+
preload(sessionId: string): Promise<void>;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Minimal dependency on the sessions index: existence checks for reconcile/cascade, the
|
|
2866
|
+
* Session's Project/Agent so a messaging failure can be filed under them (see recordError),
|
|
2867
|
+
* and the one field an outbound file needs — the Workspace a mentioned path resolves against.
|
|
2868
|
+
*/
|
|
2869
|
+
interface MessagingSessionIndex {
|
|
2870
|
+
findById(sessionId: string): {
|
|
2871
|
+
projectId: string;
|
|
2872
|
+
agentId: string;
|
|
2873
|
+
workspace: string;
|
|
2874
|
+
} | null;
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Minimal dependency on the Workspace file service. Narrow, but deliberately NOT a
|
|
2878
|
+
* reimplementation: both methods carry that service's containment rules (a path that leaves
|
|
2879
|
+
* the Workspace — by `..`, by an absolute path, or through a symlink — is refused there,
|
|
2880
|
+
* and reads happen on the canonical path). A second resolver living in the bridge is
|
|
2881
|
+
* exactly the kind of thing that ends up one security fix behind.
|
|
2882
|
+
*/
|
|
2883
|
+
interface MessagingWorkspaceFiles {
|
|
2884
|
+
/**
|
|
2885
|
+
* The subset of `rels` that exist as regular files inside the Workspace, each with the
|
|
2886
|
+
* time it was last written — which is what separates a file this run produced from one
|
|
2887
|
+
* the reply merely named (see deliverFiles).
|
|
2888
|
+
*/
|
|
2889
|
+
statExistingWithMtime(workspace: string, rels: string[]): Promise<{
|
|
2890
|
+
rel: string;
|
|
2891
|
+
mtimeMs: number;
|
|
2892
|
+
}[]>;
|
|
2893
|
+
/** Reads a file, at most `maxBytes` of it. */
|
|
2894
|
+
read(workspace: string, rel: string, options?: {
|
|
2895
|
+
maxBytes?: number;
|
|
2896
|
+
}): Promise<{
|
|
2897
|
+
data: Buffer;
|
|
2898
|
+
fileName: string;
|
|
2899
|
+
}>;
|
|
2900
|
+
}
|
|
2901
|
+
interface MessagingBridgeDeps {
|
|
2902
|
+
repo: MessagingBindings;
|
|
2903
|
+
sessions: MessagingSessionIndex;
|
|
2904
|
+
files: MessagingWorkspaceFiles;
|
|
2905
|
+
/**
|
|
2906
|
+
* The data root, so an inbound file can be written into the Session scratchpad the web
|
|
2907
|
+
* composer's uploads land in (`scratchpadDir(root, projectId, agentId)/<sessionId>`,
|
|
2908
|
+
* deleted with the Session). The bridge holds the root rather than a pre-resolved
|
|
2909
|
+
* directory because the Project and Agent are per Session, not per binding.
|
|
2910
|
+
*/
|
|
2911
|
+
root: string;
|
|
2912
|
+
/**
|
|
2913
|
+
* The attachment limits in force right now. A function, not a snapshot: the per-file and
|
|
2914
|
+
* per-message ceilings are admin-settable and read fresh per request everywhere else, so
|
|
2915
|
+
* a change has to reach the next inbound message without a restart.
|
|
2916
|
+
*/
|
|
2917
|
+
attachmentLimits: () => AttachmentLimits;
|
|
2918
|
+
channels: ChannelHub;
|
|
2919
|
+
runner: MessagingTaskRunner;
|
|
2920
|
+
/** One connector per channel; a stored binding whose channel has no connector is skipped with an error record. */
|
|
2921
|
+
connectors: readonly MessagingChannelConnector[];
|
|
2922
|
+
errors: ErrorSink;
|
|
2923
|
+
log?: (line: string) => void;
|
|
2924
|
+
now?: () => number;
|
|
2925
|
+
/** Test hook: the pace between a per-line reply's messages (default MESSAGING_LINE_DELAY_MS; tests collapse it to zero). */
|
|
2926
|
+
lineDelayMs?: number;
|
|
2927
|
+
/** Test hook: one binding's inbound image budget (default MESSAGING_INBOUND_IMAGE_BUDGET_BYTES). */
|
|
2928
|
+
inboundImageBudgetBytes?: number;
|
|
2929
|
+
}
|
|
2930
|
+
declare class MessagingBridge {
|
|
2931
|
+
private readonly deps;
|
|
2932
|
+
private readonly entries;
|
|
2933
|
+
/**
|
|
2934
|
+
* Processed inbound ids per BINDING (`sessionId:channel`), deliberately not per
|
|
2935
|
+
* connection: re-saving an enabled binding restarts its connector, and a message
|
|
2936
|
+
* already turned into a Task must not run again because the connector is new.
|
|
2937
|
+
*
|
|
2938
|
+
* This map dies with the process; `messaging_bindings.last_inbound_message_id` is the
|
|
2939
|
+
* half that does not, and `connect` folds it back in here so the two are one memory
|
|
2940
|
+
* rather than two guards. The row holds ONE id — the last message the bridge finished
|
|
2941
|
+
* with — which is what Feishu's WebSocket resume needs and all it needs: that stream
|
|
2942
|
+
* replays events it never saw acknowledged, and the SDK acknowledges each one only
|
|
2943
|
+
* after our handler returns, so at most the one in flight when the process ended is
|
|
2944
|
+
* still owed. Telegram redelivers nothing across a restart — its poller advances
|
|
2945
|
+
* `offset` past an update before handing it over, and a fresh connection drains with
|
|
2946
|
+
* `getUpdates({offset: -1})`, discarding whatever was sent while nobody was connected —
|
|
2947
|
+
* so on that channel the row is still written and still seeded, and simply never has a
|
|
2948
|
+
* replay to catch.
|
|
2949
|
+
*/
|
|
2950
|
+
private readonly recentInbound;
|
|
2951
|
+
/** Inbound image budgets, keyed like recentInbound: per BINDING, surviving a reconnect. */
|
|
2952
|
+
private readonly imageBudgets;
|
|
2953
|
+
private readonly connectors;
|
|
2954
|
+
private readonly now;
|
|
2955
|
+
private readonly log;
|
|
2956
|
+
private readonly lineDelayMs;
|
|
2957
|
+
private readonly inboundImageBudgetBytes;
|
|
2958
|
+
constructor(deps: MessagingBridgeDeps);
|
|
2959
|
+
/**
|
|
2960
|
+
* Server startup: connect every ENABLED binding (disabled ones keep their credentials
|
|
2961
|
+
* and stay dark; at most one per Session is enabled). A binding whose Session no longer
|
|
2962
|
+
* exists (deleted while this server was down, or by a bulk Agent/Project delete that
|
|
2963
|
+
* bypassed the per-session cascade) is reconciled away instead of connected.
|
|
2964
|
+
*/
|
|
2965
|
+
start(): Promise<void>;
|
|
2966
|
+
/** App dispose: close every connection; bindings persist for the successor's start(). */
|
|
2967
|
+
stop(): void;
|
|
2968
|
+
/**
|
|
2969
|
+
* Align the live connection with the stored intent: an enabled binding exists →
|
|
2970
|
+
* (re)connect with its CURRENT config, none → disconnect. The state toggle calls this
|
|
2971
|
+
* after flipping intent, and a credential save calls it only while the saved binding is
|
|
2972
|
+
* enabled — the restart that keeps stored config and live connection from diverging.
|
|
2973
|
+
*/
|
|
2974
|
+
sync(sessionId: string): Promise<void>;
|
|
2975
|
+
/** Route DELETE: disconnect (when this channel holds the connection) and drop the row. No-op when unbound. */
|
|
2976
|
+
unbind(sessionId: string, channel: string): void;
|
|
2977
|
+
/** Session-delete cascade: disconnect and drop every channel's config. */
|
|
2978
|
+
unbindSession(sessionId: string): void;
|
|
2979
|
+
/**
|
|
2980
|
+
* One channel's runtime status (only the connected channel is ever anything but
|
|
2981
|
+
* disconnected), plus what this connection has actually seen: when a message last arrived,
|
|
2982
|
+
* what last failed after one did, and the last connection failure even after it recovered.
|
|
2983
|
+
* A binding can be `connected` with nothing wrong and still never receive anything, and
|
|
2984
|
+
* that combination is invisible without them.
|
|
2985
|
+
*/
|
|
2986
|
+
statusOf(sessionId: string, channel: string): MessagingRuntimeStatus;
|
|
2987
|
+
/**
|
|
2988
|
+
* Credential probe for the test endpoints: ok/error with latency, never a throw. A
|
|
2989
|
+
* channel whose check identifies the account (Telegram: the bot's @username) passes
|
|
2990
|
+
* that label through for the route's success feedback, along with anything else the
|
|
2991
|
+
* probe learned that the user should act on (see MessagingAccountInfo).
|
|
2992
|
+
*/
|
|
2993
|
+
testCredentials(channel: string, config: Record<string, unknown>): Promise<{
|
|
2994
|
+
ok: boolean;
|
|
2995
|
+
latencyMs?: number;
|
|
2996
|
+
accountLabel?: string;
|
|
2997
|
+
readsGroupMessages?: boolean;
|
|
2998
|
+
error?: string;
|
|
2999
|
+
}>;
|
|
3000
|
+
/**
|
|
3001
|
+
* Test-message endpoints: a short fixed text to the binding's last known chat. The
|
|
3002
|
+
* route has already established that the binding and its chat exist; a race that
|
|
3003
|
+
* removed either since surfaces as the send failing.
|
|
3004
|
+
*/
|
|
3005
|
+
sendTestMessage(row: MessagingBindingRow): Promise<void>;
|
|
3006
|
+
private connectorFor;
|
|
3007
|
+
private connect;
|
|
3008
|
+
private disconnect;
|
|
3009
|
+
/** Every timestamp this bridge writes comes from the injected clock, never from `Date` directly. */
|
|
3010
|
+
private nowIso;
|
|
3011
|
+
/** Status write, guarded against a stale entry (replaced by a newer connect). */
|
|
3012
|
+
private setStatus;
|
|
3013
|
+
/**
|
|
3014
|
+
* A connection failure, put in the three places one belongs: the live `error` state, the
|
|
3015
|
+
* copy kept across recovery, and the error table.
|
|
3016
|
+
*
|
|
3017
|
+
* Behind the same stale-entry guard `setStatus` uses, applied to all three — a connector
|
|
3018
|
+
* callback firing after a newer connect replaced this entry is reporting an attempt that was
|
|
3019
|
+
* already abandoned, and neither its status writes (which nothing reads) nor its record
|
|
3020
|
+
* (which would be filed against a binding since reconfigured) belong to the binding as it
|
|
3021
|
+
* now stands.
|
|
3022
|
+
*/
|
|
3023
|
+
private recordConnectionFailure;
|
|
3024
|
+
/**
|
|
3025
|
+
* One error record for a messaging failure, filed under the Session's Project and Agent.
|
|
3026
|
+
*
|
|
3027
|
+
* The attribution is load-bearing, not decoration: `GET /api/projects/:id/usage/errors`
|
|
3028
|
+
* selects by project, and a record with none is "unattributed" — served only to admins
|
|
3029
|
+
* (`includeGlobalErrors`), so an ordinary member could never see a failure of their own
|
|
3030
|
+
* binding anywhere. The lookup uses the sessions index the bridge already holds; a row
|
|
3031
|
+
* deleted since leaves the record unattributed rather than dropping it.
|
|
3032
|
+
*/
|
|
3033
|
+
private recordError;
|
|
3034
|
+
/**
|
|
3035
|
+
* A failure AFTER an inbound message was accepted: recorded like any other, and also put
|
|
3036
|
+
* on the binding's runtime status so the panel can say it. The error table is a Project-wide
|
|
3037
|
+
* dashboard nobody visits to debug one bot; the binding's own panel is where the question
|
|
3038
|
+
* is asked.
|
|
3039
|
+
*
|
|
3040
|
+
* A later success does not clear it. An intermittent failure — a group admin who revokes
|
|
3041
|
+
* send rights for a minute, a rate limit — would otherwise be erased by the next ordinary
|
|
3042
|
+
* message and never be seen at all, which is the same blind spot `lastConnectionError`
|
|
3043
|
+
* exists to close. The panel states `at` so a stale one reads as stale.
|
|
3044
|
+
*/
|
|
3045
|
+
private recordDeliveryFailure;
|
|
3046
|
+
/**
|
|
3047
|
+
* A send that landed somewhere less right than it was addressed to. Not a failure — the
|
|
3048
|
+
* message arrived — so it stays out of `lastDeliveryError` and off the panel, and goes to
|
|
3049
|
+
* the log, which is where "the replies moved to General a while ago" is answerable at all.
|
|
3050
|
+
*/
|
|
3051
|
+
private noteSend;
|
|
3052
|
+
/** The connector's `onHeldChange`, behind the stale-entry guard every connector callback has. */
|
|
3053
|
+
private setHeldReplySince;
|
|
3054
|
+
/**
|
|
3055
|
+
* Whether a send's failure is the connector HOLDING the reply rather than losing it (see
|
|
3056
|
+
* MessagingReplyHeldError). Such a send is not a delivery failure — no error record, no
|
|
3057
|
+
* `lastDeliveryError` — and is logged once per holding episode, not once per message.
|
|
3058
|
+
*/
|
|
3059
|
+
private wasHeld;
|
|
3060
|
+
private clientFor;
|
|
3061
|
+
/**
|
|
3062
|
+
* Has this binding already processed this message? A duplicate is a complete no-op —
|
|
3063
|
+
* ahead of the chat record and the text-only reply, not just the Task start, because a
|
|
3064
|
+
* replayed sticker would otherwise answer with the notice twice.
|
|
3065
|
+
*
|
|
3066
|
+
* Nothing downstream is idempotent: `startTask` with `queueIfBusy` appends to the
|
|
3067
|
+
* follow-up queue unconditionally, and a queued input is published to the Session
|
|
3068
|
+
* channel, so one redelivery becomes two runs in the chat AND two messages in the Web
|
|
3069
|
+
* App. A connector that mints no id opts out rather than having every message after the
|
|
3070
|
+
* first read as a duplicate.
|
|
3071
|
+
*/
|
|
3072
|
+
private isRedelivery;
|
|
3073
|
+
/** Get-or-create one binding's processed-id memory (see recentInbound). */
|
|
3074
|
+
private inboundMemoryOf;
|
|
3075
|
+
private onInbound;
|
|
3076
|
+
/**
|
|
3077
|
+
* Turns a message carrying attachments into one Task, or resolves the notice to answer
|
|
3078
|
+
* the chat with when it could not be made whole.
|
|
3079
|
+
*
|
|
3080
|
+
* All-or-nothing across BOTH kinds, and in this order: the images are downloaded first,
|
|
3081
|
+
* then the files, and only then is anything written to disk. A refusal on either side
|
|
3082
|
+
* stops the message — a message is one thing the user sent, and half of it reaching the
|
|
3083
|
+
* Agent is not a partial success but a misleading one — and refusing before the write
|
|
3084
|
+
* means a rejected message leaves nothing behind in the scratchpad to clean up.
|
|
3085
|
+
*/
|
|
3086
|
+
private startAttachedTask;
|
|
3087
|
+
/**
|
|
3088
|
+
* Lands this message's files in the Session scratchpad and folds their path lines into the
|
|
3089
|
+
* input. Returns the input untouched when there are none, so a message with no files
|
|
3090
|
+
* creates no directory.
|
|
3091
|
+
*
|
|
3092
|
+
* A failure here is NOT answered in the chat, unlike a refused download: a write that
|
|
3093
|
+
* fails is a fault of this server (a full disk, a scratchpad that is not writable), the
|
|
3094
|
+
* same class as a Session that cannot load, and it reaches the user through
|
|
3095
|
+
* recordDeliveryFailure and the binding's status line. The chat is told about the things
|
|
3096
|
+
* the person in it can act on.
|
|
3097
|
+
*/
|
|
3098
|
+
private writeInboundFiles;
|
|
3099
|
+
/**
|
|
3100
|
+
* Downloads the message's files into composer attachments, or the notice to answer with
|
|
3101
|
+
* when one of them could not be delivered.
|
|
3102
|
+
*
|
|
3103
|
+
* The caps are the server's attachment limits — the very numbers an authenticated
|
|
3104
|
+
* composer upload answers to — because these files land in exactly the same place, the
|
|
3105
|
+
* Session scratchpad, and are read back the same way, through the model's bounded file
|
|
3106
|
+
* tools. Both byte budgets apply:
|
|
3107
|
+
*
|
|
3108
|
+
* - the per-file ceiling rides into each `fetch`, so an oversized attachment is refused at
|
|
3109
|
+
* the byte that crosses it and is never resident in this process;
|
|
3110
|
+
* - the per-message total is spent as the batch is collected, each later file's cap
|
|
3111
|
+
* shrinking to what is left, so a message carrying several cannot add up past it either.
|
|
3112
|
+
*
|
|
3113
|
+
* The per-message file COUNT is deliberately not enforced: attachment-limits.ts calls it a
|
|
3114
|
+
* composer-usability bound rather than a resource one, the byte budgets being what actually
|
|
3115
|
+
* bound memory and disk — and a chat message carries one file today on both channels.
|
|
3116
|
+
*
|
|
3117
|
+
* There is no rolling per-binding budget like the image one, because the cost it exists to
|
|
3118
|
+
* prevent is not paid here: an inline image is written into the Trace and re-read whole on
|
|
3119
|
+
* every history page and every resume, while a file is bytes on disk that nothing re-reads
|
|
3120
|
+
* and that are deleted with the Session.
|
|
3121
|
+
*
|
|
3122
|
+
* A refusal for size is the user's to fix and is not an error record — they sent something
|
|
3123
|
+
* too big and the chat says so. A failure is somebody's fault (a scope the bot was never
|
|
3124
|
+
* granted, most likely) and lands in the error log with the channel's reason, so it is
|
|
3125
|
+
* diagnosable from the dashboard and not only from a chat bubble.
|
|
3126
|
+
*/
|
|
3127
|
+
private inboundFileAttachments;
|
|
3128
|
+
/**
|
|
3129
|
+
* Downloads the message's images into `image_url` input parts, or the notice to answer
|
|
3130
|
+
* with when one of them could not be delivered.
|
|
3131
|
+
*
|
|
3132
|
+
* The cap is the server's inline-image ceiling — the same number the web composer's
|
|
3133
|
+
* uploads answer to, because these images land in exactly the same place (the
|
|
3134
|
+
* conversation, and from there the Trace, which is read back whole on every resume). It
|
|
3135
|
+
* happens to match Telegram's own 20MB download ceiling for bots.
|
|
3136
|
+
*
|
|
3137
|
+
* A refusal for size is the user's to fix and is not an error record — they sent
|
|
3138
|
+
* something too big and the chat says so. A failure is somebody's fault (a scope the bot
|
|
3139
|
+
* was never granted, most likely) and lands in the error log with the channel's reason,
|
|
3140
|
+
* so it is diagnosable from the dashboard and not only from a chat bubble.
|
|
3141
|
+
*/
|
|
3142
|
+
private inboundImageParts;
|
|
3143
|
+
/** This binding's rolling image budget, created on first use (see InboundImageBudget). */
|
|
3144
|
+
private imageBudgetFor;
|
|
3145
|
+
/** Reply to the inbound message itself: threaded reply in groups, plain send in direct chats. */
|
|
3146
|
+
private replyInbound;
|
|
3147
|
+
/** Channel tap: each main-conversation completed assistant message + the run-state flips. */
|
|
3148
|
+
private observe;
|
|
3149
|
+
/**
|
|
3150
|
+
* Queues one completed assistant message for the bound chat — or holds it back.
|
|
3151
|
+
*
|
|
3152
|
+
* Appended to the entry's send chain rather than started here: a run can complete several
|
|
3153
|
+
* messages within milliseconds, and two sends racing would reach the chat in the wrong
|
|
3154
|
+
* order. Returns immediately either way, so a slow channel never blocks the Session's
|
|
3155
|
+
* stream handling.
|
|
3156
|
+
*
|
|
3157
|
+
* A binding with `finalReplyOnly` set sends nothing from here: the message replaces
|
|
3158
|
+
* whatever this run was already holding, and the last one standing is delivered at the
|
|
3159
|
+
* run's idle edge (see onTaskState). The preference is read per message off the stored row,
|
|
3160
|
+
* as every delivery preference is, and no run ever sees it change: each of the three PUT
|
|
3161
|
+
* handlers calls `sync` on an enabled binding, `sync` reconnects, and `connect` opens with a
|
|
3162
|
+
* disconnect and a fresh entry — so a save made mid-run discards whatever was held at that
|
|
3163
|
+
* moment along with the rest of this entry's memory (see heldReply).
|
|
3164
|
+
*/
|
|
3165
|
+
private relay;
|
|
3166
|
+
/**
|
|
3167
|
+
* Whether this binding holds a run's intermediate messages back. A row that cannot be read
|
|
3168
|
+
* at all is deliberately not decided here: relaying is the default, and the same read
|
|
3169
|
+
* inside deliverReply then records the failure rather than letting it pass as a preference.
|
|
3170
|
+
*/
|
|
3171
|
+
private finalReplyOnly;
|
|
3172
|
+
/**
|
|
3173
|
+
* Where this entry's traffic would go right now: null before any chat is known, and null
|
|
3174
|
+
* when the row cannot be read at all — a caller holding null falls back to reading it again
|
|
3175
|
+
* at delivery time, which is where a failure to read is recorded rather than swallowed.
|
|
3176
|
+
*/
|
|
3177
|
+
private replyTargetOf;
|
|
3178
|
+
private onTaskState;
|
|
3179
|
+
/**
|
|
3180
|
+
* Sends one relayed assistant message to the bound chat, in chunks under the channel's cap.
|
|
3181
|
+
* Which messages arrive here is the caller's business — every completed message of the run,
|
|
3182
|
+
* or its last one alone when the binding holds the rest (`finalReplyOnly`) — and nothing
|
|
3183
|
+
* below is affected by the difference.
|
|
3184
|
+
*
|
|
3185
|
+
* `at` is where to put it. A message relayed as it completes passes none and goes to the
|
|
3186
|
+
* chat the row names now, which is where the conversation is. A held reply passes the target
|
|
3187
|
+
* captured when its run started (see onTaskState), because it leaves at the run's END: a
|
|
3188
|
+
* second user writing in another forum topic meanwhile would otherwise take delivery of a
|
|
3189
|
+
* whole answer to a question they never asked, quoted onto their own message.
|
|
3190
|
+
*
|
|
3191
|
+
* A send failure is recorded, never thrown, so the chain behind it keeps
|
|
3192
|
+
* moving — and so do the messages behind it in THIS reply: one refused message (a 429 on
|
|
3193
|
+
* the third line of twelve) must not abandon the rest, which would leave a reply stopping
|
|
3194
|
+
* mid-sentence with nothing in the chat to say why. A binding with `linePerMessage` set
|
|
3195
|
+
* sends one message per non-blank line instead of one per reply (see splitReplyLines,
|
|
3196
|
+
* capped at the channel's own `replyBudget` where it declares one), paced
|
|
3197
|
+
* MESSAGING_LINE_DELAY_MS apart so the burst stays inside the channel's per-chat
|
|
3198
|
+
* allowance — chunking, threading and ordering are identical either way, and with the flag
|
|
3199
|
+
* off the send sequence is unchanged. In a group the run's
|
|
3200
|
+
* first outbound chunk threads onto the inbound message and
|
|
3201
|
+
* everything after it is a plain send: the reply-to relation names which message is being
|
|
3202
|
+
* answered, and one of them says it — repeating it per message and per chunk stacks quote
|
|
3203
|
+
* headers over the whole conversation.
|
|
3204
|
+
*
|
|
3205
|
+
* A binding with `renderMarkdown` set relays the model's Markdown as formatting rather
|
|
3206
|
+
* than as characters. The bridge only carries the REQUEST: the conversion is the
|
|
3207
|
+
* connector's, because what a channel can show and what it does with a rendering the
|
|
3208
|
+
* channel refuses are both channel-shaped (see MessagingSendOptions). What is not
|
|
3209
|
+
* channel-shaped is where a reply too long for one message may be cut, and that is why the
|
|
3210
|
+
* chunker changes with the flag — a cut through a code fence or an emphasis run costs the
|
|
3211
|
+
* construct everywhere, and on a strict parser costs the whole message's formatting.
|
|
3212
|
+
*/
|
|
3213
|
+
private deliverReply;
|
|
3214
|
+
/**
|
|
3215
|
+
* Sends the files THIS RUN produced and its reply mentioned, after its text.
|
|
3216
|
+
*
|
|
3217
|
+
* `replyText` is what the run actually SENT, not everything it said: a binding with
|
|
3218
|
+
* `finalReplyOnly` set delivered one message, so that message is the whole of what this
|
|
3219
|
+
* scans (see onTaskState). A file named only in a held-back working note has nothing in
|
|
3220
|
+
* the chat to explain its arrival.
|
|
3221
|
+
*
|
|
3222
|
+
* `at` is where those words went, for the one caller whose text is not addressed to the live
|
|
3223
|
+
* chat (again the held reply). Files follow their words: a picture landing in a forum topic
|
|
3224
|
+
* where nothing named it is the same defect as one arriving unmentioned.
|
|
3225
|
+
*
|
|
3226
|
+
* Two filters, and both are load-bearing. The reply having said the name is what picks
|
|
3227
|
+
* the one output that was the point out of the dozen a run writes. The file being newer
|
|
3228
|
+
* than the run's start is what keeps that from becoming an exfiltration primitive: the
|
|
3229
|
+
* reply is steerable by whoever is in the chat, and on a mention-only rule a group member
|
|
3230
|
+
* who asks "what is in secrets.json?" gets the file uploaded by the very refusal that
|
|
3231
|
+
* declined to paste it. Containment (the Workspace and nothing narrower) lives in the
|
|
3232
|
+
* file service either way — see MessagingWorkspaceFiles.
|
|
3233
|
+
*
|
|
3234
|
+
* A file the run did not write is dropped SILENTLY, and so is a name matching no file at
|
|
3235
|
+
* all: the rule reads prose, so a reply that mentions the config it read — or describes a
|
|
3236
|
+
* `hello-world.md` it never wrote — is the ordinary case, and announcing those puts an
|
|
3237
|
+
* error-shaped line under correct answers. Both are logged instead. What IS recorded is a
|
|
3238
|
+
* file that exists and still did not go out — over a ceiling, past the count cap, or
|
|
3239
|
+
* refused on the way: the reply promised something that then did not arrive, so it is filed
|
|
3240
|
+
* under the Session's Project, once the batch is done, in one record per cause naming every
|
|
3241
|
+
* file it held back (see messagingFilesNotSentRecords). None of it is posted into the chat,
|
|
3242
|
+
* which carries the reply and its files and nothing about the ones that stayed behind.
|
|
3243
|
+
*
|
|
3244
|
+
* Always plain sends, never a threaded reply: a run that mentions a file has by
|
|
3245
|
+
* definition already sent the text that mentions it, and that message took the group's
|
|
3246
|
+
* one reply-to. Nothing here throws — a batch that fails is recorded, and a single file
|
|
3247
|
+
* that fails does not stop the ones behind it.
|
|
3248
|
+
*/
|
|
3249
|
+
private deliverFiles;
|
|
3250
|
+
/**
|
|
3251
|
+
* One mirrored file: read under the outer ceiling, then sent as a picture or an attachment.
|
|
3252
|
+
* Resolves null once the file went out. A file over the ceiling for its kind is not sent and
|
|
3253
|
+
* resolves to that ceiling instead; a read or a send the channel refused throws. The caller
|
|
3254
|
+
* records both (see messagingFilesNotSentRecords).
|
|
3255
|
+
*/
|
|
3256
|
+
private deliverOneFile;
|
|
3257
|
+
/** One-line notice (approval waiting) to the last known chat; silent before one exists. */
|
|
3258
|
+
private deliverNotice;
|
|
3259
|
+
/**
|
|
3260
|
+
* Where this entry's outbound traffic goes, with a client for it: null when no chat is
|
|
3261
|
+
* known yet (nothing mirrors before the first inbound message) and null when the binding
|
|
3262
|
+
* or its client could not be read at all, which is recorded like the send it stands in for.
|
|
3263
|
+
*/
|
|
3264
|
+
private sendTarget;
|
|
3265
|
+
/**
|
|
3266
|
+
* Whether this entry still holds its Session's connection — asked at the head of every
|
|
3267
|
+
* step of the send chain, the way `observe` asks it of every event.
|
|
3268
|
+
*
|
|
3269
|
+
* The chain outlives what fed it. A run's last messages and the files behind them are
|
|
3270
|
+
* queued on it, and `stop()` — App dispose, a hot swap — drops the entry and takes the
|
|
3271
|
+
* database with it while they are still waiting their turn. A step that ran anyway would
|
|
3272
|
+
* read a closed database, and it would fail again inside its own error recording, leaving
|
|
3273
|
+
* the chain rejected with nothing left to catch it.
|
|
3274
|
+
*/
|
|
3275
|
+
private live;
|
|
3276
|
+
/** The pace between a per-line reply's messages; zero (tests) waits for nothing. */
|
|
3277
|
+
private pace;
|
|
3278
|
+
}
|
|
3279
|
+
declare const MessagingTaskRunner_base: (abstract new () => MessagingTaskRunnerShape) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
3280
|
+
/** What the bridge needs of the session runtime — declared at the consumer. */
|
|
3281
|
+
declare abstract class MessagingTaskRunner extends MessagingTaskRunner_base {
|
|
3282
|
+
}
|
|
3283
|
+
declare const Messaging_base: (abstract new () => Pick<MessagingBridge, "stop" | "sync" | "start" | "statusOf" | "unbind" | "unbindSession" | "testCredentials" | "sendTestMessage">) & (<C>(target: C, context: ClassDecoratorContext) => C);
|
|
3284
|
+
/** The chat-channel bridge: bindings, delivery, status. */
|
|
3285
|
+
declare abstract class Messaging extends Messaging_base {
|
|
3286
|
+
}
|
|
3287
|
+
interface MessagingSlots {
|
|
3288
|
+
/** A channel connector: which channel it speaks (static), and the connector (code). */
|
|
3289
|
+
connectors: Slot<{
|
|
3290
|
+
channel: "feishu" | "telegram" | "qq" | "wechat";
|
|
3291
|
+
}, MessagingChannelConnector>;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
/** A settings group as a module declares it: a configuration, and where the page draws it. */
|
|
3295
|
+
interface SettingsGroupDecl extends PluginConfiguration {
|
|
3296
|
+
/** Another group's name (its contribution id): this one is drawn inside that card and saved with it. */
|
|
3297
|
+
parent?: string;
|
|
3298
|
+
/** Position among the groups: lower first (absent = 100), then declaration order. */
|
|
3299
|
+
order?: number;
|
|
3300
|
+
}
|
|
3301
|
+
/** What a module reads: the group it declared, and a watch on it. */
|
|
3302
|
+
declare abstract class PluginConfig {
|
|
3303
|
+
/** The stored values merged onto the declared defaults; `{}` for a name no group answers to. */
|
|
3304
|
+
abstract get(name: string): Record<string, unknown>;
|
|
3305
|
+
/** Fires with the new document after every save of `name`; returns the unsubscribe. */
|
|
3306
|
+
abstract watch(name: string, cb: (values: Record<string, unknown>) => void): () => void;
|
|
3307
|
+
/** Whether anything was ever saved under `name` — what tells a default from a choice. */
|
|
3308
|
+
abstract saved(name: string): boolean;
|
|
3309
|
+
/** The configuration `name` was declared with (what `resolveTable` reads a table by); undefined for none. */
|
|
3310
|
+
abstract schema(name: string): PluginConfiguration | undefined;
|
|
3311
|
+
}
|
|
3312
|
+
interface PluginConfigSlots {
|
|
3313
|
+
/** A settings group, as data: its id is its name, the data its configuration. */
|
|
3314
|
+
groups: Slot<SettingsGroupDecl>;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
/** The assembled business surface: one request in, one response (or a decline) out. */
|
|
3318
|
+
declare abstract class Http {
|
|
3319
|
+
abstract fetch(request: Opaque<"Request", Request>): Promise<Opaque<"Response", Response>>;
|
|
3320
|
+
}
|
|
3321
|
+
interface HttpSlots {
|
|
3322
|
+
/**
|
|
3323
|
+
* A route group. `auth: "user"` mounts it behind the cookie gate, `"none"` in front of
|
|
3324
|
+
* it; `order` is the mount position (a stable number, since Hono matches in order). The
|
|
3325
|
+
* code half is the group's Hono app.
|
|
3326
|
+
*/
|
|
3327
|
+
routes: Slot<{
|
|
3328
|
+
prefix: string;
|
|
3329
|
+
auth: "user" | "none";
|
|
3330
|
+
order: number;
|
|
3331
|
+
}, Opaque<"Hono", Hono<AppEnv>>>;
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
/**
|
|
3335
|
+
* GET /api/contributions — the web slots' contributions, as data. What a pushed platform
|
|
3336
|
+
* or an installed plugin adds to the web app arrives here; the app merges it with its
|
|
3337
|
+
* own module.json and renders what it has a renderer for.
|
|
3338
|
+
*/
|
|
3339
|
+
|
|
3340
|
+
/**
|
|
3341
|
+
* The frontend's slots, declared on the SERVER so a server module (or a plugin) can
|
|
3342
|
+
* contribute a page, an Agent settings tab or a Session tab as manifest data. The web
|
|
3343
|
+
* app reads them back through GET /api/contributions and renders the ones whose
|
|
3344
|
+
* renderer it knows: a `builtin` name from its own registry, or an `iframe`. No code
|
|
3345
|
+
* crosses this boundary — only data.
|
|
3346
|
+
*/
|
|
3347
|
+
declare abstract class WebShell {
|
|
3348
|
+
/** Every contribution to the web slots, by slot, in module order. */
|
|
3349
|
+
abstract contributions(): ContributionsResponse;
|
|
3350
|
+
}
|
|
3351
|
+
interface WebShellSlots {
|
|
3352
|
+
/** A page: its route, whether it sits in the main nav, whether it is admin-only. */
|
|
3353
|
+
pages: {
|
|
3354
|
+
key: string;
|
|
3355
|
+
path: string;
|
|
3356
|
+
nav: "main" | "none";
|
|
3357
|
+
admin: boolean;
|
|
3358
|
+
renderer: RendererRef;
|
|
3359
|
+
};
|
|
3360
|
+
/** A tab on the Agent settings page. */
|
|
3361
|
+
agentTabs: {
|
|
3362
|
+
key: string;
|
|
3363
|
+
order: number;
|
|
3364
|
+
renderer: RendererRef;
|
|
3365
|
+
};
|
|
3366
|
+
/**
|
|
3367
|
+
* A tab beside a Session's chat. A workflow contributes here too, from its own tree, where
|
|
3368
|
+
* the host publishes this slot under this module's name and scopes the tab to its Agent.
|
|
3369
|
+
*/
|
|
3370
|
+
sessionTabs: {
|
|
3371
|
+
key: string;
|
|
3372
|
+
title: string;
|
|
3373
|
+
titleZh?: string;
|
|
3374
|
+
renderer: {
|
|
3375
|
+
builtin: string;
|
|
3376
|
+
} | {
|
|
3377
|
+
iframe: {
|
|
3378
|
+
src: string;
|
|
3379
|
+
};
|
|
3380
|
+
};
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
export { AgentConfigService, AgentService, Http, type HttpSlots, Messaging, type MessagingSlots, PluginConfig, type PluginConfigSlots, PluginConfiguration, Sandbox, type SandboxSlots, SessionServiceIface, Sessions, type SettingsGroupDecl, Terminals, WebShell, type WebShellSlots };
|