termdock 1.4.260 → 1.4.262
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/dist/client/assets/App-Cb_XllaU.js +156 -0
- package/dist/client/assets/App-Cb_XllaU.js.br +0 -0
- package/dist/client/assets/App-Cb_XllaU.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-C-WqvjtC.js → ChangeWalkthroughPanel-jX6d1ENn.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-jX6d1ENn.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-jX6d1ENn.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-BZDpEJGy.js → DagPlayground-TGM2t4Ye.js} +1 -1
- package/dist/client/assets/DagPlayground-TGM2t4Ye.js.br +0 -0
- package/dist/client/assets/DagPlayground-TGM2t4Ye.js.gz +0 -0
- package/dist/client/assets/{DiffLab-C3-p91uh.js → DiffLab-Cw8cAI4p.js} +2 -2
- package/dist/client/assets/DiffLab-Cw8cAI4p.js.br +0 -0
- package/dist/client/assets/DiffLab-Cw8cAI4p.js.gz +0 -0
- package/dist/client/assets/{DiffReview-CWhKgYZN.js → DiffReview-BpxhFZLY.js} +1 -1
- package/dist/client/assets/DiffReview-BpxhFZLY.js.br +0 -0
- package/dist/client/assets/DiffReview-BpxhFZLY.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-DVwuJrVG.js → DiffReviewLab-CV_QO6UR.js} +1 -1
- package/dist/client/assets/DiffReviewLab-CV_QO6UR.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CV_QO6UR.js.gz +0 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js +36 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js.br +0 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-CB63ulOp.js → ModelPreview-CUlJCDwX.js} +1 -1
- package/dist/client/assets/ModelPreview-CUlJCDwX.js.br +0 -0
- package/dist/client/assets/ModelPreview-CUlJCDwX.js.gz +0 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js +1678 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js.br +0 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DXqSSIIQ.js → abnfDiagram-VCTEODGH-BOd7FTr2.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BOd7FTr2.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BOd7FTr2.js.gz +0 -0
- package/dist/client/assets/app-Bl11JYak.js +35 -0
- package/dist/client/assets/app-Bl11JYak.js.br +0 -0
- package/dist/client/assets/app-Bl11JYak.js.gz +0 -0
- package/dist/client/assets/{arc-DbWPugQ5.js → arc-BPAf1nZK.js} +1 -1
- package/dist/client/assets/arc-BPAf1nZK.js.br +0 -0
- package/dist/client/assets/arc-BPAf1nZK.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-DMFOu5nX.js → architectureDiagram-5GKGNRK7-fQvzZnB9.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-fQvzZnB9.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-fQvzZnB9.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-CKLkxDEj.js → blockDiagram-I7D4REHJ-BwLMUrdi.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BwLMUrdi.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BwLMUrdi.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-C78zNGxr.js → c4Diagram-7LVT6UL2-EoyhDhQx.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-EoyhDhQx.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-EoyhDhQx.js.gz +0 -0
- package/dist/client/assets/channel-BUQABAmH.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-uCAhVp1i.js → chunk-2Q5K7J3B-BV2VpCfF.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-T4xbmOTq.js → chunk-5VM5RSS4-BFiQW7Ta.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-ClPpIDx4.js → chunk-F27PBJKO-DqlURJFH.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DqlURJFH.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DqlURJFH.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-Bx2Jj2by.js → chunk-IMKFNOWR-BcM_EtAP.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BcM_EtAP.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BcM_EtAP.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-CIVmiUX8.js → chunk-JWPE2WC7-B8NvVdil.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-DU6qR259.js → chunk-POPQ4Y6H-B5Vz70dF.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-zxsu_mRX.js → chunk-SVP7TREG-CVv3_3sI.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-CVv3_3sI.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CVv3_3sI.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DfhkM5vB.js → chunk-TICWLB2K-Wpnx00aD.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Wpnx00aD.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Wpnx00aD.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-mPuTW6Qu.js → chunk-XXDRQBXY-aBQvQ4Ji.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-ZOMwbuDI.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-ZOMwbuDI.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-nFW6grC2.js → cose-bilkent-JH36ORCC-DWySu0Hm.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DWySu0Hm.js.br +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-nFW6grC2.js.gz → cose-bilkent-JH36ORCC-DWySu0Hm.js.gz} +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-WWNymBE7.js → cynefin-OW5HDTMX-BEUAB_wN.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-BEUAB_wN.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BEUAB_wN.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Cnu1881_.js → cynefinDiagram-5FMLGOSQ-CCfHefWz.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CCfHefWz.js.br +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Cnu1881_.js.gz → cynefinDiagram-5FMLGOSQ-CCfHefWz.js.gz} +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-ZnFSJ_PQ.js → dagre-GXQ25YYZ-CRPX2oo7.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CRPX2oo7.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CRPX2oo7.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-NDwa39k2.js → diagram-S7CK7UJ4-Pg--K2pK.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-Pg--K2pK.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-Pg--K2pK.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-GvOo_Md9.js → diagram-UQ7AKVKN-CzU1IUDD.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-CzU1IUDD.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CzU1IUDD.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-D0tpjVaU.js → diagram-VSXAHHWV-D3ns7ZFW.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-D3ns7ZFW.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-D3ns7ZFW.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CE8gl6ZC.js → diagram-VX7I27RA-0PzViCov.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-0PzViCov.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-0PzViCov.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-Dn9svAcp.js → diagram-Z3DM3KII-Cm6dBe6-.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-Cm6dBe6-.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Cm6dBe6-.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BeIVOITU.js → ebnfDiagram-PWID7BFC-BxSW6eBA.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BxSW6eBA.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BxSW6eBA.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-CYb2hJnW.js → erDiagram-RLTQ6QDP-DpWE-TjX.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-CYb2hJnW.js.br → erDiagram-RLTQ6QDP-DpWE-TjX.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DpWE-TjX.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BrCWf4Jw.js → flowDiagram-HODETNUW-DYO0LUmp.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DYO0LUmp.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DYO0LUmp.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CaEV35iE.js → ganttDiagram-EL5Y4UJY-CIq6FHXs.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CIq6FHXs.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CIq6FHXs.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-CqdDGsl4.js → gestureArbiter-DPglmaJZ.js} +1 -1
- package/dist/client/assets/gestureArbiter-DPglmaJZ.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DPglmaJZ.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BSW6F8uE.js → gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js.gz +0 -0
- package/dist/client/assets/{index-mZxreyvP.css → index-ddQQwU7M.css} +1 -1
- package/dist/client/assets/index-ddQQwU7M.css.br +0 -0
- package/dist/client/assets/index-ddQQwU7M.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DC9P1VSK.js → infoDiagram-27XIBGKW-DCWx_lXp.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CC5aFdsA.js → ishikawaDiagram-5VMMS53U-Dri_MMmE.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Dri_MMmE.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Dri_MMmE.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-2Aoz11wC.js → journeyDiagram-3NMN7TZE-BIBa8ZPU.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIBa8ZPU.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIBa8ZPU.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-D6YFB_Z0.js → kanban-definition-UXKFOSKX-CVvCyGTR.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVvCyGTR.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVvCyGTR.js.gz +0 -0
- package/dist/client/assets/{layout-Dd8_VbyA.js → layout-CePKkZZM.js} +1 -1
- package/dist/client/assets/layout-CePKkZZM.js.br +0 -0
- package/dist/client/assets/layout-CePKkZZM.js.gz +0 -0
- package/dist/client/assets/{linear-CJMS1SaH.js → linear-C2Jk6X94.js} +1 -1
- package/dist/client/assets/linear-C2Jk6X94.js.br +0 -0
- package/dist/client/assets/linear-C2Jk6X94.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-D2G7U75v.js → mermaid.core-B0TLkbaQ.js} +5 -5
- package/dist/client/assets/mermaid.core-B0TLkbaQ.js.br +0 -0
- package/dist/client/assets/mermaid.core-B0TLkbaQ.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-BMKObGIP.js → mindmap-definition-YA3MSWOX-BiqaDyAI.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BiqaDyAI.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BiqaDyAI.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-Bq6gHj2O.js → passwordBootstrap-D8QzMobL.js} +2 -2
- package/dist/client/assets/passwordBootstrap-D8QzMobL.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-D8QzMobL.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DqLntL3k.js → pegDiagram-XKGWAZYB-mBmk-WA4.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-mBmk-WA4.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-mBmk-WA4.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-C-vUASJL.js → pieDiagram-E7YTZNPT-CNLgv2X-.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CNLgv2X-.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CNLgv2X-.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BD5TrRYq.js → quadrantDiagram-AXDQQJYC-B6R_6sAT.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B6R_6sAT.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B6R_6sAT.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BwYnnmcE.js → railroadDiagram-O6MQD6OU-CMHZvyFR.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CMHZvyFR.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CMHZvyFR.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-DC0noWIU.js → referenceLongPress-BclZaogy.js} +1 -1
- package/dist/client/assets/referenceLongPress-BclZaogy.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BclZaogy.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-B0rZBE2S.js → requirementDiagram-BXWQKSXE-Chcr9JIV.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Chcr9JIV.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Chcr9JIV.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-8xihEeEz.js → sankeyDiagram-P5KCCOFB-DgOdSJOh.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DgOdSJOh.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DgOdSJOh.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-C7nrszfj.js → sequenceDiagram-WJ2MYXX4-CLgh_PNU.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLgh_PNU.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLgh_PNU.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-Cpbpxkib.js → sizeCapture-INFHLROL-C7Ai3zIt.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-C7Ai3zIt.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-C7Ai3zIt.js.gz +0 -0
- package/dist/client/assets/{sparkles-C6EB6xDX.js → sparkles-DikPE3OY.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-LLshgiUk.js → stateDiagram-D77RDMKH-BjzRCePZ.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-BjzRCePZ.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BjzRCePZ.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-C9yeiSIs.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-D5kogY3u.js → swimlanes-42K2YHIH-DfyuNxsB.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DfyuNxsB.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DfyuNxsB.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BFAodsZz.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-F4vFRXIm.js → timeline-definition-24CTP7MA-BvSf8YQs.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-BvSf8YQs.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BvSf8YQs.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-HOIUoGzB.js → triangle-alert-nWNzdjS5.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-C1W4mA7H.js → vennDiagram-4TSXK5OY-BPs48uzW.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-C1W4mA7H.js.br → vennDiagram-4TSXK5OY-BPs48uzW.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BPs48uzW.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-Bj-fiIIR.js → wardleyDiagram-VM6X3IG4-DSa8jkGg.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DSa8jkGg.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DSa8jkGg.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BBUrV_aE.js → xychartDiagram-S5SC5T6Z-C4Gb4X25.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C4Gb4X25.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C4Gb4X25.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/cli.js +336 -99
- package/dist/server/entry.js +11 -0
- package/dist/server/routes/terminal.js +45 -0
- package/dist/server/supervisor.js +525 -0
- package/dist/server/utils/backoff.js +15 -0
- package/dist/server/utils/crashForensics.js +227 -0
- package/dist/server/utils/healthProbe.js +92 -0
- package/dist/server/utils/ptySpawnBackoff.js +2 -1
- package/dist/server/utils/serverHealth.js +126 -0
- package/dist/server/utils/serverLogger.js +27 -4
- package/dist/server/utils/supervisorClient.js +151 -0
- package/dist/server/utils/supervisorProtocol.js +159 -0
- package/dist/server/utils/termdockState.js +62 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CZk_DziZ.js +0 -156
- package/dist/client/assets/App-CZk_DziZ.js.br +0 -0
- package/dist/client/assets/App-CZk_DziZ.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-C-WqvjtC.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-C-WqvjtC.js.gz +0 -0
- package/dist/client/assets/DagPlayground-BZDpEJGy.js.br +0 -0
- package/dist/client/assets/DagPlayground-BZDpEJGy.js.gz +0 -0
- package/dist/client/assets/DiffLab-C3-p91uh.js.br +0 -0
- package/dist/client/assets/DiffLab-C3-p91uh.js.gz +0 -0
- package/dist/client/assets/DiffReview-CWhKgYZN.js.br +0 -0
- package/dist/client/assets/DiffReview-CWhKgYZN.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DVwuJrVG.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DVwuJrVG.js.gz +0 -0
- package/dist/client/assets/DiffViewer-CVrIRwTf.js +0 -35
- package/dist/client/assets/DiffViewer-CVrIRwTf.js.br +0 -0
- package/dist/client/assets/DiffViewer-CVrIRwTf.js.gz +0 -0
- package/dist/client/assets/ModelPreview-CB63ulOp.js.br +0 -0
- package/dist/client/assets/ModelPreview-CB63ulOp.js.gz +0 -0
- package/dist/client/assets/RightSidebar-B9JmuyiA.js +0 -1678
- package/dist/client/assets/RightSidebar-B9JmuyiA.js.br +0 -0
- package/dist/client/assets/RightSidebar-B9JmuyiA.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DXqSSIIQ.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DXqSSIIQ.js.gz +0 -0
- package/dist/client/assets/app-B32WWSws.js +0 -35
- package/dist/client/assets/app-B32WWSws.js.br +0 -0
- package/dist/client/assets/app-B32WWSws.js.gz +0 -0
- package/dist/client/assets/arc-DbWPugQ5.js.br +0 -0
- package/dist/client/assets/arc-DbWPugQ5.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DMFOu5nX.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DMFOu5nX.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CKLkxDEj.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CKLkxDEj.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C78zNGxr.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C78zNGxr.js.gz +0 -0
- package/dist/client/assets/channel-Dv1JMWuX.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-ClPpIDx4.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-ClPpIDx4.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Bx2Jj2by.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Bx2Jj2by.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-zxsu_mRX.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-zxsu_mRX.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DfhkM5vB.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DfhkM5vB.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BlTnvDlc.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BlTnvDlc.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-nFW6grC2.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-WWNymBE7.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-WWNymBE7.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Cnu1881_.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-ZnFSJ_PQ.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-ZnFSJ_PQ.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-NDwa39k2.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-NDwa39k2.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-GvOo_Md9.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-GvOo_Md9.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-D0tpjVaU.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-D0tpjVaU.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CE8gl6ZC.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CE8gl6ZC.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Dn9svAcp.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Dn9svAcp.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BeIVOITU.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BeIVOITU.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CYb2hJnW.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BrCWf4Jw.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BrCWf4Jw.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CaEV35iE.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CaEV35iE.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-CqdDGsl4.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CqdDGsl4.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BSW6F8uE.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BSW6F8uE.js.gz +0 -0
- package/dist/client/assets/index-mZxreyvP.css.br +0 -0
- package/dist/client/assets/index-mZxreyvP.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CC5aFdsA.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CC5aFdsA.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-2Aoz11wC.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-2Aoz11wC.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-D6YFB_Z0.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-D6YFB_Z0.js.gz +0 -0
- package/dist/client/assets/layout-Dd8_VbyA.js.br +0 -0
- package/dist/client/assets/layout-Dd8_VbyA.js.gz +0 -0
- package/dist/client/assets/linear-CJMS1SaH.js.br +0 -0
- package/dist/client/assets/linear-CJMS1SaH.js.gz +0 -0
- package/dist/client/assets/mermaid.core-D2G7U75v.js.br +0 -0
- package/dist/client/assets/mermaid.core-D2G7U75v.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BMKObGIP.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BMKObGIP.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Bq6gHj2O.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Bq6gHj2O.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DqLntL3k.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DqLntL3k.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C-vUASJL.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C-vUASJL.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BD5TrRYq.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BD5TrRYq.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwYnnmcE.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwYnnmcE.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-DC0noWIU.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DC0noWIU.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B0rZBE2S.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B0rZBE2S.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-8xihEeEz.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-8xihEeEz.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C7nrszfj.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C7nrszfj.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Cpbpxkib.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Cpbpxkib.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-LLshgiUk.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-LLshgiUk.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-rk1xljvU.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-D5kogY3u.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-D5kogY3u.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DsAFJkqm.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-F4vFRXIm.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-F4vFRXIm.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C1W4mA7H.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Bj-fiIIR.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Bj-fiIIR.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BBUrV_aE.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BBUrV_aE.js.gz +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程内崩溃取证:进程在"运行中挂了"时,至少留下一份能说清原因的记录。
|
|
3
|
+
*
|
|
4
|
+
* 现状是反过来的——全仓没有 uncaughtException/unhandledRejection 处理,
|
|
5
|
+
* 而 `~/.termdock/server.log` 连时间戳都没有;挂掉之后没人知道为什么。
|
|
6
|
+
*
|
|
7
|
+
* 职责边界:这里只负责**记录**,不负责重启(那是 supervisor 的事,见 supervisor.ts)。
|
|
8
|
+
* 两者故意解耦:supervisor 覆盖不了"进程还在但内部已错乱",进程内处理器覆盖不了
|
|
9
|
+
* SIGKILL/OOM——两边各自记录,缺一边都不完整。
|
|
10
|
+
*
|
|
11
|
+
* 所有写盘都走 `writeJsonLogSync`:崩溃路径上事件循环已经停了,
|
|
12
|
+
* 排队中的异步写永远不会落盘。
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import { getTermdockLogPath, writeJsonLogSync } from './serverLogger.js';
|
|
16
|
+
import { RUNTIME_METRICS_PATH, TERMDOCK_DIR } from './termdockState.js';
|
|
17
|
+
import { getTermdockVersion } from './version.js';
|
|
18
|
+
const CRASH_LOG_NAME = 'crash.log';
|
|
19
|
+
const LOG_TAIL_MAX_LINES = 40;
|
|
20
|
+
const LOG_TAIL_MAX_BYTES = 64 * 1024;
|
|
21
|
+
const CRASH_LOG_TAIL_LINES = 60;
|
|
22
|
+
const CRASH_LOG_TAIL_BYTES = 64 * 1024;
|
|
23
|
+
const RUNTIME_TAIL_MAX_SAMPLES = 3;
|
|
24
|
+
const RUNTIME_TAIL_MAX_BYTES = 32 * 1024;
|
|
25
|
+
const RUNTIME_TAIL_SCAN_LINES = 200;
|
|
26
|
+
/**
|
|
27
|
+
* RSS 逼近这个值就标注 oomSuspect。与 runtimeMonitor 的 PROCESS_RSS_HIGH 临界阈值一致。
|
|
28
|
+
* 这只是**启发式**:OOM killer 杀进程发的是普通 SIGKILL,和任意一次 kill -9 在信号上
|
|
29
|
+
* 无法区分。它给出的是一份"当时内存有多高"的旁证,不是结论。
|
|
30
|
+
*/
|
|
31
|
+
export const OOM_SUSPECT_RSS_BYTES = 1024 ** 3;
|
|
32
|
+
/** 从文件尾部倒着读,最多 maxBytes,再按行切。避免为了 40 行读整个 2 MiB 日志。 */
|
|
33
|
+
function readTailLines(filePath, maxLines, maxBytes) {
|
|
34
|
+
let fd = null;
|
|
35
|
+
try {
|
|
36
|
+
const stat = fs.statSync(filePath, { throwIfNoEntry: false });
|
|
37
|
+
if (!stat?.isFile() || stat.size === 0)
|
|
38
|
+
return [];
|
|
39
|
+
const readBytes = Math.min(stat.size, maxBytes);
|
|
40
|
+
fd = fs.openSync(filePath, 'r');
|
|
41
|
+
const buffer = Buffer.allocUnsafe(readBytes);
|
|
42
|
+
fs.readSync(fd, buffer, 0, readBytes, stat.size - readBytes);
|
|
43
|
+
const text = buffer.toString('utf8');
|
|
44
|
+
const lines = text.split('\n');
|
|
45
|
+
// 起点若不是文件开头,首行大概率是被切断的半行,丢掉。
|
|
46
|
+
if (readBytes < stat.size)
|
|
47
|
+
lines.shift();
|
|
48
|
+
return lines.filter((line) => line.length > 0).slice(-maxLines);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
if (fd !== null) {
|
|
55
|
+
try {
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
catch { /* 已在错误路径上 */ }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* crash.log 尾部的记录(已解析,从旧到新)。解析不了的行直接跳过——
|
|
64
|
+
* 崩溃现场写到一半被打断是常态,不能因为一行坏数据就让整份证据作废。
|
|
65
|
+
*/
|
|
66
|
+
export function readCrashLogTail(maxLines = CRASH_LOG_TAIL_LINES, crashLogPath = getTermdockLogPath(CRASH_LOG_NAME)) {
|
|
67
|
+
return readTailLines(crashLogPath, maxLines, CRASH_LOG_TAIL_BYTES).flatMap((line) => {
|
|
68
|
+
try {
|
|
69
|
+
const entry = JSON.parse(line);
|
|
70
|
+
return entry && typeof entry === 'object' ? [entry] : [];
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** server.log 尾部若干行,给人看的那一面(内容原样,不做解析)。 */
|
|
78
|
+
export function readServerLogTail(maxLines = LOG_TAIL_MAX_LINES, logDirectory = TERMDOCK_DIR) {
|
|
79
|
+
return readTailLines(getTermdockLogPath('server.log', logDirectory), maxLines, LOG_TAIL_MAX_BYTES);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* runtime-metrics.log 尾部若干条运行指标。样本里**没有 pid**,所以只能按时间戳过滤,
|
|
83
|
+
* 并用 uptimeSeconds 交叉印证是不是同一世代。
|
|
84
|
+
*/
|
|
85
|
+
export function readRuntimeTail(sinceTimestamp, maxSamples = RUNTIME_TAIL_MAX_SAMPLES, metricsPath = RUNTIME_METRICS_PATH) {
|
|
86
|
+
const lines = readTailLines(metricsPath, RUNTIME_TAIL_SCAN_LINES, RUNTIME_TAIL_MAX_BYTES);
|
|
87
|
+
return lines.flatMap((line) => {
|
|
88
|
+
try {
|
|
89
|
+
const sample = JSON.parse(line);
|
|
90
|
+
if (!Number.isFinite(sample.timestamp))
|
|
91
|
+
return [];
|
|
92
|
+
if (sinceTimestamp !== undefined && sample.timestamp < sinceTimestamp)
|
|
93
|
+
return [];
|
|
94
|
+
return [{
|
|
95
|
+
timestamp: sample.timestamp,
|
|
96
|
+
uptimeSeconds: sample.uptimeSeconds ?? 0,
|
|
97
|
+
rssBytes: sample.rssBytes ?? 0,
|
|
98
|
+
eventLoopDelayP99Ms: sample.eventLoopDelayP99Ms ?? 0,
|
|
99
|
+
activeConnections: sample.activeConnections ?? 0,
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}).slice(-maxSamples);
|
|
106
|
+
}
|
|
107
|
+
export function isOomSuspect(samples) {
|
|
108
|
+
const last = samples.at(-1);
|
|
109
|
+
return Boolean(last && last.rssBytes >= OOM_SUSPECT_RSS_BYTES);
|
|
110
|
+
}
|
|
111
|
+
function describeError(error) {
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
return { name: error.name, message: error.message, stack: error.stack };
|
|
114
|
+
}
|
|
115
|
+
if (error === undefined)
|
|
116
|
+
return null;
|
|
117
|
+
return { name: 'NonError', message: String(error) };
|
|
118
|
+
}
|
|
119
|
+
/** 组装一条完整现场:进程身份 + 退出信息 + 错误 + server.log 尾部 + 运行指标尾部。 */
|
|
120
|
+
export function collectCrashEvidence(options) {
|
|
121
|
+
const runtimeTail = readRuntimeTail(options.startedAt, undefined, options.runtimeMetricsPath);
|
|
122
|
+
return {
|
|
123
|
+
role: options.role,
|
|
124
|
+
event: options.event,
|
|
125
|
+
version: options.version ?? null,
|
|
126
|
+
uptimeMs: options.startedAt !== undefined ? Date.now() - options.startedAt : null,
|
|
127
|
+
exitCode: options.exitCode ?? null,
|
|
128
|
+
signal: options.signal ?? null,
|
|
129
|
+
oomSuspect: isOomSuspect(runtimeTail),
|
|
130
|
+
error: describeError(options.error),
|
|
131
|
+
...(options.extra ?? {}),
|
|
132
|
+
runtimeTail,
|
|
133
|
+
logTail: readServerLogTail(undefined, options.logDirectory),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function defaultRecord(entry) {
|
|
137
|
+
writeJsonLogSync(CRASH_LOG_NAME, entry);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 安装全局崩溃取证。返回的 `recordIncident` 供调用方记录已知的异常事件。
|
|
141
|
+
*
|
|
142
|
+
* 注意 `unhandledRejection`:装上监听器就等于**接管了 Node 默认的 crash-on-unhandled-rejection**。
|
|
143
|
+
* 对一台会话服务器,这是有意的政策——一个没人 await 的 promise 不该干掉整台服务;
|
|
144
|
+
* 真卡死了还有 supervisor 的健康探测兜底。代价是坏不变量可能带病存续,
|
|
145
|
+
* 所以这里记录得足够响(crash.log + stderr),任何一条都不该被忽略。
|
|
146
|
+
*/
|
|
147
|
+
export function installCrashForensics(options) {
|
|
148
|
+
const record = options.record ?? defaultRecord;
|
|
149
|
+
const log = options.log ?? ((message, error) => console.error(message, error ?? ''));
|
|
150
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
151
|
+
// 崩溃处理器自己抛异常会再次进入处理器,这是经典死循环;守卫必须是闭包级的。
|
|
152
|
+
let recording = false;
|
|
153
|
+
// uncaughtException 已经记过一条完整的,退出处理器不要再记一条重复的。
|
|
154
|
+
let fatalRecorded = false;
|
|
155
|
+
const recordIncident = (event, extra = {}) => {
|
|
156
|
+
if (recording)
|
|
157
|
+
return;
|
|
158
|
+
recording = true;
|
|
159
|
+
try {
|
|
160
|
+
const { extra: nested, ...fields } = extra;
|
|
161
|
+
record(collectCrashEvidence({
|
|
162
|
+
role: options.role,
|
|
163
|
+
event,
|
|
164
|
+
startedAt: options.startedAt,
|
|
165
|
+
version: options.version,
|
|
166
|
+
...fields,
|
|
167
|
+
extra: { ...(options.context?.() ?? {}), ...(nested ?? {}) },
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// 取证失败绝不能变成第二个故障。
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
recording = false;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const handlers = {
|
|
178
|
+
onUncaughtException: (error) => {
|
|
179
|
+
fatalRecorded = true;
|
|
180
|
+
recordIncident('uncaught-exception', { error });
|
|
181
|
+
log('[termdock] uncaught exception:', error);
|
|
182
|
+
exit(1);
|
|
183
|
+
},
|
|
184
|
+
onUnhandledRejection: (reason) => {
|
|
185
|
+
recordIncident('unhandled-rejection', { error: reason });
|
|
186
|
+
log('[termdock] unhandled rejection (service keeps running):', reason);
|
|
187
|
+
},
|
|
188
|
+
onExit: (code) => {
|
|
189
|
+
if (code === 0 || fatalRecorded)
|
|
190
|
+
return;
|
|
191
|
+
// 这里事件循环已经停了:只能同步写。
|
|
192
|
+
recordIncident('exit-nonzero', { exitCode: code });
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const installed = options.install !== false;
|
|
196
|
+
if (installed) {
|
|
197
|
+
process.on('uncaughtException', handlers.onUncaughtException);
|
|
198
|
+
process.on('unhandledRejection', handlers.onUnhandledRejection);
|
|
199
|
+
process.on('exit', handlers.onExit);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
recordIncident,
|
|
203
|
+
handlers,
|
|
204
|
+
dispose: () => {
|
|
205
|
+
if (!installed)
|
|
206
|
+
return;
|
|
207
|
+
process.off('uncaughtException', handlers.onUncaughtException);
|
|
208
|
+
process.off('unhandledRejection', handlers.onUnhandledRejection);
|
|
209
|
+
process.off('exit', handlers.onExit);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* supervisor 断链时由服务侧记录。**不自杀**:服务本身是健康的,
|
|
215
|
+
* 丢监督好过丢服务。界面红点靠这条记录来提示"现在没人看着它了"。
|
|
216
|
+
*/
|
|
217
|
+
export function recordSupervisorLost(detail) {
|
|
218
|
+
writeJsonLogSync(CRASH_LOG_NAME, {
|
|
219
|
+
role: 'server',
|
|
220
|
+
event: 'supervisor-lost',
|
|
221
|
+
detail,
|
|
222
|
+
version: getTermdockVersion(),
|
|
223
|
+
serverPid: process.pid,
|
|
224
|
+
uptimeMs: Math.round(process.uptime() * 1_000),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
export { CRASH_LOG_NAME };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 服务就绪/存活探测。CLI 首次拉起服务、supervisor 长期盯守都走这里,
|
|
3
|
+
* 避免"启动时怎么判活着"和"运行时怎么判活着"长出两套标准。
|
|
4
|
+
*
|
|
5
|
+
* 从 cli.ts 原样搬出(waitForHealth / DAEMON_START_TIMEOUT_MS),额外拆出
|
|
6
|
+
* 单次探测 probeHealthOnce() 供 supervisor 的卡死判定复用——原来的实现是
|
|
7
|
+
* 写死在循环里的,无法单独调用。
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import http from 'http';
|
|
11
|
+
import https from 'https';
|
|
12
|
+
/** 等 daemon 子进程响应 /health 的上限。 */
|
|
13
|
+
export const DAEMON_START_TIMEOUT_MS = 10_000;
|
|
14
|
+
/** 单次 /health 请求的 socket 超时。 */
|
|
15
|
+
export const HEALTH_PROBE_TIMEOUT_MS = 2_000;
|
|
16
|
+
/**
|
|
17
|
+
* 单次探测。注意它同时是"存活"信号:事件循环若卡死,请求根本不会被应答,
|
|
18
|
+
* 于是以 timeout 收场——这正是 supervisor 判 wedge 需要的语义。
|
|
19
|
+
* 自签 HTTPS 通过 caPath 放行;没给 caPath 就不做证书校验(与原实现一致)。
|
|
20
|
+
*/
|
|
21
|
+
export async function probeHealthOnce(healthUrl, caPath, timeoutMs = HEALTH_PROBE_TIMEOUT_MS) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
let settled = false;
|
|
24
|
+
const finish = (result) => {
|
|
25
|
+
if (settled)
|
|
26
|
+
return;
|
|
27
|
+
settled = true;
|
|
28
|
+
resolve(result);
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const transport = healthUrl.startsWith('https:') ? https : http;
|
|
32
|
+
const req = transport.get(healthUrl, {
|
|
33
|
+
ca: caPath ? fs.readFileSync(caPath) : undefined,
|
|
34
|
+
rejectUnauthorized: Boolean(caPath),
|
|
35
|
+
timeout: timeoutMs,
|
|
36
|
+
}, (res) => {
|
|
37
|
+
let data = '';
|
|
38
|
+
res.setEncoding('utf8');
|
|
39
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
40
|
+
res.on('end', () => {
|
|
41
|
+
const status = res.statusCode ?? 0;
|
|
42
|
+
if (status < 200 || status >= 300) {
|
|
43
|
+
finish({ ok: false, failure: 'bad-status', detail: `HTTP ${status}` });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!data.includes('"status":"ok"')) {
|
|
47
|
+
finish({ ok: false, failure: 'bad-body', detail: data.slice(0, 200) });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
finish({ ok: true });
|
|
51
|
+
});
|
|
52
|
+
res.on('error', (error) => {
|
|
53
|
+
finish({ ok: false, failure: 'unreachable', detail: error.message });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
req.on('error', (error) => {
|
|
57
|
+
finish({ ok: false, failure: 'unreachable', detail: error.message });
|
|
58
|
+
});
|
|
59
|
+
req.on('timeout', () => {
|
|
60
|
+
req.destroy();
|
|
61
|
+
finish({ ok: false, failure: 'timeout' });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
finish({
|
|
66
|
+
ok: false,
|
|
67
|
+
failure: 'unreachable',
|
|
68
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 轮询直到就绪、超时或进程死亡。语义与搬迁前完全一致:
|
|
75
|
+
* 先等 isReady() 翻真,再要求 /health 应答且 isAlive() 仍成立。
|
|
76
|
+
*/
|
|
77
|
+
export async function waitForHealth(healthUrl, caPath, timeoutMs, isReady, isAlive) {
|
|
78
|
+
const deadline = Date.now() + timeoutMs;
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
if (!isAlive())
|
|
81
|
+
return false;
|
|
82
|
+
if (!isReady()) {
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const probe = await probeHealthOnce(healthUrl, caPath);
|
|
87
|
+
if (probe.ok && isAlive())
|
|
88
|
+
return true;
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { backoffDelayMs } from './backoff.js';
|
|
1
2
|
/** Shared across WebSockets and sessions: PTYs/FDs are process-wide resources. */
|
|
2
3
|
export class PtySpawnDeferredError extends Error {
|
|
3
4
|
retryAfterMs;
|
|
@@ -28,7 +29,7 @@ export class PtySpawnBackoff {
|
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
31
|
this.failures = Math.min(this.failures + 1, 6);
|
|
31
|
-
this.retryAt = this.now() +
|
|
32
|
+
this.retryAt = this.now() + backoffDelayMs(this.failures);
|
|
32
33
|
throw error; // Preserve errno and the original diagnostic in the real failure log.
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「服务最近出过什么事」——给界面红点与设置面板用的只读快照。
|
|
3
|
+
*
|
|
4
|
+
* 数据源是 **crash.log**,不是 supervisor.json:crash.log 由两个角色一起写
|
|
5
|
+
* (supervisor 记子进程怎么死的,服务自己记 uncaughtException / supervisor-lost),
|
|
6
|
+
* 因此在**没有监督者**的部署里同样有内容。supervisor.json 只补两样它才知道的事:
|
|
7
|
+
* 谁在盯着、重启了几次、当前处于哪个阶段。
|
|
8
|
+
*
|
|
9
|
+
* 这个模块只读不写(dismiss 除外),且绝不抛异常:它服务于"出事了怎么告诉用户",
|
|
10
|
+
* 自己再炸一次就太难看了。
|
|
11
|
+
*/
|
|
12
|
+
import { INCIDENT_DISMISSED_PATH, readJsonFile, writeJsonStateAtomic, } from './termdockState.js';
|
|
13
|
+
import { readCrashLogTail } from './crashForensics.js';
|
|
14
|
+
import { getSupervisorStatus } from './supervisorClient.js';
|
|
15
|
+
/**
|
|
16
|
+
* 值得用户知道的事件。刻意排除 `stop`(是用户自己停的)、
|
|
17
|
+
* `update-restart` / `manual-restart`(我们自己发起的,成功了就没什么可说)。
|
|
18
|
+
*/
|
|
19
|
+
const NOTABLE_EVENTS = new Set([
|
|
20
|
+
'startup-failure',
|
|
21
|
+
'crash',
|
|
22
|
+
'crash-native',
|
|
23
|
+
'killed',
|
|
24
|
+
'wedge',
|
|
25
|
+
'port-conflict',
|
|
26
|
+
'gave-up',
|
|
27
|
+
'exit-zero-unexpected',
|
|
28
|
+
'terminated-externally',
|
|
29
|
+
'supervisor-lost',
|
|
30
|
+
'uncaught-exception',
|
|
31
|
+
'unhandled-rejection',
|
|
32
|
+
'exit-nonzero',
|
|
33
|
+
]);
|
|
34
|
+
function pickString(value) {
|
|
35
|
+
return typeof value === 'string' ? value : null;
|
|
36
|
+
}
|
|
37
|
+
function pickNumber(value) {
|
|
38
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
39
|
+
}
|
|
40
|
+
/** 把一条 crash.log 记录投影成界面要的形状。缺字段一律降级为 null,不猜。 */
|
|
41
|
+
export function toIncident(entry) {
|
|
42
|
+
const event = pickString(entry.event);
|
|
43
|
+
if (!event)
|
|
44
|
+
return null;
|
|
45
|
+
const parsed = pickString(entry.ts) ? Date.parse(entry.ts) : Number.NaN;
|
|
46
|
+
return {
|
|
47
|
+
at: Number.isFinite(parsed) ? parsed : 0,
|
|
48
|
+
event,
|
|
49
|
+
detail: pickString(entry.detail) ?? '',
|
|
50
|
+
role: pickString(entry.role) ?? 'unknown',
|
|
51
|
+
exitCode: pickNumber(entry.exitCode),
|
|
52
|
+
signal: pickString(entry.signal),
|
|
53
|
+
uptimeMs: pickNumber(entry.uptimeMs),
|
|
54
|
+
version: pickString(entry.version),
|
|
55
|
+
oomSuspect: entry.oomSuspect === true,
|
|
56
|
+
restartCount: pickNumber(entry.restartCount),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** 最新一条值得注意的事件(没有就返回 null)。 */
|
|
60
|
+
export function findLatestNotableIncident(entries) {
|
|
61
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
62
|
+
const incident = toIncident(entries[index]);
|
|
63
|
+
if (incident && NOTABLE_EVENTS.has(incident.event))
|
|
64
|
+
return incident;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function readDismissal(path) {
|
|
69
|
+
const record = readJsonFile(path);
|
|
70
|
+
return typeof record?.dismissedAt === 'number' ? record.dismissedAt : null;
|
|
71
|
+
}
|
|
72
|
+
function projectSupervisor(state, alive) {
|
|
73
|
+
if (!state)
|
|
74
|
+
return null;
|
|
75
|
+
return {
|
|
76
|
+
pid: state.pid,
|
|
77
|
+
alive,
|
|
78
|
+
phase: state.phase,
|
|
79
|
+
restarts: state.restarts,
|
|
80
|
+
consecutiveCrashes: state.consecutiveCrashes,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function buildServerHealthState(options = {}) {
|
|
84
|
+
const now = options.now ?? (() => Date.now());
|
|
85
|
+
const crashLogPath = options.crashLogPath;
|
|
86
|
+
const dismissedPath = options.dismissedPath ?? INCIDENT_DISMISSED_PATH;
|
|
87
|
+
let incident = null;
|
|
88
|
+
try {
|
|
89
|
+
incident = findLatestNotableIncident(readCrashLogTail(undefined, crashLogPath));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// 取证读取失败不该让界面也挂掉:没有消息就是没有消息。
|
|
93
|
+
}
|
|
94
|
+
let supervisor = null;
|
|
95
|
+
try {
|
|
96
|
+
const status = getSupervisorStatus(options.supervisorStatePath);
|
|
97
|
+
supervisor = projectSupervisor(status?.state ?? null, status?.alive ?? false);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
supervisor = null;
|
|
101
|
+
}
|
|
102
|
+
const dismissedAt = readDismissal(dismissedPath);
|
|
103
|
+
return {
|
|
104
|
+
supervised: process.env.TERMDOCK_SUPERVISED === '1',
|
|
105
|
+
supervisor,
|
|
106
|
+
incident,
|
|
107
|
+
dismissedAt,
|
|
108
|
+
attention: Boolean(incident && incident.at > (dismissedAt ?? 0)),
|
|
109
|
+
generatedAt: now(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** 「知道了」:把当前这条记成已被确认,红点随之消失。 */
|
|
113
|
+
export function dismissServerHealth(options = {}) {
|
|
114
|
+
const before = buildServerHealthState(options);
|
|
115
|
+
if (before.incident) {
|
|
116
|
+
try {
|
|
117
|
+
writeJsonStateAtomic(options.dismissedPath ?? INCIDENT_DISMISSED_PATH, {
|
|
118
|
+
dismissedAt: before.incident.at,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// 写不进去就等于没确认,红点会再出现——比谎报"已读"好。
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return buildServerHealthState(options);
|
|
126
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
|
|
3
|
+
import { TERMDOCK_DIR } from './termdockState.js';
|
|
5
4
|
const MIB = 1024 * 1024;
|
|
6
5
|
const DEFAULT_MAX_BYTES = 4 * MIB;
|
|
7
6
|
const SERVER_LOG_MAX_BYTES = 2 * MIB;
|
|
@@ -10,6 +9,7 @@ const LOG_MAINTENANCE_INTERVAL_MS = 10 * 60 * 1000;
|
|
|
10
9
|
const MANAGED_LOG_NAMES = [
|
|
11
10
|
'access.log',
|
|
12
11
|
'client.log',
|
|
12
|
+
'crash.log',
|
|
13
13
|
'diff-trace.log',
|
|
14
14
|
'errors.log',
|
|
15
15
|
'fs-io.log',
|
|
@@ -138,8 +138,8 @@ function enqueueWrite(filePath, line, maxBytes) {
|
|
|
138
138
|
.catch(() => undefined);
|
|
139
139
|
writeChains.set(filePath, next);
|
|
140
140
|
}
|
|
141
|
-
export function getTermdockLogPath(name) {
|
|
142
|
-
return path.join(
|
|
141
|
+
export function getTermdockLogPath(name, directory = TERMDOCK_DIR) {
|
|
142
|
+
return path.join(directory, name);
|
|
143
143
|
}
|
|
144
144
|
export function writeJsonLog(name, entry, maxBytes = DEFAULT_MAX_BYTES) {
|
|
145
145
|
const filePath = getTermdockLogPath(name);
|
|
@@ -150,6 +150,29 @@ export function writeJsonLog(name, entry, maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
150
150
|
}) + '\n';
|
|
151
151
|
enqueueWrite(filePath, line, maxBytes);
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* 同步写一条 JSONL。**只给崩溃取证用**,不要拿它写常规日志:它绕过写队列与轮转,
|
|
155
|
+
* 会阻塞事件循环。
|
|
156
|
+
*
|
|
157
|
+
* 存在的理由:`writeJsonLog` 走 `fs.promises.appendFile`,而 `process.on('exit')`
|
|
158
|
+
* 里事件循环已经停了,排队的异步写永远不会落盘——崩溃现场会静默消失。
|
|
159
|
+
* 因此这里既不能 await 也不能 rotate,只能直接 append。
|
|
160
|
+
*/
|
|
161
|
+
export function writeJsonLogSync(name, entry) {
|
|
162
|
+
try {
|
|
163
|
+
const filePath = getTermdockLogPath(name);
|
|
164
|
+
const line = JSON.stringify({
|
|
165
|
+
ts: new Date().toISOString(),
|
|
166
|
+
pid: process.pid,
|
|
167
|
+
...entry,
|
|
168
|
+
}) + '\n';
|
|
169
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
170
|
+
fs.appendFileSync(filePath, line, { encoding: 'utf8', mode: 0o600 });
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// 已经在崩溃/退出路径上,写不进去也不能再抛一次。
|
|
174
|
+
}
|
|
175
|
+
}
|
|
153
176
|
export function writeTextLog(name, line, maxBytes = DEFAULT_MAX_BYTES) {
|
|
154
177
|
const filePath = getTermdockLogPath(name);
|
|
155
178
|
enqueueWrite(filePath, `${line.replace(/\n$/, '')}\n`, maxBytes);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* launcher(`td` / `td --status` / `td --stop`)看向 supervisor 的那一侧。
|
|
3
|
+
*
|
|
4
|
+
* 与 supervisor.ts 分开是为了单向依赖:supervisor 是独立入口,不能反过来
|
|
5
|
+
* 依赖 CLI 的进程;CLI 只是它的客户端。
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { isProcessRunning, readJsonFile, SUPERVISOR_STATE_PATH } from './termdockState.js';
|
|
11
|
+
import { isSupervisorState } from './supervisorProtocol.js';
|
|
12
|
+
import { STOP_GRACE_MS } from './termdockState.js';
|
|
13
|
+
/** supervisor.js 与 cli.js 同目录,所以从本模块(dist/server/utils/)回退一级。 */
|
|
14
|
+
export function resolveSupervisorScript() {
|
|
15
|
+
return fileURLToPath(new URL('../supervisor.js', import.meta.url));
|
|
16
|
+
}
|
|
17
|
+
export function readSupervisorState() {
|
|
18
|
+
const state = readJsonFile(SUPERVISOR_STATE_PATH);
|
|
19
|
+
return isSupervisorState(state) ? state : null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 读出 supervisor 状态,并单独给出"它现在还活着吗"。
|
|
23
|
+
*
|
|
24
|
+
* **进程死了也不删状态文件**:`gave-up` 正是它死的原因,那份记录就是结论本身;
|
|
25
|
+
* 别的 phase 下进程没了则意味着"supervisor 自己崩了,服务现在无人监督"——
|
|
26
|
+
* 两件事都必须能被告知用户。陈旧的判断交给调用方(它才知道自己在问什么),
|
|
27
|
+
* 真正的清理留给 `td --stop`。
|
|
28
|
+
*/
|
|
29
|
+
export function getSupervisorStatus(statePath = SUPERVISOR_STATE_PATH) {
|
|
30
|
+
const raw = readJsonFile(statePath);
|
|
31
|
+
if (!isSupervisorState(raw))
|
|
32
|
+
return null;
|
|
33
|
+
return { state: raw, alive: isProcessRunning(raw.pid) };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 起一个 detached 的 supervisor,并等它把子进程的结局告诉我们。
|
|
37
|
+
*
|
|
38
|
+
* 与旧实现的关键差别:这里**会等结果**。旧 `node -e` bridge 是发射后不管,
|
|
39
|
+
* 新进程起没起来只有用户下次访问才知道。
|
|
40
|
+
*/
|
|
41
|
+
export function spawnSupervisor(options) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const args = [
|
|
44
|
+
resolveSupervisorScript(),
|
|
45
|
+
'--health-url', options.healthUrl,
|
|
46
|
+
'--child', options.childEntry,
|
|
47
|
+
];
|
|
48
|
+
if (options.healthCaPath)
|
|
49
|
+
args.push('--health-ca', options.healthCaPath);
|
|
50
|
+
if (options.version)
|
|
51
|
+
args.push('--version', options.version);
|
|
52
|
+
args.push('--', ...options.childArgs);
|
|
53
|
+
let child;
|
|
54
|
+
try {
|
|
55
|
+
child = spawn(process.execPath, args, {
|
|
56
|
+
detached: true,
|
|
57
|
+
stdio: ['ignore', options.logFileFd, options.logFileFd, 'ipc'],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
resolve({ ok: false, reason: 'spawn-failed', detail: error.message });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let settled = false;
|
|
65
|
+
let spawnError = null;
|
|
66
|
+
// 拿到结局就断开 IPC 并 unref:supervisor 继续跑,launcher 可以退出。
|
|
67
|
+
const finish = (result) => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
if (child.connected)
|
|
73
|
+
child.disconnect();
|
|
74
|
+
child.unref();
|
|
75
|
+
resolve(result);
|
|
76
|
+
};
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
finish({ ok: false, reason: 'timeout', detail: `no ready within ${options.readyTimeoutMs}ms` });
|
|
79
|
+
}, options.readyTimeoutMs);
|
|
80
|
+
child.on('error', (error) => {
|
|
81
|
+
spawnError = error;
|
|
82
|
+
finish({ ok: false, reason: 'spawn-failed', detail: error.message });
|
|
83
|
+
});
|
|
84
|
+
child.on('message', (message) => {
|
|
85
|
+
if (!message || typeof message !== 'object' || !('type' in message))
|
|
86
|
+
return;
|
|
87
|
+
const typed = message;
|
|
88
|
+
if (typed.type === 'termdock-ready') {
|
|
89
|
+
finish({ ok: true, pid: child.pid });
|
|
90
|
+
}
|
|
91
|
+
else if (typed.type === 'termdock-startup-failed') {
|
|
92
|
+
finish({ ok: false, reason: typed.reason, detail: typed.detail });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
child.on('exit', (code, signal) => {
|
|
96
|
+
finish({
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: 'supervisor-exited',
|
|
99
|
+
detail: spawnError?.message ?? `exited (code ${code ?? 'unknown'}, signal ${signal ?? 'none'})`,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 停 supervisor:它是唯一该被停的东西,子进程由它负责。
|
|
106
|
+
*
|
|
107
|
+
* 先发 SIGTERM 让它自己收拾(杀子进程、删状态、退出 0);宽限期到了还在,
|
|
108
|
+
* 才 SIGKILL。**不**在这里直接杀子进程——那会让它变成"意外死亡"从而被重启。
|
|
109
|
+
*/
|
|
110
|
+
export async function stopSupervisor(pid, graceMs = STOP_GRACE_MS) {
|
|
111
|
+
if (!isProcessRunning(pid)) {
|
|
112
|
+
try {
|
|
113
|
+
fs.rmSync(SUPERVISOR_STATE_PATH, { force: true });
|
|
114
|
+
}
|
|
115
|
+
catch { /* nothing to clean */ }
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, 'SIGTERM');
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const deadline = Date.now() + graceMs;
|
|
125
|
+
while (isProcessRunning(pid) && Date.now() < deadline) {
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
127
|
+
}
|
|
128
|
+
if (isProcessRunning(pid)) {
|
|
129
|
+
try {
|
|
130
|
+
process.kill(pid, 'SIGKILL');
|
|
131
|
+
}
|
|
132
|
+
catch { /* already gone */ }
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
fs.rmSync(SUPERVISOR_STATE_PATH, { force: true });
|
|
137
|
+
}
|
|
138
|
+
catch { /* nothing to clean */ }
|
|
139
|
+
return !isProcessRunning(pid);
|
|
140
|
+
}
|
|
141
|
+
/** `td --restart`:让 supervisor 重启子进程,但继续监督。 */
|
|
142
|
+
export function signalSupervisorRestart(pid) {
|
|
143
|
+
try {
|
|
144
|
+
process.kill(pid, 'SIGHUP');
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export { SUPERVISOR_STATE_PATH };
|