jialing-code 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -0
- package/dist/chunk-065m026w.js +97 -0
- package/dist/chunk-06saje2v.js +39 -0
- package/dist/chunk-0731m51q.js +8 -0
- package/dist/chunk-0975ccyw.js +265 -0
- package/dist/chunk-09n3fjx5.js +67 -0
- package/dist/chunk-0bdjdzew.js +41 -0
- package/dist/chunk-0ce6kd7y.js +212 -0
- package/dist/chunk-0e1xsncc.js +969 -0
- package/dist/chunk-0m2861gw.js +154 -0
- package/dist/chunk-0paqc2yw.js +15 -0
- package/dist/chunk-0rg6yrhy.js +99 -0
- package/dist/chunk-0sakxrcf.js +643 -0
- package/dist/chunk-0v9hwxvz.js +105 -0
- package/dist/chunk-0vkfrmqm.js +690 -0
- package/dist/chunk-0vvfnhhv.js +248 -0
- package/dist/chunk-0w4rsycj.js +145 -0
- package/dist/chunk-0x9b2nmd.js +63 -0
- package/dist/chunk-0xjaqda8.js +1124 -0
- package/dist/chunk-1141xmr4.js +8 -0
- package/dist/chunk-13d1842d.js +154 -0
- package/dist/chunk-1921a6yb.js +32 -0
- package/dist/chunk-1ad8mk9g.js +527 -0
- package/dist/chunk-1ax41pws.js +28 -0
- package/dist/chunk-1c8z1b5v.js +16 -0
- package/dist/chunk-1cbn5kxf.js +7 -0
- package/dist/chunk-1eapde8a.js +726 -0
- package/dist/chunk-1h2famwb.js +49 -0
- package/dist/chunk-1jggnf7z.js +85 -0
- package/dist/chunk-1k92pn9c.js +32 -0
- package/dist/chunk-1qakq4sn.js +802 -0
- package/dist/chunk-1rvz0433.js +722 -0
- package/dist/chunk-1xkekb9y.js +19 -0
- package/dist/chunk-238g70xa.js +36 -0
- package/dist/chunk-24ge0eqa.js +106 -0
- package/dist/chunk-278vghwm.js +5395 -0
- package/dist/chunk-27xc1csx.js +104 -0
- package/dist/chunk-2a42s11t.js +412 -0
- package/dist/chunk-2aa02aw6.js +438 -0
- package/dist/chunk-2eykm0j0.js +20 -0
- package/dist/chunk-2gzv8nrw.js +205 -0
- package/dist/chunk-2k995y2x.js +880 -0
- package/dist/chunk-2nayx6q1.js +63 -0
- package/dist/chunk-2sd2w2h4.js +44 -0
- package/dist/chunk-2tw2ve8h.js +145 -0
- package/dist/chunk-30rst83v.js +168 -0
- package/dist/chunk-3aavh06g.js +1581 -0
- package/dist/chunk-3b0yvt2h.js +469 -0
- package/dist/chunk-3be7ka25.js +56 -0
- package/dist/chunk-3c25bcsw.js +17 -0
- package/dist/chunk-3dyxka97.js +584 -0
- package/dist/chunk-3f7rypmf.js +16544 -0
- package/dist/chunk-3fsd7f51.js +93 -0
- package/dist/chunk-3gqdqmzb.js +75 -0
- package/dist/chunk-3h8a89gy.js +46 -0
- package/dist/chunk-3m84sret.js +71 -0
- package/dist/chunk-3nb7j3js.js +96 -0
- package/dist/chunk-3p08nvn2.js +98 -0
- package/dist/chunk-3r09htpc.js +318 -0
- package/dist/chunk-3r24h7t6.js +113 -0
- package/dist/chunk-3rkfxjtq.js +80 -0
- package/dist/chunk-3tmk7dc2.js +604 -0
- package/dist/chunk-3w6s9m5w.js +16 -0
- package/dist/chunk-43qjymy5.js +92 -0
- package/dist/chunk-44fpr6jq.js +434 -0
- package/dist/chunk-45kxdsp8.js +444 -0
- package/dist/chunk-495d85x1.js +8066 -0
- package/dist/chunk-4ba796se.js +1095 -0
- package/dist/chunk-4g3v8y12.js +23 -0
- package/dist/chunk-4h53xj8n.js +3445 -0
- package/dist/chunk-4jm600zv.js +13 -0
- package/dist/chunk-4jy9dtwk.js +116 -0
- package/dist/chunk-4p60dd45.js +55 -0
- package/dist/chunk-4z0jsrqg.js +81 -0
- package/dist/chunk-4zfkzkt6.js +37 -0
- package/dist/chunk-55wgxwa9.js +13877 -0
- package/dist/chunk-5dbk24zg.js +890 -0
- package/dist/chunk-5hzvp4va.js +115 -0
- package/dist/chunk-5kbt1mbt.js +500 -0
- package/dist/chunk-5pen7vr8.js +176 -0
- package/dist/chunk-5r280eng.js +198 -0
- package/dist/chunk-5srym52s.js +88 -0
- package/dist/chunk-600kg7k6.js +41 -0
- package/dist/chunk-616w0qj3.js +160 -0
- package/dist/chunk-62jw1t8c.js +94 -0
- package/dist/chunk-62xmt9mk.js +372 -0
- package/dist/chunk-64bvg7c3.js +229 -0
- package/dist/chunk-65yhe4s0.js +281 -0
- package/dist/chunk-6aewh27m.js +102 -0
- package/dist/chunk-6bd8brc4.js +48 -0
- package/dist/chunk-6dj5t602.js +341 -0
- package/dist/chunk-6exxdk1p.js +6956 -0
- package/dist/chunk-6f5j8fs0.js +752 -0
- package/dist/chunk-6g62sjpf.js +328 -0
- package/dist/chunk-6gecq2ta.js +1493 -0
- package/dist/chunk-6gr3c3w9.js +378 -0
- package/dist/chunk-6kpbgc5w.js +23 -0
- package/dist/chunk-6kseqw79.js +154 -0
- package/dist/chunk-6mxm1qd0.js +40764 -0
- package/dist/chunk-6n2qgm9v.js +8 -0
- package/dist/chunk-6tq2v3rk.js +186 -0
- package/dist/chunk-6wsdhj3v.js +93 -0
- package/dist/chunk-6zw9fhgb.js +4389 -0
- package/dist/chunk-71grc1mw.js +111 -0
- package/dist/chunk-73rpbt04.js +1947 -0
- package/dist/chunk-748feghg.js +604 -0
- package/dist/chunk-75e8gtg9.js +84 -0
- package/dist/chunk-75th4717.js +780 -0
- package/dist/chunk-760252na.js +101 -0
- package/dist/chunk-7739pg2c.js +4261 -0
- package/dist/chunk-778fnx46.js +472 -0
- package/dist/chunk-77g09znh.js +391 -0
- package/dist/chunk-7aw745vx.js +40175 -0
- package/dist/chunk-7m2nd8da.js +110 -0
- package/dist/chunk-7n35vjtw.js +444 -0
- package/dist/chunk-7n5ss4sh.js +144 -0
- package/dist/chunk-7rjnxcfe.js +107 -0
- package/dist/chunk-7sb5axvf.js +122 -0
- package/dist/chunk-7wm5s02e.js +216 -0
- package/dist/chunk-7ymfj7m3.js +151 -0
- package/dist/chunk-7z8j9qfn.js +121 -0
- package/dist/chunk-7zsapntc.js +216 -0
- package/dist/chunk-805fhkfh.js +908 -0
- package/dist/chunk-80k1nj24.js +253 -0
- package/dist/chunk-83hfzbx3.js +10 -0
- package/dist/chunk-85cypsdd.js +92 -0
- package/dist/chunk-8760caxf.js +2644 -0
- package/dist/chunk-88f6egg6.js +387 -0
- package/dist/chunk-88r7kwgj.js +22 -0
- package/dist/chunk-89e1v45e.js +463 -0
- package/dist/chunk-8bedvdm1.js +32 -0
- package/dist/chunk-8c4x4vdz.js +177 -0
- package/dist/chunk-8hq5kk3y.js +44 -0
- package/dist/chunk-8ngxagxq.js +18 -0
- package/dist/chunk-8tnsngw2.js +31 -0
- package/dist/chunk-8wjnca8h.js +307 -0
- package/dist/chunk-8y12jxg8.js +10 -0
- package/dist/chunk-8ymf4e6z.js +48 -0
- package/dist/chunk-90wp6wez.js +10510 -0
- package/dist/chunk-92bp5bnf.js +650 -0
- package/dist/chunk-92q8sx5z.js +90 -0
- package/dist/chunk-958rtmtx.js +10476 -0
- package/dist/chunk-9e2kqv5g.js +125 -0
- package/dist/chunk-9f4f1hy5.js +281 -0
- package/dist/chunk-9gbamk79.js +93 -0
- package/dist/chunk-9k5s3ryh.js +167 -0
- package/dist/chunk-9xzfqm15.js +97 -0
- package/dist/chunk-9zgdvbm6.js +110 -0
- package/dist/chunk-a0p3q8jw.js +24 -0
- package/dist/chunk-a22sayzp.js +220 -0
- package/dist/chunk-a8ejc632.js +3094 -0
- package/dist/chunk-a8gj9d9z.js +120 -0
- package/dist/chunk-a9vdeb6y.js +258 -0
- package/dist/chunk-a9yev47v.js +674 -0
- package/dist/chunk-a9zh40sj.js +48 -0
- package/dist/chunk-ack5mfba.js +105 -0
- package/dist/chunk-ae76ded0.js +30 -0
- package/dist/chunk-akjw4dh6.js +538 -0
- package/dist/chunk-asc6wz4q.js +1188 -0
- package/dist/chunk-atqejh3p.js +273 -0
- package/dist/chunk-atv2e6b7.js +61 -0
- package/dist/chunk-axvvkwz8.js +1551 -0
- package/dist/chunk-azgz7kj9.js +38 -0
- package/dist/chunk-b3zave8q.js +275 -0
- package/dist/chunk-b4wg70y1.js +54 -0
- package/dist/chunk-b8b13qn2.js +100 -0
- package/dist/chunk-bg3mt9bm.js +28 -0
- package/dist/chunk-bh4jvcjn.js +91 -0
- package/dist/chunk-bhdt6k7w.js +15 -0
- package/dist/chunk-bm1qb16p.js +17 -0
- package/dist/chunk-bpvsd1j1.js +266 -0
- package/dist/chunk-bqfnp99q.js +477 -0
- package/dist/chunk-bsbmmfyt.js +17 -0
- package/dist/chunk-bsbt34jm.js +42 -0
- package/dist/chunk-c78akdhr.js +458 -0
- package/dist/chunk-cdz5yb0r.js +57 -0
- package/dist/chunk-cfv996bs.js +22 -0
- package/dist/chunk-cgfdkzhb.js +12 -0
- package/dist/chunk-ckh4r5er.js +257 -0
- package/dist/chunk-cmgjnvn9.js +89 -0
- package/dist/chunk-cpjgvay8.js +687 -0
- package/dist/chunk-crfryjx9.js +276 -0
- package/dist/chunk-ctw5jwcd.js +213 -0
- package/dist/chunk-cvy3vntc.js +65 -0
- package/dist/chunk-cwbzz504.js +94 -0
- package/dist/chunk-cy2hswr1.js +15 -0
- package/dist/chunk-cyejkay3.js +222 -0
- package/dist/chunk-d18z9pna.js +106 -0
- package/dist/chunk-d5cq0n0v.js +162 -0
- package/dist/chunk-db45aryp.js +50 -0
- package/dist/chunk-de8nqh0z.js +790 -0
- package/dist/chunk-dfw6h350.js +2336 -0
- package/dist/chunk-dgqrcy74.js +48 -0
- package/dist/chunk-dn75ptgd.js +184 -0
- package/dist/chunk-dnh7jtpb.js +37 -0
- package/dist/chunk-dpshyv9m.js +90 -0
- package/dist/chunk-dt8cdvnm.js +642 -0
- package/dist/chunk-dtxby6fr.js +306 -0
- package/dist/chunk-dv1wfr85.js +6373 -0
- package/dist/chunk-dw363edx.js +1154 -0
- package/dist/chunk-dwrdacck.js +62 -0
- package/dist/chunk-dxnnv5e3.js +276 -0
- package/dist/chunk-e3g8q4cn.js +1646 -0
- package/dist/chunk-e3m9k9s2.js +542 -0
- package/dist/chunk-e4q49asn.js +155 -0
- package/dist/chunk-e5pntxye.js +37 -0
- package/dist/chunk-e7393td6.js +42 -0
- package/dist/chunk-ead42yrh.js +87 -0
- package/dist/chunk-eb90vwvr.js +152 -0
- package/dist/chunk-ecvwk9hg.js +250 -0
- package/dist/chunk-edjd6aay.js +173 -0
- package/dist/chunk-eg0h8mtm.js +130 -0
- package/dist/chunk-ehtwnxpg.js +1591 -0
- package/dist/chunk-ek686gx1.js +94 -0
- package/dist/chunk-eqp1rfft.js +17 -0
- package/dist/chunk-evhwt0ar.js +1015 -0
- package/dist/chunk-ewadzrm8.js +103 -0
- package/dist/chunk-ewsgsw9h.js +172291 -0
- package/dist/chunk-f0pa0r7e.js +3198 -0
- package/dist/chunk-f2mhrmww.js +62 -0
- package/dist/chunk-f39zxvwn.js +401 -0
- package/dist/chunk-f46z54tq.js +374 -0
- package/dist/chunk-f5ma3nh5.js +3436 -0
- package/dist/chunk-f60q23az.js +3038 -0
- package/dist/chunk-f6v2nz57.js +107 -0
- package/dist/chunk-f6yjan38.js +38 -0
- package/dist/chunk-fbv4apne.js +51 -0
- package/dist/chunk-fem4s778.js +70 -0
- package/dist/chunk-fezm1kn8.js +376 -0
- package/dist/chunk-fm1n3ysp.js +6108 -0
- package/dist/chunk-frg83bfw.js +225 -0
- package/dist/chunk-fscm8db0.js +692 -0
- package/dist/chunk-fyc5fepv.js +10 -0
- package/dist/chunk-g0j0t6qk.js +26 -0
- package/dist/chunk-g0nc1ftf.js +849 -0
- package/dist/chunk-g338npwr.js +1061 -0
- package/dist/chunk-g3t0act8.js +8 -0
- package/dist/chunk-g75w4hw3.js +26 -0
- package/dist/chunk-g8vp82en.js +1866 -0
- package/dist/chunk-gax0fcbx.js +182 -0
- package/dist/chunk-gsz4dh3y.js +606 -0
- package/dist/chunk-gtfffm7h.js +440 -0
- package/dist/chunk-gver9zb9.js +420 -0
- package/dist/chunk-gyj242zr.js +20 -0
- package/dist/chunk-h0qngp9w.js +157 -0
- package/dist/chunk-h14kemnk.js +123 -0
- package/dist/chunk-h8wj1a74.js +8960 -0
- package/dist/chunk-h9mcb2eg.js +87 -0
- package/dist/chunk-h9nh2980.js +91 -0
- package/dist/chunk-hakdhagh.js +16606 -0
- package/dist/chunk-hbc6ymdv.js +55 -0
- package/dist/chunk-hh7cmy4k.js +20 -0
- package/dist/chunk-hjhc4cpt.js +69 -0
- package/dist/chunk-hjwez8qc.js +5018 -0
- package/dist/chunk-hk9xz7gk.js +118 -0
- package/dist/chunk-hqmz36b3.js +552 -0
- package/dist/chunk-hrzerbfw.js +1299 -0
- package/dist/chunk-hs8ph4p8.js +8 -0
- package/dist/chunk-ht1xwrnd.js +716 -0
- package/dist/chunk-hvmfg2dc.js +338 -0
- package/dist/chunk-hx2tr4ep.js +7189 -0
- package/dist/chunk-hzhe8ygc.js +547 -0
- package/dist/chunk-j2k4p94p.js +55 -0
- package/dist/chunk-j3a4p81y.js +184 -0
- package/dist/chunk-j49g6g3s.js +57 -0
- package/dist/chunk-j5d5hp9h.js +71 -0
- package/dist/chunk-j64ga6ta.js +8035 -0
- package/dist/chunk-j7tyxan1.js +116 -0
- package/dist/chunk-j8v4774z.js +424 -0
- package/dist/chunk-jafes477.js +29 -0
- package/dist/chunk-jd32zbps.js +15 -0
- package/dist/chunk-jdgeec04.js +4249 -0
- package/dist/chunk-jftd4jq5.js +4521 -0
- package/dist/chunk-jkxqhv6y.js +812 -0
- package/dist/chunk-jmfr6h0n.js +120 -0
- package/dist/chunk-jn4aabmx.js +40 -0
- package/dist/chunk-jnhkref0.js +30 -0
- package/dist/chunk-jsbpm1gz.js +27 -0
- package/dist/chunk-jtcyjc3k.js +5802 -0
- package/dist/chunk-jx817w05.js +11 -0
- package/dist/chunk-jzmz18nn.js +65 -0
- package/dist/chunk-k0p9w03v.js +4957 -0
- package/dist/chunk-k3dcdnck.js +89 -0
- package/dist/chunk-k3wadzcd.js +88 -0
- package/dist/chunk-kaeshsk1.js +713 -0
- package/dist/chunk-kbkey9ed.js +522 -0
- package/dist/chunk-kcwfhqbb.js +196 -0
- package/dist/chunk-key6jr7p.js +224 -0
- package/dist/chunk-knay8cy9.js +173 -0
- package/dist/chunk-kr3rg2y1.js +95 -0
- package/dist/chunk-kwekc97v.js +18 -0
- package/dist/chunk-m06q12hg.js +61 -0
- package/dist/chunk-m2mcpbhk.js +43 -0
- package/dist/chunk-m8v220gz.js +71 -0
- package/dist/chunk-mgpqy78h.js +208 -0
- package/dist/chunk-mkejwh4t.js +16 -0
- package/dist/chunk-mptyw5n1.js +107 -0
- package/dist/chunk-mqajm0dp.js +66 -0
- package/dist/chunk-mrksbgxj.js +490 -0
- package/dist/chunk-msjfc5ba.js +246 -0
- package/dist/chunk-mt25echc.js +6157 -0
- package/dist/chunk-mt3jfnr8.js +277 -0
- package/dist/chunk-mtn242d4.js +9300 -0
- package/dist/chunk-mzfkvave.js +169 -0
- package/dist/chunk-n0qaeaa5.js +256 -0
- package/dist/chunk-n1erf6kh.js +22820 -0
- package/dist/chunk-n2avk1r2.js +61 -0
- package/dist/chunk-n6atnpcq.js +765 -0
- package/dist/chunk-n7ttdtk0.js +641 -0
- package/dist/chunk-ncnfzby5.js +822 -0
- package/dist/chunk-ncnr1jtf.js +2058 -0
- package/dist/chunk-nerv0jvx.js +74 -0
- package/dist/chunk-ngd2abep.js +90 -0
- package/dist/chunk-nka1g8f4.js +773 -0
- package/dist/chunk-nnaxkc2z.js +128 -0
- package/dist/chunk-npyrtj8j.js +691 -0
- package/dist/chunk-nq0fxyxh.js +752 -0
- package/dist/chunk-nrmffak7.js +114 -0
- package/dist/chunk-nsz546wq.js +1170 -0
- package/dist/chunk-nt837qt9.js +21 -0
- package/dist/chunk-nv64x1z5.js +86 -0
- package/dist/chunk-nxsh6de2.js +1711 -0
- package/dist/chunk-nz4getcr.js +40 -0
- package/dist/chunk-p1f607pa.js +83 -0
- package/dist/chunk-p2816w9z.js +1486 -0
- package/dist/chunk-p2d5nh3g.js +342 -0
- package/dist/chunk-p4r5y02t.js +90 -0
- package/dist/chunk-p7g7pf3f.js +336 -0
- package/dist/chunk-pb4ad8r2.js +110 -0
- package/dist/chunk-pbrhz0zr.js +716 -0
- package/dist/chunk-ps49ymvj.js +43 -0
- package/dist/chunk-pwnt3veb.js +103 -0
- package/dist/chunk-pwwa7s62.js +11 -0
- package/dist/chunk-px3w0kde.js +299 -0
- package/dist/chunk-pzn777jb.js +756 -0
- package/dist/chunk-q25bjaev.js +15 -0
- package/dist/chunk-q5by3da6.js +8 -0
- package/dist/chunk-q6av622g.js +38 -0
- package/dist/chunk-q6xkvtf2.js +1318 -0
- package/dist/chunk-q81jx1tq.js +476 -0
- package/dist/chunk-qehb6fk5.js +339 -0
- package/dist/chunk-qj1avx1q.js +87 -0
- package/dist/chunk-qn2dxr66.js +105 -0
- package/dist/chunk-qnfx3qtx.js +617 -0
- package/dist/chunk-qp18kd4h.js +48 -0
- package/dist/chunk-qp2qdcda.js +100 -0
- package/dist/chunk-qpjy9tcf.js +133 -0
- package/dist/chunk-qqfa0dqg.js +2343 -0
- package/dist/chunk-qwh3htcz.js +202 -0
- package/dist/chunk-qz2x630m.js +49145 -0
- package/dist/chunk-r3vakcs1.js +496 -0
- package/dist/chunk-r66hz8j6.js +34 -0
- package/dist/chunk-r6b58y3x.js +17 -0
- package/dist/chunk-rekp48rk.js +62 -0
- package/dist/chunk-rn0v1hk8.js +34 -0
- package/dist/chunk-rs1xrs6m.js +534 -0
- package/dist/chunk-rs45skes.js +123 -0
- package/dist/chunk-rzszqp82.js +524 -0
- package/dist/chunk-s274nv8k.js +8 -0
- package/dist/chunk-s3pzvdss.js +50 -0
- package/dist/chunk-s9mesaw0.js +26840 -0
- package/dist/chunk-sd36yzx8.js +954 -0
- package/dist/chunk-sdj9b9wh.js +782 -0
- package/dist/chunk-sdw2q69p.js +227 -0
- package/dist/chunk-sfgp9cc0.js +130 -0
- package/dist/chunk-spqaamnc.js +63 -0
- package/dist/chunk-spzrs4df.js +73 -0
- package/dist/chunk-sxkr7k43.js +55 -0
- package/dist/chunk-szj5wvdy.js +28 -0
- package/dist/chunk-t0rgmccj.js +39 -0
- package/dist/chunk-t1nnzjgg.js +17 -0
- package/dist/chunk-t94fcnt8.js +566 -0
- package/dist/chunk-tjaqa99q.js +125 -0
- package/dist/chunk-tjq2evtw.js +328 -0
- package/dist/chunk-ts2p6bv1.js +224 -0
- package/dist/chunk-ttk5dzz8.js +25 -0
- package/dist/chunk-tw0q7ynt.js +59 -0
- package/dist/chunk-v02jkvgy.js +131 -0
- package/dist/chunk-v084bqt3.js +1529 -0
- package/dist/chunk-v0amythm.js +164 -0
- package/dist/chunk-v1kzp02e.js +785 -0
- package/dist/chunk-v1mgv1et.js +15 -0
- package/dist/chunk-v399mzxk.js +58 -0
- package/dist/chunk-v3gm2day.js +8 -0
- package/dist/chunk-v43hrrxp.js +71 -0
- package/dist/chunk-v6aqb2aj.js +102 -0
- package/dist/chunk-v78fj8by.js +145 -0
- package/dist/chunk-v7qmhjdn.js +1921 -0
- package/dist/chunk-v9smspw2.js +4301 -0
- package/dist/chunk-vdw93s4h.js +2431 -0
- package/dist/chunk-vest0y6x.js +3830 -0
- package/dist/chunk-vf5sd1nq.js +12 -0
- package/dist/chunk-vratq94g.js +349 -0
- package/dist/chunk-vrz7t3m3.js +20 -0
- package/dist/chunk-vtb185z9.js +70 -0
- package/dist/chunk-vx01rvg8.js +654 -0
- package/dist/chunk-vyc0rjyx.js +31 -0
- package/dist/chunk-vyjeh50y.js +2149 -0
- package/dist/chunk-vzpffsza.js +269 -0
- package/dist/chunk-w2ef3kxk.js +3353 -0
- package/dist/chunk-w2z5pqd3.js +336 -0
- package/dist/chunk-w3whm4mn.js +432 -0
- package/dist/chunk-w7wgpqcp.js +157 -0
- package/dist/chunk-waa00cvb.js +301 -0
- package/dist/chunk-wb0qm4es.js +155 -0
- package/dist/chunk-wbkahkdc.js +27 -0
- package/dist/chunk-wfz0qffj.js +3939 -0
- package/dist/chunk-wn0cx0pt.js +283 -0
- package/dist/chunk-wv6gzt9c.js +726 -0
- package/dist/chunk-wx42ycm4.js +998 -0
- package/dist/chunk-wxkpgy05.js +268 -0
- package/dist/chunk-wzpdet3m.js +843 -0
- package/dist/chunk-x0mwzt38.js +714 -0
- package/dist/chunk-x592wbfh.js +244 -0
- package/dist/chunk-x5pqsp9r.js +100 -0
- package/dist/chunk-x679thag.js +825 -0
- package/dist/chunk-x8b7vft8.js +132 -0
- package/dist/chunk-x8cftch7.js +119 -0
- package/dist/chunk-x9q7r885.js +348 -0
- package/dist/chunk-xawkt0vb.js +1585 -0
- package/dist/chunk-xdw5dv00.js +8 -0
- package/dist/chunk-xe9cc860.js +287 -0
- package/dist/chunk-xjr9c3vt.js +101 -0
- package/dist/chunk-xk7zaqta.js +21933 -0
- package/dist/chunk-xktbmawc.js +678 -0
- package/dist/chunk-xn6yw46v.js +140 -0
- package/dist/chunk-xsq9ae7x.js +90 -0
- package/dist/chunk-xszk7n10.js +14358 -0
- package/dist/chunk-xt8a5r1t.js +84 -0
- package/dist/chunk-xydyndkv.js +195 -0
- package/dist/chunk-xzt10yc5.js +60 -0
- package/dist/chunk-y04y95dr.js +91 -0
- package/dist/chunk-y1784krc.js +19 -0
- package/dist/chunk-y1x32h3x.js +128 -0
- package/dist/chunk-y3s7aww5.js +99 -0
- package/dist/chunk-y47cg3kz.js +42 -0
- package/dist/chunk-y7kzvepn.js +469 -0
- package/dist/chunk-y89kbsyc.js +287 -0
- package/dist/chunk-yd87p1c3.js +159 -0
- package/dist/chunk-yf3yw8fw.js +105 -0
- package/dist/chunk-ygbf0ezx.js +1391 -0
- package/dist/chunk-yjbxnshv.js +208 -0
- package/dist/chunk-ynednmqd.js +192 -0
- package/dist/chunk-yvhez44y.js +259 -0
- package/dist/chunk-ywxd4qw4.js +22 -0
- package/dist/chunk-yzrmgpy4.js +117 -0
- package/dist/chunk-z0b2vb24.js +862 -0
- package/dist/chunk-z1zj044v.js +655 -0
- package/dist/chunk-z2dp53wn.js +17 -0
- package/dist/chunk-z3w0xasa.js +165 -0
- package/dist/chunk-z7ycmrb3.js +644 -0
- package/dist/chunk-zh11tdkb.js +347 -0
- package/dist/chunk-zk2wsm7d.js +15 -0
- package/dist/chunk-zqfqcf22.js +229 -0
- package/dist/chunk-zqk2j8px.js +699 -0
- package/dist/chunk-zqvsc10j.js +2833 -0
- package/dist/chunk-zsfxha74.js +17655 -0
- package/dist/chunk-zvc4snyb.js +117 -0
- package/dist/cli.js +194 -0
- package/package.json +173 -0
|
@@ -0,0 +1,2431 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
createCapacityWake
|
|
4
|
+
} from "./chunk-ae76ded0.js";
|
|
5
|
+
import {
|
|
6
|
+
getPollIntervalConfig
|
|
7
|
+
} from "./chunk-atv2e6b7.js";
|
|
8
|
+
import {
|
|
9
|
+
buildCCRv2SdkUrl,
|
|
10
|
+
buildSdkUrl,
|
|
11
|
+
decodeWorkSecret,
|
|
12
|
+
registerWorker,
|
|
13
|
+
sameSessionId
|
|
14
|
+
} from "./chunk-cvy3vntc.js";
|
|
15
|
+
import {
|
|
16
|
+
createTokenRefreshScheduler
|
|
17
|
+
} from "./chunk-eb90vwvr.js";
|
|
18
|
+
import {
|
|
19
|
+
$toString,
|
|
20
|
+
init_server
|
|
21
|
+
} from "./chunk-jdgeec04.js";
|
|
22
|
+
import {
|
|
23
|
+
BridgeFatalError,
|
|
24
|
+
FAILED_FOOTER_TEXT,
|
|
25
|
+
TOOL_DISPLAY_EXPIRY_MS,
|
|
26
|
+
buildActiveFooterText,
|
|
27
|
+
buildBridgeConnectUrl,
|
|
28
|
+
buildBridgeSessionUrl,
|
|
29
|
+
buildIdleFooterText,
|
|
30
|
+
createAgentWorktree,
|
|
31
|
+
createBridgeApiClient,
|
|
32
|
+
getRemoteSessionUrl,
|
|
33
|
+
init_bridgeApi,
|
|
34
|
+
init_bridgeStatusUtil,
|
|
35
|
+
init_product,
|
|
36
|
+
init_worktree,
|
|
37
|
+
isExpiredErrorType,
|
|
38
|
+
isSuppressible403,
|
|
39
|
+
removeAgentWorktree,
|
|
40
|
+
timestamp,
|
|
41
|
+
validateBridgeId,
|
|
42
|
+
wrapWithOsc8Link
|
|
43
|
+
} from "./chunk-ewsgsw9h.js";
|
|
44
|
+
import"./chunk-z1zj044v.js";
|
|
45
|
+
import"./chunk-t0rgmccj.js";
|
|
46
|
+
import"./chunk-key6jr7p.js";
|
|
47
|
+
import {
|
|
48
|
+
getTrustedDeviceToken,
|
|
49
|
+
init_trustedDevice
|
|
50
|
+
} from "./chunk-0m2861gw.js";
|
|
51
|
+
import"./chunk-0x9b2nmd.js";
|
|
52
|
+
import"./chunk-89e1v45e.js";
|
|
53
|
+
import"./chunk-2gzv8nrw.js";
|
|
54
|
+
import"./chunk-ehtwnxpg.js";
|
|
55
|
+
import"./chunk-3be7ka25.js";
|
|
56
|
+
import"./chunk-x679thag.js";
|
|
57
|
+
import"./chunk-cgfdkzhb.js";
|
|
58
|
+
import {
|
|
59
|
+
BRIDGE_LOGIN_ERROR,
|
|
60
|
+
DEFAULT_SESSION_TIMEOUT_MS,
|
|
61
|
+
init_types
|
|
62
|
+
} from "./chunk-bhdt6k7w.js";
|
|
63
|
+
import"./chunk-45kxdsp8.js";
|
|
64
|
+
import"./chunk-5dbk24zg.js";
|
|
65
|
+
import {
|
|
66
|
+
init_datadog,
|
|
67
|
+
shutdownDatadog
|
|
68
|
+
} from "./chunk-msjfc5ba.js";
|
|
69
|
+
import"./chunk-7zsapntc.js";
|
|
70
|
+
import"./chunk-0rg6yrhy.js";
|
|
71
|
+
import"./chunk-9gbamk79.js";
|
|
72
|
+
import"./chunk-x0mwzt38.js";
|
|
73
|
+
import {
|
|
74
|
+
debugTruncate,
|
|
75
|
+
describeAxiosError,
|
|
76
|
+
init_debugUtils,
|
|
77
|
+
init_sessionIdCompat,
|
|
78
|
+
toCompatSessionId
|
|
79
|
+
} from "./chunk-9e2kqv5g.js";
|
|
80
|
+
import"./chunk-1qakq4sn.js";
|
|
81
|
+
import"./chunk-s9mesaw0.js";
|
|
82
|
+
import"./chunk-7m2nd8da.js";
|
|
83
|
+
import"./chunk-ps49ymvj.js";
|
|
84
|
+
import"./chunk-g338npwr.js";
|
|
85
|
+
import"./chunk-zk2wsm7d.js";
|
|
86
|
+
import"./chunk-tw0q7ynt.js";
|
|
87
|
+
import"./chunk-xe9cc860.js";
|
|
88
|
+
import"./chunk-m06q12hg.js";
|
|
89
|
+
import"./chunk-ygbf0ezx.js";
|
|
90
|
+
import"./chunk-spqaamnc.js";
|
|
91
|
+
import"./chunk-4jm600zv.js";
|
|
92
|
+
import"./chunk-qz2x630m.js";
|
|
93
|
+
import"./chunk-7sb5axvf.js";
|
|
94
|
+
import"./chunk-v9smspw2.js";
|
|
95
|
+
import {
|
|
96
|
+
BRIDGE_FAILED_INDICATOR,
|
|
97
|
+
BRIDGE_READY_INDICATOR,
|
|
98
|
+
BRIDGE_SPINNER_FRAMES,
|
|
99
|
+
checkGate_CACHED_OR_BLOCKING,
|
|
100
|
+
init_figures,
|
|
101
|
+
init_firstPartyEventLogger,
|
|
102
|
+
init_growthbook,
|
|
103
|
+
init_sleep,
|
|
104
|
+
init_source,
|
|
105
|
+
shutdown1PEventLogging,
|
|
106
|
+
sleep,
|
|
107
|
+
source_default
|
|
108
|
+
} from "./chunk-3f7rypmf.js";
|
|
109
|
+
import"./chunk-e3g8q4cn.js";
|
|
110
|
+
import"./chunk-ctw5jwcd.js";
|
|
111
|
+
import"./chunk-0e1xsncc.js";
|
|
112
|
+
import"./chunk-vratq94g.js";
|
|
113
|
+
import"./chunk-g0j0t6qk.js";
|
|
114
|
+
import"./chunk-6kpbgc5w.js";
|
|
115
|
+
import"./chunk-3c25bcsw.js";
|
|
116
|
+
import"./chunk-tjaqa99q.js";
|
|
117
|
+
import"./chunk-55wgxwa9.js";
|
|
118
|
+
import {
|
|
119
|
+
init_bundledMode,
|
|
120
|
+
isInBundledMode
|
|
121
|
+
} from "./chunk-w2z5pqd3.js";
|
|
122
|
+
import"./chunk-7ymfj7m3.js";
|
|
123
|
+
import"./chunk-f5ma3nh5.js";
|
|
124
|
+
import"./chunk-v1kzp02e.js";
|
|
125
|
+
import"./chunk-1eapde8a.js";
|
|
126
|
+
import"./chunk-p2816w9z.js";
|
|
127
|
+
import"./chunk-0vkfrmqm.js";
|
|
128
|
+
import"./chunk-0xjaqda8.js";
|
|
129
|
+
import {
|
|
130
|
+
formatDuration,
|
|
131
|
+
init_format,
|
|
132
|
+
init_stringWidth,
|
|
133
|
+
stringWidth,
|
|
134
|
+
truncateToWidth
|
|
135
|
+
} from "./chunk-1rvz0433.js";
|
|
136
|
+
import"./chunk-cdz5yb0r.js";
|
|
137
|
+
import {
|
|
138
|
+
init_analytics,
|
|
139
|
+
logEvent,
|
|
140
|
+
logEventAsync
|
|
141
|
+
} from "./chunk-f2mhrmww.js";
|
|
142
|
+
import"./chunk-7z8j9qfn.js";
|
|
143
|
+
import {
|
|
144
|
+
init_diagLogs,
|
|
145
|
+
logForDiagnosticsNoPII
|
|
146
|
+
} from "./chunk-hrzerbfw.js";
|
|
147
|
+
import"./chunk-d18z9pna.js";
|
|
148
|
+
import"./chunk-qnfx3qtx.js";
|
|
149
|
+
import"./chunk-hjhc4cpt.js";
|
|
150
|
+
import"./chunk-vyc0rjyx.js";
|
|
151
|
+
import"./chunk-hx2tr4ep.js";
|
|
152
|
+
import {
|
|
153
|
+
init_log,
|
|
154
|
+
logError
|
|
155
|
+
} from "./chunk-p7g7pf3f.js";
|
|
156
|
+
import"./chunk-8tnsngw2.js";
|
|
157
|
+
import"./chunk-8bedvdm1.js";
|
|
158
|
+
import"./chunk-7wm5s02e.js";
|
|
159
|
+
import {
|
|
160
|
+
errorMessage,
|
|
161
|
+
init_debug,
|
|
162
|
+
init_errors,
|
|
163
|
+
init_slowOperations,
|
|
164
|
+
jsonParse,
|
|
165
|
+
jsonStringify,
|
|
166
|
+
logForDebugging
|
|
167
|
+
} from "./chunk-nxsh6de2.js";
|
|
168
|
+
import"./chunk-fbv4apne.js";
|
|
169
|
+
import {
|
|
170
|
+
init_envUtils,
|
|
171
|
+
isEnvTruthy,
|
|
172
|
+
isInProtectedNamespace
|
|
173
|
+
} from "./chunk-3r24h7t6.js";
|
|
174
|
+
import"./chunk-qqfa0dqg.js";
|
|
175
|
+
import"./chunk-hqmz36b3.js";
|
|
176
|
+
import"./chunk-4g3v8y12.js";
|
|
177
|
+
import"./chunk-7739pg2c.js";
|
|
178
|
+
import"./chunk-xszk7n10.js";
|
|
179
|
+
import"./chunk-sdj9b9wh.js";
|
|
180
|
+
import {
|
|
181
|
+
__require
|
|
182
|
+
} from "./chunk-qp2qdcda.js";
|
|
183
|
+
|
|
184
|
+
// src/bridge/bridgeMain.ts
|
|
185
|
+
init_product();
|
|
186
|
+
init_datadog();
|
|
187
|
+
init_firstPartyEventLogger();
|
|
188
|
+
init_growthbook();
|
|
189
|
+
init_analytics();
|
|
190
|
+
init_bundledMode();
|
|
191
|
+
init_debug();
|
|
192
|
+
init_diagLogs();
|
|
193
|
+
init_envUtils();
|
|
194
|
+
init_errors();
|
|
195
|
+
init_format();
|
|
196
|
+
init_log();
|
|
197
|
+
init_sleep();
|
|
198
|
+
init_worktree();
|
|
199
|
+
init_bridgeApi();
|
|
200
|
+
init_bridgeStatusUtil();
|
|
201
|
+
import { randomUUID } from "crypto";
|
|
202
|
+
import { hostname, tmpdir as tmpdir2 } from "os";
|
|
203
|
+
import { basename, join as join2, resolve } from "path";
|
|
204
|
+
|
|
205
|
+
// src/bridge/bridgeUI.ts
|
|
206
|
+
init_source();
|
|
207
|
+
init_server();
|
|
208
|
+
init_figures();
|
|
209
|
+
init_stringWidth();
|
|
210
|
+
init_debug();
|
|
211
|
+
init_bridgeStatusUtil();
|
|
212
|
+
var QR_OPTIONS = {
|
|
213
|
+
type: "utf8",
|
|
214
|
+
errorCorrectionLevel: "L",
|
|
215
|
+
small: true
|
|
216
|
+
};
|
|
217
|
+
async function generateQr(url) {
|
|
218
|
+
const qr = await $toString(url, QR_OPTIONS);
|
|
219
|
+
return qr.split(`
|
|
220
|
+
`).filter((line) => line.length > 0);
|
|
221
|
+
}
|
|
222
|
+
function createBridgeLogger(options) {
|
|
223
|
+
const write = options.write ?? ((s) => process.stdout.write(s));
|
|
224
|
+
const verbose = options.verbose;
|
|
225
|
+
let statusLineCount = 0;
|
|
226
|
+
let currentState = "idle";
|
|
227
|
+
let currentStateText = "Ready";
|
|
228
|
+
let repoName = "";
|
|
229
|
+
let branch = "";
|
|
230
|
+
let debugLogPath = "";
|
|
231
|
+
let connectUrl = "";
|
|
232
|
+
let cachedIngressUrl = "";
|
|
233
|
+
let cachedEnvironmentId = "";
|
|
234
|
+
let activeSessionUrl = null;
|
|
235
|
+
let qrLines = [];
|
|
236
|
+
let qrVisible = false;
|
|
237
|
+
let lastToolSummary = null;
|
|
238
|
+
let lastToolTime = 0;
|
|
239
|
+
let sessionActive = 0;
|
|
240
|
+
let sessionMax = 1;
|
|
241
|
+
let spawnModeDisplay = null;
|
|
242
|
+
let spawnMode = "single-session";
|
|
243
|
+
const sessionDisplayInfo = new Map;
|
|
244
|
+
let connectingTimer = null;
|
|
245
|
+
let connectingTick = 0;
|
|
246
|
+
function countVisualLines(text) {
|
|
247
|
+
const cols = process.stdout.columns || 80;
|
|
248
|
+
let count = 0;
|
|
249
|
+
for (const logical of text.split(`
|
|
250
|
+
`)) {
|
|
251
|
+
if (logical.length === 0) {
|
|
252
|
+
count++;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const width = stringWidth(logical);
|
|
256
|
+
count += Math.max(1, Math.ceil(width / cols));
|
|
257
|
+
}
|
|
258
|
+
if (text.endsWith(`
|
|
259
|
+
`)) {
|
|
260
|
+
count--;
|
|
261
|
+
}
|
|
262
|
+
return count;
|
|
263
|
+
}
|
|
264
|
+
function writeStatus(text) {
|
|
265
|
+
write(text);
|
|
266
|
+
statusLineCount += countVisualLines(text);
|
|
267
|
+
}
|
|
268
|
+
function clearStatusLines() {
|
|
269
|
+
if (statusLineCount <= 0)
|
|
270
|
+
return;
|
|
271
|
+
logForDebugging(`[bridge:ui] clearStatusLines count=${statusLineCount}`);
|
|
272
|
+
write(`\x1B[${statusLineCount}A`);
|
|
273
|
+
write("\x1B[J");
|
|
274
|
+
statusLineCount = 0;
|
|
275
|
+
}
|
|
276
|
+
function printLog(line) {
|
|
277
|
+
clearStatusLines();
|
|
278
|
+
write(line);
|
|
279
|
+
}
|
|
280
|
+
function regenerateQr(url) {
|
|
281
|
+
generateQr(url).then((lines) => {
|
|
282
|
+
qrLines = lines;
|
|
283
|
+
renderStatusLine();
|
|
284
|
+
}).catch((e) => {
|
|
285
|
+
logForDebugging(`QR code generation failed: ${e}`, { level: "error" });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function renderConnectingLine() {
|
|
289
|
+
clearStatusLines();
|
|
290
|
+
const frame = BRIDGE_SPINNER_FRAMES[connectingTick % BRIDGE_SPINNER_FRAMES.length];
|
|
291
|
+
let suffix = "";
|
|
292
|
+
if (repoName) {
|
|
293
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(repoName);
|
|
294
|
+
}
|
|
295
|
+
if (branch) {
|
|
296
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(branch);
|
|
297
|
+
}
|
|
298
|
+
writeStatus(`${source_default.yellow(frame)} ${source_default.yellow("Connecting")}${suffix}
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
function startConnecting() {
|
|
302
|
+
stopConnecting();
|
|
303
|
+
renderConnectingLine();
|
|
304
|
+
connectingTimer = setInterval(() => {
|
|
305
|
+
connectingTick++;
|
|
306
|
+
renderConnectingLine();
|
|
307
|
+
}, 150);
|
|
308
|
+
}
|
|
309
|
+
function stopConnecting() {
|
|
310
|
+
if (connectingTimer) {
|
|
311
|
+
clearInterval(connectingTimer);
|
|
312
|
+
connectingTimer = null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function renderStatusLine() {
|
|
316
|
+
if (currentState === "reconnecting" || currentState === "failed") {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
clearStatusLines();
|
|
320
|
+
const isIdle = currentState === "idle";
|
|
321
|
+
if (qrVisible) {
|
|
322
|
+
for (const line of qrLines) {
|
|
323
|
+
writeStatus(`${source_default.dim(line)}
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const indicator = BRIDGE_READY_INDICATOR;
|
|
328
|
+
const indicatorColor = isIdle ? source_default.green : source_default.cyan;
|
|
329
|
+
const baseColor = isIdle ? source_default.green : source_default.cyan;
|
|
330
|
+
const stateText = baseColor(currentStateText);
|
|
331
|
+
let suffix = "";
|
|
332
|
+
if (repoName) {
|
|
333
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(repoName);
|
|
334
|
+
}
|
|
335
|
+
if (branch && spawnMode !== "worktree") {
|
|
336
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(branch);
|
|
337
|
+
}
|
|
338
|
+
if (process.env.USER_TYPE === "ant" && debugLogPath) {
|
|
339
|
+
writeStatus(`${source_default.yellow("[ANT-ONLY] Logs:")} ${source_default.dim(debugLogPath)}
|
|
340
|
+
`);
|
|
341
|
+
}
|
|
342
|
+
writeStatus(`${indicatorColor(indicator)} ${stateText}${suffix}
|
|
343
|
+
`);
|
|
344
|
+
if (sessionMax > 1) {
|
|
345
|
+
const modeHint = spawnMode === "worktree" ? "New sessions will be created in an isolated worktree" : "New sessions will be created in the current directory";
|
|
346
|
+
writeStatus(` ${source_default.dim(`Capacity: ${sessionActive}/${sessionMax} \xB7 ${modeHint}`)}
|
|
347
|
+
`);
|
|
348
|
+
for (const [, info] of sessionDisplayInfo) {
|
|
349
|
+
const titleText = info.title ? truncateToWidth(info.title, 35) : source_default.dim("Attached");
|
|
350
|
+
const titleLinked = wrapWithOsc8Link(titleText, info.url);
|
|
351
|
+
const act = info.activity;
|
|
352
|
+
const showAct = act && act.type !== "result" && act.type !== "error";
|
|
353
|
+
const actText = showAct ? source_default.dim(` ${truncateToWidth(act.summary, 40)}`) : "";
|
|
354
|
+
writeStatus(` ${titleLinked}${actText}
|
|
355
|
+
`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (sessionMax === 1) {
|
|
359
|
+
const modeText = spawnMode === "single-session" ? "Single session \xB7 exits when complete" : spawnMode === "worktree" ? `Capacity: ${sessionActive}/1 \xB7 New sessions will be created in an isolated worktree` : `Capacity: ${sessionActive}/1 \xB7 New sessions will be created in the current directory`;
|
|
360
|
+
writeStatus(` ${source_default.dim(modeText)}
|
|
361
|
+
`);
|
|
362
|
+
}
|
|
363
|
+
if (sessionMax === 1 && !isIdle && lastToolSummary && Date.now() - lastToolTime < TOOL_DISPLAY_EXPIRY_MS) {
|
|
364
|
+
writeStatus(` ${source_default.dim(truncateToWidth(lastToolSummary, 60))}
|
|
365
|
+
`);
|
|
366
|
+
}
|
|
367
|
+
const url = activeSessionUrl ?? connectUrl;
|
|
368
|
+
if (url) {
|
|
369
|
+
writeStatus(`
|
|
370
|
+
`);
|
|
371
|
+
const footerText = isIdle ? buildIdleFooterText(url) : buildActiveFooterText(url);
|
|
372
|
+
const qrHint = qrVisible ? source_default.dim.italic("space to hide QR code") : source_default.dim.italic("space to show QR code");
|
|
373
|
+
const toggleHint = spawnModeDisplay ? source_default.dim.italic(" \xB7 w to toggle spawn mode") : "";
|
|
374
|
+
writeStatus(`${source_default.dim(footerText)}
|
|
375
|
+
`);
|
|
376
|
+
writeStatus(`${qrHint}${toggleHint}
|
|
377
|
+
`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
printBanner(config, environmentId) {
|
|
382
|
+
cachedIngressUrl = config.sessionIngressUrl;
|
|
383
|
+
cachedEnvironmentId = environmentId;
|
|
384
|
+
connectUrl = buildBridgeConnectUrl(environmentId, cachedIngressUrl);
|
|
385
|
+
regenerateQr(connectUrl);
|
|
386
|
+
if (verbose) {
|
|
387
|
+
write(source_default.dim(`Remote Control`) + ` v${MACRO.VERSION}
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
390
|
+
if (verbose) {
|
|
391
|
+
if (config.spawnMode !== "single-session") {
|
|
392
|
+
write(source_default.dim(`Spawn mode: `) + `${config.spawnMode}
|
|
393
|
+
`);
|
|
394
|
+
write(source_default.dim(`Max concurrent sessions: `) + `${config.maxSessions}
|
|
395
|
+
`);
|
|
396
|
+
}
|
|
397
|
+
write(source_default.dim(`Environment ID: `) + `${environmentId}
|
|
398
|
+
`);
|
|
399
|
+
}
|
|
400
|
+
if (config.sandbox) {
|
|
401
|
+
write(source_default.dim(`Sandbox: `) + `${source_default.green("Enabled")}
|
|
402
|
+
`);
|
|
403
|
+
}
|
|
404
|
+
write(`
|
|
405
|
+
`);
|
|
406
|
+
startConnecting();
|
|
407
|
+
},
|
|
408
|
+
logSessionStart(sessionId, prompt) {
|
|
409
|
+
if (verbose) {
|
|
410
|
+
const short = truncateToWidth(prompt, 80);
|
|
411
|
+
printLog(source_default.dim(`[${timestamp()}]`) + ` Session started: ${source_default.white(`"${short}"`)} (${source_default.dim(sessionId)})
|
|
412
|
+
`);
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
logSessionComplete(sessionId, durationMs) {
|
|
416
|
+
printLog(source_default.dim(`[${timestamp()}]`) + ` Session ${source_default.green("completed")} (${formatDuration(durationMs)}) ${source_default.dim(sessionId)}
|
|
417
|
+
`);
|
|
418
|
+
},
|
|
419
|
+
logSessionFailed(sessionId, error) {
|
|
420
|
+
printLog(source_default.dim(`[${timestamp()}]`) + ` Session ${source_default.red("failed")}: ${error} ${source_default.dim(sessionId)}
|
|
421
|
+
`);
|
|
422
|
+
},
|
|
423
|
+
logStatus(message) {
|
|
424
|
+
printLog(source_default.dim(`[${timestamp()}]`) + ` ${message}
|
|
425
|
+
`);
|
|
426
|
+
},
|
|
427
|
+
logVerbose(message) {
|
|
428
|
+
if (verbose) {
|
|
429
|
+
printLog(source_default.dim(`[${timestamp()}] ${message}`) + `
|
|
430
|
+
`);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
logError(message) {
|
|
434
|
+
printLog(source_default.red(`[${timestamp()}] Error: ${message}`) + `
|
|
435
|
+
`);
|
|
436
|
+
},
|
|
437
|
+
logReconnected(disconnectedMs) {
|
|
438
|
+
printLog(source_default.dim(`[${timestamp()}]`) + ` ${source_default.green("Reconnected")} after ${formatDuration(disconnectedMs)}
|
|
439
|
+
`);
|
|
440
|
+
},
|
|
441
|
+
setRepoInfo(repo, branchName) {
|
|
442
|
+
repoName = repo;
|
|
443
|
+
branch = branchName;
|
|
444
|
+
},
|
|
445
|
+
setDebugLogPath(path) {
|
|
446
|
+
debugLogPath = path;
|
|
447
|
+
},
|
|
448
|
+
updateIdleStatus() {
|
|
449
|
+
stopConnecting();
|
|
450
|
+
currentState = "idle";
|
|
451
|
+
currentStateText = "Ready";
|
|
452
|
+
lastToolSummary = null;
|
|
453
|
+
lastToolTime = 0;
|
|
454
|
+
activeSessionUrl = null;
|
|
455
|
+
regenerateQr(connectUrl);
|
|
456
|
+
renderStatusLine();
|
|
457
|
+
},
|
|
458
|
+
setAttached(sessionId) {
|
|
459
|
+
stopConnecting();
|
|
460
|
+
currentState = "attached";
|
|
461
|
+
currentStateText = "Connected";
|
|
462
|
+
lastToolSummary = null;
|
|
463
|
+
lastToolTime = 0;
|
|
464
|
+
if (sessionMax <= 1) {
|
|
465
|
+
activeSessionUrl = buildBridgeSessionUrl(sessionId, cachedEnvironmentId, cachedIngressUrl);
|
|
466
|
+
regenerateQr(activeSessionUrl);
|
|
467
|
+
}
|
|
468
|
+
renderStatusLine();
|
|
469
|
+
},
|
|
470
|
+
updateReconnectingStatus(delayStr, elapsedStr) {
|
|
471
|
+
stopConnecting();
|
|
472
|
+
clearStatusLines();
|
|
473
|
+
currentState = "reconnecting";
|
|
474
|
+
if (qrVisible) {
|
|
475
|
+
for (const line of qrLines) {
|
|
476
|
+
writeStatus(`${source_default.dim(line)}
|
|
477
|
+
`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const frame = BRIDGE_SPINNER_FRAMES[connectingTick % BRIDGE_SPINNER_FRAMES.length];
|
|
481
|
+
connectingTick++;
|
|
482
|
+
writeStatus(`${source_default.yellow(frame)} ${source_default.yellow("Reconnecting")} ${source_default.dim("\xB7")} ${source_default.dim(`retrying in ${delayStr}`)} ${source_default.dim("\xB7")} ${source_default.dim(`disconnected ${elapsedStr}`)}
|
|
483
|
+
`);
|
|
484
|
+
},
|
|
485
|
+
updateFailedStatus(error) {
|
|
486
|
+
stopConnecting();
|
|
487
|
+
clearStatusLines();
|
|
488
|
+
currentState = "failed";
|
|
489
|
+
let suffix = "";
|
|
490
|
+
if (repoName) {
|
|
491
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(repoName);
|
|
492
|
+
}
|
|
493
|
+
if (branch) {
|
|
494
|
+
suffix += source_default.dim(" \xB7 ") + source_default.dim(branch);
|
|
495
|
+
}
|
|
496
|
+
writeStatus(`${source_default.red(BRIDGE_FAILED_INDICATOR)} ${source_default.red("Remote Control Failed")}${suffix}
|
|
497
|
+
`);
|
|
498
|
+
writeStatus(`${source_default.dim(FAILED_FOOTER_TEXT)}
|
|
499
|
+
`);
|
|
500
|
+
if (error) {
|
|
501
|
+
writeStatus(`${source_default.red(error)}
|
|
502
|
+
`);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
updateSessionStatus(_sessionId, _elapsed, activity, _trail) {
|
|
506
|
+
if (activity.type === "tool_start") {
|
|
507
|
+
lastToolSummary = activity.summary;
|
|
508
|
+
lastToolTime = Date.now();
|
|
509
|
+
}
|
|
510
|
+
renderStatusLine();
|
|
511
|
+
},
|
|
512
|
+
clearStatus() {
|
|
513
|
+
stopConnecting();
|
|
514
|
+
clearStatusLines();
|
|
515
|
+
},
|
|
516
|
+
toggleQr() {
|
|
517
|
+
qrVisible = !qrVisible;
|
|
518
|
+
renderStatusLine();
|
|
519
|
+
},
|
|
520
|
+
updateSessionCount(active, max, mode) {
|
|
521
|
+
if (sessionActive === active && sessionMax === max && spawnMode === mode)
|
|
522
|
+
return;
|
|
523
|
+
sessionActive = active;
|
|
524
|
+
sessionMax = max;
|
|
525
|
+
spawnMode = mode;
|
|
526
|
+
},
|
|
527
|
+
setSpawnModeDisplay(mode) {
|
|
528
|
+
if (spawnModeDisplay === mode)
|
|
529
|
+
return;
|
|
530
|
+
spawnModeDisplay = mode;
|
|
531
|
+
if (mode)
|
|
532
|
+
spawnMode = mode;
|
|
533
|
+
},
|
|
534
|
+
addSession(sessionId, url) {
|
|
535
|
+
sessionDisplayInfo.set(sessionId, { url });
|
|
536
|
+
},
|
|
537
|
+
updateSessionActivity(sessionId, activity) {
|
|
538
|
+
const info = sessionDisplayInfo.get(sessionId);
|
|
539
|
+
if (!info)
|
|
540
|
+
return;
|
|
541
|
+
info.activity = activity;
|
|
542
|
+
},
|
|
543
|
+
setSessionTitle(sessionId, title) {
|
|
544
|
+
const info = sessionDisplayInfo.get(sessionId);
|
|
545
|
+
if (!info)
|
|
546
|
+
return;
|
|
547
|
+
info.title = title;
|
|
548
|
+
if (currentState === "reconnecting" || currentState === "failed")
|
|
549
|
+
return;
|
|
550
|
+
if (sessionMax === 1) {
|
|
551
|
+
currentState = "titled";
|
|
552
|
+
currentStateText = truncateToWidth(title, 40);
|
|
553
|
+
}
|
|
554
|
+
renderStatusLine();
|
|
555
|
+
},
|
|
556
|
+
removeSession(sessionId) {
|
|
557
|
+
sessionDisplayInfo.delete(sessionId);
|
|
558
|
+
},
|
|
559
|
+
refreshDisplay() {
|
|
560
|
+
if (currentState === "reconnecting" || currentState === "failed")
|
|
561
|
+
return;
|
|
562
|
+
renderStatusLine();
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/bridge/bridgeMain.ts
|
|
568
|
+
init_debugUtils();
|
|
569
|
+
init_sessionIdCompat();
|
|
570
|
+
|
|
571
|
+
// src/bridge/sessionRunner.ts
|
|
572
|
+
init_slowOperations();
|
|
573
|
+
init_debugUtils();
|
|
574
|
+
import { spawn } from "child_process";
|
|
575
|
+
import { createWriteStream } from "fs";
|
|
576
|
+
import { tmpdir } from "os";
|
|
577
|
+
import { dirname, join } from "path";
|
|
578
|
+
import { createInterface } from "readline";
|
|
579
|
+
var MAX_ACTIVITIES = 10;
|
|
580
|
+
var MAX_STDERR_LINES = 10;
|
|
581
|
+
function safeFilenameId(id) {
|
|
582
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
583
|
+
}
|
|
584
|
+
var TOOL_VERBS = {
|
|
585
|
+
Read: "Reading",
|
|
586
|
+
Write: "Writing",
|
|
587
|
+
Edit: "Editing",
|
|
588
|
+
MultiEdit: "Editing",
|
|
589
|
+
Bash: "Running",
|
|
590
|
+
Glob: "Searching",
|
|
591
|
+
Grep: "Searching",
|
|
592
|
+
WebFetch: "Fetching",
|
|
593
|
+
WebSearch: "Searching",
|
|
594
|
+
Task: "Running task",
|
|
595
|
+
FileReadTool: "Reading",
|
|
596
|
+
FileWriteTool: "Writing",
|
|
597
|
+
FileEditTool: "Editing",
|
|
598
|
+
GlobTool: "Searching",
|
|
599
|
+
GrepTool: "Searching",
|
|
600
|
+
BashTool: "Running",
|
|
601
|
+
NotebookEditTool: "Editing notebook",
|
|
602
|
+
LSP: "LSP"
|
|
603
|
+
};
|
|
604
|
+
function toolSummary(name, input) {
|
|
605
|
+
const verb = TOOL_VERBS[name] ?? name;
|
|
606
|
+
const target = input.file_path ?? input.filePath ?? input.pattern ?? input.command?.slice(0, 60) ?? input.url ?? input.query ?? "";
|
|
607
|
+
if (target) {
|
|
608
|
+
return `${verb} ${target}`;
|
|
609
|
+
}
|
|
610
|
+
return verb;
|
|
611
|
+
}
|
|
612
|
+
function extractActivities(line, sessionId, onDebug) {
|
|
613
|
+
let parsed;
|
|
614
|
+
try {
|
|
615
|
+
parsed = jsonParse(line);
|
|
616
|
+
} catch {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
if (!parsed || typeof parsed !== "object") {
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
const msg = parsed;
|
|
623
|
+
const activities = [];
|
|
624
|
+
const now = Date.now();
|
|
625
|
+
switch (msg.type) {
|
|
626
|
+
case "assistant": {
|
|
627
|
+
const message = msg.message;
|
|
628
|
+
if (!message)
|
|
629
|
+
break;
|
|
630
|
+
const content = message.content;
|
|
631
|
+
if (!Array.isArray(content))
|
|
632
|
+
break;
|
|
633
|
+
for (const block of content) {
|
|
634
|
+
if (!block || typeof block !== "object")
|
|
635
|
+
continue;
|
|
636
|
+
const b = block;
|
|
637
|
+
if (b.type === "tool_use") {
|
|
638
|
+
const name = b.name ?? "Tool";
|
|
639
|
+
const input = b.input ?? {};
|
|
640
|
+
const summary = toolSummary(name, input);
|
|
641
|
+
activities.push({
|
|
642
|
+
type: "tool_start",
|
|
643
|
+
summary,
|
|
644
|
+
timestamp: now
|
|
645
|
+
});
|
|
646
|
+
onDebug(`[bridge:activity] sessionId=${sessionId} tool_use name=${name} ${inputPreview(input)}`);
|
|
647
|
+
} else if (b.type === "text") {
|
|
648
|
+
const text = b.text ?? "";
|
|
649
|
+
if (text.length > 0) {
|
|
650
|
+
activities.push({
|
|
651
|
+
type: "text",
|
|
652
|
+
summary: text.slice(0, 80),
|
|
653
|
+
timestamp: now
|
|
654
|
+
});
|
|
655
|
+
onDebug(`[bridge:activity] sessionId=${sessionId} text "${text.slice(0, 100)}"`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
case "result": {
|
|
662
|
+
const subtype = msg.subtype;
|
|
663
|
+
if (subtype === "success") {
|
|
664
|
+
activities.push({
|
|
665
|
+
type: "result",
|
|
666
|
+
summary: "Session completed",
|
|
667
|
+
timestamp: now
|
|
668
|
+
});
|
|
669
|
+
onDebug(`[bridge:activity] sessionId=${sessionId} result subtype=success`);
|
|
670
|
+
} else if (subtype) {
|
|
671
|
+
const errors = msg.errors;
|
|
672
|
+
const errorSummary = errors?.[0] ?? `Error: ${subtype}`;
|
|
673
|
+
activities.push({
|
|
674
|
+
type: "error",
|
|
675
|
+
summary: errorSummary,
|
|
676
|
+
timestamp: now
|
|
677
|
+
});
|
|
678
|
+
onDebug(`[bridge:activity] sessionId=${sessionId} result subtype=${subtype} error="${errorSummary}"`);
|
|
679
|
+
} else {
|
|
680
|
+
onDebug(`[bridge:activity] sessionId=${sessionId} result subtype=undefined`);
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
default:
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
return activities;
|
|
688
|
+
}
|
|
689
|
+
function extractUserMessageText(msg) {
|
|
690
|
+
if (msg.parent_tool_use_id != null || msg.isSynthetic || msg.isReplay)
|
|
691
|
+
return;
|
|
692
|
+
const message = msg.message;
|
|
693
|
+
const content = message?.content;
|
|
694
|
+
let text;
|
|
695
|
+
if (typeof content === "string") {
|
|
696
|
+
text = content;
|
|
697
|
+
} else if (Array.isArray(content)) {
|
|
698
|
+
for (const block of content) {
|
|
699
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
700
|
+
text = block.text;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
text = text?.trim();
|
|
706
|
+
return text ? text : undefined;
|
|
707
|
+
}
|
|
708
|
+
function inputPreview(input) {
|
|
709
|
+
const parts = [];
|
|
710
|
+
for (const [key, val] of Object.entries(input)) {
|
|
711
|
+
if (typeof val === "string") {
|
|
712
|
+
parts.push(`${key}="${val.slice(0, 100)}"`);
|
|
713
|
+
}
|
|
714
|
+
if (parts.length >= 3)
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
return parts.join(" ");
|
|
718
|
+
}
|
|
719
|
+
function createSessionSpawner(deps) {
|
|
720
|
+
return {
|
|
721
|
+
spawn(opts, dir) {
|
|
722
|
+
const safeId = safeFilenameId(opts.sessionId);
|
|
723
|
+
let debugFile;
|
|
724
|
+
if (deps.debugFile) {
|
|
725
|
+
const ext = deps.debugFile.lastIndexOf(".");
|
|
726
|
+
if (ext > 0) {
|
|
727
|
+
debugFile = `${deps.debugFile.slice(0, ext)}-${safeId}${deps.debugFile.slice(ext)}`;
|
|
728
|
+
} else {
|
|
729
|
+
debugFile = `${deps.debugFile}-${safeId}`;
|
|
730
|
+
}
|
|
731
|
+
} else if (deps.verbose || process.env.USER_TYPE === "ant") {
|
|
732
|
+
debugFile = join(tmpdir(), "claude", `bridge-session-${safeId}.log`);
|
|
733
|
+
}
|
|
734
|
+
let transcriptStream = null;
|
|
735
|
+
let transcriptPath;
|
|
736
|
+
if (deps.debugFile) {
|
|
737
|
+
transcriptPath = join(dirname(deps.debugFile), `bridge-transcript-${safeId}.jsonl`);
|
|
738
|
+
transcriptStream = createWriteStream(transcriptPath, { flags: "a" });
|
|
739
|
+
transcriptStream.on("error", (err) => {
|
|
740
|
+
deps.onDebug(`[bridge:session] Transcript write error: ${err.message}`);
|
|
741
|
+
transcriptStream = null;
|
|
742
|
+
});
|
|
743
|
+
deps.onDebug(`[bridge:session] Transcript log: ${transcriptPath}`);
|
|
744
|
+
}
|
|
745
|
+
const args = [
|
|
746
|
+
...deps.scriptArgs,
|
|
747
|
+
"--print",
|
|
748
|
+
"--sdk-url",
|
|
749
|
+
opts.sdkUrl,
|
|
750
|
+
"--session-id",
|
|
751
|
+
opts.sessionId,
|
|
752
|
+
"--input-format",
|
|
753
|
+
"stream-json",
|
|
754
|
+
"--output-format",
|
|
755
|
+
"stream-json",
|
|
756
|
+
"--replay-user-messages",
|
|
757
|
+
...deps.verbose ? ["--verbose"] : [],
|
|
758
|
+
...debugFile ? ["--debug-file", debugFile] : [],
|
|
759
|
+
...deps.permissionMode ? ["--permission-mode", deps.permissionMode] : []
|
|
760
|
+
];
|
|
761
|
+
const env = {
|
|
762
|
+
...deps.env,
|
|
763
|
+
CLAUDE_CODE_OAUTH_TOKEN: undefined,
|
|
764
|
+
CLAUDE_CODE_ENVIRONMENT_KIND: "bridge",
|
|
765
|
+
...deps.sandbox && { CLAUDE_CODE_FORCE_SANDBOX: "1" },
|
|
766
|
+
CLAUDE_CODE_SESSION_ACCESS_TOKEN: opts.accessToken,
|
|
767
|
+
CLAUDE_CODE_POST_FOR_SESSION_INGRESS_V2: "1",
|
|
768
|
+
...opts.useCcrV2 && {
|
|
769
|
+
CLAUDE_CODE_USE_CCR_V2: "1",
|
|
770
|
+
CLAUDE_CODE_WORKER_EPOCH: String(opts.workerEpoch)
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
deps.onDebug(`[bridge:session] Spawning sessionId=${opts.sessionId} sdkUrl=${opts.sdkUrl} accessToken=${opts.accessToken ? "present" : "MISSING"}`);
|
|
774
|
+
deps.onDebug(`[bridge:session] Child args: ${args.join(" ")}`);
|
|
775
|
+
if (debugFile) {
|
|
776
|
+
deps.onDebug(`[bridge:session] Debug log: ${debugFile}`);
|
|
777
|
+
}
|
|
778
|
+
const child = spawn(deps.execPath, args, {
|
|
779
|
+
cwd: dir,
|
|
780
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
781
|
+
env,
|
|
782
|
+
windowsHide: true
|
|
783
|
+
});
|
|
784
|
+
deps.onDebug(`[bridge:session] sessionId=${opts.sessionId} pid=${child.pid}`);
|
|
785
|
+
const activities = [];
|
|
786
|
+
let currentActivity = null;
|
|
787
|
+
const lastStderr = [];
|
|
788
|
+
let sigkillSent = false;
|
|
789
|
+
let firstUserMessageSeen = false;
|
|
790
|
+
if (child.stderr) {
|
|
791
|
+
const stderrRl = createInterface({ input: child.stderr });
|
|
792
|
+
stderrRl.on("line", (line) => {
|
|
793
|
+
if (deps.verbose) {
|
|
794
|
+
process.stderr.write(line + `
|
|
795
|
+
`);
|
|
796
|
+
}
|
|
797
|
+
if (lastStderr.length >= MAX_STDERR_LINES) {
|
|
798
|
+
lastStderr.shift();
|
|
799
|
+
}
|
|
800
|
+
lastStderr.push(line);
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
if (child.stdout) {
|
|
804
|
+
const rl = createInterface({ input: child.stdout });
|
|
805
|
+
rl.on("line", (line) => {
|
|
806
|
+
if (transcriptStream) {
|
|
807
|
+
transcriptStream.write(line + `
|
|
808
|
+
`);
|
|
809
|
+
}
|
|
810
|
+
deps.onDebug(`[bridge:ws] sessionId=${opts.sessionId} <<< ${debugTruncate(line)}`);
|
|
811
|
+
if (deps.verbose) {
|
|
812
|
+
process.stderr.write(line + `
|
|
813
|
+
`);
|
|
814
|
+
}
|
|
815
|
+
const extracted = extractActivities(line, opts.sessionId, deps.onDebug);
|
|
816
|
+
for (const activity of extracted) {
|
|
817
|
+
if (activities.length >= MAX_ACTIVITIES) {
|
|
818
|
+
activities.shift();
|
|
819
|
+
}
|
|
820
|
+
activities.push(activity);
|
|
821
|
+
currentActivity = activity;
|
|
822
|
+
deps.onActivity?.(opts.sessionId, activity);
|
|
823
|
+
}
|
|
824
|
+
{
|
|
825
|
+
let parsed;
|
|
826
|
+
try {
|
|
827
|
+
parsed = jsonParse(line);
|
|
828
|
+
} catch {}
|
|
829
|
+
if (parsed && typeof parsed === "object") {
|
|
830
|
+
const msg = parsed;
|
|
831
|
+
if (msg.type === "control_request") {
|
|
832
|
+
const request = msg.request;
|
|
833
|
+
if (request?.subtype === "can_use_tool" && deps.onPermissionRequest) {
|
|
834
|
+
deps.onPermissionRequest(opts.sessionId, parsed, opts.accessToken);
|
|
835
|
+
}
|
|
836
|
+
} else if (msg.type === "user" && !firstUserMessageSeen && opts.onFirstUserMessage) {
|
|
837
|
+
const text = extractUserMessageText(msg);
|
|
838
|
+
if (text) {
|
|
839
|
+
firstUserMessageSeen = true;
|
|
840
|
+
opts.onFirstUserMessage(text);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
const done = new Promise((resolve) => {
|
|
848
|
+
child.on("close", (code, signal) => {
|
|
849
|
+
if (transcriptStream) {
|
|
850
|
+
transcriptStream.end();
|
|
851
|
+
transcriptStream = null;
|
|
852
|
+
}
|
|
853
|
+
if (signal === "SIGTERM" || signal === "SIGINT") {
|
|
854
|
+
deps.onDebug(`[bridge:session] sessionId=${opts.sessionId} interrupted signal=${signal} pid=${child.pid}`);
|
|
855
|
+
resolve("interrupted");
|
|
856
|
+
} else if (code === 0) {
|
|
857
|
+
deps.onDebug(`[bridge:session] sessionId=${opts.sessionId} completed exit_code=0 pid=${child.pid}`);
|
|
858
|
+
resolve("completed");
|
|
859
|
+
} else {
|
|
860
|
+
deps.onDebug(`[bridge:session] sessionId=${opts.sessionId} failed exit_code=${code} pid=${child.pid}`);
|
|
861
|
+
resolve("failed");
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
child.on("error", (err) => {
|
|
865
|
+
deps.onDebug(`[bridge:session] sessionId=${opts.sessionId} spawn error: ${err.message}`);
|
|
866
|
+
resolve("failed");
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
const handle = {
|
|
870
|
+
sessionId: opts.sessionId,
|
|
871
|
+
done,
|
|
872
|
+
activities,
|
|
873
|
+
accessToken: opts.accessToken,
|
|
874
|
+
lastStderr,
|
|
875
|
+
get currentActivity() {
|
|
876
|
+
return currentActivity;
|
|
877
|
+
},
|
|
878
|
+
kill() {
|
|
879
|
+
if (!child.killed) {
|
|
880
|
+
deps.onDebug(`[bridge:session] Sending SIGTERM to sessionId=${opts.sessionId} pid=${child.pid}`);
|
|
881
|
+
if (process.platform === "win32") {
|
|
882
|
+
child.kill();
|
|
883
|
+
} else {
|
|
884
|
+
child.kill("SIGTERM");
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
forceKill() {
|
|
889
|
+
if (!sigkillSent && child.pid) {
|
|
890
|
+
sigkillSent = true;
|
|
891
|
+
deps.onDebug(`[bridge:session] Sending SIGKILL to sessionId=${opts.sessionId} pid=${child.pid}`);
|
|
892
|
+
if (process.platform === "win32") {
|
|
893
|
+
child.kill();
|
|
894
|
+
} else {
|
|
895
|
+
child.kill("SIGKILL");
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
writeStdin(data) {
|
|
900
|
+
if (child.stdin && !child.stdin.destroyed) {
|
|
901
|
+
deps.onDebug(`[bridge:ws] sessionId=${opts.sessionId} >>> ${debugTruncate(data)}`);
|
|
902
|
+
child.stdin.write(data);
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
updateAccessToken(token) {
|
|
906
|
+
handle.accessToken = token;
|
|
907
|
+
handle.writeStdin(jsonStringify({
|
|
908
|
+
type: "update_environment_variables",
|
|
909
|
+
variables: { CLAUDE_CODE_SESSION_ACCESS_TOKEN: token }
|
|
910
|
+
}) + `
|
|
911
|
+
`);
|
|
912
|
+
deps.onDebug(`[bridge:session] Sent token refresh via stdin for sessionId=${opts.sessionId}`);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
return handle;
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/bridge/bridgeMain.ts
|
|
921
|
+
init_trustedDevice();
|
|
922
|
+
init_types();
|
|
923
|
+
var DEFAULT_BACKOFF = {
|
|
924
|
+
connInitialMs: 2000,
|
|
925
|
+
connCapMs: 120000,
|
|
926
|
+
connGiveUpMs: 600000,
|
|
927
|
+
generalInitialMs: 500,
|
|
928
|
+
generalCapMs: 30000,
|
|
929
|
+
generalGiveUpMs: 600000
|
|
930
|
+
};
|
|
931
|
+
var STATUS_UPDATE_INTERVAL_MS = 1000;
|
|
932
|
+
var SPAWN_SESSIONS_DEFAULT = 32;
|
|
933
|
+
async function isMultiSessionSpawnEnabled() {
|
|
934
|
+
return checkGate_CACHED_OR_BLOCKING("tengu_ccr_bridge_multi_session");
|
|
935
|
+
}
|
|
936
|
+
function pollSleepDetectionThresholdMs(backoff) {
|
|
937
|
+
return backoff.connCapMs * 2;
|
|
938
|
+
}
|
|
939
|
+
function spawnScriptArgs() {
|
|
940
|
+
if (isInBundledMode() || !process.argv[1]) {
|
|
941
|
+
return [];
|
|
942
|
+
}
|
|
943
|
+
return [process.argv[1]];
|
|
944
|
+
}
|
|
945
|
+
function safeSpawn(spawner, opts, dir) {
|
|
946
|
+
try {
|
|
947
|
+
return spawner.spawn(opts, dir);
|
|
948
|
+
} catch (err) {
|
|
949
|
+
const errMsg = errorMessage(err);
|
|
950
|
+
logError(new Error(`Session spawn failed: ${errMsg}`));
|
|
951
|
+
return errMsg;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async function runBridgeLoop(config, environmentId, environmentSecret, api, spawner, logger, signal, backoffConfig = DEFAULT_BACKOFF, initialSessionId, getAccessToken) {
|
|
955
|
+
const controller = new AbortController;
|
|
956
|
+
if (signal.aborted) {
|
|
957
|
+
controller.abort();
|
|
958
|
+
} else {
|
|
959
|
+
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
960
|
+
}
|
|
961
|
+
const loopSignal = controller.signal;
|
|
962
|
+
const activeSessions = new Map;
|
|
963
|
+
const sessionStartTimes = new Map;
|
|
964
|
+
const sessionWorkIds = new Map;
|
|
965
|
+
const sessionCompatIds = new Map;
|
|
966
|
+
const sessionIngressTokens = new Map;
|
|
967
|
+
const sessionTimers = new Map;
|
|
968
|
+
const completedWorkIds = new Set;
|
|
969
|
+
const sessionWorktrees = new Map;
|
|
970
|
+
const timedOutSessions = new Set;
|
|
971
|
+
const titledSessions = new Set;
|
|
972
|
+
const capacityWake = createCapacityWake(loopSignal);
|
|
973
|
+
async function heartbeatActiveWorkItems() {
|
|
974
|
+
let anySuccess = false;
|
|
975
|
+
let anyFatal = false;
|
|
976
|
+
const authFailedSessions = [];
|
|
977
|
+
for (const [sessionId] of activeSessions) {
|
|
978
|
+
const workId = sessionWorkIds.get(sessionId);
|
|
979
|
+
const ingressToken = sessionIngressTokens.get(sessionId);
|
|
980
|
+
if (!workId || !ingressToken) {
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
await api.heartbeatWork(environmentId, workId, ingressToken);
|
|
985
|
+
anySuccess = true;
|
|
986
|
+
} catch (err) {
|
|
987
|
+
logForDebugging(`[bridge:heartbeat] Failed for sessionId=${sessionId} workId=${workId}: ${errorMessage(err)}`);
|
|
988
|
+
if (err instanceof BridgeFatalError) {
|
|
989
|
+
logEvent("tengu_bridge_heartbeat_error", {
|
|
990
|
+
status: err.status,
|
|
991
|
+
error_type: err.status === 401 || err.status === 403 ? "auth_failed" : "fatal"
|
|
992
|
+
});
|
|
993
|
+
if (err.status === 401 || err.status === 403) {
|
|
994
|
+
authFailedSessions.push(sessionId);
|
|
995
|
+
} else {
|
|
996
|
+
anyFatal = true;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
for (const sessionId of authFailedSessions) {
|
|
1002
|
+
logger.logVerbose(`Session ${sessionId} token expired \u2014 re-queuing via bridge/reconnect`);
|
|
1003
|
+
try {
|
|
1004
|
+
await api.reconnectSession(environmentId, sessionId);
|
|
1005
|
+
logForDebugging(`[bridge:heartbeat] Re-queued sessionId=${sessionId} via bridge/reconnect`);
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
logger.logError(`Failed to refresh session ${sessionId} token: ${errorMessage(err)}`);
|
|
1008
|
+
logForDebugging(`[bridge:heartbeat] reconnectSession(${sessionId}) failed: ${errorMessage(err)}`, { level: "error" });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (anyFatal) {
|
|
1012
|
+
return "fatal";
|
|
1013
|
+
}
|
|
1014
|
+
if (authFailedSessions.length > 0) {
|
|
1015
|
+
return "auth_failed";
|
|
1016
|
+
}
|
|
1017
|
+
return anySuccess ? "ok" : "failed";
|
|
1018
|
+
}
|
|
1019
|
+
const v2Sessions = new Set;
|
|
1020
|
+
const tokenRefresh = getAccessToken ? createTokenRefreshScheduler({
|
|
1021
|
+
getAccessToken,
|
|
1022
|
+
onRefresh: (sessionId, oauthToken) => {
|
|
1023
|
+
const handle = activeSessions.get(sessionId);
|
|
1024
|
+
if (!handle) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (v2Sessions.has(sessionId)) {
|
|
1028
|
+
logger.logVerbose(`Refreshing session ${sessionId} token via bridge/reconnect`);
|
|
1029
|
+
api.reconnectSession(environmentId, sessionId).catch((err) => {
|
|
1030
|
+
logger.logError(`Failed to refresh session ${sessionId} token: ${errorMessage(err)}`);
|
|
1031
|
+
logForDebugging(`[bridge:token] reconnectSession(${sessionId}) failed: ${errorMessage(err)}`, { level: "error" });
|
|
1032
|
+
});
|
|
1033
|
+
} else {
|
|
1034
|
+
handle.updateAccessToken(oauthToken);
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
label: "bridge"
|
|
1038
|
+
}) : null;
|
|
1039
|
+
const loopStartTime = Date.now();
|
|
1040
|
+
const pendingCleanups = new Set;
|
|
1041
|
+
function trackCleanup(p) {
|
|
1042
|
+
pendingCleanups.add(p);
|
|
1043
|
+
p.finally(() => pendingCleanups.delete(p));
|
|
1044
|
+
}
|
|
1045
|
+
let connBackoff = 0;
|
|
1046
|
+
let generalBackoff = 0;
|
|
1047
|
+
let connErrorStart = null;
|
|
1048
|
+
let generalErrorStart = null;
|
|
1049
|
+
let lastPollErrorTime = null;
|
|
1050
|
+
let statusUpdateTimer = null;
|
|
1051
|
+
let fatalExit = false;
|
|
1052
|
+
logForDebugging(`[bridge:work] Starting poll loop spawnMode=${config.spawnMode} maxSessions=${config.maxSessions} environmentId=${environmentId}`);
|
|
1053
|
+
logForDiagnosticsNoPII("info", "bridge_loop_started", {
|
|
1054
|
+
max_sessions: config.maxSessions,
|
|
1055
|
+
spawn_mode: config.spawnMode
|
|
1056
|
+
});
|
|
1057
|
+
if (process.env.USER_TYPE === "ant") {
|
|
1058
|
+
let debugGlob;
|
|
1059
|
+
if (config.debugFile) {
|
|
1060
|
+
const ext = config.debugFile.lastIndexOf(".");
|
|
1061
|
+
debugGlob = ext > 0 ? `${config.debugFile.slice(0, ext)}-*${config.debugFile.slice(ext)}` : `${config.debugFile}-*`;
|
|
1062
|
+
} else {
|
|
1063
|
+
debugGlob = join2(tmpdir2(), "claude", "bridge-session-*.log");
|
|
1064
|
+
}
|
|
1065
|
+
logger.setDebugLogPath(debugGlob);
|
|
1066
|
+
}
|
|
1067
|
+
logger.printBanner(config, environmentId);
|
|
1068
|
+
logger.updateSessionCount(0, config.maxSessions, config.spawnMode);
|
|
1069
|
+
if (initialSessionId) {
|
|
1070
|
+
logger.setAttached(initialSessionId);
|
|
1071
|
+
}
|
|
1072
|
+
function updateStatusDisplay() {
|
|
1073
|
+
logger.updateSessionCount(activeSessions.size, config.maxSessions, config.spawnMode);
|
|
1074
|
+
for (const [sid, handle2] of activeSessions) {
|
|
1075
|
+
const act = handle2.currentActivity;
|
|
1076
|
+
if (act) {
|
|
1077
|
+
logger.updateSessionActivity(sessionCompatIds.get(sid) ?? sid, act);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (activeSessions.size === 0) {
|
|
1081
|
+
logger.updateIdleStatus();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const [sessionId, handle] = [...activeSessions.entries()].pop();
|
|
1085
|
+
const startTime = sessionStartTimes.get(sessionId);
|
|
1086
|
+
if (!startTime)
|
|
1087
|
+
return;
|
|
1088
|
+
const activity = handle.currentActivity;
|
|
1089
|
+
if (!activity || activity.type === "result" || activity.type === "error") {
|
|
1090
|
+
if (config.maxSessions > 1)
|
|
1091
|
+
logger.refreshDisplay();
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const elapsed = formatDuration(Date.now() - startTime);
|
|
1095
|
+
const trail = handle.activities.filter((a) => a.type === "tool_start").slice(-5).map((a) => a.summary);
|
|
1096
|
+
logger.updateSessionStatus(sessionId, elapsed, activity, trail);
|
|
1097
|
+
}
|
|
1098
|
+
function startStatusUpdates() {
|
|
1099
|
+
stopStatusUpdates();
|
|
1100
|
+
updateStatusDisplay();
|
|
1101
|
+
statusUpdateTimer = setInterval(updateStatusDisplay, STATUS_UPDATE_INTERVAL_MS);
|
|
1102
|
+
}
|
|
1103
|
+
function stopStatusUpdates() {
|
|
1104
|
+
if (statusUpdateTimer) {
|
|
1105
|
+
clearInterval(statusUpdateTimer);
|
|
1106
|
+
statusUpdateTimer = null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function onSessionDone(sessionId, startTime, handle) {
|
|
1110
|
+
return (rawStatus) => {
|
|
1111
|
+
const workId = sessionWorkIds.get(sessionId);
|
|
1112
|
+
activeSessions.delete(sessionId);
|
|
1113
|
+
sessionStartTimes.delete(sessionId);
|
|
1114
|
+
sessionWorkIds.delete(sessionId);
|
|
1115
|
+
sessionIngressTokens.delete(sessionId);
|
|
1116
|
+
const compatId = sessionCompatIds.get(sessionId) ?? sessionId;
|
|
1117
|
+
sessionCompatIds.delete(sessionId);
|
|
1118
|
+
logger.removeSession(compatId);
|
|
1119
|
+
titledSessions.delete(compatId);
|
|
1120
|
+
v2Sessions.delete(sessionId);
|
|
1121
|
+
const timer = sessionTimers.get(sessionId);
|
|
1122
|
+
if (timer) {
|
|
1123
|
+
clearTimeout(timer);
|
|
1124
|
+
sessionTimers.delete(sessionId);
|
|
1125
|
+
}
|
|
1126
|
+
tokenRefresh?.cancel(sessionId);
|
|
1127
|
+
capacityWake.wake();
|
|
1128
|
+
const wasTimedOut = timedOutSessions.delete(sessionId);
|
|
1129
|
+
const status = wasTimedOut && rawStatus === "interrupted" ? "failed" : rawStatus;
|
|
1130
|
+
const durationMs = Date.now() - startTime;
|
|
1131
|
+
logForDebugging(`[bridge:session] sessionId=${sessionId} workId=${workId ?? "unknown"} exited status=${status} duration=${formatDuration(durationMs)}`);
|
|
1132
|
+
logEvent("tengu_bridge_session_done", {
|
|
1133
|
+
status,
|
|
1134
|
+
duration_ms: durationMs
|
|
1135
|
+
});
|
|
1136
|
+
logForDiagnosticsNoPII("info", "bridge_session_done", {
|
|
1137
|
+
status,
|
|
1138
|
+
duration_ms: durationMs
|
|
1139
|
+
});
|
|
1140
|
+
logger.clearStatus();
|
|
1141
|
+
stopStatusUpdates();
|
|
1142
|
+
const stderrSummary = handle.lastStderr.length > 0 ? handle.lastStderr.join(`
|
|
1143
|
+
`) : undefined;
|
|
1144
|
+
let failureMessage;
|
|
1145
|
+
switch (status) {
|
|
1146
|
+
case "completed":
|
|
1147
|
+
logger.logSessionComplete(sessionId, durationMs);
|
|
1148
|
+
break;
|
|
1149
|
+
case "failed":
|
|
1150
|
+
if (!wasTimedOut && !loopSignal.aborted) {
|
|
1151
|
+
failureMessage = stderrSummary ?? "Process exited with error";
|
|
1152
|
+
logger.logSessionFailed(sessionId, failureMessage);
|
|
1153
|
+
logError(new Error(`Bridge session failed: ${failureMessage}`));
|
|
1154
|
+
}
|
|
1155
|
+
break;
|
|
1156
|
+
case "interrupted":
|
|
1157
|
+
logger.logVerbose(`Session ${sessionId} interrupted`);
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
if (status !== "interrupted" && workId) {
|
|
1161
|
+
trackCleanup(stopWorkWithRetry(api, environmentId, workId, logger, backoffConfig.stopWorkBaseDelayMs));
|
|
1162
|
+
completedWorkIds.add(workId);
|
|
1163
|
+
}
|
|
1164
|
+
const wt = sessionWorktrees.get(sessionId);
|
|
1165
|
+
if (wt) {
|
|
1166
|
+
sessionWorktrees.delete(sessionId);
|
|
1167
|
+
trackCleanup(removeAgentWorktree(wt.worktreePath, wt.worktreeBranch, wt.gitRoot, wt.hookBased).catch((err) => logger.logVerbose(`Failed to remove worktree ${wt.worktreePath}: ${errorMessage(err)}`)));
|
|
1168
|
+
}
|
|
1169
|
+
if (status !== "interrupted" && !loopSignal.aborted) {
|
|
1170
|
+
if (config.spawnMode !== "single-session") {
|
|
1171
|
+
trackCleanup(api.archiveSession(compatId).catch((err) => logger.logVerbose(`Failed to archive session ${sessionId}: ${errorMessage(err)}`)));
|
|
1172
|
+
logForDebugging(`[bridge:session] Session ${status}, returning to idle (multi-session mode)`);
|
|
1173
|
+
} else {
|
|
1174
|
+
logForDebugging(`[bridge:session] Session ${status}, aborting poll loop to tear down environment`);
|
|
1175
|
+
controller.abort();
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (!loopSignal.aborted) {
|
|
1180
|
+
startStatusUpdates();
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
if (!initialSessionId) {
|
|
1185
|
+
startStatusUpdates();
|
|
1186
|
+
}
|
|
1187
|
+
while (!loopSignal.aborted) {
|
|
1188
|
+
const pollConfig = getPollIntervalConfig();
|
|
1189
|
+
try {
|
|
1190
|
+
const work = await api.pollForWork(environmentId, environmentSecret, loopSignal, pollConfig.reclaim_older_than_ms);
|
|
1191
|
+
const wasDisconnected = connErrorStart !== null || generalErrorStart !== null;
|
|
1192
|
+
if (wasDisconnected) {
|
|
1193
|
+
const disconnectedMs = Date.now() - (connErrorStart ?? generalErrorStart ?? Date.now());
|
|
1194
|
+
logger.logReconnected(disconnectedMs);
|
|
1195
|
+
logForDebugging(`[bridge:poll] Reconnected after ${formatDuration(disconnectedMs)}`);
|
|
1196
|
+
logEvent("tengu_bridge_reconnected", {
|
|
1197
|
+
disconnected_ms: disconnectedMs
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
connBackoff = 0;
|
|
1201
|
+
generalBackoff = 0;
|
|
1202
|
+
connErrorStart = null;
|
|
1203
|
+
generalErrorStart = null;
|
|
1204
|
+
lastPollErrorTime = null;
|
|
1205
|
+
if (!work) {
|
|
1206
|
+
const atCap = activeSessions.size >= config.maxSessions;
|
|
1207
|
+
if (atCap) {
|
|
1208
|
+
const atCapMs = pollConfig.multisession_poll_interval_ms_at_capacity;
|
|
1209
|
+
if (pollConfig.non_exclusive_heartbeat_interval_ms > 0) {
|
|
1210
|
+
logEvent("tengu_bridge_heartbeat_mode_entered", {
|
|
1211
|
+
active_sessions: activeSessions.size,
|
|
1212
|
+
heartbeat_interval_ms: pollConfig.non_exclusive_heartbeat_interval_ms
|
|
1213
|
+
});
|
|
1214
|
+
const pollDeadline = atCapMs > 0 ? Date.now() + atCapMs : null;
|
|
1215
|
+
let hbResult = "ok";
|
|
1216
|
+
let hbCycles = 0;
|
|
1217
|
+
while (!loopSignal.aborted && activeSessions.size >= config.maxSessions && (pollDeadline === null || Date.now() < pollDeadline)) {
|
|
1218
|
+
const hbConfig = getPollIntervalConfig();
|
|
1219
|
+
if (hbConfig.non_exclusive_heartbeat_interval_ms <= 0)
|
|
1220
|
+
break;
|
|
1221
|
+
const cap = capacityWake.signal();
|
|
1222
|
+
hbResult = await heartbeatActiveWorkItems();
|
|
1223
|
+
if (hbResult === "auth_failed" || hbResult === "fatal") {
|
|
1224
|
+
cap.cleanup();
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
hbCycles++;
|
|
1228
|
+
await sleep(hbConfig.non_exclusive_heartbeat_interval_ms, cap.signal);
|
|
1229
|
+
cap.cleanup();
|
|
1230
|
+
}
|
|
1231
|
+
const exitReason = hbResult === "auth_failed" || hbResult === "fatal" ? hbResult : loopSignal.aborted ? "shutdown" : activeSessions.size < config.maxSessions ? "capacity_changed" : pollDeadline !== null && Date.now() >= pollDeadline ? "poll_due" : "config_disabled";
|
|
1232
|
+
logEvent("tengu_bridge_heartbeat_mode_exited", {
|
|
1233
|
+
reason: exitReason,
|
|
1234
|
+
heartbeat_cycles: hbCycles,
|
|
1235
|
+
active_sessions: activeSessions.size
|
|
1236
|
+
});
|
|
1237
|
+
if (exitReason === "poll_due") {
|
|
1238
|
+
logForDebugging(`[bridge:poll] Heartbeat poll_due after ${hbCycles} cycles \u2014 falling through to pollForWork`);
|
|
1239
|
+
}
|
|
1240
|
+
if (hbResult === "auth_failed" || hbResult === "fatal") {
|
|
1241
|
+
const cap = capacityWake.signal();
|
|
1242
|
+
await sleep(atCapMs > 0 ? atCapMs : pollConfig.non_exclusive_heartbeat_interval_ms, cap.signal);
|
|
1243
|
+
cap.cleanup();
|
|
1244
|
+
}
|
|
1245
|
+
} else if (atCapMs > 0) {
|
|
1246
|
+
const cap = capacityWake.signal();
|
|
1247
|
+
await sleep(atCapMs, cap.signal);
|
|
1248
|
+
cap.cleanup();
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
const interval = activeSessions.size > 0 ? pollConfig.multisession_poll_interval_ms_partial_capacity : pollConfig.multisession_poll_interval_ms_not_at_capacity;
|
|
1252
|
+
await sleep(interval, loopSignal);
|
|
1253
|
+
}
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
const atCapacityBeforeSwitch = activeSessions.size >= config.maxSessions;
|
|
1257
|
+
if (completedWorkIds.has(work.id)) {
|
|
1258
|
+
logForDebugging(`[bridge:work] Skipping already-completed workId=${work.id}`);
|
|
1259
|
+
if (atCapacityBeforeSwitch) {
|
|
1260
|
+
const cap = capacityWake.signal();
|
|
1261
|
+
if (pollConfig.non_exclusive_heartbeat_interval_ms > 0) {
|
|
1262
|
+
await heartbeatActiveWorkItems();
|
|
1263
|
+
await sleep(pollConfig.non_exclusive_heartbeat_interval_ms, cap.signal);
|
|
1264
|
+
} else if (pollConfig.multisession_poll_interval_ms_at_capacity > 0) {
|
|
1265
|
+
await sleep(pollConfig.multisession_poll_interval_ms_at_capacity, cap.signal);
|
|
1266
|
+
}
|
|
1267
|
+
cap.cleanup();
|
|
1268
|
+
} else {
|
|
1269
|
+
await sleep(1000, loopSignal);
|
|
1270
|
+
}
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
let secret;
|
|
1274
|
+
try {
|
|
1275
|
+
secret = decodeWorkSecret(work.secret);
|
|
1276
|
+
} catch (err) {
|
|
1277
|
+
const errMsg = errorMessage(err);
|
|
1278
|
+
logger.logError(`Failed to decode work secret for workId=${work.id}: ${errMsg}`);
|
|
1279
|
+
logEvent("tengu_bridge_work_secret_failed", {});
|
|
1280
|
+
completedWorkIds.add(work.id);
|
|
1281
|
+
trackCleanup(stopWorkWithRetry(api, environmentId, work.id, logger, backoffConfig.stopWorkBaseDelayMs));
|
|
1282
|
+
if (atCapacityBeforeSwitch) {
|
|
1283
|
+
const cap = capacityWake.signal();
|
|
1284
|
+
if (pollConfig.non_exclusive_heartbeat_interval_ms > 0) {
|
|
1285
|
+
await heartbeatActiveWorkItems();
|
|
1286
|
+
await sleep(pollConfig.non_exclusive_heartbeat_interval_ms, cap.signal);
|
|
1287
|
+
} else if (pollConfig.multisession_poll_interval_ms_at_capacity > 0) {
|
|
1288
|
+
await sleep(pollConfig.multisession_poll_interval_ms_at_capacity, cap.signal);
|
|
1289
|
+
}
|
|
1290
|
+
cap.cleanup();
|
|
1291
|
+
}
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
const ackWork = async () => {
|
|
1295
|
+
logForDebugging(`[bridge:work] Acknowledging workId=${work.id}`);
|
|
1296
|
+
try {
|
|
1297
|
+
await api.acknowledgeWork(environmentId, work.id, secret.session_ingress_token);
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
logForDebugging(`[bridge:work] Acknowledge failed workId=${work.id}: ${errorMessage(err)}`);
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
const workType = work.data.type;
|
|
1303
|
+
switch (work.data.type) {
|
|
1304
|
+
case "healthcheck":
|
|
1305
|
+
await ackWork();
|
|
1306
|
+
logForDebugging("[bridge:work] Healthcheck received");
|
|
1307
|
+
logger.logVerbose("Healthcheck received");
|
|
1308
|
+
break;
|
|
1309
|
+
case "session": {
|
|
1310
|
+
const sessionId = work.data.id;
|
|
1311
|
+
try {
|
|
1312
|
+
validateBridgeId(sessionId, "session_id");
|
|
1313
|
+
} catch {
|
|
1314
|
+
await ackWork();
|
|
1315
|
+
logger.logError(`Invalid session_id received: ${sessionId}`);
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
const existingHandle = activeSessions.get(sessionId);
|
|
1319
|
+
if (existingHandle) {
|
|
1320
|
+
existingHandle.updateAccessToken(secret.session_ingress_token);
|
|
1321
|
+
sessionIngressTokens.set(sessionId, secret.session_ingress_token);
|
|
1322
|
+
sessionWorkIds.set(sessionId, work.id);
|
|
1323
|
+
tokenRefresh?.schedule(sessionId, secret.session_ingress_token);
|
|
1324
|
+
logForDebugging(`[bridge:work] Updated access token for existing sessionId=${sessionId} workId=${work.id}`);
|
|
1325
|
+
await ackWork();
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
if (activeSessions.size >= config.maxSessions) {
|
|
1329
|
+
logForDebugging(`[bridge:work] At capacity (${activeSessions.size}/${config.maxSessions}), cannot spawn new session for workId=${work.id}`);
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
await ackWork();
|
|
1333
|
+
const spawnStartTime = Date.now();
|
|
1334
|
+
let sdkUrl;
|
|
1335
|
+
let useCcrV2 = false;
|
|
1336
|
+
let workerEpoch;
|
|
1337
|
+
if (secret.use_code_sessions === true || isEnvTruthy(process.env.CLAUDE_BRIDGE_USE_CCR_V2)) {
|
|
1338
|
+
sdkUrl = buildCCRv2SdkUrl(config.apiBaseUrl, sessionId);
|
|
1339
|
+
for (let attempt = 1;attempt <= 2; attempt++) {
|
|
1340
|
+
try {
|
|
1341
|
+
workerEpoch = await registerWorker(sdkUrl, secret.session_ingress_token);
|
|
1342
|
+
useCcrV2 = true;
|
|
1343
|
+
logForDebugging(`[bridge:session] CCR v2: registered worker sessionId=${sessionId} epoch=${workerEpoch} attempt=${attempt}`);
|
|
1344
|
+
break;
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
const errMsg = errorMessage(err);
|
|
1347
|
+
if (attempt < 2) {
|
|
1348
|
+
logForDebugging(`[bridge:session] CCR v2: registerWorker attempt ${attempt} failed, retrying: ${errMsg}`);
|
|
1349
|
+
await sleep(2000, loopSignal);
|
|
1350
|
+
if (loopSignal.aborted)
|
|
1351
|
+
break;
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
logger.logError(`CCR v2 worker registration failed for session ${sessionId}: ${errMsg}`);
|
|
1355
|
+
logError(new Error(`registerWorker failed: ${errMsg}`));
|
|
1356
|
+
completedWorkIds.add(work.id);
|
|
1357
|
+
trackCleanup(stopWorkWithRetry(api, environmentId, work.id, logger, backoffConfig.stopWorkBaseDelayMs));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (!useCcrV2)
|
|
1361
|
+
break;
|
|
1362
|
+
} else {
|
|
1363
|
+
sdkUrl = buildSdkUrl(config.sessionIngressUrl, sessionId);
|
|
1364
|
+
}
|
|
1365
|
+
const spawnModeAtDecision = config.spawnMode;
|
|
1366
|
+
let sessionDir = config.dir;
|
|
1367
|
+
let worktreeCreateMs = 0;
|
|
1368
|
+
if (spawnModeAtDecision === "worktree" && (initialSessionId === undefined || !sameSessionId(sessionId, initialSessionId))) {
|
|
1369
|
+
const wtStart = Date.now();
|
|
1370
|
+
try {
|
|
1371
|
+
const wt = await createAgentWorktree(`bridge-${safeFilenameId(sessionId)}`);
|
|
1372
|
+
worktreeCreateMs = Date.now() - wtStart;
|
|
1373
|
+
sessionWorktrees.set(sessionId, {
|
|
1374
|
+
worktreePath: wt.worktreePath,
|
|
1375
|
+
worktreeBranch: wt.worktreeBranch,
|
|
1376
|
+
gitRoot: wt.gitRoot,
|
|
1377
|
+
hookBased: wt.hookBased
|
|
1378
|
+
});
|
|
1379
|
+
sessionDir = wt.worktreePath;
|
|
1380
|
+
logForDebugging(`[bridge:session] Created worktree for sessionId=${sessionId} at ${wt.worktreePath}`);
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
const errMsg = errorMessage(err);
|
|
1383
|
+
logger.logError(`Failed to create worktree for session ${sessionId}: ${errMsg}`);
|
|
1384
|
+
logError(new Error(`Worktree creation failed: ${errMsg}`));
|
|
1385
|
+
completedWorkIds.add(work.id);
|
|
1386
|
+
trackCleanup(stopWorkWithRetry(api, environmentId, work.id, logger, backoffConfig.stopWorkBaseDelayMs));
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
logForDebugging(`[bridge:session] Spawning sessionId=${sessionId} sdkUrl=${sdkUrl}`);
|
|
1391
|
+
const compatSessionId = toCompatSessionId(sessionId);
|
|
1392
|
+
const spawnResult = safeSpawn(spawner, {
|
|
1393
|
+
sessionId,
|
|
1394
|
+
sdkUrl,
|
|
1395
|
+
accessToken: secret.session_ingress_token,
|
|
1396
|
+
useCcrV2,
|
|
1397
|
+
workerEpoch,
|
|
1398
|
+
onFirstUserMessage: (text) => {
|
|
1399
|
+
if (titledSessions.has(compatSessionId))
|
|
1400
|
+
return;
|
|
1401
|
+
titledSessions.add(compatSessionId);
|
|
1402
|
+
const title = deriveSessionTitle(text);
|
|
1403
|
+
logger.setSessionTitle(compatSessionId, title);
|
|
1404
|
+
logForDebugging(`[bridge:title] derived title for ${compatSessionId}: ${title}`);
|
|
1405
|
+
import("./chunk-g75w4hw3.js").then(({ updateBridgeSessionTitle }) => updateBridgeSessionTitle(compatSessionId, title, {
|
|
1406
|
+
baseUrl: config.apiBaseUrl
|
|
1407
|
+
})).catch((err) => logForDebugging(`[bridge:title] failed to update title for ${compatSessionId}: ${err}`, { level: "error" }));
|
|
1408
|
+
}
|
|
1409
|
+
}, sessionDir);
|
|
1410
|
+
if (typeof spawnResult === "string") {
|
|
1411
|
+
logger.logError(`Failed to spawn session ${sessionId}: ${spawnResult}`);
|
|
1412
|
+
const wt = sessionWorktrees.get(sessionId);
|
|
1413
|
+
if (wt) {
|
|
1414
|
+
sessionWorktrees.delete(sessionId);
|
|
1415
|
+
trackCleanup(removeAgentWorktree(wt.worktreePath, wt.worktreeBranch, wt.gitRoot, wt.hookBased).catch((err) => logger.logVerbose(`Failed to remove worktree ${wt.worktreePath}: ${errorMessage(err)}`)));
|
|
1416
|
+
}
|
|
1417
|
+
completedWorkIds.add(work.id);
|
|
1418
|
+
trackCleanup(stopWorkWithRetry(api, environmentId, work.id, logger, backoffConfig.stopWorkBaseDelayMs));
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
const handle = spawnResult;
|
|
1422
|
+
const spawnDurationMs = Date.now() - spawnStartTime;
|
|
1423
|
+
logEvent("tengu_bridge_session_started", {
|
|
1424
|
+
active_sessions: activeSessions.size,
|
|
1425
|
+
spawn_mode: spawnModeAtDecision,
|
|
1426
|
+
in_worktree: sessionWorktrees.has(sessionId),
|
|
1427
|
+
spawn_duration_ms: spawnDurationMs,
|
|
1428
|
+
worktree_create_ms: worktreeCreateMs,
|
|
1429
|
+
inProtectedNamespace: isInProtectedNamespace()
|
|
1430
|
+
});
|
|
1431
|
+
logForDiagnosticsNoPII("info", "bridge_session_started", {
|
|
1432
|
+
spawn_mode: spawnModeAtDecision,
|
|
1433
|
+
in_worktree: sessionWorktrees.has(sessionId),
|
|
1434
|
+
spawn_duration_ms: spawnDurationMs,
|
|
1435
|
+
worktree_create_ms: worktreeCreateMs
|
|
1436
|
+
});
|
|
1437
|
+
activeSessions.set(sessionId, handle);
|
|
1438
|
+
sessionWorkIds.set(sessionId, work.id);
|
|
1439
|
+
sessionIngressTokens.set(sessionId, secret.session_ingress_token);
|
|
1440
|
+
sessionCompatIds.set(sessionId, compatSessionId);
|
|
1441
|
+
const startTime = Date.now();
|
|
1442
|
+
sessionStartTimes.set(sessionId, startTime);
|
|
1443
|
+
logger.logSessionStart(sessionId, `Session ${sessionId}`);
|
|
1444
|
+
const safeId = safeFilenameId(sessionId);
|
|
1445
|
+
let sessionDebugFile;
|
|
1446
|
+
if (config.debugFile) {
|
|
1447
|
+
const ext = config.debugFile.lastIndexOf(".");
|
|
1448
|
+
if (ext > 0) {
|
|
1449
|
+
sessionDebugFile = `${config.debugFile.slice(0, ext)}-${safeId}${config.debugFile.slice(ext)}`;
|
|
1450
|
+
} else {
|
|
1451
|
+
sessionDebugFile = `${config.debugFile}-${safeId}`;
|
|
1452
|
+
}
|
|
1453
|
+
} else if (config.verbose || process.env.USER_TYPE === "ant") {
|
|
1454
|
+
sessionDebugFile = join2(tmpdir2(), "claude", `bridge-session-${safeId}.log`);
|
|
1455
|
+
}
|
|
1456
|
+
if (sessionDebugFile) {
|
|
1457
|
+
logger.logVerbose(`Debug log: ${sessionDebugFile}`);
|
|
1458
|
+
}
|
|
1459
|
+
logger.addSession(compatSessionId, getRemoteSessionUrl(compatSessionId, config.sessionIngressUrl));
|
|
1460
|
+
startStatusUpdates();
|
|
1461
|
+
logger.setAttached(compatSessionId);
|
|
1462
|
+
fetchSessionTitle(compatSessionId, config.apiBaseUrl).then((title) => {
|
|
1463
|
+
if (title && activeSessions.has(sessionId)) {
|
|
1464
|
+
titledSessions.add(compatSessionId);
|
|
1465
|
+
logger.setSessionTitle(compatSessionId, title);
|
|
1466
|
+
logForDebugging(`[bridge:title] server title for ${compatSessionId}: ${title}`);
|
|
1467
|
+
}
|
|
1468
|
+
}).catch((err) => logForDebugging(`[bridge:title] failed to fetch title for ${compatSessionId}: ${err}`, { level: "error" }));
|
|
1469
|
+
const timeoutMs = config.sessionTimeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;
|
|
1470
|
+
if (timeoutMs > 0) {
|
|
1471
|
+
const timer = setTimeout(onSessionTimeout, timeoutMs, sessionId, timeoutMs, logger, timedOutSessions, handle);
|
|
1472
|
+
sessionTimers.set(sessionId, timer);
|
|
1473
|
+
}
|
|
1474
|
+
if (useCcrV2) {
|
|
1475
|
+
v2Sessions.add(sessionId);
|
|
1476
|
+
}
|
|
1477
|
+
tokenRefresh?.schedule(sessionId, secret.session_ingress_token);
|
|
1478
|
+
handle.done.then(onSessionDone(sessionId, startTime, handle));
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
default:
|
|
1482
|
+
await ackWork();
|
|
1483
|
+
logForDebugging(`[bridge:work] Unknown work type: ${workType}, skipping`);
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
if (atCapacityBeforeSwitch) {
|
|
1487
|
+
const cap = capacityWake.signal();
|
|
1488
|
+
if (pollConfig.non_exclusive_heartbeat_interval_ms > 0) {
|
|
1489
|
+
await heartbeatActiveWorkItems();
|
|
1490
|
+
await sleep(pollConfig.non_exclusive_heartbeat_interval_ms, cap.signal);
|
|
1491
|
+
} else if (pollConfig.multisession_poll_interval_ms_at_capacity > 0) {
|
|
1492
|
+
await sleep(pollConfig.multisession_poll_interval_ms_at_capacity, cap.signal);
|
|
1493
|
+
}
|
|
1494
|
+
cap.cleanup();
|
|
1495
|
+
}
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
if (loopSignal.aborted) {
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
if (err instanceof BridgeFatalError) {
|
|
1501
|
+
fatalExit = true;
|
|
1502
|
+
if (isExpiredErrorType(err.errorType)) {
|
|
1503
|
+
logger.logStatus(err.message);
|
|
1504
|
+
} else if (isSuppressible403(err)) {
|
|
1505
|
+
logForDebugging(`[bridge:work] Suppressed 403 error: ${err.message}`);
|
|
1506
|
+
} else {
|
|
1507
|
+
logger.logError(err.message);
|
|
1508
|
+
logError(err);
|
|
1509
|
+
}
|
|
1510
|
+
logEvent("tengu_bridge_fatal_error", {
|
|
1511
|
+
status: err.status,
|
|
1512
|
+
error_type: err.errorType
|
|
1513
|
+
});
|
|
1514
|
+
logForDiagnosticsNoPII(isExpiredErrorType(err.errorType) ? "info" : "error", "bridge_fatal_error", { status: err.status, error_type: err.errorType });
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
const errMsg = describeAxiosError(err);
|
|
1518
|
+
if (isConnectionError(err) || isServerError(err)) {
|
|
1519
|
+
const now = Date.now();
|
|
1520
|
+
if (lastPollErrorTime !== null && now - lastPollErrorTime > pollSleepDetectionThresholdMs(backoffConfig)) {
|
|
1521
|
+
logForDebugging(`[bridge:work] Detected system sleep (${Math.round((now - lastPollErrorTime) / 1000)}s gap), resetting error budget`);
|
|
1522
|
+
logForDiagnosticsNoPII("info", "bridge_poll_sleep_detected", {
|
|
1523
|
+
gapMs: now - lastPollErrorTime
|
|
1524
|
+
});
|
|
1525
|
+
connErrorStart = null;
|
|
1526
|
+
connBackoff = 0;
|
|
1527
|
+
generalErrorStart = null;
|
|
1528
|
+
generalBackoff = 0;
|
|
1529
|
+
}
|
|
1530
|
+
lastPollErrorTime = now;
|
|
1531
|
+
if (!connErrorStart) {
|
|
1532
|
+
connErrorStart = now;
|
|
1533
|
+
}
|
|
1534
|
+
const elapsed = now - connErrorStart;
|
|
1535
|
+
if (elapsed >= backoffConfig.connGiveUpMs) {
|
|
1536
|
+
logger.logError(`Server unreachable for ${Math.round(elapsed / 60000)} minutes, giving up.`);
|
|
1537
|
+
logEvent("tengu_bridge_poll_give_up", {
|
|
1538
|
+
error_type: "connection",
|
|
1539
|
+
elapsed_ms: elapsed
|
|
1540
|
+
});
|
|
1541
|
+
logForDiagnosticsNoPII("error", "bridge_poll_give_up", {
|
|
1542
|
+
error_type: "connection",
|
|
1543
|
+
elapsed_ms: elapsed
|
|
1544
|
+
});
|
|
1545
|
+
fatalExit = true;
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
generalErrorStart = null;
|
|
1549
|
+
generalBackoff = 0;
|
|
1550
|
+
connBackoff = connBackoff ? Math.min(connBackoff * 2, backoffConfig.connCapMs) : backoffConfig.connInitialMs;
|
|
1551
|
+
const delay = addJitter(connBackoff);
|
|
1552
|
+
logger.logVerbose(`Connection error, retrying in ${formatDelay(delay)} (${Math.round(elapsed / 1000)}s elapsed): ${errMsg}`);
|
|
1553
|
+
logger.updateReconnectingStatus(formatDelay(delay), formatDuration(elapsed));
|
|
1554
|
+
if (getPollIntervalConfig().non_exclusive_heartbeat_interval_ms > 0) {
|
|
1555
|
+
await heartbeatActiveWorkItems();
|
|
1556
|
+
}
|
|
1557
|
+
await sleep(delay, loopSignal);
|
|
1558
|
+
} else {
|
|
1559
|
+
const now = Date.now();
|
|
1560
|
+
if (lastPollErrorTime !== null && now - lastPollErrorTime > pollSleepDetectionThresholdMs(backoffConfig)) {
|
|
1561
|
+
logForDebugging(`[bridge:work] Detected system sleep (${Math.round((now - lastPollErrorTime) / 1000)}s gap), resetting error budget`);
|
|
1562
|
+
logForDiagnosticsNoPII("info", "bridge_poll_sleep_detected", {
|
|
1563
|
+
gapMs: now - lastPollErrorTime
|
|
1564
|
+
});
|
|
1565
|
+
connErrorStart = null;
|
|
1566
|
+
connBackoff = 0;
|
|
1567
|
+
generalErrorStart = null;
|
|
1568
|
+
generalBackoff = 0;
|
|
1569
|
+
}
|
|
1570
|
+
lastPollErrorTime = now;
|
|
1571
|
+
if (!generalErrorStart) {
|
|
1572
|
+
generalErrorStart = now;
|
|
1573
|
+
}
|
|
1574
|
+
const elapsed = now - generalErrorStart;
|
|
1575
|
+
if (elapsed >= backoffConfig.generalGiveUpMs) {
|
|
1576
|
+
logger.logError(`Persistent errors for ${Math.round(elapsed / 60000)} minutes, giving up.`);
|
|
1577
|
+
logEvent("tengu_bridge_poll_give_up", {
|
|
1578
|
+
error_type: "general",
|
|
1579
|
+
elapsed_ms: elapsed
|
|
1580
|
+
});
|
|
1581
|
+
logForDiagnosticsNoPII("error", "bridge_poll_give_up", {
|
|
1582
|
+
error_type: "general",
|
|
1583
|
+
elapsed_ms: elapsed
|
|
1584
|
+
});
|
|
1585
|
+
fatalExit = true;
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
connErrorStart = null;
|
|
1589
|
+
connBackoff = 0;
|
|
1590
|
+
generalBackoff = generalBackoff ? Math.min(generalBackoff * 2, backoffConfig.generalCapMs) : backoffConfig.generalInitialMs;
|
|
1591
|
+
const delay = addJitter(generalBackoff);
|
|
1592
|
+
logger.logVerbose(`Poll failed, retrying in ${formatDelay(delay)} (${Math.round(elapsed / 1000)}s elapsed): ${errMsg}`);
|
|
1593
|
+
logger.updateReconnectingStatus(formatDelay(delay), formatDuration(elapsed));
|
|
1594
|
+
if (getPollIntervalConfig().non_exclusive_heartbeat_interval_ms > 0) {
|
|
1595
|
+
await heartbeatActiveWorkItems();
|
|
1596
|
+
}
|
|
1597
|
+
await sleep(delay, loopSignal);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
stopStatusUpdates();
|
|
1602
|
+
logger.clearStatus();
|
|
1603
|
+
const loopDurationMs = Date.now() - loopStartTime;
|
|
1604
|
+
logEvent("tengu_bridge_shutdown", {
|
|
1605
|
+
active_sessions: activeSessions.size,
|
|
1606
|
+
loop_duration_ms: loopDurationMs
|
|
1607
|
+
});
|
|
1608
|
+
logForDiagnosticsNoPII("info", "bridge_shutdown", {
|
|
1609
|
+
active_sessions: activeSessions.size,
|
|
1610
|
+
loop_duration_ms: loopDurationMs
|
|
1611
|
+
});
|
|
1612
|
+
const sessionsToArchive = new Set(activeSessions.keys());
|
|
1613
|
+
if (initialSessionId) {
|
|
1614
|
+
sessionsToArchive.add(initialSessionId);
|
|
1615
|
+
}
|
|
1616
|
+
const compatIdSnapshot = new Map(sessionCompatIds);
|
|
1617
|
+
if (activeSessions.size > 0) {
|
|
1618
|
+
logForDebugging(`[bridge:shutdown] Shutting down ${activeSessions.size} active session(s)`);
|
|
1619
|
+
logger.logStatus(`Shutting down ${activeSessions.size} active session(s)\u2026`);
|
|
1620
|
+
const shutdownWorkIds = new Map(sessionWorkIds);
|
|
1621
|
+
for (const [sessionId, handle] of activeSessions.entries()) {
|
|
1622
|
+
logForDebugging(`[bridge:shutdown] Sending SIGTERM to sessionId=${sessionId}`);
|
|
1623
|
+
handle.kill();
|
|
1624
|
+
}
|
|
1625
|
+
const timeout = new AbortController;
|
|
1626
|
+
await Promise.race([
|
|
1627
|
+
Promise.allSettled([...activeSessions.values()].map((h) => h.done)),
|
|
1628
|
+
sleep(backoffConfig.shutdownGraceMs ?? 30000, timeout.signal)
|
|
1629
|
+
]);
|
|
1630
|
+
timeout.abort();
|
|
1631
|
+
for (const [sid, handle] of activeSessions.entries()) {
|
|
1632
|
+
logForDebugging(`[bridge:shutdown] Force-killing stuck sessionId=${sid}`);
|
|
1633
|
+
handle.forceKill();
|
|
1634
|
+
}
|
|
1635
|
+
for (const timer of sessionTimers.values()) {
|
|
1636
|
+
clearTimeout(timer);
|
|
1637
|
+
}
|
|
1638
|
+
sessionTimers.clear();
|
|
1639
|
+
tokenRefresh?.cancelAll();
|
|
1640
|
+
if (sessionWorktrees.size > 0) {
|
|
1641
|
+
const remainingWorktrees = [...sessionWorktrees.values()];
|
|
1642
|
+
sessionWorktrees.clear();
|
|
1643
|
+
logForDebugging(`[bridge:shutdown] Cleaning up ${remainingWorktrees.length} worktree(s)`);
|
|
1644
|
+
await Promise.allSettled(remainingWorktrees.map((wt) => removeAgentWorktree(wt.worktreePath, wt.worktreeBranch, wt.gitRoot, wt.hookBased)));
|
|
1645
|
+
}
|
|
1646
|
+
await Promise.allSettled([...shutdownWorkIds.entries()].map(([sessionId, workId]) => {
|
|
1647
|
+
return api.stopWork(environmentId, workId, true).catch((err) => logger.logVerbose(`Failed to stop work ${workId} for session ${sessionId}: ${errorMessage(err)}`));
|
|
1648
|
+
}));
|
|
1649
|
+
}
|
|
1650
|
+
if (pendingCleanups.size > 0) {
|
|
1651
|
+
await Promise.allSettled([...pendingCleanups]);
|
|
1652
|
+
}
|
|
1653
|
+
if (false) {}
|
|
1654
|
+
if (sessionsToArchive.size > 0) {
|
|
1655
|
+
logForDebugging(`[bridge:shutdown] Archiving ${sessionsToArchive.size} session(s)`);
|
|
1656
|
+
await Promise.allSettled([...sessionsToArchive].map((sessionId) => api.archiveSession(compatIdSnapshot.get(sessionId) ?? toCompatSessionId(sessionId)).catch((err) => logger.logVerbose(`Failed to archive session ${sessionId}: ${errorMessage(err)}`))));
|
|
1657
|
+
}
|
|
1658
|
+
try {
|
|
1659
|
+
await api.deregisterEnvironment(environmentId);
|
|
1660
|
+
logForDebugging(`[bridge:shutdown] Environment deregistered, bridge offline`);
|
|
1661
|
+
logger.logVerbose("Environment deregistered.");
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
logger.logVerbose(`Failed to deregister environment: ${errorMessage(err)}`);
|
|
1664
|
+
}
|
|
1665
|
+
const { clearBridgePointer } = await import("./chunk-2tw2ve8h.js");
|
|
1666
|
+
await clearBridgePointer(config.dir);
|
|
1667
|
+
logger.logVerbose("Environment offline.");
|
|
1668
|
+
}
|
|
1669
|
+
var CONNECTION_ERROR_CODES = new Set([
|
|
1670
|
+
"ECONNREFUSED",
|
|
1671
|
+
"ECONNRESET",
|
|
1672
|
+
"ETIMEDOUT",
|
|
1673
|
+
"ENETUNREACH",
|
|
1674
|
+
"EHOSTUNREACH"
|
|
1675
|
+
]);
|
|
1676
|
+
function isConnectionError(err) {
|
|
1677
|
+
if (err && typeof err === "object" && "code" in err && typeof err.code === "string" && CONNECTION_ERROR_CODES.has(err.code)) {
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
function isServerError(err) {
|
|
1683
|
+
return !!err && typeof err === "object" && "code" in err && typeof err.code === "string" && err.code === "ERR_BAD_RESPONSE";
|
|
1684
|
+
}
|
|
1685
|
+
function addJitter(ms) {
|
|
1686
|
+
return Math.max(0, ms + ms * 0.25 * (2 * Math.random() - 1));
|
|
1687
|
+
}
|
|
1688
|
+
function formatDelay(ms) {
|
|
1689
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;
|
|
1690
|
+
}
|
|
1691
|
+
async function stopWorkWithRetry(api, environmentId, workId, logger, baseDelayMs = 1000) {
|
|
1692
|
+
const MAX_ATTEMPTS = 3;
|
|
1693
|
+
for (let attempt = 1;attempt <= MAX_ATTEMPTS; attempt++) {
|
|
1694
|
+
try {
|
|
1695
|
+
await api.stopWork(environmentId, workId, false);
|
|
1696
|
+
logForDebugging(`[bridge:work] stopWork succeeded for workId=${workId} on attempt ${attempt}/${MAX_ATTEMPTS}`);
|
|
1697
|
+
return;
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
if (err instanceof BridgeFatalError) {
|
|
1700
|
+
if (isSuppressible403(err)) {
|
|
1701
|
+
logForDebugging(`[bridge:work] Suppressed stopWork 403 for ${workId}: ${err.message}`);
|
|
1702
|
+
} else {
|
|
1703
|
+
logger.logError(`Failed to stop work ${workId}: ${err.message}`);
|
|
1704
|
+
}
|
|
1705
|
+
logForDiagnosticsNoPII("error", "bridge_stop_work_failed", {
|
|
1706
|
+
attempts: attempt,
|
|
1707
|
+
fatal: true
|
|
1708
|
+
});
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const errMsg = errorMessage(err);
|
|
1712
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
1713
|
+
const delay = addJitter(baseDelayMs * Math.pow(2, attempt - 1));
|
|
1714
|
+
logger.logVerbose(`Failed to stop work ${workId} (attempt ${attempt}/${MAX_ATTEMPTS}), retrying in ${formatDelay(delay)}: ${errMsg}`);
|
|
1715
|
+
await sleep(delay);
|
|
1716
|
+
} else {
|
|
1717
|
+
logger.logError(`Failed to stop work ${workId} after ${MAX_ATTEMPTS} attempts: ${errMsg}`);
|
|
1718
|
+
logForDiagnosticsNoPII("error", "bridge_stop_work_failed", {
|
|
1719
|
+
attempts: MAX_ATTEMPTS
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function onSessionTimeout(sessionId, timeoutMs, logger, timedOutSessions, handle) {
|
|
1726
|
+
logForDebugging(`[bridge:session] sessionId=${sessionId} timed out after ${formatDuration(timeoutMs)}`);
|
|
1727
|
+
logEvent("tengu_bridge_session_timeout", {
|
|
1728
|
+
timeout_ms: timeoutMs
|
|
1729
|
+
});
|
|
1730
|
+
logger.logSessionFailed(sessionId, `Session timed out after ${formatDuration(timeoutMs)}`);
|
|
1731
|
+
timedOutSessions.add(sessionId);
|
|
1732
|
+
handle.kill();
|
|
1733
|
+
}
|
|
1734
|
+
var SPAWN_FLAG_VALUES = ["session", "same-dir", "worktree"];
|
|
1735
|
+
function parseSpawnValue(raw) {
|
|
1736
|
+
if (raw === "session")
|
|
1737
|
+
return "single-session";
|
|
1738
|
+
if (raw === "same-dir")
|
|
1739
|
+
return "same-dir";
|
|
1740
|
+
if (raw === "worktree")
|
|
1741
|
+
return "worktree";
|
|
1742
|
+
return `--spawn requires one of: ${SPAWN_FLAG_VALUES.join(", ")} (got: ${raw ?? "<missing>"})`;
|
|
1743
|
+
}
|
|
1744
|
+
function parseCapacityValue(raw) {
|
|
1745
|
+
const n = raw === undefined ? NaN : parseInt(raw, 10);
|
|
1746
|
+
if (isNaN(n) || n < 1) {
|
|
1747
|
+
return `--capacity requires a positive integer (got: ${raw ?? "<missing>"})`;
|
|
1748
|
+
}
|
|
1749
|
+
return n;
|
|
1750
|
+
}
|
|
1751
|
+
function parseArgs(args) {
|
|
1752
|
+
let verbose = false;
|
|
1753
|
+
let sandbox = false;
|
|
1754
|
+
let debugFile;
|
|
1755
|
+
let sessionTimeoutMs;
|
|
1756
|
+
let permissionMode;
|
|
1757
|
+
let name;
|
|
1758
|
+
let help = false;
|
|
1759
|
+
let spawnMode;
|
|
1760
|
+
let capacity;
|
|
1761
|
+
let createSessionInDir;
|
|
1762
|
+
let sessionId;
|
|
1763
|
+
let continueSession = false;
|
|
1764
|
+
for (let i = 0;i < args.length; i++) {
|
|
1765
|
+
const arg = args[i];
|
|
1766
|
+
if (arg === "--help" || arg === "-h") {
|
|
1767
|
+
help = true;
|
|
1768
|
+
} else if (arg === "--verbose" || arg === "-v") {
|
|
1769
|
+
verbose = true;
|
|
1770
|
+
} else if (arg === "--sandbox") {
|
|
1771
|
+
sandbox = true;
|
|
1772
|
+
} else if (arg === "--no-sandbox") {
|
|
1773
|
+
sandbox = false;
|
|
1774
|
+
} else if (arg === "--debug-file" && i + 1 < args.length) {
|
|
1775
|
+
debugFile = resolve(args[++i]);
|
|
1776
|
+
} else if (arg.startsWith("--debug-file=")) {
|
|
1777
|
+
debugFile = resolve(arg.slice("--debug-file=".length));
|
|
1778
|
+
} else if (arg === "--session-timeout" && i + 1 < args.length) {
|
|
1779
|
+
sessionTimeoutMs = parseInt(args[++i], 10) * 1000;
|
|
1780
|
+
} else if (arg.startsWith("--session-timeout=")) {
|
|
1781
|
+
sessionTimeoutMs = parseInt(arg.slice("--session-timeout=".length), 10) * 1000;
|
|
1782
|
+
} else if (arg === "--permission-mode" && i + 1 < args.length) {
|
|
1783
|
+
permissionMode = args[++i];
|
|
1784
|
+
} else if (arg.startsWith("--permission-mode=")) {
|
|
1785
|
+
permissionMode = arg.slice("--permission-mode=".length);
|
|
1786
|
+
} else if (arg === "--name" && i + 1 < args.length) {
|
|
1787
|
+
name = args[++i];
|
|
1788
|
+
} else if (arg.startsWith("--name=")) {
|
|
1789
|
+
name = arg.slice("--name=".length);
|
|
1790
|
+
} else if (false) {} else if (false) {} else if (false) {} else if (arg === "--spawn" || arg.startsWith("--spawn=")) {
|
|
1791
|
+
if (spawnMode !== undefined) {
|
|
1792
|
+
return makeError("--spawn may only be specified once");
|
|
1793
|
+
}
|
|
1794
|
+
const raw = arg.startsWith("--spawn=") ? arg.slice("--spawn=".length) : args[++i];
|
|
1795
|
+
const v = parseSpawnValue(raw);
|
|
1796
|
+
if (v === "single-session" || v === "same-dir" || v === "worktree") {
|
|
1797
|
+
spawnMode = v;
|
|
1798
|
+
} else {
|
|
1799
|
+
return makeError(v);
|
|
1800
|
+
}
|
|
1801
|
+
} else if (arg === "--capacity" || arg.startsWith("--capacity=")) {
|
|
1802
|
+
if (capacity !== undefined) {
|
|
1803
|
+
return makeError("--capacity may only be specified once");
|
|
1804
|
+
}
|
|
1805
|
+
const raw = arg.startsWith("--capacity=") ? arg.slice("--capacity=".length) : args[++i];
|
|
1806
|
+
const v = parseCapacityValue(raw);
|
|
1807
|
+
if (typeof v === "number")
|
|
1808
|
+
capacity = v;
|
|
1809
|
+
else
|
|
1810
|
+
return makeError(v);
|
|
1811
|
+
} else if (arg === "--create-session-in-dir") {
|
|
1812
|
+
createSessionInDir = true;
|
|
1813
|
+
} else if (arg === "--no-create-session-in-dir") {
|
|
1814
|
+
createSessionInDir = false;
|
|
1815
|
+
} else {
|
|
1816
|
+
return makeError(`Unknown argument: ${arg}
|
|
1817
|
+
Run 'claude remote-control --help' for usage.`);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (spawnMode === "single-session" && capacity !== undefined) {
|
|
1821
|
+
return makeError(`--capacity cannot be used with --spawn=session (single-session mode has fixed capacity 1).`);
|
|
1822
|
+
}
|
|
1823
|
+
if ((sessionId || continueSession) && (spawnMode !== undefined || capacity !== undefined || createSessionInDir !== undefined)) {
|
|
1824
|
+
return makeError(`--session-id and --continue cannot be used with --spawn, --capacity, or --create-session-in-dir.`);
|
|
1825
|
+
}
|
|
1826
|
+
if (sessionId && continueSession) {
|
|
1827
|
+
return makeError(`--session-id and --continue cannot be used together.`);
|
|
1828
|
+
}
|
|
1829
|
+
return {
|
|
1830
|
+
verbose,
|
|
1831
|
+
sandbox,
|
|
1832
|
+
debugFile,
|
|
1833
|
+
sessionTimeoutMs,
|
|
1834
|
+
permissionMode,
|
|
1835
|
+
name,
|
|
1836
|
+
spawnMode,
|
|
1837
|
+
capacity,
|
|
1838
|
+
createSessionInDir,
|
|
1839
|
+
sessionId,
|
|
1840
|
+
continueSession,
|
|
1841
|
+
help
|
|
1842
|
+
};
|
|
1843
|
+
function makeError(error) {
|
|
1844
|
+
return {
|
|
1845
|
+
verbose,
|
|
1846
|
+
sandbox,
|
|
1847
|
+
debugFile,
|
|
1848
|
+
sessionTimeoutMs,
|
|
1849
|
+
permissionMode,
|
|
1850
|
+
name,
|
|
1851
|
+
spawnMode,
|
|
1852
|
+
capacity,
|
|
1853
|
+
createSessionInDir,
|
|
1854
|
+
sessionId,
|
|
1855
|
+
continueSession,
|
|
1856
|
+
help,
|
|
1857
|
+
error
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async function printHelp() {
|
|
1862
|
+
const { EXTERNAL_PERMISSION_MODES } = await import("./chunk-cy2hswr1.js");
|
|
1863
|
+
const modes = EXTERNAL_PERMISSION_MODES.join(", ");
|
|
1864
|
+
const showServer = await isMultiSessionSpawnEnabled();
|
|
1865
|
+
const serverOptions = showServer ? ` --spawn <mode> Spawn mode: same-dir, worktree, session
|
|
1866
|
+
(default: same-dir)
|
|
1867
|
+
--capacity <N> Max concurrent sessions in worktree or
|
|
1868
|
+
same-dir mode (default: ${SPAWN_SESSIONS_DEFAULT})
|
|
1869
|
+
--[no-]create-session-in-dir Pre-create a session in the current
|
|
1870
|
+
directory; in worktree mode this session
|
|
1871
|
+
stays in cwd while on-demand sessions get
|
|
1872
|
+
isolated worktrees (default: on)
|
|
1873
|
+
` : "";
|
|
1874
|
+
const serverDescription = showServer ? `
|
|
1875
|
+
Remote Control runs as a persistent server that accepts multiple concurrent
|
|
1876
|
+
sessions in the current directory. One session is pre-created on start so
|
|
1877
|
+
you have somewhere to type immediately. Use --spawn=worktree to isolate
|
|
1878
|
+
each on-demand session in its own git worktree, or --spawn=session for
|
|
1879
|
+
the classic single-session mode (exits when that session ends). Press 'w'
|
|
1880
|
+
during runtime to toggle between same-dir and worktree.
|
|
1881
|
+
` : "";
|
|
1882
|
+
const serverNote = showServer ? ` - Worktree mode requires a git repository or WorktreeCreate/WorktreeRemove hooks
|
|
1883
|
+
` : "";
|
|
1884
|
+
const help = `
|
|
1885
|
+
Remote Control - Connect your local environment to claude.ai/code
|
|
1886
|
+
|
|
1887
|
+
USAGE
|
|
1888
|
+
claude remote-control [options]
|
|
1889
|
+
OPTIONS
|
|
1890
|
+
--name <name> Name for the session (shown in claude.ai/code)
|
|
1891
|
+
--permission-mode <mode> Permission mode for spawned sessions
|
|
1892
|
+
(${modes})
|
|
1893
|
+
--debug-file <path> Write debug logs to file
|
|
1894
|
+
-v, --verbose Enable verbose output
|
|
1895
|
+
-h, --help Show this help
|
|
1896
|
+
${serverOptions}
|
|
1897
|
+
DESCRIPTION
|
|
1898
|
+
Remote Control allows you to control sessions on your local device from
|
|
1899
|
+
claude.ai/code (https://claude.ai/code). Run this command in the
|
|
1900
|
+
directory you want to work in, then connect from the Claude app or web.
|
|
1901
|
+
${serverDescription}
|
|
1902
|
+
NOTES
|
|
1903
|
+
- You must be logged in with a Claude account that has a subscription
|
|
1904
|
+
- Run \`claude\` first in the directory to accept the workspace trust dialog
|
|
1905
|
+
${serverNote}`;
|
|
1906
|
+
console.log(help);
|
|
1907
|
+
}
|
|
1908
|
+
var TITLE_MAX_LEN = 80;
|
|
1909
|
+
function deriveSessionTitle(text) {
|
|
1910
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
1911
|
+
return truncateToWidth(flat, TITLE_MAX_LEN);
|
|
1912
|
+
}
|
|
1913
|
+
async function fetchSessionTitle(compatSessionId, baseUrl) {
|
|
1914
|
+
const { getBridgeSession } = await import("./chunk-g75w4hw3.js");
|
|
1915
|
+
const session = await getBridgeSession(compatSessionId, { baseUrl });
|
|
1916
|
+
return session?.title || undefined;
|
|
1917
|
+
}
|
|
1918
|
+
async function bridgeMain(args) {
|
|
1919
|
+
const parsed = parseArgs(args);
|
|
1920
|
+
if (parsed.help) {
|
|
1921
|
+
await printHelp();
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (parsed.error) {
|
|
1925
|
+
console.error(`Error: ${parsed.error}`);
|
|
1926
|
+
process.exit(1);
|
|
1927
|
+
}
|
|
1928
|
+
const {
|
|
1929
|
+
verbose,
|
|
1930
|
+
sandbox,
|
|
1931
|
+
debugFile,
|
|
1932
|
+
sessionTimeoutMs,
|
|
1933
|
+
permissionMode,
|
|
1934
|
+
name,
|
|
1935
|
+
spawnMode: parsedSpawnMode,
|
|
1936
|
+
capacity: parsedCapacity,
|
|
1937
|
+
createSessionInDir: parsedCreateSessionInDir,
|
|
1938
|
+
sessionId: parsedSessionId,
|
|
1939
|
+
continueSession
|
|
1940
|
+
} = parsed;
|
|
1941
|
+
let resumeSessionId = parsedSessionId;
|
|
1942
|
+
let resumePointerDir;
|
|
1943
|
+
const usedMultiSessionFeature = parsedSpawnMode !== undefined || parsedCapacity !== undefined || parsedCreateSessionInDir !== undefined;
|
|
1944
|
+
if (permissionMode !== undefined) {
|
|
1945
|
+
const { PERMISSION_MODES } = await import("./chunk-cy2hswr1.js");
|
|
1946
|
+
const valid = PERMISSION_MODES;
|
|
1947
|
+
if (!valid.includes(permissionMode)) {
|
|
1948
|
+
console.error(`Error: Invalid permission mode '${permissionMode}'. Valid modes: ${valid.join(", ")}`);
|
|
1949
|
+
process.exit(1);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
const dir = resolve(".");
|
|
1953
|
+
const { enableConfigs, checkHasTrustDialogAccepted } = await import("./chunk-9zgdvbm6.js");
|
|
1954
|
+
enableConfigs();
|
|
1955
|
+
const { initSinks } = await import("./chunk-qp18kd4h.js");
|
|
1956
|
+
initSinks();
|
|
1957
|
+
const multiSessionEnabled = await isMultiSessionSpawnEnabled();
|
|
1958
|
+
if (usedMultiSessionFeature && !multiSessionEnabled) {
|
|
1959
|
+
await logEventAsync("tengu_bridge_multi_session_denied", {
|
|
1960
|
+
used_spawn: parsedSpawnMode !== undefined,
|
|
1961
|
+
used_capacity: parsedCapacity !== undefined,
|
|
1962
|
+
used_create_session_in_dir: parsedCreateSessionInDir !== undefined
|
|
1963
|
+
});
|
|
1964
|
+
await Promise.race([
|
|
1965
|
+
Promise.all([shutdown1PEventLogging(), shutdownDatadog()]),
|
|
1966
|
+
sleep(500, undefined, { unref: true })
|
|
1967
|
+
]).catch(() => {});
|
|
1968
|
+
console.error("Error: Multi-session Remote Control is not enabled for your account yet.");
|
|
1969
|
+
process.exit(1);
|
|
1970
|
+
}
|
|
1971
|
+
const { setOriginalCwd, setCwdState } = await import("./chunk-44fpr6jq.js");
|
|
1972
|
+
setOriginalCwd(dir);
|
|
1973
|
+
setCwdState(dir);
|
|
1974
|
+
if (!checkHasTrustDialogAccepted()) {
|
|
1975
|
+
console.error(`Error: Workspace not trusted. Please run \`claude\` in ${dir} first to review and accept the workspace trust dialog.`);
|
|
1976
|
+
process.exit(1);
|
|
1977
|
+
}
|
|
1978
|
+
const { clearOAuthTokenCache, checkAndRefreshOAuthTokenIfNeeded } = await import("./chunk-13d1842d.js");
|
|
1979
|
+
const { getBridgeAccessToken, getBridgeBaseUrl } = await import("./chunk-hbc6ymdv.js");
|
|
1980
|
+
const bridgeToken = getBridgeAccessToken();
|
|
1981
|
+
if (!bridgeToken) {
|
|
1982
|
+
console.error(BRIDGE_LOGIN_ERROR);
|
|
1983
|
+
process.exit(1);
|
|
1984
|
+
}
|
|
1985
|
+
const {
|
|
1986
|
+
getGlobalConfig,
|
|
1987
|
+
saveGlobalConfig,
|
|
1988
|
+
getCurrentProjectConfig,
|
|
1989
|
+
saveCurrentProjectConfig
|
|
1990
|
+
} = await import("./chunk-9zgdvbm6.js");
|
|
1991
|
+
if (!getGlobalConfig().remoteDialogSeen) {
|
|
1992
|
+
const readline = await import("readline");
|
|
1993
|
+
const rl = readline.createInterface({
|
|
1994
|
+
input: process.stdin,
|
|
1995
|
+
output: process.stdout
|
|
1996
|
+
});
|
|
1997
|
+
console.log(`
|
|
1998
|
+
Remote Control lets you access this CLI session from the web (claude.ai/code)
|
|
1999
|
+
or the Claude app, so you can pick up where you left off on any device.
|
|
2000
|
+
|
|
2001
|
+
You can disconnect remote access anytime by running /remote-control again.
|
|
2002
|
+
`);
|
|
2003
|
+
const answer = await new Promise((resolve2) => {
|
|
2004
|
+
rl.question("Enable Remote Control? (y/n) ", resolve2);
|
|
2005
|
+
});
|
|
2006
|
+
rl.close();
|
|
2007
|
+
saveGlobalConfig((current) => {
|
|
2008
|
+
if (current.remoteDialogSeen)
|
|
2009
|
+
return current;
|
|
2010
|
+
return { ...current, remoteDialogSeen: true };
|
|
2011
|
+
});
|
|
2012
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
2013
|
+
process.exit(0);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (false) {}
|
|
2017
|
+
const baseUrl = getBridgeBaseUrl();
|
|
2018
|
+
if (baseUrl.startsWith("http://") && !baseUrl.includes("localhost") && !baseUrl.includes("127.0.0.1")) {
|
|
2019
|
+
console.error("Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.");
|
|
2020
|
+
process.exit(1);
|
|
2021
|
+
}
|
|
2022
|
+
const sessionIngressUrl = process.env.USER_TYPE === "ant" && process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL ? process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL : baseUrl;
|
|
2023
|
+
const { getBranch, getRemoteUrl, findGitRoot } = await import("./chunk-3gqdqmzb.js");
|
|
2024
|
+
const { hasWorktreeCreateHook } = await import("./chunk-9k5s3ryh.js");
|
|
2025
|
+
const worktreeAvailable = hasWorktreeCreateHook() || findGitRoot(dir) !== null;
|
|
2026
|
+
let savedSpawnMode = multiSessionEnabled ? getCurrentProjectConfig().remoteControlSpawnMode : undefined;
|
|
2027
|
+
if (savedSpawnMode === "worktree" && !worktreeAvailable) {
|
|
2028
|
+
console.error("Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir.");
|
|
2029
|
+
savedSpawnMode = undefined;
|
|
2030
|
+
saveCurrentProjectConfig((current) => {
|
|
2031
|
+
if (current.remoteControlSpawnMode === undefined)
|
|
2032
|
+
return current;
|
|
2033
|
+
return { ...current, remoteControlSpawnMode: undefined };
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
if (multiSessionEnabled && !savedSpawnMode && worktreeAvailable && parsedSpawnMode === undefined && !resumeSessionId && process.stdin.isTTY) {
|
|
2037
|
+
const readline = await import("readline");
|
|
2038
|
+
const rl = readline.createInterface({
|
|
2039
|
+
input: process.stdin,
|
|
2040
|
+
output: process.stdout
|
|
2041
|
+
});
|
|
2042
|
+
console.log(`
|
|
2043
|
+
Claude Remote Control is launching in spawn mode which lets you create new sessions in this project from Claude Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control
|
|
2044
|
+
|
|
2045
|
+
Spawn mode for this project:
|
|
2046
|
+
` + ` [1] same-dir \u2014 sessions share the current directory (default)
|
|
2047
|
+
` + ` [2] worktree \u2014 each session gets an isolated git worktree
|
|
2048
|
+
|
|
2049
|
+
` + `This can be changed later or explicitly set with --spawn=same-dir or --spawn=worktree.
|
|
2050
|
+
`);
|
|
2051
|
+
const answer = await new Promise((resolve2) => {
|
|
2052
|
+
rl.question("Choose [1/2] (default: 1): ", resolve2);
|
|
2053
|
+
});
|
|
2054
|
+
rl.close();
|
|
2055
|
+
const chosen = answer.trim() === "2" ? "worktree" : "same-dir";
|
|
2056
|
+
savedSpawnMode = chosen;
|
|
2057
|
+
logEvent("tengu_bridge_spawn_mode_chosen", {
|
|
2058
|
+
spawn_mode: chosen
|
|
2059
|
+
});
|
|
2060
|
+
saveCurrentProjectConfig((current) => {
|
|
2061
|
+
if (current.remoteControlSpawnMode === chosen)
|
|
2062
|
+
return current;
|
|
2063
|
+
return { ...current, remoteControlSpawnMode: chosen };
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
let spawnModeSource;
|
|
2067
|
+
let spawnMode;
|
|
2068
|
+
if (resumeSessionId) {
|
|
2069
|
+
spawnMode = "single-session";
|
|
2070
|
+
spawnModeSource = "resume";
|
|
2071
|
+
} else if (parsedSpawnMode !== undefined) {
|
|
2072
|
+
spawnMode = parsedSpawnMode;
|
|
2073
|
+
spawnModeSource = "flag";
|
|
2074
|
+
} else if (savedSpawnMode !== undefined) {
|
|
2075
|
+
spawnMode = savedSpawnMode;
|
|
2076
|
+
spawnModeSource = "saved";
|
|
2077
|
+
} else {
|
|
2078
|
+
spawnMode = multiSessionEnabled ? "same-dir" : "single-session";
|
|
2079
|
+
spawnModeSource = "gate_default";
|
|
2080
|
+
}
|
|
2081
|
+
const maxSessions = spawnMode === "single-session" ? 1 : parsedCapacity ?? SPAWN_SESSIONS_DEFAULT;
|
|
2082
|
+
const preCreateSession = parsedCreateSessionInDir ?? true;
|
|
2083
|
+
if (!resumeSessionId) {
|
|
2084
|
+
const { clearBridgePointer } = await import("./chunk-2tw2ve8h.js");
|
|
2085
|
+
await clearBridgePointer(dir);
|
|
2086
|
+
}
|
|
2087
|
+
if (spawnMode === "worktree" && !worktreeAvailable) {
|
|
2088
|
+
console.error(`Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode.`);
|
|
2089
|
+
process.exit(1);
|
|
2090
|
+
}
|
|
2091
|
+
const branch = await getBranch();
|
|
2092
|
+
const gitRepoUrl = await getRemoteUrl();
|
|
2093
|
+
const machineName = hostname();
|
|
2094
|
+
const bridgeId = randomUUID();
|
|
2095
|
+
const { handleOAuth401Error } = await import("./chunk-13d1842d.js");
|
|
2096
|
+
const api = createBridgeApiClient({
|
|
2097
|
+
baseUrl,
|
|
2098
|
+
getAccessToken: getBridgeAccessToken,
|
|
2099
|
+
runnerVersion: MACRO.VERSION,
|
|
2100
|
+
onDebug: logForDebugging,
|
|
2101
|
+
onAuth401: handleOAuth401Error,
|
|
2102
|
+
getTrustedDeviceToken
|
|
2103
|
+
});
|
|
2104
|
+
let reuseEnvironmentId;
|
|
2105
|
+
if (false) {}
|
|
2106
|
+
const config = {
|
|
2107
|
+
dir,
|
|
2108
|
+
machineName,
|
|
2109
|
+
branch,
|
|
2110
|
+
gitRepoUrl,
|
|
2111
|
+
maxSessions,
|
|
2112
|
+
spawnMode,
|
|
2113
|
+
verbose,
|
|
2114
|
+
sandbox,
|
|
2115
|
+
bridgeId,
|
|
2116
|
+
workerType: "claude_code",
|
|
2117
|
+
environmentId: randomUUID(),
|
|
2118
|
+
reuseEnvironmentId,
|
|
2119
|
+
apiBaseUrl: baseUrl,
|
|
2120
|
+
sessionIngressUrl,
|
|
2121
|
+
debugFile,
|
|
2122
|
+
sessionTimeoutMs
|
|
2123
|
+
};
|
|
2124
|
+
logForDebugging(`[bridge:init] bridgeId=${bridgeId}${reuseEnvironmentId ? ` reuseEnvironmentId=${reuseEnvironmentId}` : ""} dir=${dir} branch=${branch} gitRepoUrl=${gitRepoUrl} machine=${machineName}`);
|
|
2125
|
+
logForDebugging(`[bridge:init] apiBaseUrl=${baseUrl} sessionIngressUrl=${sessionIngressUrl}`);
|
|
2126
|
+
logForDebugging(`[bridge:init] sandbox=${sandbox}${debugFile ? ` debugFile=${debugFile}` : ""}`);
|
|
2127
|
+
let environmentId;
|
|
2128
|
+
let environmentSecret;
|
|
2129
|
+
try {
|
|
2130
|
+
const reg = await api.registerBridgeEnvironment(config);
|
|
2131
|
+
environmentId = reg.environment_id;
|
|
2132
|
+
environmentSecret = reg.environment_secret;
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
logEvent("tengu_bridge_registration_failed", {
|
|
2135
|
+
status: err instanceof BridgeFatalError ? err.status : undefined
|
|
2136
|
+
});
|
|
2137
|
+
console.error(err instanceof BridgeFatalError && err.status === 404 ? "Remote Control environments are not available for your account." : `Error: ${errorMessage(err)}`);
|
|
2138
|
+
process.exit(1);
|
|
2139
|
+
}
|
|
2140
|
+
let effectiveResumeSessionId;
|
|
2141
|
+
if (false) {}
|
|
2142
|
+
logForDebugging(`[bridge:init] Registered, server environmentId=${environmentId}`);
|
|
2143
|
+
const startupPollConfig = getPollIntervalConfig();
|
|
2144
|
+
logEvent("tengu_bridge_started", {
|
|
2145
|
+
max_sessions: config.maxSessions,
|
|
2146
|
+
has_debug_file: !!config.debugFile,
|
|
2147
|
+
sandbox: config.sandbox,
|
|
2148
|
+
verbose: config.verbose,
|
|
2149
|
+
heartbeat_interval_ms: startupPollConfig.non_exclusive_heartbeat_interval_ms,
|
|
2150
|
+
spawn_mode: config.spawnMode,
|
|
2151
|
+
spawn_mode_source: spawnModeSource,
|
|
2152
|
+
multi_session_gate: multiSessionEnabled,
|
|
2153
|
+
pre_create_session: preCreateSession,
|
|
2154
|
+
worktree_available: worktreeAvailable
|
|
2155
|
+
});
|
|
2156
|
+
logForDiagnosticsNoPII("info", "bridge_started", {
|
|
2157
|
+
max_sessions: config.maxSessions,
|
|
2158
|
+
sandbox: config.sandbox,
|
|
2159
|
+
spawn_mode: config.spawnMode
|
|
2160
|
+
});
|
|
2161
|
+
const spawner = createSessionSpawner({
|
|
2162
|
+
execPath: process.execPath,
|
|
2163
|
+
scriptArgs: spawnScriptArgs(),
|
|
2164
|
+
env: process.env,
|
|
2165
|
+
verbose,
|
|
2166
|
+
sandbox,
|
|
2167
|
+
debugFile,
|
|
2168
|
+
permissionMode,
|
|
2169
|
+
onDebug: logForDebugging,
|
|
2170
|
+
onActivity: (sessionId, activity) => {
|
|
2171
|
+
logForDebugging(`[bridge:activity] sessionId=${sessionId} ${activity.type} ${activity.summary}`);
|
|
2172
|
+
},
|
|
2173
|
+
onPermissionRequest: (sessionId, request, _accessToken) => {
|
|
2174
|
+
logForDebugging(`[bridge:perm] sessionId=${sessionId} tool=${request.request.tool_name} request_id=${request.request_id} (not auto-approving)`);
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
const logger = createBridgeLogger({ verbose });
|
|
2178
|
+
const { parseGitHubRepository } = await import("./chunk-q6av622g.js");
|
|
2179
|
+
const ownerRepo = gitRepoUrl ? parseGitHubRepository(gitRepoUrl) : null;
|
|
2180
|
+
const repoName = ownerRepo ? ownerRepo.split("/").pop() : basename(dir);
|
|
2181
|
+
logger.setRepoInfo(repoName, branch);
|
|
2182
|
+
const toggleAvailable = spawnMode !== "single-session" && worktreeAvailable;
|
|
2183
|
+
if (toggleAvailable) {
|
|
2184
|
+
logger.setSpawnModeDisplay(spawnMode);
|
|
2185
|
+
}
|
|
2186
|
+
const onStdinData = (data) => {
|
|
2187
|
+
if (data[0] === 3 || data[0] === 4) {
|
|
2188
|
+
process.emit("SIGINT");
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (data[0] === 32) {
|
|
2192
|
+
logger.toggleQr();
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (data[0] === 119) {
|
|
2196
|
+
if (!toggleAvailable)
|
|
2197
|
+
return;
|
|
2198
|
+
const newMode = config.spawnMode === "same-dir" ? "worktree" : "same-dir";
|
|
2199
|
+
config.spawnMode = newMode;
|
|
2200
|
+
logEvent("tengu_bridge_spawn_mode_toggled", {
|
|
2201
|
+
spawn_mode: newMode
|
|
2202
|
+
});
|
|
2203
|
+
logger.logStatus(newMode === "worktree" ? "Spawn mode: worktree (new sessions get isolated git worktrees)" : "Spawn mode: same-dir (new sessions share the current directory)");
|
|
2204
|
+
logger.setSpawnModeDisplay(newMode);
|
|
2205
|
+
logger.refreshDisplay();
|
|
2206
|
+
saveCurrentProjectConfig((current) => {
|
|
2207
|
+
if (current.remoteControlSpawnMode === newMode)
|
|
2208
|
+
return current;
|
|
2209
|
+
return { ...current, remoteControlSpawnMode: newMode };
|
|
2210
|
+
});
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
if (process.stdin.isTTY) {
|
|
2215
|
+
process.stdin.setRawMode(true);
|
|
2216
|
+
process.stdin.resume();
|
|
2217
|
+
process.stdin.on("data", onStdinData);
|
|
2218
|
+
}
|
|
2219
|
+
const controller = new AbortController;
|
|
2220
|
+
const onSigint = () => {
|
|
2221
|
+
logForDebugging("[bridge:shutdown] SIGINT received, shutting down");
|
|
2222
|
+
controller.abort();
|
|
2223
|
+
};
|
|
2224
|
+
const onSigterm = () => {
|
|
2225
|
+
logForDebugging("[bridge:shutdown] SIGTERM received, shutting down");
|
|
2226
|
+
controller.abort();
|
|
2227
|
+
};
|
|
2228
|
+
process.on("SIGINT", onSigint);
|
|
2229
|
+
process.on("SIGTERM", onSigterm);
|
|
2230
|
+
let initialSessionId = null;
|
|
2231
|
+
if (preCreateSession && true) {
|
|
2232
|
+
const { createBridgeSession } = await import("./chunk-g75w4hw3.js");
|
|
2233
|
+
try {
|
|
2234
|
+
initialSessionId = await createBridgeSession({
|
|
2235
|
+
environmentId,
|
|
2236
|
+
title: name,
|
|
2237
|
+
events: [],
|
|
2238
|
+
gitRepoUrl,
|
|
2239
|
+
branch,
|
|
2240
|
+
signal: controller.signal,
|
|
2241
|
+
baseUrl,
|
|
2242
|
+
getAccessToken: getBridgeAccessToken,
|
|
2243
|
+
permissionMode
|
|
2244
|
+
});
|
|
2245
|
+
if (initialSessionId) {
|
|
2246
|
+
logForDebugging(`[bridge:init] Created initial session ${initialSessionId}`);
|
|
2247
|
+
}
|
|
2248
|
+
} catch (err) {
|
|
2249
|
+
logForDebugging(`[bridge:init] Session creation failed (non-fatal): ${errorMessage(err)}`);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
let pointerRefreshTimer = null;
|
|
2253
|
+
if (initialSessionId && spawnMode === "single-session") {
|
|
2254
|
+
const { writeBridgePointer } = await import("./chunk-2tw2ve8h.js");
|
|
2255
|
+
const pointerPayload = {
|
|
2256
|
+
sessionId: initialSessionId,
|
|
2257
|
+
environmentId,
|
|
2258
|
+
source: "standalone"
|
|
2259
|
+
};
|
|
2260
|
+
await writeBridgePointer(config.dir, pointerPayload);
|
|
2261
|
+
pointerRefreshTimer = setInterval(writeBridgePointer, 3600000, config.dir, pointerPayload);
|
|
2262
|
+
pointerRefreshTimer.unref?.();
|
|
2263
|
+
}
|
|
2264
|
+
try {
|
|
2265
|
+
await runBridgeLoop(config, environmentId, environmentSecret, api, spawner, logger, controller.signal, undefined, initialSessionId ?? undefined, async () => {
|
|
2266
|
+
clearOAuthTokenCache();
|
|
2267
|
+
await checkAndRefreshOAuthTokenIfNeeded();
|
|
2268
|
+
return getBridgeAccessToken();
|
|
2269
|
+
});
|
|
2270
|
+
} finally {
|
|
2271
|
+
if (pointerRefreshTimer !== null) {
|
|
2272
|
+
clearInterval(pointerRefreshTimer);
|
|
2273
|
+
}
|
|
2274
|
+
process.off("SIGINT", onSigint);
|
|
2275
|
+
process.off("SIGTERM", onSigterm);
|
|
2276
|
+
process.stdin.off("data", onStdinData);
|
|
2277
|
+
if (process.stdin.isTTY) {
|
|
2278
|
+
process.stdin.setRawMode(false);
|
|
2279
|
+
}
|
|
2280
|
+
process.stdin.pause();
|
|
2281
|
+
}
|
|
2282
|
+
process.exit(0);
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
class BridgeHeadlessPermanentError extends Error {
|
|
2286
|
+
constructor(message) {
|
|
2287
|
+
super(message);
|
|
2288
|
+
this.name = "BridgeHeadlessPermanentError";
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
async function runBridgeHeadless(opts, signal) {
|
|
2292
|
+
const { dir, log } = opts;
|
|
2293
|
+
process.chdir(dir);
|
|
2294
|
+
const { setOriginalCwd, setCwdState } = await import("./chunk-44fpr6jq.js");
|
|
2295
|
+
setOriginalCwd(dir);
|
|
2296
|
+
setCwdState(dir);
|
|
2297
|
+
const { enableConfigs, checkHasTrustDialogAccepted } = await import("./chunk-9zgdvbm6.js");
|
|
2298
|
+
enableConfigs();
|
|
2299
|
+
const { initSinks } = await import("./chunk-qp18kd4h.js");
|
|
2300
|
+
initSinks();
|
|
2301
|
+
if (!checkHasTrustDialogAccepted()) {
|
|
2302
|
+
throw new BridgeHeadlessPermanentError(`Workspace not trusted: ${dir}. Run \`claude\` in that directory first to accept the trust dialog.`);
|
|
2303
|
+
}
|
|
2304
|
+
if (!opts.getAccessToken()) {
|
|
2305
|
+
throw new Error(BRIDGE_LOGIN_ERROR);
|
|
2306
|
+
}
|
|
2307
|
+
const { getBridgeBaseUrl } = await import("./chunk-hbc6ymdv.js");
|
|
2308
|
+
const baseUrl = getBridgeBaseUrl();
|
|
2309
|
+
if (baseUrl.startsWith("http://") && !baseUrl.includes("localhost") && !baseUrl.includes("127.0.0.1")) {
|
|
2310
|
+
throw new BridgeHeadlessPermanentError("Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.");
|
|
2311
|
+
}
|
|
2312
|
+
const sessionIngressUrl = process.env.USER_TYPE === "ant" && process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL ? process.env.CLAUDE_BRIDGE_SESSION_INGRESS_URL : baseUrl;
|
|
2313
|
+
const { getBranch, getRemoteUrl, findGitRoot } = await import("./chunk-3gqdqmzb.js");
|
|
2314
|
+
const { hasWorktreeCreateHook } = await import("./chunk-9k5s3ryh.js");
|
|
2315
|
+
if (opts.spawnMode === "worktree") {
|
|
2316
|
+
const worktreeAvailable = hasWorktreeCreateHook() || findGitRoot(dir) !== null;
|
|
2317
|
+
if (!worktreeAvailable) {
|
|
2318
|
+
throw new BridgeHeadlessPermanentError(`Worktree mode requires a git repository or WorktreeCreate hooks. Directory ${dir} has neither.`);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
const branch = await getBranch();
|
|
2322
|
+
const gitRepoUrl = await getRemoteUrl();
|
|
2323
|
+
const machineName = hostname();
|
|
2324
|
+
const bridgeId = randomUUID();
|
|
2325
|
+
const config = {
|
|
2326
|
+
dir,
|
|
2327
|
+
machineName,
|
|
2328
|
+
branch,
|
|
2329
|
+
gitRepoUrl,
|
|
2330
|
+
maxSessions: opts.capacity,
|
|
2331
|
+
spawnMode: opts.spawnMode,
|
|
2332
|
+
verbose: false,
|
|
2333
|
+
sandbox: opts.sandbox,
|
|
2334
|
+
bridgeId,
|
|
2335
|
+
workerType: "claude_code",
|
|
2336
|
+
environmentId: randomUUID(),
|
|
2337
|
+
apiBaseUrl: baseUrl,
|
|
2338
|
+
sessionIngressUrl,
|
|
2339
|
+
sessionTimeoutMs: opts.sessionTimeoutMs
|
|
2340
|
+
};
|
|
2341
|
+
const api = createBridgeApiClient({
|
|
2342
|
+
baseUrl,
|
|
2343
|
+
getAccessToken: opts.getAccessToken,
|
|
2344
|
+
runnerVersion: MACRO.VERSION,
|
|
2345
|
+
onDebug: log,
|
|
2346
|
+
onAuth401: opts.onAuth401,
|
|
2347
|
+
getTrustedDeviceToken
|
|
2348
|
+
});
|
|
2349
|
+
let environmentId;
|
|
2350
|
+
let environmentSecret;
|
|
2351
|
+
try {
|
|
2352
|
+
const reg = await api.registerBridgeEnvironment(config);
|
|
2353
|
+
environmentId = reg.environment_id;
|
|
2354
|
+
environmentSecret = reg.environment_secret;
|
|
2355
|
+
} catch (err) {
|
|
2356
|
+
throw new Error(`Bridge registration failed: ${errorMessage(err)}`);
|
|
2357
|
+
}
|
|
2358
|
+
const spawner = createSessionSpawner({
|
|
2359
|
+
execPath: process.execPath,
|
|
2360
|
+
scriptArgs: spawnScriptArgs(),
|
|
2361
|
+
env: process.env,
|
|
2362
|
+
verbose: false,
|
|
2363
|
+
sandbox: opts.sandbox,
|
|
2364
|
+
permissionMode: opts.permissionMode,
|
|
2365
|
+
onDebug: log
|
|
2366
|
+
});
|
|
2367
|
+
const logger = createHeadlessBridgeLogger(log);
|
|
2368
|
+
logger.printBanner(config, environmentId);
|
|
2369
|
+
let initialSessionId;
|
|
2370
|
+
if (opts.createSessionOnStart) {
|
|
2371
|
+
const { createBridgeSession } = await import("./chunk-g75w4hw3.js");
|
|
2372
|
+
try {
|
|
2373
|
+
const sid = await createBridgeSession({
|
|
2374
|
+
environmentId,
|
|
2375
|
+
title: opts.name,
|
|
2376
|
+
events: [],
|
|
2377
|
+
gitRepoUrl,
|
|
2378
|
+
branch,
|
|
2379
|
+
signal,
|
|
2380
|
+
baseUrl,
|
|
2381
|
+
getAccessToken: opts.getAccessToken,
|
|
2382
|
+
permissionMode: opts.permissionMode
|
|
2383
|
+
});
|
|
2384
|
+
if (sid) {
|
|
2385
|
+
initialSessionId = sid;
|
|
2386
|
+
log(`created initial session ${sid}`);
|
|
2387
|
+
}
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
log(`session pre-creation failed (non-fatal): ${errorMessage(err)}`);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
await runBridgeLoop(config, environmentId, environmentSecret, api, spawner, logger, signal, undefined, initialSessionId, async () => opts.getAccessToken());
|
|
2393
|
+
}
|
|
2394
|
+
function createHeadlessBridgeLogger(log) {
|
|
2395
|
+
const noop = () => {};
|
|
2396
|
+
return {
|
|
2397
|
+
printBanner: (cfg, envId) => log(`registered environmentId=${envId} dir=${cfg.dir} spawnMode=${cfg.spawnMode} capacity=${cfg.maxSessions}`),
|
|
2398
|
+
logSessionStart: (id, _prompt) => log(`session start ${id}`),
|
|
2399
|
+
logSessionComplete: (id, ms) => log(`session complete ${id} (${ms}ms)`),
|
|
2400
|
+
logSessionFailed: (id, err) => log(`session failed ${id}: ${err}`),
|
|
2401
|
+
logStatus: log,
|
|
2402
|
+
logVerbose: log,
|
|
2403
|
+
logError: (s) => log(`error: ${s}`),
|
|
2404
|
+
logReconnected: (ms) => log(`reconnected after ${ms}ms`),
|
|
2405
|
+
addSession: (id, _url) => log(`session attached ${id}`),
|
|
2406
|
+
removeSession: (id) => log(`session detached ${id}`),
|
|
2407
|
+
updateIdleStatus: noop,
|
|
2408
|
+
updateReconnectingStatus: noop,
|
|
2409
|
+
updateSessionStatus: noop,
|
|
2410
|
+
updateSessionActivity: noop,
|
|
2411
|
+
updateSessionCount: noop,
|
|
2412
|
+
updateFailedStatus: noop,
|
|
2413
|
+
setSpawnModeDisplay: noop,
|
|
2414
|
+
setRepoInfo: noop,
|
|
2415
|
+
setDebugLogPath: noop,
|
|
2416
|
+
setAttached: noop,
|
|
2417
|
+
setSessionTitle: noop,
|
|
2418
|
+
clearStatus: noop,
|
|
2419
|
+
toggleQr: noop,
|
|
2420
|
+
refreshDisplay: noop
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
export {
|
|
2424
|
+
runBridgeLoop,
|
|
2425
|
+
runBridgeHeadless,
|
|
2426
|
+
parseArgs,
|
|
2427
|
+
isServerError,
|
|
2428
|
+
isConnectionError,
|
|
2429
|
+
bridgeMain,
|
|
2430
|
+
BridgeHeadlessPermanentError
|
|
2431
|
+
};
|