termdock 1.4.43 → 1.4.45
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/{abnfDiagram-VRR7QNED-B3X7vTL7.js → abnfDiagram-VRR7QNED-Bt36T7gE.js} +1 -1
- package/dist/client/assets/{arc-OmYbmu75.js → arc-DhZtXG4q.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-CNNIlew_.js → architectureDiagram-ZJ3FMSHR-C4BLot_l.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-eoDvsGst.js → blockDiagram-677ZJIJ3-BG8NrPiD.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-BYElS7nn.js → c4Diagram-LMCZKHZV-m3lWYsQI.js} +1 -1
- package/dist/client/assets/channel-Ck9tD_4_.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-DgBhC1KN.js → chunk-2Q5K7J3B-BiBc5Uv0.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-B4LjEHPP.js → chunk-32BRIVSS-C1g4c11U.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BrBK8Xy3.js → chunk-5VM5RSS4-Ctw1x2FG.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-QndtZFQ6.js → chunk-EX3LRPZG-BdI4v3f4.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-B8bZHm4g.js → chunk-JWPE2WC7-K8nPht_5.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-B5Kzsd5Z.js → chunk-MOJQB5TN-DWsVsFoK.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-BdNa9gwg.js → chunk-RYQCIY6F-0MHmqGNs.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-DpQA4l-k.js → chunk-V7JOEXUC-BjQQBzKq.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-CtGdy3K8.js → chunk-VR4S4FIN-Bau1q0xN.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CZBBj4P_.js → chunk-XXDRQBXY-BIzI5y55.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CtEpUyds.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CtEpUyds.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-gtzwCMyn.js → cose-bilkent-JH36ORCC-C_6nPODG.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-B7X5gKj-.js → cynefin-VYW2F7L2-DZfIXs6F.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-BoKfaePi.js → cynefinDiagram-TSTJHNR4-Cwtffn_E.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-Bjh8y-RO.js → dagre-VKFMJZFB-n2L4piqF.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-Cx8vWSw0.js → diagram-FQU43EPY-DYc1Xsf9.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-DyKuyGsn.js → diagram-G47NLZAW-Bty2-g6M.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-DwnG0pD9.js → diagram-NH7WQ7WH-B7NwHUrU.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-wn7Eux1Q.js → diagram-OA4YK3LP-BB8nlvCx.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-DNy0FovQ.js → diagram-WEI45ONY-2hoKf8a6.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-TFnZlH4k.js → ebnfDiagram-CCIWWBDH-BARVSv3m.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-Cx3_8T9Q.js → erDiagram-Q63AITRT-5jgm5Ksi.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CZ7KiBe4.js → flowDiagram-23GEKE2U-BqCA33p5.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-BRTRytgo.js → ganttDiagram-NO4QXBWP-BL1NGUYm.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-DyNUwP6-.js → gitGraphDiagram-IHSO6WYX-D9Qw6ov4.js} +1 -1
- package/dist/client/assets/index-BYpFR_L5.js +436 -0
- package/dist/client/assets/index-DyTRm1PJ.css +1 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-ClLiuIL8.js → infoDiagram-FWYZ7A6U-ZrovoSsZ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CbO_ecRd.js → ishikawaDiagram-FXEZZL3T-uFSMTzKn.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-YKjq3svJ.js → journeyDiagram-5HDEW3XC-DNXNzmef.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CcnV3jvx.js → kanban-definition-HUTT4EX6-Db2NpkWT.js} +1 -1
- package/dist/client/assets/{linear-3N9evV-e.js → linear-DjF6fII5.js} +1 -1
- package/dist/client/assets/{mermaid.core-C8HeTU54.js → mermaid.core-BXI9ENYY.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-CgkCCxMO.js → mindmap-definition-LN4V7U3C-CjcF3QgK.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-CQRK4LBF.js → pegDiagram-2B236MQR-BckzTuEj.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-BHrDXKeR.js → pieDiagram-ENE6RG2P-Ch2Ty0Y2.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-CuJ9UyOO.js → quadrantDiagram-ABIIQ3AL-DvXeQj4a.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D0lUBYT1.js → railroadDiagram-RFXS5EU6-BUurE4S_.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-s5kYB_Ya.js → requirementDiagram-TGXJPOKE-CfnNu5HR.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-Bhw1mAOH.js → sankeyDiagram-HTMAVEWB-BfpeK6jA.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-qnNrmb6g.js → sequenceDiagram-DBY2YBRQ-CmkcMlBD.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-DufN5SxK.js → sizeCapture-X5ZJPWSS-BvnnE9Ev.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-Y5rMnVoS.js → stateDiagram-2N3HPSRC-D-gs0yW-.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-gXYMxyju.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-Cmrmdfpk.js → swimlanes-5IMT3BWC-BW65nQch.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-DmhG9XuP.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-CqHH1NSC.js → timeline-definition-FHXFAJF6-Mj3zCwTc.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-C2l_9LuC.js → vennDiagram-L72KCM5P-gXnoVb66.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-CNf23qHc.js → wardleyDiagram-EHGQE667-Cgbh1_F7.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-RIp6RoVE.js → xychartDiagram-FW5EYKEG-DQW7oknN.js} +1 -1
- package/dist/client/icons/agents/kimi.svg +5 -0
- package/dist/client/index.html +3 -3
- package/dist/client/sw-notifications.js +163 -36
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/installers.js +145 -1
- package/dist/server/agent/registry.js +3 -0
- package/dist/server/cli.js +25 -0
- package/dist/server/entry.js +22 -1
- package/dist/server/notifications/pushService.js +121 -35
- package/dist/server/notifications/pushViewers.js +44 -0
- package/dist/server/ptyHost.js +17 -0
- package/dist/server/ptyhost/manager.js +152 -5
- package/dist/server/quota/tokenDiscovery.js +25 -0
- package/dist/server/routes/filesystem.js +4 -2
- package/dist/server/routes/notifications.js +9 -3
- package/dist/server/routes/terminal.js +82 -106
- package/dist/server/utils/contextDraft.js +36 -0
- package/dist/server/utils/requestDeadline.js +104 -0
- package/dist/server/utils/settings.js +18 -9
- package/package.json +1 -1
- package/dist/client/assets/channel-DBZQbHSi.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BsrNzS08.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BsrNzS08.js +0 -1
- package/dist/client/assets/index-CbYQuue6.css +0 -1
- package/dist/client/assets/index-DB-0aiWT.js +0 -433
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BSqYeylv.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CUwNpVXZ.js +0 -8
package/dist/client/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let s,e={};const i=(i,l)=>(i=new URL(i+".js",l).href,e[i]||new Promise(e=>{if("document"in self){const s=document.createElement("script");s.src=i,s.onload=e,document.head.appendChild(s)}else s=i,importScripts(i),e()}).then(()=>{let s=e[i];if(!s)throw new Error(`Module ${i} didn’t register its module`);return s}));self.define=(l,n)=>{const r=s||("document"in self?document.currentScript.src:"")||location.href;if(e[r])return;let a={};const o=s=>i(s,r),u={module:{uri:r},exports:a,require:o};e[r]=Promise.all(l.map(s=>u[s]||o(s))).then(s=>(n(...s),a))}}define(["./workbox-9c191d2f"],function(s){"use strict";importScripts("sw-notifications.js"),self.skipWaiting(),s.clientsClaim(),s.precacheAndRoute([{url:"sw-notifications.js",revision:"2ed386d64dc020bf9a7da8efd9dbea42"},{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"pwa-64x64.png",revision:"c1ca9f6daa67743b38564b230314277a"},{url:"pwa-512x512.svg",revision:"149a151133f52feb34ab6ecedf349936"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"pwa-192x192.svg",revision:"541ace289ca5d6f3d269d1286cde5283"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"index.html",revision:"75a3a2ea7df85d2ed6ccce0deb75df09"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"icons/agents/opencode.svg",revision:"851c2931d95821ba2b39b68587b48976"},{url:"icons/agents/grok.svg",revision:"cd745b0b964545bc7a6b4f36021c3298"},{url:"icons/agents/goose.svg",revision:"c79b927278983c19098d77b4b0c11ab3"},{url:"icons/agents/gemini.svg",revision:"7a64f6d6f80a908255e9c0256903882e"},{url:"icons/agents/droid.svg",revision:"f28723b8c5c115e0908ba3c72613e256"},{url:"icons/agents/cursor.svg",revision:"a1528f3d3cda497f75a5db28defb19de"},{url:"icons/agents/copilot.svg",revision:"dd52e96c1eac0b6ec4bedcd0ddd3b52d"},{url:"icons/agents/codex.svg",revision:"608e55babb0b217e1f2da287342b46d3"},{url:"icons/agents/claude.svg",revision:"08af0225b7acdd8e8f573f3247e5e1c3"},{url:"icons/agents/amp.svg",revision:"af12f8b33faa81e4d46ea7b93c7e43f3"},{url:"fonts/TermdockStarSymbol-Subset.woff2",revision:"853a8d84112dd97a91d6cff1e7ab6bdf"},{url:"fonts/SymbolsNerdFontMono-Regular.woff2",revision:"54113ea8487e607f9d9a482c5877aed4"},{url:"fonts/NotoSansSymbols2-Subset.woff2",revision:"8257a6beabcc8858199b208908a96ee9"},{url:"fonts/NotoSansMonoCJKsc-Subset.woff2",revision:"e52357967aa973c04cce881c50cce0c8"},{url:"fonts/NotoSansMonoCJKsc-Bold-Subset.woff2",revision:"7e37d6b9fb1339898c00ac3b49c8b12e"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Regular.ttf",revision:"ed6fa47e3c1fc9aa68e9015a5300419f"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Italic.ttf",revision:"5624a73ef0a476e93ef5949dd6e452e1"},{url:"fonts/JetBrainsMonoNLNerdFontMono-BoldItalic.ttf",revision:"9b7603423a37fd2f544f75e9669689a0"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Bold.ttf",revision:"7deed80e7a4195a51f2c0b82aa364e97"},{url:"fonts/JetBrainsMonoNL-Regular.woff2",revision:"3bb268888f64810d496dd10295d4337c"},{url:"fonts/JetBrainsMonoNL-Italic.woff2",revision:"e035d3c7673175237d40687fc14d5b40"},{url:"fonts/JetBrainsMonoNL-BoldItalic.woff2",revision:"bc3577cc820eaef00d9a985058bca2b7"},{url:"fonts/JetBrainsMonoNL-Bold.woff2",revision:"7c0305a4db8f2977b061631755556ec0"},{url:"assets/xychartDiagram-FW5EYKEG-RIp6RoVE.js",revision:null},{url:"assets/wardleyDiagram-EHGQE667-CNf23qHc.js",revision:null},{url:"assets/vennDiagram-L72KCM5P-C2l_9LuC.js",revision:null},{url:"assets/timeline-definition-FHXFAJF6-CqHH1NSC.js",revision:null},{url:"assets/terminal-C7X98Amf.js",revision:null},{url:"assets/swimlanesDiagram-G3AALYLV-CUwNpVXZ.js",revision:null},{url:"assets/swimlanes-5IMT3BWC-Cmrmdfpk.js",revision:null},{url:"assets/stateDiagram-v2-6OUMAXLB-BSqYeylv.js",revision:null},{url:"assets/stateDiagram-2N3HPSRC-Y5rMnVoS.js",revision:null},{url:"assets/sizeCapture-X5ZJPWSS-DufN5SxK.js",revision:null},{url:"assets/sequenceDiagram-DBY2YBRQ-qnNrmb6g.js",revision:null},{url:"assets/sankeyDiagram-HTMAVEWB-Bhw1mAOH.js",revision:null},{url:"assets/requirementDiagram-TGXJPOKE-s5kYB_Ya.js",revision:null},{url:"assets/react-XDCjal2T.js",revision:null},{url:"assets/railroadDiagram-RFXS5EU6-D0lUBYT1.js",revision:null},{url:"assets/quadrantDiagram-ABIIQ3AL-CuJ9UyOO.js",revision:null},{url:"assets/purify.es-CC4Brkr_.js",revision:null},{url:"assets/pieDiagram-ENE6RG2P-BHrDXKeR.js",revision:null},{url:"assets/pegDiagram-2B236MQR-CQRK4LBF.js",revision:null},{url:"assets/ordinal-Cboi1Yqb.js",revision:null},{url:"assets/mindmap-definition-LN4V7U3C-CgkCCxMO.js",revision:null},{url:"assets/mermaid.core-C8HeTU54.js",revision:null},{url:"assets/map-BdqsVdb3.js",revision:null},{url:"assets/linear-3N9evV-e.js",revision:null},{url:"assets/layout-1eBIAoSl.js",revision:null},{url:"assets/katex-HP8lGamR.js",revision:null},{url:"assets/katex-DnlPpQZa.js",revision:null},{url:"assets/katex-55ZPZeOF.css",revision:null},{url:"assets/kanban-definition-HUTT4EX6-CcnV3jvx.js",revision:null},{url:"assets/journeyDiagram-5HDEW3XC-YKjq3svJ.js",revision:null},{url:"assets/ishikawaDiagram-FXEZZL3T-CbO_ecRd.js",revision:null},{url:"assets/init-Gi6I4Gst.js",revision:null},{url:"assets/infoDiagram-FWYZ7A6U-ClLiuIL8.js",revision:null},{url:"assets/index-DB-0aiWT.js",revision:null},{url:"assets/index-CbYQuue6.css",revision:null},{url:"assets/index-Ca-rq7yX.js",revision:null},{url:"assets/graph-D2o_JWn5.js",revision:null},{url:"assets/gitGraphDiagram-IHSO6WYX-DyNUwP6-.js",revision:null},{url:"assets/ganttDiagram-NO4QXBWP-BRTRytgo.js",revision:null},{url:"assets/flowDiagram-23GEKE2U-CZ7KiBe4.js",revision:null},{url:"assets/erDiagram-Q63AITRT-Cx3_8T9Q.js",revision:null},{url:"assets/ebnfDiagram-CCIWWBDH-TFnZlH4k.js",revision:null},{url:"assets/dnd-BCFAPC0L.js",revision:null},{url:"assets/diffWorker-BFIcKxCt.js",revision:null},{url:"assets/diagram-WEI45ONY-DNy0FovQ.js",revision:null},{url:"assets/diagram-OA4YK3LP-wn7Eux1Q.js",revision:null},{url:"assets/diagram-NH7WQ7WH-DwnG0pD9.js",revision:null},{url:"assets/diagram-G47NLZAW-DyKuyGsn.js",revision:null},{url:"assets/diagram-FQU43EPY-Cx8vWSw0.js",revision:null},{url:"assets/defaultLocale-CrowFXzY.js",revision:null},{url:"assets/dagre-VKFMJZFB-Bjh8y-RO.js",revision:null},{url:"assets/cytoscape.esm-D3_iZ_3b.js",revision:null},{url:"assets/cynefinDiagram-TSTJHNR4-BoKfaePi.js",revision:null},{url:"assets/cynefin-VYW2F7L2-B7X5gKj-.js",revision:null},{url:"assets/cose-bilkent-JH36ORCC-gtzwCMyn.js",revision:null},{url:"assets/classDiagram-v2-EOCWNBFH-BsrNzS08.js",revision:null},{url:"assets/classDiagram-OUVF2IWQ-BsrNzS08.js",revision:null},{url:"assets/chunk-XXDRQBXY-CZBBj4P_.js",revision:null},{url:"assets/chunk-VR4S4FIN-CtGdy3K8.js",revision:null},{url:"assets/chunk-V7JOEXUC-DpQA4l-k.js",revision:null},{url:"assets/chunk-RYQCIY6F-BdNa9gwg.js",revision:null},{url:"assets/chunk-MOJQB5TN-B5Kzsd5Z.js",revision:null},{url:"assets/chunk-JWPE2WC7-B8bZHm4g.js",revision:null},{url:"assets/chunk-EX3LRPZG-QndtZFQ6.js",revision:null},{url:"assets/chunk-5VM5RSS4-BrBK8Xy3.js",revision:null},{url:"assets/chunk-32BRIVSS-B4LjEHPP.js",revision:null},{url:"assets/chunk-2Q5K7J3B-DgBhC1KN.js",revision:null},{url:"assets/channel-DBZQbHSi.js",revision:null},{url:"assets/c4Diagram-LMCZKHZV-BYElS7nn.js",revision:null},{url:"assets/blockDiagram-677ZJIJ3-eoDvsGst.js",revision:null},{url:"assets/architectureDiagram-ZJ3FMSHR-CNNIlew_.js",revision:null},{url:"assets/arc-OmYbmu75.js",revision:null},{url:"assets/abnfDiagram-VRR7QNED-B3X7vTL7.js",revision:null},{url:"assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf",revision:null},{url:"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2",revision:null},{url:"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2",revision:null},{url:"assets/KaTeX_Size4-Regular-DWFBv043.ttf",revision:null},{url:"assets/KaTeX_Size3-Regular-DgpXs0kz.ttf",revision:null},{url:"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2",revision:null},{url:"assets/KaTeX_Size2-Regular-B7gKUWhC.ttf",revision:null},{url:"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2",revision:null},{url:"assets/KaTeX_Size1-Regular-Dbsnue_I.ttf",revision:null},{url:"assets/KaTeX_Script-Regular-D3wIWfF6.woff2",revision:null},{url:"assets/KaTeX_Script-Regular-C5JkGWo-.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf",revision:null},{url:"assets/KaTeX_Math-Italic-t53AETM-.woff2",revision:null},{url:"assets/KaTeX_Math-Italic-flOr_0UB.ttf",revision:null},{url:"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2",revision:null},{url:"assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf",revision:null},{url:"assets/KaTeX_Main-Regular-ypZvNtVU.ttf",revision:null},{url:"assets/KaTeX_Main-Regular-B22Nviop.woff2",revision:null},{url:"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2",revision:null},{url:"assets/KaTeX_Main-Italic-3WenGoN9.ttf",revision:null},{url:"assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf",revision:null},{url:"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2",revision:null},{url:"assets/KaTeX_Main-Bold-waoOVXN0.ttf",revision:null},{url:"assets/KaTeX_Main-Bold-Cx986IdX.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Regular-CB_wures.ttf",revision:null},{url:"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf",revision:null},{url:"assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf",revision:null},{url:"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2",revision:null},{url:"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2",revision:null},{url:"assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf",revision:null},{url:"assets/KaTeX_AMS-Regular-DRggAlZN.ttf",revision:null},{url:"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2",revision:null},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"robots.txt",revision:"d9339907142917dee4aac78494ac9cfe"},{url:"sw-notifications.js",revision:"2ed386d64dc020bf9a7da8efd9dbea42"},{url:"fonts/JetBrainsMonoNL-Bold.woff2",revision:"7c0305a4db8f2977b061631755556ec0"},{url:"fonts/JetBrainsMonoNL-BoldItalic.woff2",revision:"bc3577cc820eaef00d9a985058bca2b7"},{url:"fonts/JetBrainsMonoNL-Italic.woff2",revision:"e035d3c7673175237d40687fc14d5b40"},{url:"fonts/JetBrainsMonoNL-Regular.woff2",revision:"3bb268888f64810d496dd10295d4337c"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Bold.ttf",revision:"7deed80e7a4195a51f2c0b82aa364e97"},{url:"fonts/JetBrainsMonoNLNerdFontMono-BoldItalic.ttf",revision:"9b7603423a37fd2f544f75e9669689a0"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Italic.ttf",revision:"5624a73ef0a476e93ef5949dd6e452e1"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Regular.ttf",revision:"ed6fa47e3c1fc9aa68e9015a5300419f"},{url:"fonts/NotoSansMonoCJKsc-Bold-Subset.woff2",revision:"7e37d6b9fb1339898c00ac3b49c8b12e"},{url:"fonts/NotoSansMonoCJKsc-Subset.woff2",revision:"e52357967aa973c04cce881c50cce0c8"},{url:"fonts/NotoSansSymbols2-Subset.woff2",revision:"8257a6beabcc8858199b208908a96ee9"},{url:"fonts/SymbolsNerdFontMono-Regular.woff2",revision:"54113ea8487e607f9d9a482c5877aed4"},{url:"fonts/TermdockStarSymbol-Subset.woff2",revision:"853a8d84112dd97a91d6cff1e7ab6bdf"},{url:"manifest.webmanifest",revision:"83f9a43b74c236012adafb077fb6d774"}],{}),s.cleanupOutdatedCaches(),s.registerRoute(new s.NavigationRoute(s.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//,/^\/health$/]}))});
|
|
1
|
+
if(!self.define){let s,e={};const i=(i,n)=>(i=new URL(i+".js",n).href,e[i]||new Promise(e=>{if("document"in self){const s=document.createElement("script");s.src=i,s.onload=e,document.head.appendChild(s)}else s=i,importScripts(i),e()}).then(()=>{let s=e[i];if(!s)throw new Error(`Module ${i} didn’t register its module`);return s}));self.define=(n,l)=>{const r=s||("document"in self?document.currentScript.src:"")||location.href;if(e[r])return;let a={};const o=s=>i(s,r),u={module:{uri:r},exports:a,require:o};e[r]=Promise.all(n.map(s=>u[s]||o(s))).then(s=>(l(...s),a))}}define(["./workbox-9c191d2f"],function(s){"use strict";importScripts("sw-notifications.js"),self.skipWaiting(),s.clientsClaim(),s.precacheAndRoute([{url:"sw-notifications.js",revision:"91964c9b57d28d34a4e9e4911f8d72d8"},{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"pwa-64x64.png",revision:"c1ca9f6daa67743b38564b230314277a"},{url:"pwa-512x512.svg",revision:"149a151133f52feb34ab6ecedf349936"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"pwa-192x192.svg",revision:"541ace289ca5d6f3d269d1286cde5283"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"index.html",revision:"1ab8065947d404bcd4d7af31a5bb373f"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"icons/agents/opencode.svg",revision:"851c2931d95821ba2b39b68587b48976"},{url:"icons/agents/kimi.svg",revision:"5366c710f4f763022fe159a69cf17409"},{url:"icons/agents/grok.svg",revision:"cd745b0b964545bc7a6b4f36021c3298"},{url:"icons/agents/goose.svg",revision:"c79b927278983c19098d77b4b0c11ab3"},{url:"icons/agents/gemini.svg",revision:"7a64f6d6f80a908255e9c0256903882e"},{url:"icons/agents/droid.svg",revision:"f28723b8c5c115e0908ba3c72613e256"},{url:"icons/agents/cursor.svg",revision:"a1528f3d3cda497f75a5db28defb19de"},{url:"icons/agents/copilot.svg",revision:"dd52e96c1eac0b6ec4bedcd0ddd3b52d"},{url:"icons/agents/codex.svg",revision:"608e55babb0b217e1f2da287342b46d3"},{url:"icons/agents/claude.svg",revision:"08af0225b7acdd8e8f573f3247e5e1c3"},{url:"icons/agents/amp.svg",revision:"af12f8b33faa81e4d46ea7b93c7e43f3"},{url:"fonts/TermdockStarSymbol-Subset.woff2",revision:"853a8d84112dd97a91d6cff1e7ab6bdf"},{url:"fonts/SymbolsNerdFontMono-Regular.woff2",revision:"54113ea8487e607f9d9a482c5877aed4"},{url:"fonts/NotoSansSymbols2-Subset.woff2",revision:"8257a6beabcc8858199b208908a96ee9"},{url:"fonts/NotoSansMonoCJKsc-Subset.woff2",revision:"e52357967aa973c04cce881c50cce0c8"},{url:"fonts/NotoSansMonoCJKsc-Bold-Subset.woff2",revision:"7e37d6b9fb1339898c00ac3b49c8b12e"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Regular.ttf",revision:"ed6fa47e3c1fc9aa68e9015a5300419f"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Italic.ttf",revision:"5624a73ef0a476e93ef5949dd6e452e1"},{url:"fonts/JetBrainsMonoNLNerdFontMono-BoldItalic.ttf",revision:"9b7603423a37fd2f544f75e9669689a0"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Bold.ttf",revision:"7deed80e7a4195a51f2c0b82aa364e97"},{url:"fonts/JetBrainsMonoNL-Regular.woff2",revision:"3bb268888f64810d496dd10295d4337c"},{url:"fonts/JetBrainsMonoNL-Italic.woff2",revision:"e035d3c7673175237d40687fc14d5b40"},{url:"fonts/JetBrainsMonoNL-BoldItalic.woff2",revision:"bc3577cc820eaef00d9a985058bca2b7"},{url:"fonts/JetBrainsMonoNL-Bold.woff2",revision:"7c0305a4db8f2977b061631755556ec0"},{url:"assets/xychartDiagram-FW5EYKEG-DQW7oknN.js",revision:null},{url:"assets/wardleyDiagram-EHGQE667-Cgbh1_F7.js",revision:null},{url:"assets/vennDiagram-L72KCM5P-gXnoVb66.js",revision:null},{url:"assets/timeline-definition-FHXFAJF6-Mj3zCwTc.js",revision:null},{url:"assets/terminal-C7X98Amf.js",revision:null},{url:"assets/swimlanesDiagram-G3AALYLV-DmhG9XuP.js",revision:null},{url:"assets/swimlanes-5IMT3BWC-BW65nQch.js",revision:null},{url:"assets/stateDiagram-v2-6OUMAXLB-gXYMxyju.js",revision:null},{url:"assets/stateDiagram-2N3HPSRC-D-gs0yW-.js",revision:null},{url:"assets/sizeCapture-X5ZJPWSS-BvnnE9Ev.js",revision:null},{url:"assets/sequenceDiagram-DBY2YBRQ-CmkcMlBD.js",revision:null},{url:"assets/sankeyDiagram-HTMAVEWB-BfpeK6jA.js",revision:null},{url:"assets/requirementDiagram-TGXJPOKE-CfnNu5HR.js",revision:null},{url:"assets/react-XDCjal2T.js",revision:null},{url:"assets/railroadDiagram-RFXS5EU6-BUurE4S_.js",revision:null},{url:"assets/quadrantDiagram-ABIIQ3AL-DvXeQj4a.js",revision:null},{url:"assets/purify.es-CC4Brkr_.js",revision:null},{url:"assets/pieDiagram-ENE6RG2P-Ch2Ty0Y2.js",revision:null},{url:"assets/pegDiagram-2B236MQR-BckzTuEj.js",revision:null},{url:"assets/ordinal-Cboi1Yqb.js",revision:null},{url:"assets/mindmap-definition-LN4V7U3C-CjcF3QgK.js",revision:null},{url:"assets/mermaid.core-BXI9ENYY.js",revision:null},{url:"assets/map-BdqsVdb3.js",revision:null},{url:"assets/linear-DjF6fII5.js",revision:null},{url:"assets/layout-1eBIAoSl.js",revision:null},{url:"assets/katex-HP8lGamR.js",revision:null},{url:"assets/katex-DnlPpQZa.js",revision:null},{url:"assets/katex-55ZPZeOF.css",revision:null},{url:"assets/kanban-definition-HUTT4EX6-Db2NpkWT.js",revision:null},{url:"assets/journeyDiagram-5HDEW3XC-DNXNzmef.js",revision:null},{url:"assets/ishikawaDiagram-FXEZZL3T-uFSMTzKn.js",revision:null},{url:"assets/init-Gi6I4Gst.js",revision:null},{url:"assets/infoDiagram-FWYZ7A6U-ZrovoSsZ.js",revision:null},{url:"assets/index-DyTRm1PJ.css",revision:null},{url:"assets/index-Ca-rq7yX.js",revision:null},{url:"assets/index-BYpFR_L5.js",revision:null},{url:"assets/graph-D2o_JWn5.js",revision:null},{url:"assets/gitGraphDiagram-IHSO6WYX-D9Qw6ov4.js",revision:null},{url:"assets/ganttDiagram-NO4QXBWP-BL1NGUYm.js",revision:null},{url:"assets/flowDiagram-23GEKE2U-BqCA33p5.js",revision:null},{url:"assets/erDiagram-Q63AITRT-5jgm5Ksi.js",revision:null},{url:"assets/ebnfDiagram-CCIWWBDH-BARVSv3m.js",revision:null},{url:"assets/dnd-BCFAPC0L.js",revision:null},{url:"assets/diffWorker-BFIcKxCt.js",revision:null},{url:"assets/diagram-WEI45ONY-2hoKf8a6.js",revision:null},{url:"assets/diagram-OA4YK3LP-BB8nlvCx.js",revision:null},{url:"assets/diagram-NH7WQ7WH-B7NwHUrU.js",revision:null},{url:"assets/diagram-G47NLZAW-Bty2-g6M.js",revision:null},{url:"assets/diagram-FQU43EPY-DYc1Xsf9.js",revision:null},{url:"assets/defaultLocale-CrowFXzY.js",revision:null},{url:"assets/dagre-VKFMJZFB-n2L4piqF.js",revision:null},{url:"assets/cytoscape.esm-D3_iZ_3b.js",revision:null},{url:"assets/cynefinDiagram-TSTJHNR4-Cwtffn_E.js",revision:null},{url:"assets/cynefin-VYW2F7L2-DZfIXs6F.js",revision:null},{url:"assets/cose-bilkent-JH36ORCC-C_6nPODG.js",revision:null},{url:"assets/classDiagram-v2-EOCWNBFH-CtEpUyds.js",revision:null},{url:"assets/classDiagram-OUVF2IWQ-CtEpUyds.js",revision:null},{url:"assets/chunk-XXDRQBXY-BIzI5y55.js",revision:null},{url:"assets/chunk-VR4S4FIN-Bau1q0xN.js",revision:null},{url:"assets/chunk-V7JOEXUC-BjQQBzKq.js",revision:null},{url:"assets/chunk-RYQCIY6F-0MHmqGNs.js",revision:null},{url:"assets/chunk-MOJQB5TN-DWsVsFoK.js",revision:null},{url:"assets/chunk-JWPE2WC7-K8nPht_5.js",revision:null},{url:"assets/chunk-EX3LRPZG-BdI4v3f4.js",revision:null},{url:"assets/chunk-5VM5RSS4-Ctw1x2FG.js",revision:null},{url:"assets/chunk-32BRIVSS-C1g4c11U.js",revision:null},{url:"assets/chunk-2Q5K7J3B-BiBc5Uv0.js",revision:null},{url:"assets/channel-Ck9tD_4_.js",revision:null},{url:"assets/c4Diagram-LMCZKHZV-m3lWYsQI.js",revision:null},{url:"assets/blockDiagram-677ZJIJ3-BG8NrPiD.js",revision:null},{url:"assets/architectureDiagram-ZJ3FMSHR-C4BLot_l.js",revision:null},{url:"assets/arc-DhZtXG4q.js",revision:null},{url:"assets/abnfDiagram-VRR7QNED-Bt36T7gE.js",revision:null},{url:"assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf",revision:null},{url:"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2",revision:null},{url:"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2",revision:null},{url:"assets/KaTeX_Size4-Regular-DWFBv043.ttf",revision:null},{url:"assets/KaTeX_Size3-Regular-DgpXs0kz.ttf",revision:null},{url:"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2",revision:null},{url:"assets/KaTeX_Size2-Regular-B7gKUWhC.ttf",revision:null},{url:"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2",revision:null},{url:"assets/KaTeX_Size1-Regular-Dbsnue_I.ttf",revision:null},{url:"assets/KaTeX_Script-Regular-D3wIWfF6.woff2",revision:null},{url:"assets/KaTeX_Script-Regular-C5JkGWo-.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf",revision:null},{url:"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2",revision:null},{url:"assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf",revision:null},{url:"assets/KaTeX_Math-Italic-t53AETM-.woff2",revision:null},{url:"assets/KaTeX_Math-Italic-flOr_0UB.ttf",revision:null},{url:"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2",revision:null},{url:"assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf",revision:null},{url:"assets/KaTeX_Main-Regular-ypZvNtVU.ttf",revision:null},{url:"assets/KaTeX_Main-Regular-B22Nviop.woff2",revision:null},{url:"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2",revision:null},{url:"assets/KaTeX_Main-Italic-3WenGoN9.ttf",revision:null},{url:"assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf",revision:null},{url:"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2",revision:null},{url:"assets/KaTeX_Main-Bold-waoOVXN0.ttf",revision:null},{url:"assets/KaTeX_Main-Bold-Cx986IdX.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Regular-CB_wures.ttf",revision:null},{url:"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2",revision:null},{url:"assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf",revision:null},{url:"assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf",revision:null},{url:"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2",revision:null},{url:"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2",revision:null},{url:"assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf",revision:null},{url:"assets/KaTeX_AMS-Regular-DRggAlZN.ttf",revision:null},{url:"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2",revision:null},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"robots.txt",revision:"d9339907142917dee4aac78494ac9cfe"},{url:"sw-notifications.js",revision:"91964c9b57d28d34a4e9e4911f8d72d8"},{url:"fonts/JetBrainsMonoNL-Bold.woff2",revision:"7c0305a4db8f2977b061631755556ec0"},{url:"fonts/JetBrainsMonoNL-BoldItalic.woff2",revision:"bc3577cc820eaef00d9a985058bca2b7"},{url:"fonts/JetBrainsMonoNL-Italic.woff2",revision:"e035d3c7673175237d40687fc14d5b40"},{url:"fonts/JetBrainsMonoNL-Regular.woff2",revision:"3bb268888f64810d496dd10295d4337c"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Bold.ttf",revision:"7deed80e7a4195a51f2c0b82aa364e97"},{url:"fonts/JetBrainsMonoNLNerdFontMono-BoldItalic.ttf",revision:"9b7603423a37fd2f544f75e9669689a0"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Italic.ttf",revision:"5624a73ef0a476e93ef5949dd6e452e1"},{url:"fonts/JetBrainsMonoNLNerdFontMono-Regular.ttf",revision:"ed6fa47e3c1fc9aa68e9015a5300419f"},{url:"fonts/NotoSansMonoCJKsc-Bold-Subset.woff2",revision:"7e37d6b9fb1339898c00ac3b49c8b12e"},{url:"fonts/NotoSansMonoCJKsc-Subset.woff2",revision:"e52357967aa973c04cce881c50cce0c8"},{url:"fonts/NotoSansSymbols2-Subset.woff2",revision:"8257a6beabcc8858199b208908a96ee9"},{url:"fonts/SymbolsNerdFontMono-Regular.woff2",revision:"54113ea8487e607f9d9a482c5877aed4"},{url:"fonts/TermdockStarSymbol-Subset.woff2",revision:"853a8d84112dd97a91d6cff1e7ab6bdf"},{url:"manifest.webmanifest",revision:"83f9a43b74c236012adafb077fb6d774"}],{}),s.cleanupOutdatedCaches(),s.registerRoute(new s.NavigationRoute(s.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//,/^\/health$/]}))});
|
|
@@ -19,7 +19,7 @@ import path from 'node:path';
|
|
|
19
19
|
import { execFile } from 'node:child_process';
|
|
20
20
|
import { agentBySlug, isPluginAgent } from './registry.js';
|
|
21
21
|
import { loadPlugins, resolveHookTarget } from './plugins.js';
|
|
22
|
-
export const HOOK_AGENTS = ['claude', 'codex', 'copilot', 'opencode', 'pi', 'grok'];
|
|
22
|
+
export const HOOK_AGENTS = ['claude', 'codex', 'copilot', 'opencode', 'pi', 'grok', 'kimi'];
|
|
23
23
|
const DISPLAY_NAMES = {
|
|
24
24
|
claude: 'Claude Code',
|
|
25
25
|
codex: 'Codex',
|
|
@@ -27,6 +27,7 @@ const DISPLAY_NAMES = {
|
|
|
27
27
|
opencode: 'OpenCode',
|
|
28
28
|
pi: 'Pi',
|
|
29
29
|
grok: 'Grok Build',
|
|
30
|
+
kimi: 'Kimi Code',
|
|
30
31
|
};
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Paths and the hook command line
|
|
@@ -58,6 +59,7 @@ function targetPath(agent) {
|
|
|
58
59
|
case 'opencode': return path.join(xdgConfigDir(), 'opencode', 'plugins', OWNED_FILE_STEM_JS);
|
|
59
60
|
case 'pi': return path.join(homeDir(), '.pi', 'agent', 'extensions', 'termdock', 'index.ts');
|
|
60
61
|
case 'grok': return path.join(homeDir(), '.grok', 'hooks', OWNED_FILE_STEM_JSON);
|
|
62
|
+
case 'kimi': return path.join(homeDir(), '.kimi-code', 'config.toml');
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
function abbreviateHome(p) {
|
|
@@ -127,6 +129,29 @@ const CODEX_HOOK_EVENTS = [
|
|
|
127
129
|
['UserPromptSubmit', 'prompt-submit'],
|
|
128
130
|
['Stop', 'stop'],
|
|
129
131
|
];
|
|
132
|
+
/**
|
|
133
|
+
* Kimi Code's hook events (~/.kimi-code/config.toml `[[hooks]]` entries, TOML).
|
|
134
|
+
* `PermissionRequest` is the genuine block (fires right before the approval
|
|
135
|
+
* wait); `PostToolUse` is the way *back* — Kimi has a `PermissionResult`
|
|
136
|
+
* event, but the approved tool completing is the same recovery edge Claude
|
|
137
|
+
* uses, and it keeps the state machine on the shared vocabulary. `Stop` does
|
|
138
|
+
* NOT fire when the user interrupts (Esc) or the turn errors — Kimi sends
|
|
139
|
+
* `Interrupt` / `StopFailure` instead, so both fold onto `stop` to keep the
|
|
140
|
+
* pane from sticking in working.
|
|
141
|
+
*/
|
|
142
|
+
export const KIMI_HOOK_EVENTS = [
|
|
143
|
+
['SessionStart', 'session-start'],
|
|
144
|
+
['UserPromptSubmit', 'prompt-submit'],
|
|
145
|
+
['PermissionRequest', 'permission-request'],
|
|
146
|
+
['PostToolUse', 'tool-complete'],
|
|
147
|
+
['Stop', 'stop'],
|
|
148
|
+
['StopFailure', 'stop'],
|
|
149
|
+
['Interrupt', 'stop'],
|
|
150
|
+
['SessionEnd', 'session-end'],
|
|
151
|
+
];
|
|
152
|
+
/** Seconds Kimi gives one termdock hook before killing it (its default is
|
|
153
|
+
* 30s; ours writes a few bytes). */
|
|
154
|
+
const KIMI_HOOK_TIMEOUT_SECS = 5;
|
|
130
155
|
/**
|
|
131
156
|
* Grok Build's hook events — Claude Code's vocabulary (grok mirrors it
|
|
132
157
|
* deliberately, it even reads ~/.claude/settings.json) — plus the matcher
|
|
@@ -473,6 +498,113 @@ function ownedFileUninstall(file, mark) {
|
|
|
473
498
|
return 'Removed';
|
|
474
499
|
}
|
|
475
500
|
// ---------------------------------------------------------------------------
|
|
501
|
+
// TOML hooks installer (Kimi Code): kimi keeps hooks in `[[hooks]]` array-of-
|
|
502
|
+
// tables inside ~/.kimi-code/config.toml — free-form TOML we must NOT rewrite
|
|
503
|
+
// wholesale (comments, ordering, other tables are the user's). So instead of
|
|
504
|
+
// parsing, the file is treated as a sequence of *sections* (a `[table]` /
|
|
505
|
+
// `[[array]]` header line plus the lines up to the next header); termdock
|
|
506
|
+
// owns exactly the sections whose command carries the marker, appends fresh
|
|
507
|
+
// `[[hooks]]` blocks at the end of the file, and never touches the rest.
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
/** Split TOML text into sections: everything before the first header is one
|
|
510
|
+
* (preamble) section; each later section starts at its header line.
|
|
511
|
+
* `sections.join('\n')` reproduces the input byte-for-byte. */
|
|
512
|
+
function splitTomlSections(text) {
|
|
513
|
+
const lines = text.split('\n');
|
|
514
|
+
const sections = [];
|
|
515
|
+
let current = [];
|
|
516
|
+
for (const line of lines) {
|
|
517
|
+
if (/^\s*\[/.test(line) && current.length > 0) {
|
|
518
|
+
sections.push(current.join('\n'));
|
|
519
|
+
current = [];
|
|
520
|
+
}
|
|
521
|
+
current.push(line);
|
|
522
|
+
}
|
|
523
|
+
sections.push(current.join('\n'));
|
|
524
|
+
return sections;
|
|
525
|
+
}
|
|
526
|
+
/** The `event = "…"` value of a `[[hooks]]` section, if it declares one. */
|
|
527
|
+
function tomlSectionEvent(section) {
|
|
528
|
+
const m = section.match(/^\s*event\s*=\s*"([^"]+)"\s*(?:#.*)?$/m);
|
|
529
|
+
return m ? m[1] : null;
|
|
530
|
+
}
|
|
531
|
+
/** The `command = '…'` value of a `[[hooks]]` section (literal-string form —
|
|
532
|
+
* the only form termdock writes; the command embeds double quotes). */
|
|
533
|
+
function tomlSectionCommand(section) {
|
|
534
|
+
const m = section.match(/^\s*command\s*=\s*'(.*)'\s*(?:#.*)?$/m);
|
|
535
|
+
return m ? m[1] : null;
|
|
536
|
+
}
|
|
537
|
+
/** One `[[hooks]]` block as termdock writes it. Kimi allows exactly the
|
|
538
|
+
* fields event/matcher/command/timeout. */
|
|
539
|
+
function tomlHookBlock(hookEvent, command) {
|
|
540
|
+
return `[[hooks]]\nevent = "${hookEvent}"\ncommand = '${command}'\ntimeout = ${KIMI_HOOK_TIMEOUT_SECS}`;
|
|
541
|
+
}
|
|
542
|
+
function readTextFile(file) {
|
|
543
|
+
try {
|
|
544
|
+
return fs.readFileSync(file, 'utf8');
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Exported for tests — the public entry points are hooksState/installHooks/
|
|
551
|
+
// uninstallHooks; these take an explicit file path so tests can use tmp files.
|
|
552
|
+
export function tomlHooksState(file, agent, events) {
|
|
553
|
+
const text = readTextFile(file);
|
|
554
|
+
if (text === null)
|
|
555
|
+
return 'not-installed';
|
|
556
|
+
const mark = marker(agent);
|
|
557
|
+
const ours = splitTomlSections(text).filter((s) => s.includes(mark));
|
|
558
|
+
if (ours.length === 0)
|
|
559
|
+
return 'not-installed';
|
|
560
|
+
for (const [hookEvent, sentinel] of events) {
|
|
561
|
+
const expected = hookCommand(agent, sentinel);
|
|
562
|
+
const ok = ours.some((s) => tomlSectionEvent(s) === hookEvent && tomlSectionCommand(s) === expected);
|
|
563
|
+
if (!ok)
|
|
564
|
+
return 'outdated';
|
|
565
|
+
}
|
|
566
|
+
return 'installed';
|
|
567
|
+
}
|
|
568
|
+
/** Merge termdock's `[[hooks]]` blocks into config.toml, preserving
|
|
569
|
+
* everything else (including the user's own hooks) byte-for-byte. */
|
|
570
|
+
export function tomlHooksInstall(file, agent, events) {
|
|
571
|
+
const text = readTextFile(file) ?? '';
|
|
572
|
+
const mark = marker(agent);
|
|
573
|
+
// Drop any previous termdock blocks (stale exe path / older event set)…
|
|
574
|
+
const kept = splitTomlSections(text).filter((s) => !s.includes(mark));
|
|
575
|
+
let out = kept.join('\n');
|
|
576
|
+
// …then append the fresh set at the end of the file, where new
|
|
577
|
+
// `[[hooks]]` tables can never swallow the user's trailing keys.
|
|
578
|
+
if (out.length > 0 && !out.endsWith('\n'))
|
|
579
|
+
out += '\n';
|
|
580
|
+
if (out.trim().length > 0)
|
|
581
|
+
out += '\n';
|
|
582
|
+
out += events
|
|
583
|
+
.map(([hookEvent, sentinel]) => tomlHookBlock(hookEvent, hookCommand(agent, sentinel)))
|
|
584
|
+
.join('\n\n');
|
|
585
|
+
out += '\n';
|
|
586
|
+
writeAtomic(file, out);
|
|
587
|
+
}
|
|
588
|
+
/** Remove every termdock `[[hooks]]` block, leaving the rest of the file
|
|
589
|
+
* (user hooks included) untouched. */
|
|
590
|
+
export function tomlHooksUninstall(file, agent) {
|
|
591
|
+
const text = readTextFile(file);
|
|
592
|
+
if (text === null)
|
|
593
|
+
return 'Nothing installed; nothing to remove';
|
|
594
|
+
const mark = marker(agent);
|
|
595
|
+
const sections = splitTomlSections(text);
|
|
596
|
+
const removed = sections.filter((s) => s.includes(mark)).length;
|
|
597
|
+
if (removed === 0)
|
|
598
|
+
return 'No termdock hooks found; nothing to remove';
|
|
599
|
+
const out = sections
|
|
600
|
+
.filter((s) => !s.includes(mark))
|
|
601
|
+
.join('\n')
|
|
602
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
603
|
+
.replace(/\n+$/, '\n');
|
|
604
|
+
writeAtomic(file, out);
|
|
605
|
+
return 'Removed';
|
|
606
|
+
}
|
|
607
|
+
// ---------------------------------------------------------------------------
|
|
476
608
|
// Public API — built-in agents
|
|
477
609
|
// ---------------------------------------------------------------------------
|
|
478
610
|
export function hooksState(agent) {
|
|
@@ -481,6 +613,8 @@ export function hooksState(agent) {
|
|
|
481
613
|
return hookMapState(file, agent, CLAUDE_HOOK_EVENTS);
|
|
482
614
|
if (agent === 'codex')
|
|
483
615
|
return hookMapState(file, agent, CODEX_HOOK_EVENTS);
|
|
616
|
+
if (agent === 'kimi')
|
|
617
|
+
return tomlHooksState(file, agent, KIMI_HOOK_EVENTS);
|
|
484
618
|
const expected = ownedFileContent(agent);
|
|
485
619
|
if (expected === null)
|
|
486
620
|
return 'not-installed';
|
|
@@ -518,6 +652,13 @@ export async function installHooks(agent) {
|
|
|
518
652
|
};
|
|
519
653
|
}
|
|
520
654
|
}
|
|
655
|
+
if (agent === 'kimi') {
|
|
656
|
+
tomlHooksInstall(file, agent, KIMI_HOOK_EVENTS);
|
|
657
|
+
return {
|
|
658
|
+
summary: 'Installed — run /reload in any open Kimi session (or restart it) for hooks to take effect',
|
|
659
|
+
devMode,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
521
662
|
const content = ownedFileContent(agent);
|
|
522
663
|
if (content === null)
|
|
523
664
|
throw new Error(`no owned-file integration for ${agent}`);
|
|
@@ -529,6 +670,9 @@ export async function uninstallHooks(agent) {
|
|
|
529
670
|
if (agent === 'claude' || agent === 'codex') {
|
|
530
671
|
return hookMapUninstall(file, agent);
|
|
531
672
|
}
|
|
673
|
+
if (agent === 'kimi') {
|
|
674
|
+
return tomlHooksUninstall(file, agent);
|
|
675
|
+
}
|
|
532
676
|
return ownedFileUninstall(file, marker(agent));
|
|
533
677
|
}
|
|
534
678
|
/** Collect all plugin agents that define hooks. */
|
|
@@ -18,6 +18,7 @@ const AGENTS = [
|
|
|
18
18
|
{ slug: 'antigravity', displayName: 'Antigravity', aliases: ['agy', 'antigravity'], accentColor: '#2563EB', icon: null },
|
|
19
19
|
{ slug: 'grok', displayName: 'Grok', aliases: ['grok'], accentColor: '#000000', icon: 'grok' },
|
|
20
20
|
{ slug: 'qwen', displayName: 'Qwen Code', aliases: ['qwen', 'qwen-code'], accentColor: '#7C3AED', icon: null },
|
|
21
|
+
{ slug: 'kimi', displayName: 'Kimi Code', aliases: ['kimi', 'kimi-code'], accentColor: '#000000', icon: 'kimi' },
|
|
21
22
|
];
|
|
22
23
|
const BY_ALIAS = new Map();
|
|
23
24
|
const BY_SLUG = new Map();
|
|
@@ -137,6 +138,7 @@ function resumeCommandFor(agent, sessionId, flags) {
|
|
|
137
138
|
case 'cursor': return `cursor-agent${f} --resume ${sessionId}`;
|
|
138
139
|
case 'copilot': return `copilot${f} --resume ${sessionId}`;
|
|
139
140
|
case 'grok': return `grok${f} --resume ${sessionId}`;
|
|
141
|
+
case 'kimi': return `kimi${f} --session ${sessionId}`;
|
|
140
142
|
default: return null;
|
|
141
143
|
}
|
|
142
144
|
}
|
|
@@ -150,6 +152,7 @@ const STALE_SESSION_FLAGS = {
|
|
|
150
152
|
codex: ['--last'],
|
|
151
153
|
grok: ['--resume', '-r', '--load', '--continue', '-c', '--session-id', '-s',
|
|
152
154
|
'--fork-session', '--worktree', '-w', '--worktree-ref', '--ref'],
|
|
155
|
+
kimi: ['--session', '-S', '--continue', '-c'],
|
|
153
156
|
};
|
|
154
157
|
/**
|
|
155
158
|
* The launch-flag tail of argv worth replaying on a resume command, or null
|
package/dist/server/cli.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// libuv 线程池默认只有 4 个线程,慢/挂起的异步 IO(fs.promises、crypto 等)
|
|
3
|
+
// 会把池耗尽,表现为整个服务的 IO 全部卡死。必须在任何异步 fs 调用之前
|
|
4
|
+
// 设置(同步 fs 不占线程池,所以放在文件体顶部仍然足够早)。
|
|
5
|
+
process.env.UV_THREADPOOL_SIZE ||= '16';
|
|
2
6
|
import { config as loadDotenv } from 'dotenv';
|
|
3
7
|
import os from 'os';
|
|
4
8
|
import { resolve } from 'path';
|
|
@@ -1915,6 +1919,26 @@ async function ensureTmuxFocusEvents() {
|
|
|
1915
1919
|
maxBuffer: 64 * 1024,
|
|
1916
1920
|
});
|
|
1917
1921
|
}
|
|
1922
|
+
// Let tmux forward modified Enter keys (Ctrl/Shift/Alt+Enter) to pane apps.
|
|
1923
|
+
// The option only exists in tmux >= 3.2; on older versions the probe fails
|
|
1924
|
+
// and we silently skip.
|
|
1925
|
+
async function ensureTmuxExtendedKeys() {
|
|
1926
|
+
try {
|
|
1927
|
+
const { stdout } = await execFileAsync('tmux', ['show-options', '-gqv', 'extended-keys'], {
|
|
1928
|
+
timeout: 5000,
|
|
1929
|
+
maxBuffer: 64 * 1024,
|
|
1930
|
+
});
|
|
1931
|
+
if (stdout.trim() === 'on')
|
|
1932
|
+
return;
|
|
1933
|
+
await execFileAsync('tmux', ['set-option', '-g', 'extended-keys', 'on'], {
|
|
1934
|
+
timeout: 5000,
|
|
1935
|
+
maxBuffer: 64 * 1024,
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
catch {
|
|
1939
|
+
// extended-keys unsupported on this tmux; nothing to do
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1918
1942
|
async function ensureTmuxScrollbackProfile(sessionName) {
|
|
1919
1943
|
await execFileAsync('tmux', ['set-option', '-g', 'history-limit', String(TERMDOCK_TMUX_HISTORY_LIMIT)], {
|
|
1920
1944
|
timeout: 5000,
|
|
@@ -2060,6 +2084,7 @@ async function ensureStampedTmuxSession(sessionName, cwd) {
|
|
|
2060
2084
|
});
|
|
2061
2085
|
}
|
|
2062
2086
|
await ensureTmuxFocusEvents();
|
|
2087
|
+
await ensureTmuxExtendedKeys();
|
|
2063
2088
|
await ensureTmuxColorEnvironment(sessionName);
|
|
2064
2089
|
await ensureTmuxCliSessionOptions(sessionName);
|
|
2065
2090
|
await setTmuxOption(sessionName, '@termdock-version', TERMDOCK_VERSION);
|
package/dist/server/entry.js
CHANGED
|
@@ -22,6 +22,7 @@ import { isUpgradeRequestAuthenticated, requireAuth } from './utils/authProtecti
|
|
|
22
22
|
import { localAccessManager } from './utils/localAccess.js';
|
|
23
23
|
import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, } from './utils/requestSecurity.js';
|
|
24
24
|
import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
|
|
25
|
+
import { requestDeadlineMiddleware } from './utils/requestDeadline.js';
|
|
25
26
|
import { startOnboardingServer, stopOnboardingServer, getOnboardingServerUrl } from './onboardingServer.js';
|
|
26
27
|
import { CertificateWatcher } from './certificateWatcher.js';
|
|
27
28
|
import { writeDiffTraceLog, writeErrorLog, writeJsonLog, writeTextLog } from './utils/serverLogger.js';
|
|
@@ -29,6 +30,22 @@ import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, }
|
|
|
29
30
|
import { PORT, DEFAULT_HOST } from './config.js';
|
|
30
31
|
const CLIENT_STATE_COOKIE = 'termdock-client';
|
|
31
32
|
export const DEFAULT_PORT = PORT.backend;
|
|
33
|
+
/** Read one cookie from a raw Cookie header (upgrade requests bypass the
|
|
34
|
+
* cookie-parser middleware). Values are percent-encoded. */
|
|
35
|
+
function getCookieValue(cookieHeader, name) {
|
|
36
|
+
if (!cookieHeader)
|
|
37
|
+
return undefined;
|
|
38
|
+
const pattern = new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=([^;]*)`);
|
|
39
|
+
const match = cookieHeader.match(pattern);
|
|
40
|
+
if (!match)
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
return decodeURIComponent(match[1]);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return match[1];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
32
49
|
const CLIENT_LOG_DEDUP_WINDOW_MS = 5_000;
|
|
33
50
|
const CLIENT_LOG_RATE_WINDOW_MS = 10_000;
|
|
34
51
|
const CLIENT_LOG_RATE_LIMIT = 120;
|
|
@@ -302,6 +319,9 @@ export function createApp(options = {}) {
|
|
|
302
319
|
});
|
|
303
320
|
next();
|
|
304
321
|
});
|
|
322
|
+
// 全局请求兜底超时:任何 handler 挂起最多活 30s(长连接流按表豁免),
|
|
323
|
+
// 到期销毁 socket 释放浏览器连接槽。必须在所有路由与静态资源之前挂载。
|
|
324
|
+
app.use(requestDeadlineMiddleware());
|
|
305
325
|
app.use('/api', (req, res, next) => {
|
|
306
326
|
// JSON/API responses must never hit browser conditional caching. A 304 with
|
|
307
327
|
// an empty body breaks fetch().json() callers and looks like random IO
|
|
@@ -523,11 +543,12 @@ export function startServer(options = {}) {
|
|
|
523
543
|
}
|
|
524
544
|
const sessionId = match[1];
|
|
525
545
|
const clientId = crypto.randomUUID();
|
|
546
|
+
const pushClientId = getCookieValue(cookieHeader, CLIENT_STATE_COOKIE);
|
|
526
547
|
// 短线重连时客户端会带上 ?since=<lastSeq>,让服务端只补发增量。
|
|
527
548
|
const sinceParam = url.searchParams.get('since');
|
|
528
549
|
const sinceSeq = sinceParam ? Math.max(0, Number.parseInt(sinceParam, 10) || 0) : 0;
|
|
529
550
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
530
|
-
handleTerminalWebSocket(ws, sessionId, clientId, { sinceSeq });
|
|
551
|
+
handleTerminalWebSocket(ws, sessionId, clientId, { sinceSeq, pushClientId });
|
|
531
552
|
});
|
|
532
553
|
});
|
|
533
554
|
server.listen(port, host, () => {
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import webpush from 'web-push';
|
|
5
|
+
import { getViewingPushClientIds } from './pushViewers.js';
|
|
5
6
|
const storeDirectory = path.join(os.homedir(), '.termdock');
|
|
6
7
|
const storePath = path.join(storeDirectory, 'push-notifications.json');
|
|
7
8
|
let store = null;
|
|
@@ -20,7 +21,14 @@ function readStore() {
|
|
|
20
21
|
&& typeof parsed.vapid?.publicKey === 'string'
|
|
21
22
|
&& typeof parsed.vapid?.privateKey === 'string'
|
|
22
23
|
&& Array.isArray(parsed.subscriptions)) {
|
|
23
|
-
|
|
24
|
+
// Subscriptions written before exitEnabled existed default to opt-out.
|
|
25
|
+
store = {
|
|
26
|
+
...parsed,
|
|
27
|
+
subscriptions: parsed.subscriptions.map((entry) => ({
|
|
28
|
+
...entry,
|
|
29
|
+
exitEnabled: entry.exitEnabled === true,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
24
32
|
}
|
|
25
33
|
}
|
|
26
34
|
catch {
|
|
@@ -34,7 +42,11 @@ function readStore() {
|
|
|
34
42
|
};
|
|
35
43
|
persistStore(store);
|
|
36
44
|
}
|
|
37
|
-
|
|
45
|
+
// VAPID subject must be a mailto: or https: contact URI. Configurable for
|
|
46
|
+
// self-hosters; defaults to the project repository instead of a fake mailbox.
|
|
47
|
+
const vapidSubject = process.env.TERMDOCK_VAPID_SUBJECT?.trim()
|
|
48
|
+
|| 'https://github.com/Jovines/termdock';
|
|
49
|
+
webpush.setVapidDetails(vapidSubject, store.vapid.publicKey, store.vapid.privateKey);
|
|
38
50
|
return store;
|
|
39
51
|
}
|
|
40
52
|
export function getVapidPublicKey() {
|
|
@@ -67,10 +79,43 @@ export function removePushSubscription(clientId, endpoint) {
|
|
|
67
79
|
if (current.subscriptions.length !== previousLength)
|
|
68
80
|
persistStore(current);
|
|
69
81
|
}
|
|
70
|
-
|
|
82
|
+
function isZhLocale(locale) {
|
|
83
|
+
return locale.toLowerCase().startsWith('zh');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Localized push copy, mirroring the client-side getAgentNotificationText in
|
|
87
|
+
* useTerminalStore.ts so both delivery paths read the same for one locale.
|
|
88
|
+
*/
|
|
89
|
+
export function agentNotificationText(locale, kind, agentName) {
|
|
90
|
+
if (isZhLocale(locale)) {
|
|
91
|
+
if (kind === 'waiting')
|
|
92
|
+
return { title: `${agentName} 需要你的处理`, body: '点按返回对应会话' };
|
|
93
|
+
if (kind === 'done')
|
|
94
|
+
return { title: `${agentName} 已完成`, body: '点按查看结果' };
|
|
95
|
+
return { title: `${agentName} 已退出`, body: '点按查看结果' };
|
|
96
|
+
}
|
|
97
|
+
if (kind === 'waiting')
|
|
98
|
+
return { title: `${agentName} needs your input`, body: 'Tap to return to the session' };
|
|
99
|
+
if (kind === 'done')
|
|
100
|
+
return { title: `${agentName} finished`, body: 'Tap to see the result' };
|
|
101
|
+
return { title: `${agentName} exited`, body: 'Tap to see the result' };
|
|
102
|
+
}
|
|
103
|
+
export function terminalExitText(locale, exitCode) {
|
|
104
|
+
if (isZhLocale(locale)) {
|
|
105
|
+
return {
|
|
106
|
+
title: '终端会话已退出',
|
|
107
|
+
body: exitCode !== null ? `进程已退出(代码 ${exitCode}),点按查看` : '进程已退出,点按查看',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
title: 'Terminal session ended',
|
|
112
|
+
body: exitCode !== null ? `Process exited (code ${exitCode}). Tap to view.` : 'Process exited. Tap to view.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function sendPayload(options) {
|
|
71
116
|
const current = readStore();
|
|
72
117
|
const invalidEndpoints = new Set();
|
|
73
|
-
await Promise.all(current.subscriptions.filter((entry) => entry.
|
|
118
|
+
await Promise.all(current.subscriptions.filter((entry) => (options.enabled(entry) && !options.skipClientIds?.has(entry.clientId))).map(async (entry) => {
|
|
74
119
|
try {
|
|
75
120
|
const subscription = {
|
|
76
121
|
endpoint: entry.endpoint,
|
|
@@ -78,12 +123,12 @@ async function sendPayload(payload) {
|
|
|
78
123
|
keys: entry.keys,
|
|
79
124
|
};
|
|
80
125
|
await webpush.sendNotification(subscription, JSON.stringify({
|
|
81
|
-
...
|
|
126
|
+
...options.build(entry.locale),
|
|
82
127
|
alertStyle: entry.alertStyle,
|
|
83
128
|
locale: entry.locale,
|
|
84
129
|
}), {
|
|
85
|
-
TTL: 60 * 60,
|
|
86
|
-
urgency:
|
|
130
|
+
TTL: options.ttlSeconds ?? 60 * 60,
|
|
131
|
+
urgency: options.urgency ?? 'normal',
|
|
87
132
|
});
|
|
88
133
|
}
|
|
89
134
|
catch (error) {
|
|
@@ -104,39 +149,80 @@ async function sendPayload(payload) {
|
|
|
104
149
|
export function notifyAgentTransition(sessionId, previous, current) {
|
|
105
150
|
if (!shouldNotifyAgentTransition(previous, current))
|
|
106
151
|
return;
|
|
107
|
-
const agentExited = current.agentStatus === null
|
|
108
|
-
|
|
152
|
+
const agentExited = (current.agentStatus === null || current.agentStatus === 'idle')
|
|
153
|
+
&& (previous?.agentStatus === 'working' || previous?.agentStatus === 'waiting');
|
|
154
|
+
const kind = (agentExited ? 'exited' : current.agentStatus);
|
|
109
155
|
const agentName = current.agent?.displayName ?? previous?.agent?.displayName ?? 'Agent';
|
|
110
|
-
const title = kind === 'waiting'
|
|
111
|
-
? `${agentName} 需要你的处理`
|
|
112
|
-
: kind === 'done'
|
|
113
|
-
? `${agentName} 已完成`
|
|
114
|
-
: `${agentName} 已退出`;
|
|
115
156
|
void sendPayload({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
enabled: (entry) => entry.aiEnabled,
|
|
158
|
+
skipClientIds: getViewingPushClientIds(sessionId),
|
|
159
|
+
urgency: kind === 'waiting' ? 'high' : 'normal',
|
|
160
|
+
ttlSeconds: kind === 'waiting' ? 24 * 60 * 60 : 60 * 60,
|
|
161
|
+
build: (locale) => {
|
|
162
|
+
const text = agentNotificationText(locale, kind, agentName);
|
|
163
|
+
return {
|
|
164
|
+
kind,
|
|
165
|
+
title: text.title,
|
|
166
|
+
body: current.agentMessage ?? text.body,
|
|
167
|
+
// Same tag for all transitions of one session → new notification
|
|
168
|
+
// replaces the old one (key behaviour on iOS where getNotifications()
|
|
169
|
+
// is unavailable for programmatic cleanup).
|
|
170
|
+
tag: `agent:${sessionId}`,
|
|
171
|
+
// Per-transition key so the 5 s dedup window only suppresses
|
|
172
|
+
// duplicate deliveries of the same transition, not later ones.
|
|
173
|
+
dedupKey: `agent:${sessionId}:${kind}:${current.agentActivity}`,
|
|
174
|
+
sessionId,
|
|
175
|
+
url: `/?session=${encodeURIComponent(sessionId)}`,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Push for a terminal process that exited on its own (not a user-initiated
|
|
182
|
+
* close — those dispose the exit listener before killing the pty). Sessions
|
|
183
|
+
* with a live agent are covered by notifyAgentTransition instead, so the
|
|
184
|
+
* caller skips those to avoid double notifications for one event.
|
|
185
|
+
*/
|
|
186
|
+
export function notifyTerminalExit(sessionId, exitCode) {
|
|
187
|
+
void sendPayload({
|
|
188
|
+
enabled: (entry) => entry.exitEnabled,
|
|
189
|
+
skipClientIds: getViewingPushClientIds(sessionId),
|
|
190
|
+
build: (locale) => {
|
|
191
|
+
const text = terminalExitText(locale, exitCode);
|
|
192
|
+
return {
|
|
193
|
+
kind: 'exit',
|
|
194
|
+
title: text.title,
|
|
195
|
+
body: text.body,
|
|
196
|
+
tag: `exit:${sessionId}`,
|
|
197
|
+
dedupKey: `exit:${sessionId}:${exitCode ?? 'signal'}`,
|
|
198
|
+
sessionId,
|
|
199
|
+
url: `/?session=${encodeURIComponent(sessionId)}`,
|
|
200
|
+
};
|
|
201
|
+
},
|
|
128
202
|
});
|
|
129
203
|
}
|
|
130
204
|
export function shouldNotifyAgentTransition(previous, current) {
|
|
131
205
|
if (!previous)
|
|
132
206
|
return false;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
// Only status or activity changes carry new information. A `reviewed` flip
|
|
208
|
+
// is the user's own acknowledgement (or a stop stamping an unread turn) and
|
|
209
|
+
// must not re-notify by itself — the previous reviewed-based gate here
|
|
210
|
+
// silently suppressed every mid-turn waiting event, because reviewed stays
|
|
211
|
+
// true while an agent is working or waiting and only `stop` clears it.
|
|
212
|
+
const stateChanged = previous.agentStatus !== current.agentStatus
|
|
213
|
+
|| previous.agentActivity !== current.agentActivity;
|
|
214
|
+
if (!stateChanged)
|
|
215
|
+
return false;
|
|
216
|
+
if (current.agentStatus === 'waiting' || current.agentStatus === 'done') {
|
|
217
|
+
// waiting: any new wait event (entering waiting, or a new activity round
|
|
218
|
+
// while waiting) deserves a push. done: only entering done — a second
|
|
219
|
+
// stop for the same turn, or an ack-only broadcast, must stay silent.
|
|
220
|
+
return current.agentStatus === 'waiting' || previous.agentStatus !== 'done';
|
|
221
|
+
}
|
|
222
|
+
// Agent exited: from working or waiting (including a hook-emitted
|
|
223
|
+
// session-end that lands on 'idle' before the process poll clears the
|
|
224
|
+
// session). done → exit is deliberately silent — the completion was
|
|
225
|
+
// already notified.
|
|
226
|
+
return (current.agentStatus === null || current.agentStatus === 'idle')
|
|
227
|
+
&& (previous.agentStatus === 'working' || previous.agentStatus === 'waiting');
|
|
142
228
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks which push-subscribed clients are actively viewing which terminal
|
|
3
|
+
* session, so agent-transition pushes can skip the very client that is
|
|
4
|
+
* already looking at the session (its notification would be redundant).
|
|
5
|
+
*
|
|
6
|
+
* The per-terminal WebSocket reports viewing state (session active + document
|
|
7
|
+
* visible + window focused + stream ready — deliberately NOT textarea focus,
|
|
8
|
+
* since mobile users routinely watch output with the keyboard dismissed) on
|
|
9
|
+
* every change, including a viewing=false when the user switches sessions,
|
|
10
|
+
* backgrounds the app, or the window blurs — and the WS close handler clears
|
|
11
|
+
* the entry when the connection dies (e.g. the iOS PWA was killed without a
|
|
12
|
+
* clean viewing=false).
|
|
13
|
+
*/
|
|
14
|
+
const viewingSessionsByClient = new Map();
|
|
15
|
+
export function setClientViewingSession(pushClientId, sessionId, viewing) {
|
|
16
|
+
if (!pushClientId || !sessionId)
|
|
17
|
+
return;
|
|
18
|
+
if (!viewing) {
|
|
19
|
+
const sessions = viewingSessionsByClient.get(pushClientId);
|
|
20
|
+
if (!sessions)
|
|
21
|
+
return;
|
|
22
|
+
sessions.delete(sessionId);
|
|
23
|
+
if (sessions.size === 0)
|
|
24
|
+
viewingSessionsByClient.delete(pushClientId);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let sessions = viewingSessionsByClient.get(pushClientId);
|
|
28
|
+
if (!sessions) {
|
|
29
|
+
sessions = new Set();
|
|
30
|
+
viewingSessionsByClient.set(pushClientId, sessions);
|
|
31
|
+
}
|
|
32
|
+
sessions.add(sessionId);
|
|
33
|
+
}
|
|
34
|
+
/** Push subscription clientIds currently looking at the given session. */
|
|
35
|
+
export function getViewingPushClientIds(sessionId) {
|
|
36
|
+
const result = new Set();
|
|
37
|
+
if (!sessionId)
|
|
38
|
+
return result;
|
|
39
|
+
for (const [pushClientId, sessions] of viewingSessionsByClient) {
|
|
40
|
+
if (sessions.has(sessionId))
|
|
41
|
+
result.add(pushClientId);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|