@maycombhq/atticus 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +8 -4
- package/dist/chunk-265LWMOA.js +91 -0
- package/dist/chunk-26WBY34Y.js +37 -0
- package/dist/{chunk-UBBC2XSG.js → chunk-26Z7OGQW.js} +518 -93
- package/dist/chunk-2D7PWSOK.js +172 -0
- package/dist/{chunk-JCZIKA4C.js → chunk-2HH3UCZM.js} +4 -4
- package/dist/{chunk-GDLULHTC.js → chunk-2HTEMYTB.js} +12 -26
- package/dist/chunk-2UCRFXFZ.js +130 -0
- package/dist/{chunk-MVZVGYXH.js → chunk-2V2ZPQQU.js} +81 -13
- package/dist/{chunk-BOHJZTEQ.js → chunk-36RY7ZXM.js} +130 -71
- package/dist/{chunk-I3E7CX4H.js → chunk-37WE3LS2.js} +3 -3
- package/dist/{chunk-IPKHE5D6.js → chunk-3AQ2R5NL.js} +1520 -871
- package/dist/chunk-3G6S7LAA.js +43 -0
- package/dist/chunk-3J5I7ZOU.js +58 -0
- package/dist/{chunk-65NYPGRC.js → chunk-3LZ6P5EP.js} +1 -1
- package/dist/chunk-3SSTNB4E.js +191 -0
- package/dist/chunk-3TD6S4TC.js +49 -0
- package/dist/{chunk-3C7EYRFZ.js → chunk-3UTYPHN3.js} +1 -1
- package/dist/{chunk-G2B4ZQGH.js → chunk-3WON4G3M.js} +2 -2
- package/dist/{chunk-S3QNRWYR.js → chunk-42EPP7OB.js} +12 -12
- package/dist/chunk-455SYIIE.js +113 -0
- package/dist/{chunk-YISXLPHN.js → chunk-4B6BT75O.js} +166 -23
- package/dist/{chunk-C6EKFH7J.js → chunk-4CLK2RJO.js} +7 -7
- package/dist/chunk-4CYTJ5TZ.js +38 -0
- package/dist/{chunk-EYWIDJFU.js → chunk-4D62IZII.js} +5 -5
- package/dist/chunk-4FOAYLI4.js +328 -0
- package/dist/{chunk-7JUKDRSZ.js → chunk-4KNOJC6J.js} +294 -215
- package/dist/{chunk-YMZ3TNYB.js → chunk-4PYC2UGC.js} +2 -2
- package/dist/{chunk-27NKUS3X.js → chunk-4SLBCEPD.js} +2104 -726
- package/dist/chunk-4TKVU2I2.js +122 -0
- package/dist/{chunk-RUVFXE2L.js → chunk-4WXRPQDE.js} +448 -47
- package/dist/{chunk-7ZUIMTOU.js → chunk-5CMPVC3N.js} +24 -24
- package/dist/chunk-5G7ILBUG.js +66 -0
- package/dist/{chunk-HFQCFSJR.js → chunk-5PV7S2ZP.js} +3 -3
- package/dist/{chunk-FITWGOJ2.js → chunk-5QUOPNLN.js} +4 -4
- package/dist/{chunk-Z6LF7HQN.js → chunk-5TDRBOAV.js} +5 -5
- package/dist/{chunk-T6AIP7H5.js → chunk-5WERBKLE.js} +199 -63
- package/dist/{chunk-XPCM366L.js → chunk-62DLJSLV.js} +8 -8
- package/dist/{chunk-L24C55VN.js → chunk-64AADMTW.js} +99 -25
- package/dist/{chunk-K65BJIMT.js → chunk-67BP7HXL.js} +17 -17
- package/dist/chunk-6DQYZU2W.js +379 -0
- package/dist/{chunk-EDS2L2LD.js → chunk-6EXDI5GI.js} +52 -26
- package/dist/chunk-6FNAKCET.js +38 -0
- package/dist/{chunk-6IU67AEK.js → chunk-75ASLHBT.js} +21 -15
- package/dist/{chunk-A3GY6SOA.js → chunk-75ZOZFGH.js} +18 -18
- package/dist/{chunk-MGYZM27B.js → chunk-77O633L5.js} +49 -23
- package/dist/{chunk-GQ4N4WKF.js → chunk-7AB6OWTV.js} +3 -3
- package/dist/{chunk-M766ZJ6W.js → chunk-7CZHUZBL.js} +25 -25
- package/dist/{chunk-A7UPUQFY.js → chunk-7FHNFCHK.js} +643 -124
- package/dist/chunk-7FUIVIBG.js +415 -0
- package/dist/{chunk-6QSERWXH.js → chunk-7QPCGTPS.js} +21 -21
- package/dist/{chunk-LR3R6Z2A.js → chunk-A2G72UI4.js} +7 -7
- package/dist/{chunk-I4JRCD6M.js → chunk-AE4P57KK.js} +23 -17
- package/dist/{chunk-AVJDSJ2F.js → chunk-AHW3RY3X.js} +7 -7
- package/dist/chunk-AM6FP6JW.js +43 -0
- package/dist/{chunk-CSZ4C5N6.js → chunk-AOCWFA7T.js} +93 -10
- package/dist/{chunk-EPTBZ3GP.js → chunk-API5TOUB.js} +1 -1
- package/dist/{chunk-652QWTLZ.js → chunk-AZDITBCT.js} +3 -3
- package/dist/{chunk-DPUYDDQG.js → chunk-BA2HUMDN.js} +25 -3
- package/dist/chunk-BA3U7ALS.js +99 -0
- package/dist/{chunk-HVUPKCGJ.js → chunk-BC6KKZ67.js} +5 -5
- package/dist/{chunk-CKSITVS4.js → chunk-BEPCKDCP.js} +2554 -722
- package/dist/{chunk-3ALD2W2A.js → chunk-BPPU3PW7.js} +4 -4
- package/dist/{chunk-QRAOANID.js → chunk-BVWWPBKD.js} +21 -21
- package/dist/{chunk-ZDPAS56H.js → chunk-BXBCMGIQ.js} +7 -1
- package/dist/chunk-BXZ4PKIT.js +233 -0
- package/dist/chunk-C7CEK3KW.js +247 -0
- package/dist/chunk-CFH3QH26.js +29 -0
- package/dist/chunk-CG6CULP6.js +170 -0
- package/dist/chunk-CLD4RVTA.js +45 -0
- package/dist/{chunk-DKJA76CJ.js → chunk-CNPY4NTZ.js} +13 -13
- package/dist/chunk-CNU5Q2D5.js +575 -0
- package/dist/{chunk-T2UYZSCQ.js → chunk-COYN3UAN.js} +126 -23
- package/dist/{chunk-23W7VWKR.js → chunk-CPK7QSBG.js} +1360 -113
- package/dist/{chunk-4AK7IBVP.js → chunk-D2WSW4GU.js} +1 -1
- package/dist/{chunk-ABWHYWNI.js → chunk-D57MFJPP.js} +3 -3
- package/dist/chunk-D5KV5PSK.js +45 -0
- package/dist/chunk-D5MASS4I.js +48 -0
- package/dist/{chunk-7EOBHUMC.js → chunk-DCWN3HFV.js} +2 -2
- package/dist/{chunk-QSXK2RGH.js → chunk-DDJPM5YX.js} +2 -2
- package/dist/chunk-DN4A2Q2O.js +57 -0
- package/dist/{chunk-IHMLFRVV.js → chunk-DUPZPDCJ.js} +58 -16
- package/dist/{chunk-PRPPGYZG.js → chunk-E2VXBSIR.js} +4 -4
- package/dist/chunk-E57AOFYN.js +32 -0
- package/dist/{chunk-UGLN7QHV.js → chunk-ECLXZWAG.js} +12 -12
- package/dist/{chunk-5WG5YP4T.js → chunk-EDZ45WIR.js} +14 -14
- package/dist/chunk-EFCQSMXB.js +37 -0
- package/dist/{chunk-2ANOROX3.js → chunk-EZIN5E5B.js} +8 -8
- package/dist/{chunk-BWZ77RQT.js → chunk-F6H2RJ3N.js} +1 -1
- package/dist/{chunk-OLQJTJJU.js → chunk-FAJDKNSD.js} +7 -3
- package/dist/chunk-FGAGWYZB.js +47 -0
- package/dist/{chunk-4WBVUHXN.js → chunk-FLVZFTLQ.js} +18 -18
- package/dist/chunk-FQ6WH6PB.js +140 -0
- package/dist/{chunk-T6XQL73F.js → chunk-FRHD22WM.js} +61 -23
- package/dist/{chunk-XAKWMCXI.js → chunk-FVMPFCDS.js} +4 -4
- package/dist/{chunk-B2WLAWNJ.js → chunk-FY3QIRTL.js} +3 -3
- package/dist/{chunk-CPA6HPVR.js → chunk-FYBF5MB2.js} +15 -11
- package/dist/{chunk-V2YTAIQO.js → chunk-G5YJBNDZ.js} +21 -6
- package/dist/chunk-G6IQYM25.js +339 -0
- package/dist/chunk-G6X4YL4D.js +161 -0
- package/dist/{chunk-SJRGMG6A.js → chunk-GC2VK3JT.js} +25 -25
- package/dist/{chunk-SP2I63OO.js → chunk-GCS3FZOB.js} +77 -3
- package/dist/{chunk-6TACQN3N.js → chunk-GDZB57IU.js} +5 -5
- package/dist/chunk-GIFMBCTW.js +221 -0
- package/dist/{chunk-YY6ZQQBP.js → chunk-GK4PYVEC.js} +18 -8
- package/dist/{chunk-TDD55CJH.js → chunk-GNWAOVFQ.js} +6 -5
- package/dist/chunk-GS76RXQE.js +226 -0
- package/dist/{chunk-YYQN46FY.js → chunk-GSGIZ2OO.js} +23 -23
- package/dist/chunk-GUYL7RMK.js +60 -0
- package/dist/chunk-GX74YWR7.js +321 -0
- package/dist/chunk-HE3HRDNZ.js +122 -0
- package/dist/{chunk-AIOQPLA5.js → chunk-HGHRSF3E.js} +30 -12
- package/dist/{chunk-2RMYG36Q.js → chunk-HK3VOHIE.js} +198 -336
- package/dist/{chunk-QZSX3F7C.js → chunk-HS4BGB4G.js} +24 -22
- package/dist/{chunk-XY3P7JW7.js → chunk-HSZMPH5T.js} +834 -149
- package/dist/{chunk-L6SGQIR3.js → chunk-HWD4JPU7.js} +4 -2
- package/dist/{chunk-PCVDV3UZ.js → chunk-HXXP2NTE.js} +6 -6
- package/dist/{chunk-AA6YULYB.js → chunk-I2A3Z5AK.js} +7 -7
- package/dist/{chunk-N7IXEHK3.js → chunk-ICHDSWEY.js} +7 -7
- package/dist/chunk-IFFRGC3N.js +31 -0
- package/dist/{chunk-4BN53TG3.js → chunk-IFHDV6KQ.js} +31 -23
- package/dist/chunk-J2O4LFVI.js +80 -0
- package/dist/chunk-J5AAZ6OJ.js +161 -0
- package/dist/{chunk-ZMFPW66A.js → chunk-JAABT3DA.js} +3 -3
- package/dist/chunk-JITJP64K.js +34 -0
- package/dist/chunk-JKIGL3XQ.js +162 -0
- package/dist/chunk-JRVNXLZP.js +615 -0
- package/dist/chunk-JRXHON4E.js +41 -0
- package/dist/{chunk-DHPACDG7.js → chunk-JS6WMC3J.js} +8 -8
- package/dist/chunk-JU4QNJSR.js +62 -0
- package/dist/{chunk-PAMPPUR6.js → chunk-JYMIB5XY.js} +1697 -172
- package/dist/{chunk-RRTKKSLQ.js → chunk-JYTIHTIK.js} +8 -6
- package/dist/{chunk-3HI6Z55Y.js → chunk-K332XW7K.js} +324 -39
- package/dist/{chunk-GLUX4EEP.js → chunk-K6JB6XTX.js} +9 -9
- package/dist/chunk-KDAHXQIE.js +294 -0
- package/dist/chunk-KYZMRSPS.js +93 -0
- package/dist/chunk-L4CESDPB.js +22 -0
- package/dist/{chunk-NXFPS2LR.js → chunk-LEBR5NHW.js} +14 -14
- package/dist/chunk-LIJJCBQI.js +223 -0
- package/dist/{chunk-DE76JROX.js → chunk-LIRVU3HW.js} +16 -16
- package/dist/{chunk-JMSCMUCA.js → chunk-LN77DXBM.js} +145 -21
- package/dist/{chunk-JIITQXFB.js → chunk-LNWEWYCM.js} +2969 -788
- package/dist/{chunk-TANFV3MZ.js → chunk-LO3BLMVG.js} +3 -3
- package/dist/chunk-LPV6T362.js +250 -0
- package/dist/{chunk-2VHF7OXM.js → chunk-LQH4P45K.js} +1 -1
- package/dist/chunk-LU4MQVAL.js +41 -0
- package/dist/chunk-LWDNKE4X.js +273 -0
- package/dist/chunk-M4H2UG4R.js +218 -0
- package/dist/{chunk-6BVONWJ7.js → chunk-M6D2U3YM.js} +3 -3
- package/dist/chunk-MD7KUEJ3.js +514 -0
- package/dist/{chunk-66YVAGGM.js → chunk-MMZUN7ZO.js} +20 -20
- package/dist/chunk-MZGRDONP.js +916 -0
- package/dist/chunk-N3RUGTGV.js +39 -0
- package/dist/{chunk-JGZCG5VM.js → chunk-NCB6656Z.js} +7 -7
- package/dist/{chunk-4NDAAFQT.js → chunk-NRQ65LTQ.js} +7 -5
- package/dist/{chunk-UNM6ZOKP.js → chunk-NTAVOWYD.js} +145 -18
- package/dist/{chunk-3USYZGRF.js → chunk-NYOABJNK.js} +36 -27
- package/dist/{chunk-WVPJSRRJ.js → chunk-O4IKAIZ2.js} +24 -20
- package/dist/{chunk-D3PO4DOZ.js → chunk-ODMBPDDV.js} +51 -14
- package/dist/chunk-OKUB6RJA.js +533 -0
- package/dist/chunk-OMPENNWY.js +39 -0
- package/dist/chunk-OPQZISG3.js +25 -0
- package/dist/{chunk-5GGOLAKC.js → chunk-OV57AHCG.js} +26 -26
- package/dist/chunk-OZJ3JLYI.js +58 -0
- package/dist/{chunk-KEM4SDDP.js → chunk-P2VBXX7N.js} +11 -10
- package/dist/{chunk-S2A7YC3Q.js → chunk-PAINMSID.js} +10150 -1720
- package/dist/chunk-PKJAV25S.js +43 -0
- package/dist/{chunk-EBEW3XXQ.js → chunk-PNQLCRSS.js} +4 -4
- package/dist/{chunk-WY6PP6AA.js → chunk-PQH56YF6.js} +37 -5
- package/dist/chunk-PRTBPXYA.js +50 -0
- package/dist/{chunk-YEDDOYSC.js → chunk-PRW3HXM3.js} +2 -2
- package/dist/chunk-PRZ7KMWL.js +42 -0
- package/dist/{chunk-6M5NK3K3.js → chunk-PTQ3ZALZ.js} +42 -4
- package/dist/{chunk-5GDRCDQB.js → chunk-PUEWDYGP.js} +2 -2
- package/dist/chunk-Q26LDIUV.js +236 -0
- package/dist/{chunk-CR3MTH32.js → chunk-Q46QDDMF.js} +77 -7
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/chunk-QE527XRY.js +342 -0
- package/dist/{chunk-CNUKCS2T.js → chunk-QHJJMZLX.js} +1518 -54
- package/dist/chunk-QJCJOMW3.js +425 -0
- package/dist/{chunk-VYDBJ73M.js → chunk-QKMRFNMA.js} +15 -15
- package/dist/chunk-QOOEYDNJ.js +317 -0
- package/dist/chunk-QSIDVFB7.js +45 -0
- package/dist/{chunk-R77KLRB3.js → chunk-RA33EYS4.js} +19 -18
- package/dist/{chunk-DFBCSHVT.js → chunk-RALY3E5U.js} +4 -4
- package/dist/{chunk-ZCJDOK2S.js → chunk-S2RY3BOD.js} +35 -21
- package/dist/chunk-S5LSDXV4.js +40 -0
- package/dist/chunk-SB5GRUFM.js +54 -0
- package/dist/chunk-SH27FYPF.js +150 -0
- package/dist/chunk-T74WQQGS.js +40 -0
- package/dist/{chunk-O3WI5KWW.js → chunk-TGVVUKTG.js} +2 -2
- package/dist/chunk-THN26IEF.js +48 -0
- package/dist/chunk-TJH2EUAZ.js +47 -0
- package/dist/{chunk-ZNCEKR4J.js → chunk-TQ4TG57V.js} +12 -8
- package/dist/{chunk-7FBGJFEE.js → chunk-TQTRWENN.js} +12 -12
- package/dist/{chunk-TYAID7B7.js → chunk-TSM6T6GQ.js} +23 -23
- package/dist/{chunk-DPZHZQKE.js → chunk-TW3KOSQK.js} +43 -10
- package/dist/{chunk-MC3H7736.js → chunk-TWQRPM76.js} +37 -4
- package/dist/chunk-TYD6I4CZ.js +109 -0
- package/dist/chunk-U3NBK4EU.js +420 -0
- package/dist/{chunk-6JCPI55B.js → chunk-UDXWIPCB.js} +47 -7
- package/dist/{chunk-MMOSZPWJ.js → chunk-UEVZZZU2.js} +4 -4
- package/dist/{chunk-JBTK5OP2.js → chunk-UF4UHCIG.js} +2 -2
- package/dist/{chunk-6JUBZBMW.js → chunk-UFE36WJH.js} +4 -4
- package/dist/{chunk-ONLS5QA5.js → chunk-URIKUID7.js} +12 -12
- package/dist/chunk-UYLBRRFK.js +1106 -0
- package/dist/chunk-UZACEG2C.js +515 -0
- package/dist/{chunk-7SW2QWLU.js → chunk-VAMHOHUU.js} +12 -12
- package/dist/{chunk-RZPYKDWS.js → chunk-VUVGX5L2.js} +1 -1
- package/dist/{chunk-Q4D5X7QB.js → chunk-VYOCPTUB.js} +44 -24
- package/dist/{chunk-CAQZUNOH.js → chunk-WAZEXEJH.js} +567 -3
- package/dist/chunk-WCEF7H6L.js +221 -0
- package/dist/{chunk-2DIQQJCB.js → chunk-WEOMJHPV.js} +12 -12
- package/dist/{chunk-FHH4I4ZV.js → chunk-WFU77T3A.js} +12 -6
- package/dist/chunk-WIWTJMMR.js +292 -0
- package/dist/{chunk-2BFCZ3RK.js → chunk-WNTIOBOG.js} +18 -18
- package/dist/{chunk-7WD63HU2.js → chunk-X2CUXTWU.js} +6 -6
- package/dist/chunk-X7EMBM5X.js +34 -0
- package/dist/chunk-XEQCQO66.js +33 -0
- package/dist/{chunk-F7HYOUFF.js → chunk-XG4WJHLQ.js} +20 -4
- package/dist/{chunk-EFHQ3TWP.js → chunk-XOL3GFW2.js} +171 -31
- package/dist/{chunk-NE2W3OO7.js → chunk-XQ3YZ4QW.js} +22 -22
- package/dist/{chunk-CEXSU4S6.js → chunk-Y6K3CJBU.js} +723 -85
- package/dist/{chunk-4RS5KZQD.js → chunk-YAH5IQAL.js} +5 -5
- package/dist/{chunk-64EQYWCA.js → chunk-YBOVNMHC.js} +51 -14
- package/dist/chunk-YF5DZCPN.js +1212 -0
- package/dist/chunk-YIGBJNH4.js +37 -0
- package/dist/{chunk-BKVWTZ6A.js → chunk-YJ3TBOTK.js} +25 -25
- package/dist/{chunk-Q5AMFEWD.js → chunk-YKTE2RU3.js} +2 -2
- package/dist/{chunk-7EKXJZ2Y.js → chunk-YO2PKTXT.js} +296 -37
- package/dist/{chunk-6J5OKA4Y.js → chunk-YTTNTQOX.js} +15 -15
- package/dist/{chunk-OT2O76OS.js → chunk-YWAKDNMH.js} +183 -33
- package/dist/{chunk-R6XF6UR3.js → chunk-YZDZOEV4.js} +4 -4
- package/dist/chunk-Z3QFH7YG.js +17 -0
- package/dist/{chunk-W5BVQF44.js → chunk-Z6VLC5X4.js} +17 -17
- package/dist/{chunk-HLJAXMM4.js → chunk-ZFQMMU5Y.js} +2 -2
- package/dist/{chunk-W3BQAZ3Q.js → chunk-ZKYRDV7A.js} +12 -12
- package/dist/{chunk-ZMAINWBW.js → chunk-ZLS6PU2O.js} +3 -3
- package/dist/{chunk-ZVM6AKKM.js → chunk-ZOSD4FXV.js} +3 -3
- package/dist/chunk-ZPDDEVQX.js +47 -0
- package/dist/{chunk-TXLH6WHQ.js → chunk-ZQLRCA2V.js} +11 -11
- package/dist/{chunk-HITVBIJS.js → chunk-ZX7235KR.js} +128 -54
- package/dist/{chunk-GT4I4ZWM.js → chunk-ZZFHLRQW.js} +18 -16
- package/dist/files.json +636 -364
- package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
- package/dist/packages/sdk/npm/index.js +5614 -2343
- package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CowTnhEJ.js} +2 -2
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
- package/dist/packages/ui/dist/index.html +2 -2
- package/package.json +4 -4
- package/dist/chunk-2BF2EYJV.js +0 -174
- package/dist/chunk-42EVSKME.js +0 -37
- package/dist/chunk-4BKGMUIK.js +0 -53
- package/dist/chunk-5KLGBXG5.js +0 -48
- package/dist/chunk-6BNC6EQ4.js +0 -41
- package/dist/chunk-6D2CQ5II.js +0 -41
- package/dist/chunk-7AVHLZGD.js +0 -45
- package/dist/chunk-D46WUV3T.js +0 -33
- package/dist/chunk-E36QOJMD.js +0 -47
- package/dist/chunk-FQXYPF7E.js +0 -147
- package/dist/chunk-GBRFVT63.js +0 -45
- package/dist/chunk-ICY36XCH.js +0 -34
- package/dist/chunk-J4K3KVLD.js +0 -131
- package/dist/chunk-JZU3H6UQ.js +0 -54
- package/dist/chunk-NN2ZQEB4.js +0 -38
- package/dist/chunk-PCOCACY3.js +0 -385
- package/dist/chunk-RTEYOSJY.js +0 -29
- package/dist/chunk-RVH5SUW5.js +0 -69
- package/dist/chunk-VN3DP4LQ.js +0 -43
- package/dist/chunk-XPTOBS2D.js +0 -48
- package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
- package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
STEP_CLASSES,
|
|
14
14
|
changesDirOf,
|
|
15
15
|
changesRelOf,
|
|
16
|
+
copilotAcpOf,
|
|
16
17
|
currentSignal,
|
|
17
18
|
differentThings,
|
|
18
19
|
identifierWords,
|
|
@@ -27,14 +28,114 @@ import {
|
|
|
27
28
|
runsRelOf,
|
|
28
29
|
stateDirOf,
|
|
29
30
|
stepModelsOf
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-PAINMSID.js";
|
|
31
32
|
import {
|
|
32
33
|
writeFileAtomic
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-ODMBPDDV.js";
|
|
34
35
|
import {
|
|
35
36
|
__toESM
|
|
36
37
|
} from "./chunk-TV45EUGM.js";
|
|
37
38
|
|
|
39
|
+
// packages/ai/dist/person-only.js
|
|
40
|
+
var flag = (a, ...names) => a.some((x) => names.some((n) => x === n || x.startsWith(`${n}=`)));
|
|
41
|
+
var RULES = [
|
|
42
|
+
{
|
|
43
|
+
test: (w, a) => w[0] === "change" && w[1] === "release-prs" && flag(a, "--approve"),
|
|
44
|
+
why: "a person raises the release PRs into each repo's default branch (ADR 0184); a session never approves a push or a PR"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
test: (w, a) => w[0] === "task" && w[1] === "run-ready" && flag(a, "--approve"),
|
|
48
|
+
why: "a person approves each ready task's push and PR into the change branch (ADR 0181); a session never approves a push or a PR"
|
|
49
|
+
},
|
|
50
|
+
{ test: (w) => w[0] === "questions" && w[1] === "answer", why: "a person answers questions" },
|
|
51
|
+
{
|
|
52
|
+
// ADR 0181: the AI decides as itself, through Atticus; correcting it, spreading the
|
|
53
|
+
// correction and undoing a batch are a person's word.
|
|
54
|
+
test: (w) => w[0] === "questions" && (w[1] === "correct" || w[1] === "undo"),
|
|
55
|
+
why: "a person corrects an AI decision and undoes a batch of decisions (ADR 0181); the AI decides only as itself, through Atticus"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
test: (w, a) => w[0] === "questions" && w[1] === "sheet" && flag(a, "--apply"),
|
|
59
|
+
why: "a person answers questions (questions sheet --apply answers as them)"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
test: (w, a) => w[0] === "verify" && w[1] === "audit" && flag(a, "--right", "--wrong", "--unsure", "--answers"),
|
|
63
|
+
why: "a person answers an audit: its record says who confirmed each fact"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
test: (w) => w[0] === "knowledge" && ["verify", "correct", "reject"].includes(w[1] ?? ""),
|
|
67
|
+
why: "a person verifies, corrects or rejects a knowledge item"
|
|
68
|
+
},
|
|
69
|
+
{ test: (w) => w[1] === "accept", why: "a person accepts what is proposed" },
|
|
70
|
+
{ test: (w) => w[0] === "baseline", why: "a person accepts the catalog as the baseline" },
|
|
71
|
+
{ test: (w) => w[0] === "triage" && w[1] === "decide", why: "a person decides a triage item" },
|
|
72
|
+
{ test: (w) => w[0] === "finding", why: "a person acts on a finding (it is recorded with them)" },
|
|
73
|
+
{
|
|
74
|
+
test: (w) => w[0] === "feature" && ["name", "link"].includes(w[1] ?? ""),
|
|
75
|
+
why: "a person names a feature or links it to work"
|
|
76
|
+
},
|
|
77
|
+
{ test: (w) => w[0] === "todo" && w[1] === "done", why: "a person records a manual step done" },
|
|
78
|
+
{
|
|
79
|
+
test: (w) => {
|
|
80
|
+
const n = w[0] === "change" && w[1] === "needs" ? w.slice(1) : w;
|
|
81
|
+
return n[0] === "needs" && ["done", "skip"].includes(n[1] ?? "");
|
|
82
|
+
},
|
|
83
|
+
why: "a person records a need done or not needed"
|
|
84
|
+
},
|
|
85
|
+
{ test: (_w, a) => flag(a, "--by"), why: "--by names a person; a session acts as no one" },
|
|
86
|
+
{
|
|
87
|
+
test: (_w, a) => flag(a, "--approve", "--send"),
|
|
88
|
+
why: "--approve and --send are a person's: a human approves every write to shared systems"
|
|
89
|
+
},
|
|
90
|
+
{ test: (_w, a) => flag(a, "--as"), why: "--as names a person; a session acts as no one" },
|
|
91
|
+
{
|
|
92
|
+
test: (w, a) => w[0] === "rules" && (w[1] === "activate" || w[1] === "off" || w[1] === "add" && !flag(a, "--draft")),
|
|
93
|
+
why: "a person sets a reading rule active or off (a session drafts)"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
test: (w) => w[0] === "initiative" && ["commit", "new", "plan", "request"].includes(w[1] ?? ""),
|
|
97
|
+
why: "a person starts, plans and records an initiative"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
test: (w) => w[0] === "questions" && w[1] === "owner",
|
|
101
|
+
why: "an owner answers a knowledge gap's question"
|
|
102
|
+
},
|
|
103
|
+
{ test: (w) => w[0] === "outcome" && w[1] === "record", why: "a person records an outcome" },
|
|
104
|
+
{
|
|
105
|
+
test: (_w, a) => flag(a, "--accept", "--accept-proposal"),
|
|
106
|
+
why: "a person accepts what is proposed (--accept is theirs)"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
test: (w, a) => w[0] === "repo" && (w[1] === "add" || w[1] === "describe" && flag(a, "--description", "--reject")),
|
|
110
|
+
why: "a person adds a repo and writes or drops its description (a session drafts with --drafted)"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
test: (w) => w[0] === "knowledge" && w[1] === "keep",
|
|
114
|
+
why: "a person keeps a knowledge item (it is recorded as verified by them)"
|
|
115
|
+
},
|
|
116
|
+
{ test: (w) => w[0] === "alias", why: "a person decides what a name means" },
|
|
117
|
+
{
|
|
118
|
+
test: (w) => w[0] === "glossary" && ["define", "link", "alias"].includes(w[1] ?? ""),
|
|
119
|
+
why: "a person defines, links or aliases a glossary term"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// Seventh UX walk: shown by anyone; set (a handle given) only by a person, as in the app.
|
|
123
|
+
test: (w) => w[0] === "orgpack" && w[1] === "owner" && !!w[2],
|
|
124
|
+
why: "a person says who looks after the org pack"
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
function personOnlyWhy(argv) {
|
|
128
|
+
const words2 = [];
|
|
129
|
+
for (const x of argv) {
|
|
130
|
+
if (x.startsWith("-"))
|
|
131
|
+
break;
|
|
132
|
+
words2.push(x);
|
|
133
|
+
}
|
|
134
|
+
return RULES.find((r) => r.test(words2, argv))?.why;
|
|
135
|
+
}
|
|
136
|
+
var isPersonsFactsFile = (name) => /\.audit\.yaml$|\.corrections\.jsonl$/i.test(windowsName(name));
|
|
137
|
+
var windowsName = (name) => name.replace(/:.*$/, "").replace(/[. ]+$/, "");
|
|
138
|
+
|
|
38
139
|
// packages/ai/dist/context.js
|
|
39
140
|
import { createHash as createHash2 } from "node:crypto";
|
|
40
141
|
|
|
@@ -1018,6 +1119,10 @@ var NEXT_CLASS = {
|
|
|
1018
1119
|
survey: "draft",
|
|
1019
1120
|
verify: "read",
|
|
1020
1121
|
map: "read",
|
|
1122
|
+
// ADR 0141: a mapped repo read again where its clone moved on.
|
|
1123
|
+
reread: "read",
|
|
1124
|
+
// ADR 0167: a read repo topped up for reading rules added since.
|
|
1125
|
+
rules: "read",
|
|
1021
1126
|
spec: "draft",
|
|
1022
1127
|
knowledge: "knowledge",
|
|
1023
1128
|
feature: "draft",
|
|
@@ -1107,8 +1212,8 @@ function checkAllowed(kind, agent) {
|
|
|
1107
1212
|
}
|
|
1108
1213
|
function makeRunId(now, seq) {
|
|
1109
1214
|
const stamp = now.toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
|
|
1110
|
-
const
|
|
1111
|
-
return `run-${stamp}-${
|
|
1215
|
+
const tail2 = seq !== void 0 ? String(seq).padStart(4, "0") : Math.random().toString(36).slice(2, 8);
|
|
1216
|
+
return `run-${stamp}-${tail2}`;
|
|
1112
1217
|
}
|
|
1113
1218
|
function toRunSummary(run) {
|
|
1114
1219
|
return {
|
|
@@ -1128,7 +1233,10 @@ function toRunSummary(run) {
|
|
|
1128
1233
|
...run.tools ? { tools: run.tools } : {},
|
|
1129
1234
|
...run.request.retrieval ? { retrieval: run.request.retrieval } : {},
|
|
1130
1235
|
stepClass: run.stepClass ?? stepClassOf(run.request),
|
|
1131
|
-
...run.model ? { model: run.model } : {}
|
|
1236
|
+
...run.model ? { model: run.model } : {},
|
|
1237
|
+
...run.assistant ? { assistant: run.assistant } : {},
|
|
1238
|
+
...run.batch ? { batch: run.batch } : {},
|
|
1239
|
+
...run.parts?.length ? { parts: run.parts } : {}
|
|
1132
1240
|
};
|
|
1133
1241
|
}
|
|
1134
1242
|
var OneShotRuntime = class {
|
|
@@ -1145,11 +1253,15 @@ var OneShotRuntime = class {
|
|
|
1145
1253
|
modelFor(_request) {
|
|
1146
1254
|
return void 0;
|
|
1147
1255
|
}
|
|
1256
|
+
/** The kind an agent's allowedRuntimes must name for this runtime (copilot-acp: hand-off too). */
|
|
1257
|
+
allowedAs(_agent) {
|
|
1258
|
+
return this.kind;
|
|
1259
|
+
}
|
|
1148
1260
|
/** Keep a finished run where Model runs reads it (the model runtime, given a root). */
|
|
1149
1261
|
async keep(_run) {
|
|
1150
1262
|
}
|
|
1151
1263
|
async prepare(request) {
|
|
1152
|
-
checkAllowed(this.
|
|
1264
|
+
checkAllowed(this.allowedAs(request.agent), request.agent);
|
|
1153
1265
|
if (request.pack.manifest.totalTokens > request.agent.tokenBudget) {
|
|
1154
1266
|
throw new RuntimeError(`context pack (${request.pack.manifest.totalTokens} tokens) is over agent ${request.agent.id}'s budget of ${request.agent.tokenBudget}`);
|
|
1155
1267
|
}
|
|
@@ -1177,8 +1289,14 @@ var OneShotRuntime = class {
|
|
|
1177
1289
|
return run;
|
|
1178
1290
|
}
|
|
1179
1291
|
try {
|
|
1180
|
-
const { output, usage, tools } = await this.produce(run, signal);
|
|
1181
|
-
run = transition(run, "produced", this.now(), {
|
|
1292
|
+
const { output, usage, tools, model, assistant } = await this.produce(run, signal);
|
|
1293
|
+
run = transition(run, "produced", this.now(), {
|
|
1294
|
+
output,
|
|
1295
|
+
usage,
|
|
1296
|
+
...tools ? { tools } : {},
|
|
1297
|
+
...model ? { model } : {},
|
|
1298
|
+
...assistant ? { assistant } : {}
|
|
1299
|
+
});
|
|
1182
1300
|
const problems = await this.opts.validate?.(output, run.request) ?? [];
|
|
1183
1301
|
run = problems.length === 0 ? transition(run, "validated", this.now()) : transition(run, "failed", this.now(), { errors: problems });
|
|
1184
1302
|
} catch (e) {
|
|
@@ -1538,6 +1656,36 @@ ${DATA_RULE}`;
|
|
|
1538
1656
|
// packages/ai/dist/handoff.js
|
|
1539
1657
|
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile3, rm, stat, writeFile as writeFile3 } from "node:fs/promises";
|
|
1540
1658
|
import { join as join3 } from "node:path";
|
|
1659
|
+
var BATCH_OUTPUT = "answers.md";
|
|
1660
|
+
var DEFAULT_BATCH_MAX_TOKENS = 6e4;
|
|
1661
|
+
var SECTION = /^===[ \t]*([a-z][a-z0-9-]*)[ \t]*$/;
|
|
1662
|
+
function batchSections(text) {
|
|
1663
|
+
const out = /* @__PURE__ */ new Map();
|
|
1664
|
+
let name;
|
|
1665
|
+
let lines = [];
|
|
1666
|
+
const end = () => {
|
|
1667
|
+
if (name === void 0)
|
|
1668
|
+
return;
|
|
1669
|
+
while (lines.length && !lines[0].trim())
|
|
1670
|
+
lines.shift();
|
|
1671
|
+
while (lines.length && !lines.at(-1).trim())
|
|
1672
|
+
lines.pop();
|
|
1673
|
+
const body = lines.length ? `${lines.join("\n")}
|
|
1674
|
+
` : "";
|
|
1675
|
+
out.set(name, out.has(name) ? { text: body, twice: true } : { text: body });
|
|
1676
|
+
};
|
|
1677
|
+
for (const line of text.replace(/\r\n/g, "\n").split("\n")) {
|
|
1678
|
+
const m = SECTION.exec(line);
|
|
1679
|
+
if (m) {
|
|
1680
|
+
end();
|
|
1681
|
+
name = m[1];
|
|
1682
|
+
lines = [];
|
|
1683
|
+
} else
|
|
1684
|
+
lines.push(line);
|
|
1685
|
+
}
|
|
1686
|
+
end();
|
|
1687
|
+
return out;
|
|
1688
|
+
}
|
|
1541
1689
|
var HandoffRuntime = class _HandoffRuntime {
|
|
1542
1690
|
o;
|
|
1543
1691
|
kind = "hand-off";
|
|
@@ -1563,6 +1711,39 @@ var HandoffRuntime = class _HandoffRuntime {
|
|
|
1563
1711
|
const { outputPath, change } = run.request;
|
|
1564
1712
|
return run.request.outputIn === "run" ? `${runsRel}/${change}/${run.runId}/${outputPath}` : `${changesRel}/${change}/${outputPath}`;
|
|
1565
1713
|
}
|
|
1714
|
+
/**
|
|
1715
|
+
* A batched hand-off's prompt (ADR 0185): one section to write per part, then each part's own
|
|
1716
|
+
* prompt, whole and in order, under its name. It ends with the completion command.
|
|
1717
|
+
*/
|
|
1718
|
+
static promptForBatch(run, parts, changesRel = "openspec/changes", runsRel = ".runs") {
|
|
1719
|
+
const { agent, change } = run.request;
|
|
1720
|
+
const where = _HandoffRuntime.outputRel(run, changesRel, runsRel);
|
|
1721
|
+
const n = parts.length;
|
|
1722
|
+
return [
|
|
1723
|
+
`# ${agent.id}: ${n} parts for change ${change}, in one answer`,
|
|
1724
|
+
"",
|
|
1725
|
+
agent.description,
|
|
1726
|
+
"",
|
|
1727
|
+
`Write the result to ${where}. Change no other files.`,
|
|
1728
|
+
"",
|
|
1729
|
+
`The file holds one section per part below. Start each section with a line that holds only "=== " and the part's name, then write that part's answer exactly as the part asks for it, and nothing else. Answer each part from its own instructions and its own data only: the parts are separate. Leave no part out. Atticus checks each section on its own, as if that part were answered alone: a section that is missing or fails its check is asked for again, and the others are kept.`,
|
|
1730
|
+
"",
|
|
1731
|
+
...parts.flatMap((p) => [`=== ${p.name}`, `<the ${p.name} part's answer>`]),
|
|
1732
|
+
"",
|
|
1733
|
+
...parts.flatMap((p, i) => [
|
|
1734
|
+
"---",
|
|
1735
|
+
"",
|
|
1736
|
+
`Part ${i + 1} of ${n}: ${p.name}. Its answer is the section "=== ${p.name}".`,
|
|
1737
|
+
"",
|
|
1738
|
+
p.prompt.trimEnd(),
|
|
1739
|
+
""
|
|
1740
|
+
]),
|
|
1741
|
+
"---",
|
|
1742
|
+
"",
|
|
1743
|
+
`When the file is complete, run: atticus handoff done ${run.runId}`,
|
|
1744
|
+
""
|
|
1745
|
+
].join("\n");
|
|
1746
|
+
}
|
|
1566
1747
|
/** The prompt the person's assistant gets. It ends with the completion command. */
|
|
1567
1748
|
static promptFor(run, changesRel = "openspec/changes", runsRel = ".runs") {
|
|
1568
1749
|
const { agent, pack, outputPath, change } = run.request;
|
|
@@ -1580,15 +1761,96 @@ var HandoffRuntime = class _HandoffRuntime {
|
|
|
1580
1761
|
""
|
|
1581
1762
|
].join("\n");
|
|
1582
1763
|
}
|
|
1764
|
+
/** A run id no run folder of the change has yet. */
|
|
1765
|
+
async freshId(change, now) {
|
|
1766
|
+
let runId = makeRunId(now, ++this.seq);
|
|
1767
|
+
while (await stat(this.runDir(change, runId)).catch(() => void 0))
|
|
1768
|
+
runId = makeRunId(now, ++this.seq);
|
|
1769
|
+
return runId;
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Whether `parts` runs that wait together, `tokens` in all, are asked in one prompt (ADR 0185):
|
|
1773
|
+
* only when this runtime batches, for two parts or more, within the size a batch may hold.
|
|
1774
|
+
*/
|
|
1775
|
+
canBatch(parts, tokens) {
|
|
1776
|
+
return !!this.o.batch && parts >= 2 && tokens <= (this.o.batchMaxTokens ?? DEFAULT_BATCH_MAX_TOKENS);
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Ask several waiting runs of one change in one prompt (ADR 0185). Each part keeps its own run,
|
|
1780
|
+
* prompt, pack and answer file, and can still be answered alone; the batch links them, and each
|
|
1781
|
+
* part names the batch. The batch's pack is its parts' packs together: their sources, each id
|
|
1782
|
+
* under its part's name, and their sizes added up. Run it (`run`) to hand it over.
|
|
1783
|
+
*/
|
|
1784
|
+
async prepareBatch(o) {
|
|
1785
|
+
checkAllowed(this.kind, o.agent);
|
|
1786
|
+
if (o.parts.length < 2)
|
|
1787
|
+
throw new RuntimeError("a batched hand-off needs two parts or more");
|
|
1788
|
+
const twice = o.parts.find((p, i) => o.parts.findIndex((x) => x.name === p.name) !== i);
|
|
1789
|
+
if (twice)
|
|
1790
|
+
throw new RuntimeError(`a batched hand-off can't have two parts named ${twice.name}`);
|
|
1791
|
+
const parts = [];
|
|
1792
|
+
for (const p of o.parts) {
|
|
1793
|
+
const run2 = await this.load(p.runId);
|
|
1794
|
+
if (run2.request.change !== o.change)
|
|
1795
|
+
throw new RuntimeError(`run ${p.runId} isn't a run of ${o.change}`);
|
|
1796
|
+
if (run2.state !== "waiting" && run2.state !== "prepared")
|
|
1797
|
+
throw new RuntimeError(`run ${p.runId} is ${run2.state}: only a run still waiting for its answer can be batched`);
|
|
1798
|
+
if (run2.batch)
|
|
1799
|
+
throw new RuntimeError(`run ${p.runId} is already asked in ${run2.batch}`);
|
|
1800
|
+
parts.push({ name: p.name, run: run2 });
|
|
1801
|
+
}
|
|
1802
|
+
const now = this.now();
|
|
1803
|
+
const runId = await this.freshId(o.change, now);
|
|
1804
|
+
const manifests = parts.map((p) => ({ name: p.name, m: p.run.request.pack.manifest }));
|
|
1805
|
+
const totalTokens = manifests.reduce((n, x) => n + x.m.totalTokens, 0);
|
|
1806
|
+
const agent = { ...o.agent, tokenBudget: Math.max(o.agent.tokenBudget, totalTokens) };
|
|
1807
|
+
const run = {
|
|
1808
|
+
runId,
|
|
1809
|
+
request: {
|
|
1810
|
+
change: o.change,
|
|
1811
|
+
agent,
|
|
1812
|
+
pack: { prompt: "", manifest: manifests[0].m },
|
|
1813
|
+
outputPath: BATCH_OUTPUT,
|
|
1814
|
+
outputIn: "run",
|
|
1815
|
+
...o.stepClass ? { stepClass: o.stepClass } : {}
|
|
1816
|
+
},
|
|
1817
|
+
runtime: this.kind,
|
|
1818
|
+
state: "prepared",
|
|
1819
|
+
attempt: 1,
|
|
1820
|
+
startedAt: now.toISOString(),
|
|
1821
|
+
errors: [],
|
|
1822
|
+
stepClass: o.stepClass ?? stepClassOf({ agent }),
|
|
1823
|
+
parts: parts.map((p) => ({ name: p.name, runId: p.run.runId }))
|
|
1824
|
+
};
|
|
1825
|
+
const prompt = _HandoffRuntime.promptForBatch(run, parts.map((p) => ({ name: p.name, prompt: p.run.request.pack.prompt })), changesRelOf(this.o.root), runsRelOf(this.o.root));
|
|
1826
|
+
const manifest = {
|
|
1827
|
+
schemaVersion: 1,
|
|
1828
|
+
change: o.change,
|
|
1829
|
+
agent: o.agent.id,
|
|
1830
|
+
createdAt: now.toISOString(),
|
|
1831
|
+
budget: manifests.reduce((n, x) => n + x.m.budget, 0),
|
|
1832
|
+
totalTokens,
|
|
1833
|
+
sources: manifests.flatMap((x) => x.m.sources.map((s) => ({ ...s, id: `${x.name}/${s.id}` }))),
|
|
1834
|
+
dropped: manifests.flatMap((x) => x.m.dropped.map((d) => ({ ...d, id: `${x.name}/${d.id}` }))),
|
|
1835
|
+
promptSha256: sha2562(prompt)
|
|
1836
|
+
};
|
|
1837
|
+
run.request.pack = { prompt, manifest };
|
|
1838
|
+
const dir = this.runDir(o.change, runId);
|
|
1839
|
+
await mkdir3(dir, { recursive: true });
|
|
1840
|
+
await writeFile3(join3(dir, "prompt.md"), prompt, "utf8");
|
|
1841
|
+
await writeFile3(join3(dir, "context-manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
1842
|
+
await this.save(run);
|
|
1843
|
+
for (const p of parts)
|
|
1844
|
+
await this.save({ ...p.run, batch: runId });
|
|
1845
|
+
return run;
|
|
1846
|
+
}
|
|
1583
1847
|
async prepare(request) {
|
|
1584
1848
|
checkAllowed(this.kind, request.agent);
|
|
1585
1849
|
if (request.pack.manifest.totalTokens > request.agent.tokenBudget) {
|
|
1586
1850
|
throw new RuntimeError(`context pack (${request.pack.manifest.totalTokens} tokens) is over agent ${request.agent.id}'s budget of ${request.agent.tokenBudget}`);
|
|
1587
1851
|
}
|
|
1588
1852
|
const now = this.now();
|
|
1589
|
-
|
|
1590
|
-
while (await stat(this.runDir(request.change, runId)).catch(() => void 0))
|
|
1591
|
-
runId = makeRunId(now, ++this.seq);
|
|
1853
|
+
const runId = await this.freshId(request.change, now);
|
|
1592
1854
|
const run = {
|
|
1593
1855
|
runId,
|
|
1594
1856
|
request,
|
|
@@ -1622,6 +1884,8 @@ var HandoffRuntime = class _HandoffRuntime {
|
|
|
1622
1884
|
async done(runId, o = {}) {
|
|
1623
1885
|
let run = await this.load(runId);
|
|
1624
1886
|
const model = o.model?.trim();
|
|
1887
|
+
if (run.parts?.length)
|
|
1888
|
+
return this.doneBatch(run, model);
|
|
1625
1889
|
if (model)
|
|
1626
1890
|
run = { ...run, model };
|
|
1627
1891
|
if (run.state === "failed") {
|
|
@@ -1645,8 +1909,61 @@ var HandoffRuntime = class _HandoffRuntime {
|
|
|
1645
1909
|
return run;
|
|
1646
1910
|
if (this.now().getTime() - info.mtimeMs < (this.o.stableMs ?? 3e4))
|
|
1647
1911
|
return run;
|
|
1912
|
+
if (run.parts?.length)
|
|
1913
|
+
return this.doneBatch(run);
|
|
1648
1914
|
return this.validate(transition(run, "produced", this.now()));
|
|
1649
1915
|
}
|
|
1916
|
+
/**
|
|
1917
|
+
* A batched hand-off's Done (ADR 0185): its answer is split into its parts' sections, each
|
|
1918
|
+
* written to its part's own answer file and checked there as that part's Done would check it.
|
|
1919
|
+
* A part that passes is in; a missing, doubled or failing section fails only its part, which can
|
|
1920
|
+
* be fixed in the batch (Done again checks only the parts still out) or answered alone. The
|
|
1921
|
+
* batch passes once every part is in; until then it fails, naming each part that is out and why.
|
|
1922
|
+
*/
|
|
1923
|
+
async doneBatch(start, model) {
|
|
1924
|
+
let run = start;
|
|
1925
|
+
if (run.state === "validated" || run.state === "cancelled")
|
|
1926
|
+
return run;
|
|
1927
|
+
if (run.state === "failed")
|
|
1928
|
+
run = transition(transition(run, "prepared", this.now(), { attempt: run.attempt + 1, errors: [] }), "waiting", this.now());
|
|
1929
|
+
if (model)
|
|
1930
|
+
run = { ...run, model };
|
|
1931
|
+
const output = await readFile3(this.outputFile(run), "utf8").catch(() => void 0);
|
|
1932
|
+
if (output === void 0)
|
|
1933
|
+
return this.save(transition(run, "failed", this.now(), {
|
|
1934
|
+
errors: [`${run.request.outputPath} was not written`]
|
|
1935
|
+
}));
|
|
1936
|
+
const names = run.parts.map((p) => p.name);
|
|
1937
|
+
const sections = batchSections(output);
|
|
1938
|
+
if (!names.some((n) => sections.has(n)))
|
|
1939
|
+
return this.save(transition(run, "failed", this.now(), {
|
|
1940
|
+
output,
|
|
1941
|
+
errors: [
|
|
1942
|
+
`${run.request.outputPath} has no section of any part. Start each part's answer with its own line "=== <part>": ${names.map((n) => `=== ${n}`).join(", ")}.`
|
|
1943
|
+
]
|
|
1944
|
+
}));
|
|
1945
|
+
run = transition(run, "produced", this.now(), { output });
|
|
1946
|
+
const errors = [];
|
|
1947
|
+
const runsRel = runsRelOf(this.o.root);
|
|
1948
|
+
for (const p of run.parts) {
|
|
1949
|
+
const part = await this.load(p.runId);
|
|
1950
|
+
if (part.state === "validated" || part.state === "cancelled")
|
|
1951
|
+
continue;
|
|
1952
|
+
const s = sections.get(p.name);
|
|
1953
|
+
const alone = `answer that part alone: its prompt is ${_HandoffRuntime.promptRel(part, runsRel)}.`;
|
|
1954
|
+
if (!s || s.twice) {
|
|
1955
|
+
errors.push(s ? `${p.name}: the answer has two "=== ${p.name}" sections. Keep one, or ${alone}` : `${p.name}: the answer has no "=== ${p.name}" section. Add it, or ${alone}`);
|
|
1956
|
+
const why = `The batched answer (run ${run.runId}) has ${s ? 'two "=== ' + p.name + '" sections' : 'no "=== ' + p.name + '" section'}. Answer this part here.`;
|
|
1957
|
+
await this.save(part.state === "failed" ? { ...part, errors: [why] } : transition(part, "failed", this.now(), { errors: [why] }));
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
await writeFile3(this.outputFile(part), s.text, "utf8");
|
|
1961
|
+
const r = await this.done(p.runId, model ? { model } : {});
|
|
1962
|
+
if (r.state !== "validated")
|
|
1963
|
+
errors.push(...r.errors.map((e) => `${p.name}: ${e}`));
|
|
1964
|
+
}
|
|
1965
|
+
return this.save(errors.length ? transition(run, "failed", this.now(), { errors }) : transition(run, "validated", this.now()));
|
|
1966
|
+
}
|
|
1650
1967
|
/** Retry after a failure: a fresh attempt with the same request. */
|
|
1651
1968
|
async retry(runId) {
|
|
1652
1969
|
const run = await this.load(runId);
|
|
@@ -1695,12 +2012,1064 @@ async function pruneRuns(root, days, now = /* @__PURE__ */ new Date(), only) {
|
|
|
1695
2012
|
}
|
|
1696
2013
|
|
|
1697
2014
|
// packages/ai/dist/factory.js
|
|
1698
|
-
import {
|
|
1699
|
-
import {
|
|
2015
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2016
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2017
|
+
import { mkdir as mkdir5, mkdtemp, readFile as readFile5, rm as rm2, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
1700
2018
|
import { tmpdir } from "node:os";
|
|
1701
|
-
import { dirname, join as
|
|
2019
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2020
|
+
|
|
2021
|
+
// packages/ai/dist/copilot-acp.js
|
|
2022
|
+
import { spawn } from "node:child_process";
|
|
2023
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2024
|
+
import { mkdir as mkdir4, readFile as readFile4, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
2025
|
+
import { basename, dirname, isAbsolute, join as join4, relative, resolve, sep } from "node:path";
|
|
2026
|
+
import { createInterface } from "node:readline";
|
|
2027
|
+
|
|
2028
|
+
// packages/ai/dist/acp.js
|
|
2029
|
+
var ACP_PROTOCOL_VERSION = 1;
|
|
2030
|
+
var ACP_METHODS = {
|
|
2031
|
+
initialize: "initialize",
|
|
2032
|
+
authenticate: "authenticate",
|
|
2033
|
+
sessionNew: "session/new",
|
|
2034
|
+
sessionSetModel: "session/set_model",
|
|
2035
|
+
sessionPrompt: "session/prompt",
|
|
2036
|
+
sessionCancel: "session/cancel",
|
|
2037
|
+
sessionUpdate: "session/update",
|
|
2038
|
+
requestPermission: "session/request_permission",
|
|
2039
|
+
readTextFile: "fs/read_text_file",
|
|
2040
|
+
writeTextFile: "fs/write_text_file",
|
|
2041
|
+
terminalCreate: "terminal/create",
|
|
2042
|
+
terminalOutput: "terminal/output",
|
|
2043
|
+
terminalWaitForExit: "terminal/wait_for_exit",
|
|
2044
|
+
terminalKill: "terminal/kill",
|
|
2045
|
+
terminalRelease: "terminal/release"
|
|
2046
|
+
};
|
|
2047
|
+
var ACP_ERRORS = {
|
|
2048
|
+
invalidParams: -32602,
|
|
2049
|
+
methodNotFound: -32601,
|
|
2050
|
+
internal: -32603,
|
|
2051
|
+
authRequired: -32e3
|
|
2052
|
+
};
|
|
2053
|
+
var AcpRpcError = class extends Error {
|
|
2054
|
+
code;
|
|
2055
|
+
data;
|
|
2056
|
+
constructor(code, message, data) {
|
|
2057
|
+
super(message);
|
|
2058
|
+
this.code = code;
|
|
2059
|
+
this.data = data;
|
|
2060
|
+
this.name = "AcpRpcError";
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
var AcpClient = class {
|
|
2064
|
+
t;
|
|
2065
|
+
h;
|
|
2066
|
+
tap;
|
|
2067
|
+
next = 1;
|
|
2068
|
+
pending = /* @__PURE__ */ new Map();
|
|
2069
|
+
closed;
|
|
2070
|
+
constructor(t, h, tap = () => {
|
|
2071
|
+
}) {
|
|
2072
|
+
this.t = t;
|
|
2073
|
+
this.h = h;
|
|
2074
|
+
this.tap = tap;
|
|
2075
|
+
t.onLine((line) => this.receive(line));
|
|
2076
|
+
t.onClose((why) => {
|
|
2077
|
+
this.closed = why;
|
|
2078
|
+
for (const p of this.pending.values())
|
|
2079
|
+
p.reject(new AcpRpcError(ACP_ERRORS.internal, why));
|
|
2080
|
+
this.pending.clear();
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
out(message) {
|
|
2084
|
+
const m = { jsonrpc: "2.0", ...message };
|
|
2085
|
+
this.tap("out", m);
|
|
2086
|
+
this.t.send(JSON.stringify(m));
|
|
2087
|
+
}
|
|
2088
|
+
request(method, params) {
|
|
2089
|
+
if (this.closed)
|
|
2090
|
+
return Promise.reject(new AcpRpcError(ACP_ERRORS.internal, this.closed));
|
|
2091
|
+
const id = this.next++;
|
|
2092
|
+
return new Promise((resolve2, reject) => {
|
|
2093
|
+
this.pending.set(id, { resolve: resolve2, reject });
|
|
2094
|
+
this.out({ id, method, params });
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
notify(method, params) {
|
|
2098
|
+
if (!this.closed)
|
|
2099
|
+
this.out({ method, params });
|
|
2100
|
+
}
|
|
2101
|
+
receive(line) {
|
|
2102
|
+
if (!line.trim())
|
|
2103
|
+
return;
|
|
2104
|
+
let m;
|
|
2105
|
+
try {
|
|
2106
|
+
m = JSON.parse(line);
|
|
2107
|
+
} catch {
|
|
2108
|
+
this.tap("in", { notJson: line.slice(0, 500) });
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
this.tap("in", m);
|
|
2112
|
+
if (m.method === void 0 && m.id !== void 0) {
|
|
2113
|
+
const p = this.pending.get(Number(m.id));
|
|
2114
|
+
if (!p)
|
|
2115
|
+
return;
|
|
2116
|
+
this.pending.delete(Number(m.id));
|
|
2117
|
+
if (m.error)
|
|
2118
|
+
p.reject(new AcpRpcError(m.error.code, m.error.message, m.error.data));
|
|
2119
|
+
else
|
|
2120
|
+
p.resolve(m.result ?? null);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (m.method === ACP_METHODS.sessionUpdate) {
|
|
2124
|
+
const u = m.params?.update;
|
|
2125
|
+
if (u && typeof u === "object")
|
|
2126
|
+
this.h.update(u);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (m.id === void 0)
|
|
2130
|
+
return;
|
|
2131
|
+
const id = m.id;
|
|
2132
|
+
void this.answer(m.method ?? "", m.params ?? {}).then((result) => this.out({ id, result }), (e) => this.out({
|
|
2133
|
+
id,
|
|
2134
|
+
error: {
|
|
2135
|
+
code: e instanceof AcpRpcError ? e.code : ACP_ERRORS.invalidParams,
|
|
2136
|
+
message: e.message
|
|
2137
|
+
}
|
|
2138
|
+
}));
|
|
2139
|
+
}
|
|
2140
|
+
answer(method, p) {
|
|
2141
|
+
const s = (k) => typeof p[k] === "string" ? p[k] : "";
|
|
2142
|
+
switch (method) {
|
|
2143
|
+
case ACP_METHODS.readTextFile:
|
|
2144
|
+
return this.h.readTextFile({
|
|
2145
|
+
path: s("path"),
|
|
2146
|
+
...typeof p.line === "number" ? { line: p.line } : {},
|
|
2147
|
+
...typeof p.limit === "number" ? { limit: p.limit } : {}
|
|
2148
|
+
});
|
|
2149
|
+
case ACP_METHODS.writeTextFile:
|
|
2150
|
+
return this.h.writeTextFile({ path: s("path"), content: s("content") });
|
|
2151
|
+
case ACP_METHODS.requestPermission:
|
|
2152
|
+
return this.h.requestPermission({
|
|
2153
|
+
toolCall: p.toolCall ?? {},
|
|
2154
|
+
options: Array.isArray(p.options) ? p.options : []
|
|
2155
|
+
});
|
|
2156
|
+
case ACP_METHODS.terminalCreate:
|
|
2157
|
+
return this.h.terminalCreate({
|
|
2158
|
+
command: s("command"),
|
|
2159
|
+
...Array.isArray(p.args) ? { args: p.args.map(String) } : {},
|
|
2160
|
+
...typeof p.cwd === "string" ? { cwd: p.cwd } : {},
|
|
2161
|
+
...Array.isArray(p.env) ? { env: p.env } : {},
|
|
2162
|
+
...typeof p.outputByteLimit === "number" ? { outputByteLimit: p.outputByteLimit } : {}
|
|
2163
|
+
});
|
|
2164
|
+
case ACP_METHODS.terminalOutput:
|
|
2165
|
+
return this.h.terminalOutput({ terminalId: s("terminalId") });
|
|
2166
|
+
case ACP_METHODS.terminalWaitForExit:
|
|
2167
|
+
return this.h.terminalWaitForExit({ terminalId: s("terminalId") });
|
|
2168
|
+
case ACP_METHODS.terminalKill:
|
|
2169
|
+
return this.h.terminalKill({ terminalId: s("terminalId") });
|
|
2170
|
+
case ACP_METHODS.terminalRelease:
|
|
2171
|
+
return this.h.terminalRelease({ terminalId: s("terminalId") });
|
|
2172
|
+
default:
|
|
2173
|
+
return Promise.reject(new AcpRpcError(ACP_ERRORS.methodNotFound, `Atticus doesn't offer ${method}`));
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
// ------------------------------------------------------------ the agent's methods
|
|
2177
|
+
initialize(clientInfo) {
|
|
2178
|
+
return this.request(ACP_METHODS.initialize, {
|
|
2179
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
2180
|
+
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: true },
|
|
2181
|
+
clientInfo
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
newSession(cwd) {
|
|
2185
|
+
return this.request(ACP_METHODS.sessionNew, { cwd, mcpServers: [] });
|
|
2186
|
+
}
|
|
2187
|
+
setModel(sessionId, modelId) {
|
|
2188
|
+
return this.request(ACP_METHODS.sessionSetModel, { sessionId, modelId });
|
|
2189
|
+
}
|
|
2190
|
+
prompt(sessionId, text) {
|
|
2191
|
+
return this.request(ACP_METHODS.sessionPrompt, {
|
|
2192
|
+
sessionId,
|
|
2193
|
+
prompt: [{ type: "text", text }]
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
cancel(sessionId) {
|
|
2197
|
+
this.notify(ACP_METHODS.sessionCancel, { sessionId });
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
function chunkText(u) {
|
|
2201
|
+
const c = u.content;
|
|
2202
|
+
return c?.type === "text" && typeof c.text === "string" ? c.text : void 0;
|
|
2203
|
+
}
|
|
2204
|
+
var UPDATE = {
|
|
2205
|
+
message: "agent_message_chunk",
|
|
2206
|
+
thought: "agent_thought_chunk",
|
|
2207
|
+
toolCall: "tool_call",
|
|
2208
|
+
toolCallUpdate: "tool_call_update",
|
|
2209
|
+
plan: "plan"
|
|
2210
|
+
};
|
|
2211
|
+
function usageOf(u) {
|
|
2212
|
+
if (!u)
|
|
2213
|
+
return void 0;
|
|
2214
|
+
const n = (...ks) => {
|
|
2215
|
+
for (const k of ks)
|
|
2216
|
+
if (typeof u[k] === "number")
|
|
2217
|
+
return u[k];
|
|
2218
|
+
return void 0;
|
|
2219
|
+
};
|
|
2220
|
+
const i = n("inputTokens", "input_tokens", "promptTokens");
|
|
2221
|
+
const o = n("outputTokens", "output_tokens", "completionTokens");
|
|
2222
|
+
return i === void 0 && o === void 0 ? void 0 : {
|
|
2223
|
+
inputTokens: Math.max(0, Math.round(i ?? 0)),
|
|
2224
|
+
outputTokens: Math.max(0, Math.round(o ?? 0))
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
var isAuthRequired = (e) => e instanceof AcpRpcError && (e.code === ACP_ERRORS.authRequired || /auth|sign.?in|log.?in/i.test(e.message));
|
|
2228
|
+
|
|
2229
|
+
// packages/ai/dist/copilot-acp.js
|
|
2230
|
+
function words(line) {
|
|
2231
|
+
const out = [];
|
|
2232
|
+
let cur = "";
|
|
2233
|
+
let quote;
|
|
2234
|
+
let any = false;
|
|
2235
|
+
let meta = false;
|
|
2236
|
+
for (const ch of line) {
|
|
2237
|
+
if (quote) {
|
|
2238
|
+
if (ch === quote)
|
|
2239
|
+
quote = void 0;
|
|
2240
|
+
else
|
|
2241
|
+
cur += ch;
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
if (ch === '"' || ch === "'") {
|
|
2245
|
+
quote = ch;
|
|
2246
|
+
any = true;
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
if (/\s/.test(ch)) {
|
|
2250
|
+
if (cur || any)
|
|
2251
|
+
out.push(cur);
|
|
2252
|
+
cur = "";
|
|
2253
|
+
any = false;
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
if ("|&;<>`$(){}\n".includes(ch))
|
|
2257
|
+
meta = true;
|
|
2258
|
+
cur += ch;
|
|
2259
|
+
}
|
|
2260
|
+
if (cur || any)
|
|
2261
|
+
out.push(cur);
|
|
2262
|
+
return { words: out, meta: meta || quote !== void 0 };
|
|
2263
|
+
}
|
|
2264
|
+
function commandLineOf(line) {
|
|
2265
|
+
const w = words(line.trim()).words;
|
|
2266
|
+
if (!w.length)
|
|
2267
|
+
throw new RuntimeConfigLikeError("copilotAcp.command is empty");
|
|
2268
|
+
const args = w.slice(1);
|
|
2269
|
+
return { command: w[0], args: args.includes("--acp") ? args : [...args, "--acp"] };
|
|
2270
|
+
}
|
|
2271
|
+
var RuntimeConfigLikeError = class extends RuntimeError {
|
|
2272
|
+
};
|
|
2273
|
+
var PERSONS = [
|
|
2274
|
+
// What only a person does is personOnlyWhy's list (person-only.ts), shared with the CLI.
|
|
2275
|
+
{
|
|
2276
|
+
test: (a) => a[0] === "map" && a.includes("--all"),
|
|
2277
|
+
why: "this session is one step of atticus map --all; Atticus runs the loop, not the session"
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
test: (a) => ["ui", "serve", "mcp"].includes(a[0] ?? ""),
|
|
2281
|
+
why: "a server that keeps running isn't a step's command"
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
test: (a) => a.some((x) => x === "--root" || x.startsWith("--root=")),
|
|
2285
|
+
why: "the session works in this metarepo; --root points elsewhere"
|
|
2286
|
+
}
|
|
2287
|
+
];
|
|
2288
|
+
var ATTICUS_NAMES = /* @__PURE__ */ new Set(["atticus", "atticus.cmd", "atticus.exe"]);
|
|
2289
|
+
function atticusArgvOf(command, args = []) {
|
|
2290
|
+
const head = words(command.trim());
|
|
2291
|
+
if (head.meta)
|
|
2292
|
+
return {
|
|
2293
|
+
refused: `Atticus runs no shell here, so a command line with | & ; < > $ or backquotes is refused: ${short(command)}. Run one atticus command at a time.`
|
|
2294
|
+
};
|
|
2295
|
+
const all = [...head.words, ...args];
|
|
2296
|
+
const program = all[0] ?? "";
|
|
2297
|
+
if (!ATTICUS_NAMES.has(program.toLowerCase()))
|
|
2298
|
+
return {
|
|
2299
|
+
refused: `Atticus runs only atticus commands in this session (atticus <command> <arguments>); ${short([program, ...all.slice(1)].join(" "))} is refused. Read files with your file tools; write only under facts/ or the step's result file.`
|
|
2300
|
+
};
|
|
2301
|
+
const argv = all.slice(1);
|
|
2302
|
+
if (!argv.length)
|
|
2303
|
+
return { refused: "atticus needs a command (atticus next, atticus verify <repo>, ...)" };
|
|
2304
|
+
for (const p of PERSONS)
|
|
2305
|
+
if (p.test(argv))
|
|
2306
|
+
return { refused: `Refused: ${p.why}.` };
|
|
2307
|
+
if (argv[0].startsWith("-") && !["--help", "--version", "-h"].includes(argv[0]))
|
|
2308
|
+
return {
|
|
2309
|
+
refused: `Put the command first: atticus <command> [arguments] (got ${short(argv.join(" "))})`
|
|
2310
|
+
};
|
|
2311
|
+
const person = personOnlyWhy(argv);
|
|
2312
|
+
if (person)
|
|
2313
|
+
return { refused: `Refused: ${person}; Atticus lets a session act as no one.` };
|
|
2314
|
+
return { argv };
|
|
2315
|
+
}
|
|
2316
|
+
var short = (s, n = 120) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
2317
|
+
function real(p) {
|
|
2318
|
+
let cur = resolve(p);
|
|
2319
|
+
const rest = [];
|
|
2320
|
+
for (; ; ) {
|
|
2321
|
+
try {
|
|
2322
|
+
return join4(realpathSync.native(cur), ...rest.reverse());
|
|
2323
|
+
} catch {
|
|
2324
|
+
const up = dirname(cur);
|
|
2325
|
+
if (up === cur)
|
|
2326
|
+
return resolve(p);
|
|
2327
|
+
rest.push(basename(cur));
|
|
2328
|
+
cur = up;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
var sameOrUnder = (root, p) => {
|
|
2333
|
+
const r = relative(root, p);
|
|
2334
|
+
return r === "" || !r.startsWith("..") && !isAbsolute(r);
|
|
2335
|
+
};
|
|
2336
|
+
var caseOf = (p) => process.platform === "win32" ? p.toLowerCase() : p;
|
|
2337
|
+
var Paths = class {
|
|
2338
|
+
p;
|
|
2339
|
+
reads;
|
|
2340
|
+
writeDirs;
|
|
2341
|
+
files;
|
|
2342
|
+
constructor(p) {
|
|
2343
|
+
this.p = p;
|
|
2344
|
+
this.reads = p.readRoots.map((r) => caseOf(real(r)));
|
|
2345
|
+
this.writeDirs = p.writeRoots.map((r) => caseOf(real(r)));
|
|
2346
|
+
this.files = (p.writeFiles ?? []).map((f) => caseOf(real(f)));
|
|
2347
|
+
}
|
|
2348
|
+
readable(path) {
|
|
2349
|
+
if (!path || !isAbsolute(path))
|
|
2350
|
+
return `the path must be absolute (got ${short(path)})`;
|
|
2351
|
+
const r = caseOf(real(path));
|
|
2352
|
+
return this.reads.some((x) => sameOrUnder(x, r)) ? void 0 : `Atticus lets this session read only under the metarepo and its clones; ${short(path)} is outside`;
|
|
2353
|
+
}
|
|
2354
|
+
writable(path) {
|
|
2355
|
+
if (!path || !isAbsolute(path))
|
|
2356
|
+
return `the path must be absolute (got ${short(path)})`;
|
|
2357
|
+
const r = caseOf(real(path));
|
|
2358
|
+
if (r.split(sep).includes(".git"))
|
|
2359
|
+
return `Atticus never lets a session write in .git (${short(path)})`;
|
|
2360
|
+
const parts = r.split(sep).slice(/^[A-Za-z]:$/.test(r.split(sep)[0] ?? "") ? 1 : 0);
|
|
2361
|
+
if (parts.some((x) => x !== "." && x !== ".." && (/[. ]$/.test(x) || x.includes(":"))))
|
|
2362
|
+
return `Atticus never lets a session write a name that ends in a dot or space or names a stream (${short(path)})`;
|
|
2363
|
+
if (isPersonsFactsFile(basename(r)))
|
|
2364
|
+
return `Atticus never lets a session write a person's record (${short(path)}: an audit's answers or a person's corrections)`;
|
|
2365
|
+
if (this.files.includes(r) || this.writeDirs.some((x) => sameOrUnder(x, r)))
|
|
2366
|
+
return void 0;
|
|
2367
|
+
const base = this.p.readRoots[0] ?? "";
|
|
2368
|
+
const rel = (x) => {
|
|
2369
|
+
const r2 = relative(base, x);
|
|
2370
|
+
return r2 && !r2.startsWith("..") && !isAbsolute(r2) ? r2.split(sep).join("/") : x;
|
|
2371
|
+
};
|
|
2372
|
+
const where = [
|
|
2373
|
+
...this.p.writeRoots.map((d) => `${rel(d)}/`),
|
|
2374
|
+
...(this.p.writeFiles ?? []).map(rel)
|
|
2375
|
+
].join(", ");
|
|
2376
|
+
return `Atticus lets this session write only its step's result: it writes only under ${where || "nothing"}; ${short(path)} is refused`;
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
var acpPathsOf = (p) => new Paths(p);
|
|
2380
|
+
var factsOnly = (root, factsDir) => ({
|
|
2381
|
+
readRoots: readRootsOf(root),
|
|
2382
|
+
writeRoots: [factsDir]
|
|
2383
|
+
});
|
|
2384
|
+
function readRootsOf(root) {
|
|
2385
|
+
const out = [root];
|
|
2386
|
+
const state = join4(stateDirOf(root), "workspace");
|
|
2387
|
+
if (!sameOrUnder(resolve(root), resolve(state)))
|
|
2388
|
+
out.push(state);
|
|
2389
|
+
return out;
|
|
2390
|
+
}
|
|
2391
|
+
var needsShell = (command, platform = process.platform) => platform === "win32" && !/\.(exe|com)$/i.test(command);
|
|
2392
|
+
var quoteForCmd = (a) => /[\s"]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a;
|
|
2393
|
+
function killTree(child, platform = process.platform, run = spawn) {
|
|
2394
|
+
if (platform === "win32" && child.pid) {
|
|
2395
|
+
try {
|
|
2396
|
+
const k = run("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
2397
|
+
windowsHide: true,
|
|
2398
|
+
stdio: "ignore"
|
|
2399
|
+
});
|
|
2400
|
+
k.on("error", () => child.kill());
|
|
2401
|
+
return;
|
|
2402
|
+
} catch {
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
child.kill();
|
|
2406
|
+
}
|
|
2407
|
+
var spawnAcpServer = (o) => {
|
|
2408
|
+
const shell = needsShell(o.command);
|
|
2409
|
+
let child;
|
|
2410
|
+
let err = "";
|
|
2411
|
+
let failed = () => {
|
|
2412
|
+
};
|
|
2413
|
+
let ok = () => {
|
|
2414
|
+
};
|
|
2415
|
+
const started = new Promise((res, rej) => {
|
|
2416
|
+
ok = res;
|
|
2417
|
+
failed = rej;
|
|
2418
|
+
});
|
|
2419
|
+
started.catch(() => void 0);
|
|
2420
|
+
try {
|
|
2421
|
+
child = spawn(shell ? quoteForCmd(o.command) : o.command, shell ? o.args.map(quoteForCmd) : o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true, shell });
|
|
2422
|
+
} catch (e) {
|
|
2423
|
+
failed(e);
|
|
2424
|
+
const dead = { send: () => {
|
|
2425
|
+
}, onLine: () => {
|
|
2426
|
+
}, onClose: (fn) => fn(String(e)) };
|
|
2427
|
+
return { transport: dead, stderr: () => "", close: async () => {
|
|
2428
|
+
}, started };
|
|
2429
|
+
}
|
|
2430
|
+
const lines = [];
|
|
2431
|
+
const closes = [];
|
|
2432
|
+
let closed;
|
|
2433
|
+
const close = (why) => {
|
|
2434
|
+
if (closed)
|
|
2435
|
+
return;
|
|
2436
|
+
closed = why;
|
|
2437
|
+
for (const f of closes)
|
|
2438
|
+
f(why);
|
|
2439
|
+
};
|
|
2440
|
+
child.on("spawn", () => ok());
|
|
2441
|
+
child.on("error", (e) => {
|
|
2442
|
+
failed(e);
|
|
2443
|
+
close(`the Copilot language server couldn't be run: ${e.message}`);
|
|
2444
|
+
});
|
|
2445
|
+
child.on("exit", (code, signal) => close(`the Copilot language server ended (${code ?? signal ?? "no code"})`));
|
|
2446
|
+
for (const s of [child.stdin, child.stdout, child.stderr])
|
|
2447
|
+
s?.on("error", () => void 0);
|
|
2448
|
+
child.stderr?.setEncoding("utf8").on("data", (d) => {
|
|
2449
|
+
err = (err + d).slice(-64e3);
|
|
2450
|
+
});
|
|
2451
|
+
createInterface({ input: child.stdout }).on("line", (l) => {
|
|
2452
|
+
for (const f of lines)
|
|
2453
|
+
f(l);
|
|
2454
|
+
});
|
|
2455
|
+
return {
|
|
2456
|
+
started,
|
|
2457
|
+
stderr: () => err,
|
|
2458
|
+
transport: {
|
|
2459
|
+
send: (t) => {
|
|
2460
|
+
if (!closed)
|
|
2461
|
+
child.stdin?.write(`${t}
|
|
2462
|
+
`);
|
|
2463
|
+
},
|
|
2464
|
+
onLine: (f) => lines.push(f),
|
|
2465
|
+
onClose: (f) => closed ? f(closed) : closes.push(f)
|
|
2466
|
+
},
|
|
2467
|
+
close: async () => {
|
|
2468
|
+
if (closed)
|
|
2469
|
+
return;
|
|
2470
|
+
child.stdin?.end();
|
|
2471
|
+
await new Promise((res) => {
|
|
2472
|
+
const t = setTimeout(() => {
|
|
2473
|
+
killTree(child);
|
|
2474
|
+
res();
|
|
2475
|
+
}, 2e3);
|
|
2476
|
+
closes.push(() => {
|
|
2477
|
+
clearTimeout(t);
|
|
2478
|
+
res();
|
|
2479
|
+
});
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
};
|
|
2484
|
+
function atticusExec(entry, node = process.execPath) {
|
|
2485
|
+
return (argv, o) => new Promise((res) => {
|
|
2486
|
+
let output = "";
|
|
2487
|
+
const child = spawn(node, [entry, ...argv], {
|
|
2488
|
+
cwd: o.cwd,
|
|
2489
|
+
env: { ...process.env, ATTICUS_ACP_SESSION: "1" },
|
|
2490
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2491
|
+
windowsHide: true,
|
|
2492
|
+
signal: o.signal
|
|
2493
|
+
});
|
|
2494
|
+
const take = (d) => {
|
|
2495
|
+
const t = d.toString("utf8");
|
|
2496
|
+
output = (output + t).slice(-1e6);
|
|
2497
|
+
o.onOutput?.(t);
|
|
2498
|
+
};
|
|
2499
|
+
child.stdout.on("data", take);
|
|
2500
|
+
child.stderr.on("data", take);
|
|
2501
|
+
child.on("error", (e) => res({ exitCode: 1, output: `${output}${e.message}
|
|
2502
|
+
` }));
|
|
2503
|
+
child.on("close", (code, signal) => res({ exitCode: code, output, signal }));
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
var AcpSignInError = class extends RuntimeError {
|
|
2507
|
+
constructor(message) {
|
|
2508
|
+
super(message);
|
|
2509
|
+
this.name = "AcpSignInError";
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
var AcpSetupError = class extends RuntimeError {
|
|
2513
|
+
constructor(message) {
|
|
2514
|
+
super(message);
|
|
2515
|
+
this.name = "AcpSetupError";
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
var MAX_NUDGES = 3;
|
|
2519
|
+
function carryOnPrompt(refusals) {
|
|
2520
|
+
return [
|
|
2521
|
+
"Atticus refused this, and you are not finished yet:",
|
|
2522
|
+
...refusals.slice(-3).map((r) => `- ${oneLine(r, 300)}`),
|
|
2523
|
+
"",
|
|
2524
|
+
"Carry on with the step. Use your own file tools to find, view and search files: they are allowed and need no shell. Don't run shell commands (cd, dir, Get-ChildItem, Select-String, Get-Content, grep, find); the only command Atticus runs is one `atticus ...` command at a time, with nothing piped or chained. The step says where its files are. Finish what the step asks for (the facts, or the result file it names), then stop."
|
|
2525
|
+
].join("\n");
|
|
2526
|
+
}
|
|
2527
|
+
function sumUsage(us) {
|
|
2528
|
+
if (!us.length)
|
|
2529
|
+
return void 0;
|
|
2530
|
+
const out = {};
|
|
2531
|
+
for (const u of us)
|
|
2532
|
+
for (const [k, v] of Object.entries(u))
|
|
2533
|
+
out[k] = typeof v === "number" ? (out[k] ?? 0) + v : out[k] ?? v;
|
|
2534
|
+
return out;
|
|
2535
|
+
}
|
|
2536
|
+
var ACP_OFFERED = [
|
|
2537
|
+
"fs/read_text_file",
|
|
2538
|
+
"fs/write_text_file",
|
|
2539
|
+
"terminal/create (atticus only)",
|
|
2540
|
+
"session/request_permission"
|
|
2541
|
+
];
|
|
2542
|
+
var withTimeout = (p, ms, what) => new Promise((res, rej) => {
|
|
2543
|
+
const t = setTimeout(() => rej(new RuntimeError(`${what} took over ${Math.round(ms / 1e3)}s`)), ms);
|
|
2544
|
+
p.then((v) => {
|
|
2545
|
+
clearTimeout(t);
|
|
2546
|
+
res(v);
|
|
2547
|
+
}, (e) => {
|
|
2548
|
+
clearTimeout(t);
|
|
2549
|
+
rej(e);
|
|
2550
|
+
});
|
|
2551
|
+
});
|
|
2552
|
+
var oneLine = (s, n = 150) => short(s.replace(/\s+/g, " ").trim(), n);
|
|
2553
|
+
var signInLines = (stderr) => stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => /https?:\/\/|code|sign|log.?in|auth/i.test(l)).slice(-5);
|
|
2554
|
+
async function runAcpSession(o) {
|
|
2555
|
+
const { command, args } = commandLineOf(o.command);
|
|
2556
|
+
const server = (o.spawn ?? spawnAcpServer)({
|
|
2557
|
+
command,
|
|
2558
|
+
args,
|
|
2559
|
+
cwd: o.cwd,
|
|
2560
|
+
// beta.17: set on the server too, so an atticus command Copilot runs in its own shell (not
|
|
2561
|
+
// through Atticus's terminal) knows it is inside a session and hands off instead of starting
|
|
2562
|
+
// a Copilot session of its own (a session within a session, waiting on itself).
|
|
2563
|
+
env: { ...process.env, ...o.env ?? {}, ATTICUS_ACP_SESSION: "1", ATTICUS_ROOT: o.cwd }
|
|
2564
|
+
});
|
|
2565
|
+
const transcript = [];
|
|
2566
|
+
const paths = new Paths(o.policy);
|
|
2567
|
+
const calls = [];
|
|
2568
|
+
const refused = [];
|
|
2569
|
+
const say = (l) => o.onProgress?.(short(l, 160));
|
|
2570
|
+
let text = "";
|
|
2571
|
+
let pendingText = "";
|
|
2572
|
+
let agentCalls = 0;
|
|
2573
|
+
let textAtRefusal = 0;
|
|
2574
|
+
let cancelWhy;
|
|
2575
|
+
let sessionId;
|
|
2576
|
+
const abort = new AbortController();
|
|
2577
|
+
const flush = () => {
|
|
2578
|
+
if (pendingText.trim())
|
|
2579
|
+
say(` Copilot: ${oneLine(pendingText)}`);
|
|
2580
|
+
pendingText = "";
|
|
2581
|
+
};
|
|
2582
|
+
const record = (name, input, output, error) => {
|
|
2583
|
+
calls.push({
|
|
2584
|
+
round: 1,
|
|
2585
|
+
name,
|
|
2586
|
+
input,
|
|
2587
|
+
output: short(output, 2e3),
|
|
2588
|
+
tokens: estimateTokens(output),
|
|
2589
|
+
...error ? { error: true } : {}
|
|
2590
|
+
});
|
|
2591
|
+
if (error) {
|
|
2592
|
+
textAtRefusal = text.length;
|
|
2593
|
+
refused.push(output);
|
|
2594
|
+
flush();
|
|
2595
|
+
say(` Refused: ${oneLine(output, 140)}`);
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
const refuse = (name, input, why) => {
|
|
2599
|
+
record(name, input, why, true);
|
|
2600
|
+
throw new AcpRpcError(ACP_ERRORS.invalidParams, why);
|
|
2601
|
+
};
|
|
2602
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
2603
|
+
let tn = 0;
|
|
2604
|
+
const box = {};
|
|
2605
|
+
const cancel = (why) => {
|
|
2606
|
+
if (cancelWhy)
|
|
2607
|
+
return;
|
|
2608
|
+
cancelWhy = why;
|
|
2609
|
+
flush();
|
|
2610
|
+
say(` Stopping the session: ${why}.`);
|
|
2611
|
+
if (sessionId)
|
|
2612
|
+
box.client?.cancel(sessionId);
|
|
2613
|
+
for (const t of terminals.values())
|
|
2614
|
+
t.kill.abort();
|
|
2615
|
+
};
|
|
2616
|
+
const handlers = {
|
|
2617
|
+
async readTextFile(p) {
|
|
2618
|
+
const why = paths.readable(p.path);
|
|
2619
|
+
if (why)
|
|
2620
|
+
return refuse("fs/read_text_file", { path: p.path }, why);
|
|
2621
|
+
const all = await readFile4(p.path, "utf8").catch((e) => {
|
|
2622
|
+
throw new AcpRpcError(ACP_ERRORS.invalidParams, `${p.path} can't be read: ${e.code ?? e.message}`);
|
|
2623
|
+
});
|
|
2624
|
+
let content = all;
|
|
2625
|
+
if (p.line !== void 0 || p.limit !== void 0) {
|
|
2626
|
+
const lines = all.split("\n");
|
|
2627
|
+
const from = Math.max(0, (p.line ?? 1) - 1);
|
|
2628
|
+
content = lines.slice(from, p.limit !== void 0 ? from + p.limit : void 0).join("\n");
|
|
2629
|
+
}
|
|
2630
|
+
record("fs/read_text_file", { path: p.path }, `${content.length} characters`);
|
|
2631
|
+
return { content };
|
|
2632
|
+
},
|
|
2633
|
+
async writeTextFile(p) {
|
|
2634
|
+
const why = paths.writable(p.path);
|
|
2635
|
+
if (why)
|
|
2636
|
+
return refuse("fs/write_text_file", { path: p.path }, why);
|
|
2637
|
+
await mkdir4(dirname(p.path), { recursive: true });
|
|
2638
|
+
await writeFile4(p.path, p.content, "utf8");
|
|
2639
|
+
record("fs/write_text_file", { path: p.path }, `${p.content.length} characters written`);
|
|
2640
|
+
return null;
|
|
2641
|
+
},
|
|
2642
|
+
async requestPermission(p) {
|
|
2643
|
+
const tc = p.toolCall;
|
|
2644
|
+
const why = permissionRefused(tc, paths);
|
|
2645
|
+
const pick = (kinds) => kinds.map((k) => p.options.find((x) => x.kind === k)).find(Boolean);
|
|
2646
|
+
const allow = why ? void 0 : pick(["allow_once"]);
|
|
2647
|
+
const input = {
|
|
2648
|
+
kind: tc.kind ?? "",
|
|
2649
|
+
title: tc.title ?? "",
|
|
2650
|
+
...tc.locations?.length ? { locations: tc.locations.map((l) => l.path) } : {}
|
|
2651
|
+
};
|
|
2652
|
+
if (allow) {
|
|
2653
|
+
record(`permission:${tc.kind ?? "other"}`, input, `allowed once: ${tc.title ?? ""}`);
|
|
2654
|
+
return { outcome: { outcome: "selected", optionId: allow.optionId } };
|
|
2655
|
+
}
|
|
2656
|
+
record(`permission:${tc.kind ?? "other"}`, input, why ?? "Refused: the server offered only to allow it always; Atticus allows once at a time", true);
|
|
2657
|
+
const reject = pick(["reject_once", "reject_always"]);
|
|
2658
|
+
return reject ? { outcome: { outcome: "selected", optionId: reject.optionId } } : { outcome: { outcome: "cancelled" } };
|
|
2659
|
+
},
|
|
2660
|
+
async terminalCreate(p) {
|
|
2661
|
+
const a = atticusArgvOf(p.command, p.args ?? []);
|
|
2662
|
+
const input = { command: p.command, args: p.args ?? [] };
|
|
2663
|
+
if (a.refused !== void 0)
|
|
2664
|
+
return refuse("terminal/create", input, a.refused);
|
|
2665
|
+
const cwd = o.cwd;
|
|
2666
|
+
const id = `atticus-${++tn}`;
|
|
2667
|
+
const kill = new AbortController();
|
|
2668
|
+
flush();
|
|
2669
|
+
say(` atticus ${oneLine(a.argv.join(" "), 140)}`);
|
|
2670
|
+
const t = {
|
|
2671
|
+
output: "",
|
|
2672
|
+
kill,
|
|
2673
|
+
...p.outputByteLimit ? { limit: p.outputByteLimit } : {}
|
|
2674
|
+
};
|
|
2675
|
+
t.done = o.exec(a.argv, {
|
|
2676
|
+
cwd,
|
|
2677
|
+
signal: kill.signal,
|
|
2678
|
+
onOutput: (s) => {
|
|
2679
|
+
t.output += s;
|
|
2680
|
+
if (t.limit && Buffer.byteLength(t.output) > t.limit)
|
|
2681
|
+
t.output = t.output.slice(-t.limit);
|
|
2682
|
+
}
|
|
2683
|
+
}).then((r) => {
|
|
2684
|
+
if (!t.output && r.output)
|
|
2685
|
+
t.output = r.output;
|
|
2686
|
+
t.exit = { exitCode: r.exitCode, signal: r.signal ?? null };
|
|
2687
|
+
record("terminal/create", input, `exit ${r.exitCode ?? "none"}: ${short(t.output, 1500)}`);
|
|
2688
|
+
return t.exit;
|
|
2689
|
+
});
|
|
2690
|
+
terminals.set(id, t);
|
|
2691
|
+
return { terminalId: id };
|
|
2692
|
+
},
|
|
2693
|
+
async terminalOutput(p) {
|
|
2694
|
+
const t = terminals.get(p.terminalId);
|
|
2695
|
+
if (!t)
|
|
2696
|
+
throw new AcpRpcError(ACP_ERRORS.invalidParams, `no terminal ${p.terminalId}`);
|
|
2697
|
+
return { output: t.output, truncated: false, ...t.exit ? { exitStatus: t.exit } : {} };
|
|
2698
|
+
},
|
|
2699
|
+
async terminalWaitForExit(p) {
|
|
2700
|
+
const t = terminals.get(p.terminalId);
|
|
2701
|
+
if (!t)
|
|
2702
|
+
throw new AcpRpcError(ACP_ERRORS.invalidParams, `no terminal ${p.terminalId}`);
|
|
2703
|
+
return t.done;
|
|
2704
|
+
},
|
|
2705
|
+
async terminalKill(p) {
|
|
2706
|
+
terminals.get(p.terminalId)?.kill.abort();
|
|
2707
|
+
return null;
|
|
2708
|
+
},
|
|
2709
|
+
async terminalRelease(p) {
|
|
2710
|
+
const t = terminals.get(p.terminalId);
|
|
2711
|
+
t?.kill.abort();
|
|
2712
|
+
terminals.delete(p.terminalId);
|
|
2713
|
+
return null;
|
|
2714
|
+
},
|
|
2715
|
+
update(u) {
|
|
2716
|
+
const kind = u.sessionUpdate;
|
|
2717
|
+
if (kind === UPDATE.message) {
|
|
2718
|
+
const t = chunkText(u);
|
|
2719
|
+
if (t) {
|
|
2720
|
+
text += t;
|
|
2721
|
+
pendingText += t;
|
|
2722
|
+
}
|
|
2723
|
+
} else if (kind === UPDATE.toolCall) {
|
|
2724
|
+
agentCalls++;
|
|
2725
|
+
flush();
|
|
2726
|
+
say(` \xB7 ${oneLine(String(u.title ?? u.kind ?? "a tool"), 140)}`);
|
|
2727
|
+
if (o.maxTurns !== void 0 && agentCalls > o.maxTurns)
|
|
2728
|
+
cancel(`maxTurns ${o.maxTurns}`);
|
|
2729
|
+
} else if (kind === UPDATE.plan) {
|
|
2730
|
+
const n = Array.isArray(u.entries) ? u.entries.length : 0;
|
|
2731
|
+
if (n)
|
|
2732
|
+
say(` Plan: ${n} steps`);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
};
|
|
2736
|
+
const client = new AcpClient(server.transport, handlers, (dir, m) => transcript.push(JSON.stringify({ dir, m })));
|
|
2737
|
+
box.client = client;
|
|
2738
|
+
const onAbort = () => cancel("stopped (Ctrl+C)");
|
|
2739
|
+
o.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2740
|
+
let timer;
|
|
2741
|
+
try {
|
|
2742
|
+
let init;
|
|
2743
|
+
try {
|
|
2744
|
+
await server.started;
|
|
2745
|
+
init = await withTimeout(client.initialize({ name: "atticus", version: o.clientVersion ?? "2" }), 6e4, "The Copilot language server's initialize");
|
|
2746
|
+
} catch (e) {
|
|
2747
|
+
throw new AcpSetupError(`Atticus couldn't start the Copilot language server (${o.command}): ${firstLine(e)}${tail(server.stderr())}. Check copilotAcp.command in runtime.yaml, and that ${command} --help lists --acp.`);
|
|
2748
|
+
}
|
|
2749
|
+
const assistant = {
|
|
2750
|
+
name: init.agentInfo?.name ?? basename(command).replace(/\.(cmd|exe)$/i, ""),
|
|
2751
|
+
...init.agentInfo?.version ? { version: init.agentInfo.version } : {},
|
|
2752
|
+
...typeof init.protocolVersion === "number" ? { protocolVersion: init.protocolVersion } : {}
|
|
2753
|
+
};
|
|
2754
|
+
let ns;
|
|
2755
|
+
try {
|
|
2756
|
+
ns = await withTimeout(client.newSession(o.cwd), 12e4, "Opening a Copilot session");
|
|
2757
|
+
} catch (e) {
|
|
2758
|
+
if (isAuthRequired(e)) {
|
|
2759
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
2760
|
+
throw signInError(e, init, server.stderr());
|
|
2761
|
+
}
|
|
2762
|
+
throw new AcpSetupError(`The Copilot language server didn't open a session: ${firstLine(e)}`);
|
|
2763
|
+
}
|
|
2764
|
+
sessionId = ns.sessionId;
|
|
2765
|
+
let model = ns.models?.currentModelId;
|
|
2766
|
+
if (o.model && o.model !== model) {
|
|
2767
|
+
const offered = (ns.models?.availableModels ?? []).map((m) => m.modelId);
|
|
2768
|
+
if (!offered.includes(o.model))
|
|
2769
|
+
throw new AcpSetupError(`The Copilot language server doesn't offer the model ${o.model} (it offers ${offered.join(", ") || "no list"}): change copilotAcp.model in runtime.yaml, or leave it out for Copilot's own.`);
|
|
2770
|
+
await client.setModel(sessionId, o.model);
|
|
2771
|
+
model = o.model;
|
|
2772
|
+
}
|
|
2773
|
+
say(` Copilot session open (${assistant.name}${assistant.version ? ` ${assistant.version}` : ""}${model ? `, ${model}` : ""}).`);
|
|
2774
|
+
if (o.signal?.aborted)
|
|
2775
|
+
cancel("stopped (Ctrl+C)");
|
|
2776
|
+
timer = setTimeout(() => cancel("timeout"), o.timeoutMs ?? 30 * 6e4);
|
|
2777
|
+
let prompt = o.prompt;
|
|
2778
|
+
let answer = {
|
|
2779
|
+
stopReason: "end_turn"
|
|
2780
|
+
};
|
|
2781
|
+
const usages = [];
|
|
2782
|
+
for (let nudge = 0; ; nudge++) {
|
|
2783
|
+
const before = refused.length;
|
|
2784
|
+
const asked = client.prompt(sessionId, prompt);
|
|
2785
|
+
try {
|
|
2786
|
+
answer = await new Promise((res, rej) => {
|
|
2787
|
+
asked.then(res, rej);
|
|
2788
|
+
const watch = setInterval(() => {
|
|
2789
|
+
if (!cancelWhy)
|
|
2790
|
+
return;
|
|
2791
|
+
clearInterval(watch);
|
|
2792
|
+
setTimeout(() => res({ stopReason: "cancelled" }), 1e4);
|
|
2793
|
+
}, 50);
|
|
2794
|
+
asked.finally(() => clearInterval(watch)).catch(() => void 0);
|
|
2795
|
+
});
|
|
2796
|
+
} catch (e) {
|
|
2797
|
+
if (isAuthRequired(e))
|
|
2798
|
+
throw signInError(e, init, server.stderr());
|
|
2799
|
+
throw new RuntimeError(`The Copilot session failed: ${firstLine(e)}${tail(server.stderr())}`);
|
|
2800
|
+
}
|
|
2801
|
+
flush();
|
|
2802
|
+
if (answer.usage)
|
|
2803
|
+
usages.push(answer.usage);
|
|
2804
|
+
const fresh = refused.slice(before);
|
|
2805
|
+
const after = text.slice(textAtRefusal);
|
|
2806
|
+
const overIt = fresh.length > 0 && !!calls.at(-1)?.error && (!after.trim() || /cancel|refused|not allowed|can'?t|cannot|couldn'?t|unable/i.test(after));
|
|
2807
|
+
if (cancelWhy || !overIt || nudge >= MAX_NUDGES || !["end_turn", "cancelled"].includes(answer.stopReason))
|
|
2808
|
+
break;
|
|
2809
|
+
say(` Atticus: told Copilot why that was refused and to carry on (${nudge + 1} of ${MAX_NUDGES}).`);
|
|
2810
|
+
prompt = carryOnPrompt(fresh);
|
|
2811
|
+
}
|
|
2812
|
+
const usage = usageOf(sumUsage(usages));
|
|
2813
|
+
return {
|
|
2814
|
+
stopReason: cancelWhy ? "cancelled" : answer.stopReason,
|
|
2815
|
+
...cancelWhy ? { stopped: cancelWhy } : {},
|
|
2816
|
+
...model ? { model } : {},
|
|
2817
|
+
assistant,
|
|
2818
|
+
...usage ? { usage } : {},
|
|
2819
|
+
text,
|
|
2820
|
+
toolCalls: calls,
|
|
2821
|
+
refused,
|
|
2822
|
+
agentToolCalls: agentCalls
|
|
2823
|
+
};
|
|
2824
|
+
} finally {
|
|
2825
|
+
clearTimeout(timer);
|
|
2826
|
+
o.signal?.removeEventListener("abort", onAbort);
|
|
2827
|
+
abort.abort();
|
|
2828
|
+
for (const t of terminals.values())
|
|
2829
|
+
t.kill.abort();
|
|
2830
|
+
await server.close().catch(() => void 0);
|
|
2831
|
+
if (o.transcript) {
|
|
2832
|
+
await mkdir4(dirname(o.transcript), { recursive: true });
|
|
2833
|
+
await writeFile4(o.transcript, transcript.join("\n") + (transcript.length ? "\n" : ""), "utf8");
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
var firstLine = (e) => String(e?.message ?? e).split("\n")[0];
|
|
2838
|
+
var tail = (stderr) => {
|
|
2839
|
+
const l = stderr.trim().split(/\r?\n/).slice(-2).join(" ").trim();
|
|
2840
|
+
return l ? ` (it said: ${short(l, 300)})` : "";
|
|
2841
|
+
};
|
|
2842
|
+
function signInError(e, init, stderr) {
|
|
2843
|
+
const how = (init.authMethods ?? []).map((m) => m.description || m.name).filter(Boolean).join("; ");
|
|
2844
|
+
const lines = signInLines(stderr);
|
|
2845
|
+
return new AcpSignInError([
|
|
2846
|
+
`GitHub Copilot isn't signed in here, so the run stopped. The Copilot language server said: ${firstLine(e)}`,
|
|
2847
|
+
...lines.length ? [`It also said: ${lines.join(" ")}`] : [],
|
|
2848
|
+
...how ? [`Its ways to sign in: ${how}.`] : [],
|
|
2849
|
+
"Sign in yourself (Copilot's sign-in in your editor, or the link and code above), then run the command again. Atticus never enters or keeps your credentials."
|
|
2850
|
+
].join("\n"));
|
|
2851
|
+
}
|
|
2852
|
+
function permissionRefused(tc, paths) {
|
|
2853
|
+
const locs = (tc.locations ?? []).map((l) => l.path);
|
|
2854
|
+
switch (tc.kind) {
|
|
2855
|
+
case "think":
|
|
2856
|
+
return void 0;
|
|
2857
|
+
case "read":
|
|
2858
|
+
case "search": {
|
|
2859
|
+
if (!locs.length)
|
|
2860
|
+
return tc.kind === "search" ? void 0 : "Refused: a read must say which file (use fs/read_text_file)";
|
|
2861
|
+
for (const l of locs) {
|
|
2862
|
+
const why = paths.readable(l);
|
|
2863
|
+
if (why)
|
|
2864
|
+
return why;
|
|
2865
|
+
}
|
|
2866
|
+
return void 0;
|
|
2867
|
+
}
|
|
2868
|
+
case "edit": {
|
|
2869
|
+
if (!locs.length)
|
|
2870
|
+
return "Refused: an edit must say which file it changes";
|
|
2871
|
+
for (const l of locs) {
|
|
2872
|
+
const why = paths.writable(l);
|
|
2873
|
+
if (why)
|
|
2874
|
+
return why;
|
|
2875
|
+
}
|
|
2876
|
+
return void 0;
|
|
2877
|
+
}
|
|
2878
|
+
case "execute": {
|
|
2879
|
+
const raw = tc.rawInput ?? {};
|
|
2880
|
+
const cmd = typeof raw.command === "string" ? raw.command : typeof raw.commandLine === "string" ? raw.commandLine : tc.title ?? "";
|
|
2881
|
+
const args = Array.isArray(raw.args) ? raw.args.map(String) : [];
|
|
2882
|
+
const a = atticusArgvOf(cmd, args);
|
|
2883
|
+
return a.refused;
|
|
2884
|
+
}
|
|
2885
|
+
default:
|
|
2886
|
+
return `Refused: Atticus doesn't let this session ${tc.kind ?? "do"} ${short(tc.title ?? "that")} (it may read the metarepo, write its step's result and run atticus commands)`;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
async function copilotAcpProbe(o) {
|
|
2890
|
+
const { command, args } = commandLineOf(o.command);
|
|
2891
|
+
const plain = args.filter((a) => a !== "--acp");
|
|
2892
|
+
const env = { ...process.env, ...o.env ?? {} };
|
|
2893
|
+
const version = await new Promise((res) => {
|
|
2894
|
+
const shell = needsShell(command);
|
|
2895
|
+
let out = "";
|
|
2896
|
+
let child;
|
|
2897
|
+
try {
|
|
2898
|
+
child = spawn(shell ? quoteForCmd(command) : command, [...shell ? plain.map(quoteForCmd) : plain, "--version"], {
|
|
2899
|
+
cwd: o.cwd,
|
|
2900
|
+
env,
|
|
2901
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2902
|
+
windowsHide: true,
|
|
2903
|
+
shell
|
|
2904
|
+
});
|
|
2905
|
+
} catch (e) {
|
|
2906
|
+
res({ ok: false, text: e.message });
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
const t = setTimeout(() => killTree(child), o.timeoutMs ?? 6e4);
|
|
2910
|
+
child.stdout?.on("data", (d) => out += d.toString("utf8"));
|
|
2911
|
+
child.stderr?.on("data", (d) => out += d.toString("utf8"));
|
|
2912
|
+
child.on("error", (e) => {
|
|
2913
|
+
clearTimeout(t);
|
|
2914
|
+
res({ ok: false, text: e.message });
|
|
2915
|
+
});
|
|
2916
|
+
child.on("close", (code) => {
|
|
2917
|
+
clearTimeout(t);
|
|
2918
|
+
res({ ok: code === 0, text: out.trim() });
|
|
2919
|
+
});
|
|
2920
|
+
});
|
|
2921
|
+
if (!version.ok)
|
|
2922
|
+
return { found: false, said: oneLine(version.text, 200) };
|
|
2923
|
+
const v = version.text.split(/\r?\n/).find((l) => l.trim())?.trim();
|
|
2924
|
+
const server = (o.spawn ?? spawnAcpServer)({ command, args, cwd: o.cwd, env });
|
|
2925
|
+
const client = new AcpClient(server.transport, {
|
|
2926
|
+
readTextFile: () => Promise.reject(new AcpRpcError(ACP_ERRORS.invalidParams, "doctor reads nothing")),
|
|
2927
|
+
writeTextFile: () => Promise.reject(new AcpRpcError(ACP_ERRORS.invalidParams, "doctor writes nothing")),
|
|
2928
|
+
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
|
|
2929
|
+
terminalCreate: () => Promise.reject(new AcpRpcError(ACP_ERRORS.invalidParams, "doctor runs nothing")),
|
|
2930
|
+
terminalOutput: () => Promise.reject(new AcpRpcError(ACP_ERRORS.invalidParams, "no terminal")),
|
|
2931
|
+
terminalWaitForExit: () => Promise.reject(new AcpRpcError(ACP_ERRORS.invalidParams, "no terminal")),
|
|
2932
|
+
terminalKill: async () => null,
|
|
2933
|
+
terminalRelease: async () => null,
|
|
2934
|
+
update: () => {
|
|
2935
|
+
}
|
|
2936
|
+
});
|
|
2937
|
+
const base = { found: true, ...v ? { version: v } : {} };
|
|
2938
|
+
try {
|
|
2939
|
+
await server.started;
|
|
2940
|
+
await withTimeout(client.initialize({ name: "atticus-doctor", version: "2" }), o.timeoutMs ?? 6e4, "initialize");
|
|
2941
|
+
try {
|
|
2942
|
+
await withTimeout(client.newSession(o.cwd), o.timeoutMs ?? 6e4, "a new session");
|
|
2943
|
+
return { ...base, signedIn: true };
|
|
2944
|
+
} catch (e) {
|
|
2945
|
+
if (isAuthRequired(e)) {
|
|
2946
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
2947
|
+
const lines = signInLines(server.stderr());
|
|
2948
|
+
return { ...base, signedIn: false, said: [firstLine(e), ...lines].join(" ") };
|
|
2949
|
+
}
|
|
2950
|
+
return { ...base, said: firstLine(e) };
|
|
2951
|
+
}
|
|
2952
|
+
} catch (e) {
|
|
2953
|
+
return { ...base, said: `the --acp handshake failed: ${firstLine(e)}` };
|
|
2954
|
+
} finally {
|
|
2955
|
+
await server.close().catch(() => void 0);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
var CopilotAcpRuntime = class extends OneShotRuntime {
|
|
2959
|
+
c;
|
|
2960
|
+
kind = "copilot-acp";
|
|
2961
|
+
constructor(c) {
|
|
2962
|
+
super({ ...c.now ? { now: c.now } : {}, ...c.validate ? { validate: c.validate } : {} });
|
|
2963
|
+
this.c = c;
|
|
2964
|
+
}
|
|
2965
|
+
allowedAs(agent) {
|
|
2966
|
+
return agent.allowedRuntimes.includes("copilot-acp") ? "copilot-acp" : "hand-off";
|
|
2967
|
+
}
|
|
2968
|
+
modelFor() {
|
|
2969
|
+
return this.c.model;
|
|
2970
|
+
}
|
|
2971
|
+
async produce(run, signal) {
|
|
2972
|
+
const { request } = run;
|
|
2973
|
+
const root = this.c.root;
|
|
2974
|
+
const dir = join4(runsDirOf(root), request.change, run.runId);
|
|
2975
|
+
await mkdir4(dir, { recursive: true });
|
|
2976
|
+
const outRel = HandoffRuntime.outputRel(run, changesRelOf(root), runsRelOf(root));
|
|
2977
|
+
const prompt = HandoffRuntime.promptFor(run, changesRelOf(root), runsRelOf(root)).replace(/\nWhen the file is complete.*\n$/s, "\n") + (request.selfCheck ? `
|
|
2978
|
+
Before you finish, check the file: run ${request.selfCheck.command} ${outRel}. Fix each problem it lists and run it again, until it says the file is clean (at most ${request.selfCheck.limit} times).
|
|
2979
|
+
` : "") + `
|
|
2980
|
+
${DATA_RULE}
|
|
2981
|
+
`;
|
|
2982
|
+
await writeFile4(join4(dir, "prompt.md"), prompt, "utf8");
|
|
2983
|
+
await writeFile4(join4(dir, "context-manifest.json"), JSON.stringify(request.pack.manifest, null, 2) + "\n", "utf8");
|
|
2984
|
+
const outputFile = request.outputIn === "run" ? join4(dir, request.outputPath) : join4(changesDirOf(root), request.change, request.outputPath);
|
|
2985
|
+
const before = (await stat2(outputFile).catch(() => void 0))?.mtimeMs;
|
|
2986
|
+
const r = await runAcpSession({
|
|
2987
|
+
command: this.c.command,
|
|
2988
|
+
cwd: root,
|
|
2989
|
+
prompt,
|
|
2990
|
+
policy: { readRoots: readRootsOf(root), writeRoots: [], writeFiles: [outputFile] },
|
|
2991
|
+
exec: this.c.exec ?? noExec,
|
|
2992
|
+
...this.c.model ? { model: this.c.model } : {},
|
|
2993
|
+
...this.c.maxTurns ? { maxTurns: this.c.maxTurns } : {},
|
|
2994
|
+
...this.c.timeoutMs ? { timeoutMs: this.c.timeoutMs } : {},
|
|
2995
|
+
...signal ? { signal } : {},
|
|
2996
|
+
...this.c.onProgress ? { onProgress: this.c.onProgress } : {},
|
|
2997
|
+
...this.c.env ? { env: this.c.env } : {},
|
|
2998
|
+
...this.c.spawn ? { spawn: this.c.spawn } : {},
|
|
2999
|
+
...this.c.clientVersion ? { clientVersion: this.c.clientVersion } : {},
|
|
3000
|
+
transcript: join4(dir, "transcript.jsonl")
|
|
3001
|
+
});
|
|
3002
|
+
const after = (await stat2(outputFile).catch(() => void 0))?.mtimeMs;
|
|
3003
|
+
const written = after !== void 0 && after !== before ? await readFile4(outputFile, "utf8") : void 0;
|
|
3004
|
+
const tools = {
|
|
3005
|
+
offered: ACP_OFFERED,
|
|
3006
|
+
calls: r.toolCalls,
|
|
3007
|
+
...r.stopped ? { stopped: r.stopped } : {}
|
|
3008
|
+
};
|
|
3009
|
+
if (written === void 0 && r.stopReason !== "end_turn")
|
|
3010
|
+
throw Object.assign(new RuntimeError(`The Copilot session ended (${r.stopped ?? r.stopReason}) without writing ${outRel}.`), { tools });
|
|
3011
|
+
return {
|
|
3012
|
+
output: written ?? r.text,
|
|
3013
|
+
usage: r.usage ?? {
|
|
3014
|
+
inputTokens: request.pack.manifest.totalTokens,
|
|
3015
|
+
outputTokens: estimateTokens(r.text)
|
|
3016
|
+
},
|
|
3017
|
+
tools,
|
|
3018
|
+
...r.model ? { model: r.model } : {},
|
|
3019
|
+
assistant: r.assistant
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
/** Kept as a hand-off run is: run.json beside the prompt, so Model runs shows it. */
|
|
3023
|
+
async keep(run) {
|
|
3024
|
+
const dir = join4(runsDirOf(this.c.root), run.request.change, run.runId);
|
|
3025
|
+
await mkdir4(dir, { recursive: true });
|
|
3026
|
+
await writeFile4(join4(dir, "run.json"), JSON.stringify(run, null, 2) + "\n", "utf8");
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
var noExec = async (argv) => ({
|
|
3030
|
+
exitCode: 127,
|
|
3031
|
+
output: `atticus ${argv.join(" ")}: this program can't run atticus commands for the session; run the step with the atticus command line.
|
|
3032
|
+
`
|
|
3033
|
+
});
|
|
3034
|
+
async function keepAcpRun(o) {
|
|
3035
|
+
const dir = join4(runsDirOf(o.root), o.change, o.runId);
|
|
3036
|
+
await mkdir4(dir, { recursive: true });
|
|
3037
|
+
if (!existsSync(join4(dir, "prompt.md")))
|
|
3038
|
+
await writeFile4(join4(dir, "prompt.md"), o.prompt, "utf8");
|
|
3039
|
+
const r = o.result;
|
|
3040
|
+
const run = {
|
|
3041
|
+
runId: o.runId,
|
|
3042
|
+
request: { change: o.change, agent: o.agent, outputPath: o.outputPath },
|
|
3043
|
+
runtime: "copilot-acp",
|
|
3044
|
+
state: o.state,
|
|
3045
|
+
attempt: 1,
|
|
3046
|
+
startedAt: o.startedAt,
|
|
3047
|
+
finishedAt: o.finishedAt,
|
|
3048
|
+
errors: o.errors,
|
|
3049
|
+
...r?.usage ? { usage: r.usage } : {},
|
|
3050
|
+
...r ? {
|
|
3051
|
+
tools: {
|
|
3052
|
+
offered: ACP_OFFERED,
|
|
3053
|
+
calls: r.toolCalls,
|
|
3054
|
+
...r.stopped ? { stopped: r.stopped } : {}
|
|
3055
|
+
}
|
|
3056
|
+
} : {},
|
|
3057
|
+
stepClass: o.stepClass ?? stepClassOf({ agent: o.agent }),
|
|
3058
|
+
...r?.model ? { model: r.model } : {},
|
|
3059
|
+
...r ? { assistant: r.assistant } : {}
|
|
3060
|
+
};
|
|
3061
|
+
await writeFile4(join4(dir, "run.json"), JSON.stringify(run, null, 2) + "\n", "utf8");
|
|
3062
|
+
return dir;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
// packages/ai/dist/factory.js
|
|
1702
3066
|
var RUNTIME_MODES = RuntimeKind.options;
|
|
1703
|
-
var CONFIGURED = [
|
|
3067
|
+
var CONFIGURED = [
|
|
3068
|
+
"hand-off",
|
|
3069
|
+
"model",
|
|
3070
|
+
"headless-claude-code",
|
|
3071
|
+
"copilot-acp"
|
|
3072
|
+
];
|
|
1704
3073
|
var RuntimeConfigError = class extends RuntimeError {
|
|
1705
3074
|
constructor(message) {
|
|
1706
3075
|
super(message);
|
|
@@ -1713,16 +3082,49 @@ var AiOffError = class extends RuntimeConfigError {
|
|
|
1713
3082
|
}
|
|
1714
3083
|
};
|
|
1715
3084
|
function claudeCodeCliRunner(o = {}) {
|
|
1716
|
-
return (i) =>
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
|
|
3085
|
+
return (i) => {
|
|
3086
|
+
const command = resolveWindowsCommand(o.command ?? "claude");
|
|
3087
|
+
const shell = isShim(command);
|
|
3088
|
+
let started;
|
|
3089
|
+
const end = () => {
|
|
3090
|
+
if (started)
|
|
3091
|
+
killTree(started);
|
|
3092
|
+
};
|
|
3093
|
+
if (shell && i.signal) {
|
|
3094
|
+
if (i.signal.aborted)
|
|
3095
|
+
queueMicrotask(end);
|
|
3096
|
+
else
|
|
3097
|
+
i.signal.addEventListener("abort", end, { once: true });
|
|
3098
|
+
}
|
|
3099
|
+
return collectChild(() => {
|
|
3100
|
+
started = spawn2(shell ? quoteForCmdAlways(command) : command, shell ? i.args.map(quoteForCmdAlways) : i.args, {
|
|
3101
|
+
cwd: i.cwd,
|
|
3102
|
+
env: { ...process.env, ...i.env },
|
|
3103
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3104
|
+
windowsHide: true,
|
|
3105
|
+
shell,
|
|
3106
|
+
...i.signal && !shell ? { signal: i.signal } : {}
|
|
3107
|
+
});
|
|
3108
|
+
return started;
|
|
3109
|
+
}, i.prompt, i.timeoutMs, i.cwd, process.platform, shell ? (c) => killTree(c) : void 0);
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
var isShim = (command, platform = process.platform) => platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
3113
|
+
function resolveWindowsCommand(command, env = process.env, platform = process.platform, exists = existsSync2) {
|
|
3114
|
+
if (platform !== "win32" || /[\\/]/.test(command) || /\.[a-z0-9]+$/i.test(command))
|
|
3115
|
+
return command;
|
|
3116
|
+
const exts = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
3117
|
+
for (const dir of (env.Path ?? env.PATH ?? "").split(";").map((d) => d.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean))
|
|
3118
|
+
for (const ext of exts) {
|
|
3119
|
+
const full = `${dir.replace(/[\\/]+$/, "")}\\${command}${ext.toLowerCase()}`;
|
|
3120
|
+
if (exists(full))
|
|
3121
|
+
return full;
|
|
3122
|
+
}
|
|
3123
|
+
return command;
|
|
3124
|
+
}
|
|
3125
|
+
var quoteForCmdAlways = (a) => `"${a.replace(/"/g, '""')}"`;
|
|
3126
|
+
function collectChild(start, prompt, timeoutMs, cwd, platform = process.platform, stop) {
|
|
3127
|
+
return new Promise((resolve2, reject) => {
|
|
1726
3128
|
let done = false;
|
|
1727
3129
|
const timer = {};
|
|
1728
3130
|
const fail = (e) => {
|
|
@@ -1743,7 +3145,7 @@ function collectChild(start, prompt, timeoutMs, cwd, platform = process.platform
|
|
|
1743
3145
|
}
|
|
1744
3146
|
let stdout = "";
|
|
1745
3147
|
let stderr = "";
|
|
1746
|
-
timer.id = setTimeout(() => child.kill("SIGINT"), timeoutMs);
|
|
3148
|
+
timer.id = setTimeout(() => stop ? stop(child) : child.kill("SIGINT"), timeoutMs);
|
|
1747
3149
|
for (const s of [child.stdin, child.stdout, child.stderr])
|
|
1748
3150
|
s.on("error", fail);
|
|
1749
3151
|
child.stdout.setEncoding("utf8").on("data", (d) => stdout += d);
|
|
@@ -1754,16 +3156,16 @@ function collectChild(start, prompt, timeoutMs, cwd, platform = process.platform
|
|
|
1754
3156
|
return;
|
|
1755
3157
|
done = true;
|
|
1756
3158
|
clearTimeout(timer.id);
|
|
1757
|
-
|
|
3159
|
+
resolve2({ exitCode: code, stdout, stderr });
|
|
1758
3160
|
});
|
|
1759
3161
|
child.stdin.end(prompt, "utf8");
|
|
1760
3162
|
});
|
|
1761
3163
|
}
|
|
1762
|
-
var cloneDirs = (root) => [
|
|
3164
|
+
var cloneDirs = (root) => [join5(root, "workspace"), join5(stateDirOf(root), "workspace")];
|
|
1763
3165
|
async function existingDirs(dirs) {
|
|
1764
3166
|
const out = [];
|
|
1765
3167
|
for (const d of [...new Set(dirs)])
|
|
1766
|
-
if ((await
|
|
3168
|
+
if ((await stat3(d).catch(() => void 0))?.isDirectory())
|
|
1767
3169
|
out.push(d);
|
|
1768
3170
|
return out;
|
|
1769
3171
|
}
|
|
@@ -1800,9 +3202,9 @@ var HeadlessClaudeCodeRuntime = class _HeadlessClaudeCodeRuntime extends OneShot
|
|
|
1800
3202
|
}
|
|
1801
3203
|
async produce(run, signal) {
|
|
1802
3204
|
const { request } = run;
|
|
1803
|
-
const dir =
|
|
3205
|
+
const dir = join5(runsDirOf(this.h.root), request.change, run.runId);
|
|
1804
3206
|
const reviewer = request.agent.outputArtifact === "findings" && request.outputIn === "run";
|
|
1805
|
-
const work = reviewer ? await mkdtemp(
|
|
3207
|
+
const work = reviewer ? await mkdtemp(join5(tmpdir(), "atticus-review-")) : void 0;
|
|
1806
3208
|
const clones = work ? await existingDirs(cloneDirs(this.h.root)) : [];
|
|
1807
3209
|
const base = work ? {
|
|
1808
3210
|
cwd: work,
|
|
@@ -1816,27 +3218,27 @@ var HeadlessClaudeCodeRuntime = class _HeadlessClaudeCodeRuntime extends OneShot
|
|
|
1816
3218
|
`Bash(${request.selfCheck.command}:*)`
|
|
1817
3219
|
]
|
|
1818
3220
|
} : base;
|
|
1819
|
-
await
|
|
1820
|
-
const promptFile =
|
|
3221
|
+
await mkdir5(dir, { recursive: true });
|
|
3222
|
+
const promptFile = join5(dir, "prompt.md");
|
|
1821
3223
|
const self = reviewer ? void 0 : request.selfCheck;
|
|
1822
3224
|
const handoff = HandoffRuntime.promptFor(run, changesRelOf(this.h.root), runsRelOf(this.h.root)).replace(/\nWhen the file is complete.*\n$/s, "\n") + // W3a: the drafter checks its own file with the stage's checks before it finishes.
|
|
1823
3225
|
(self ? `
|
|
1824
3226
|
Before you finish, check the file: run ${self.command} ${HandoffRuntime.outputRel(run, changesRelOf(this.h.root), runsRelOf(this.h.root))}. Fix each problem it lists and run it again, until it says the file is clean (at most ${self.limit} times).
|
|
1825
3227
|
` : "");
|
|
1826
3228
|
const prompt = reviewer ? handoff.replace(`Write the result to ${HandoffRuntime.outputRel(run, changesRelOf(this.h.root), runsRelOf(this.h.root))}.`, `Write the result to ${request.outputPath}.${clones.length ? ` The metarepo is at ${this.h.root}: a folder named relative to the metarepo (the repos' clones under workspace/) is there, to read, never to edit.` : ""}`) : handoff;
|
|
1827
|
-
await
|
|
1828
|
-
await
|
|
1829
|
-
if (!await
|
|
3229
|
+
await writeFile5(promptFile, prompt, "utf8");
|
|
3230
|
+
await writeFile5(join5(dir, "context-manifest.json"), JSON.stringify(request.pack.manifest, null, 2) + "\n", "utf8");
|
|
3231
|
+
if (!await stat3(inv.cwd).catch(() => void 0))
|
|
1830
3232
|
throw new RuntimeError(`the worktree ${inv.cwd} doesn't exist`);
|
|
1831
|
-
const outputFile = work ?
|
|
3233
|
+
const outputFile = work ? join5(work, request.outputPath) : request.outputIn === "run" ? join5(dir, request.outputPath) : join5(changesDirOf(this.h.root), request.change, request.outputPath);
|
|
1832
3234
|
try {
|
|
1833
3235
|
return await this.runIn(run, dir, inv, prompt, promptFile, outputFile, signal);
|
|
1834
3236
|
} finally {
|
|
1835
3237
|
if (work) {
|
|
1836
|
-
const kept = await
|
|
3238
|
+
const kept = await readFile5(outputFile, "utf8").catch(() => void 0);
|
|
1837
3239
|
if (kept !== void 0) {
|
|
1838
|
-
await
|
|
1839
|
-
await
|
|
3240
|
+
await mkdir5(dirname2(join5(dir, request.outputPath)), { recursive: true });
|
|
3241
|
+
await writeFile5(join5(dir, request.outputPath), kept, "utf8");
|
|
1840
3242
|
}
|
|
1841
3243
|
await rm2(work, { recursive: true, force: true, maxRetries: 5 }).catch(() => void 0);
|
|
1842
3244
|
}
|
|
@@ -1844,7 +3246,7 @@ Before you finish, check the file: run ${self.command} ${HandoffRuntime.outputRe
|
|
|
1844
3246
|
}
|
|
1845
3247
|
async runIn(run, dir, inv, prompt, promptFile, outputFile, signal) {
|
|
1846
3248
|
const { request } = run;
|
|
1847
|
-
const before = (await
|
|
3249
|
+
const before = (await stat3(outputFile).catch(() => void 0))?.mtimeMs;
|
|
1848
3250
|
const res = await this.runner({
|
|
1849
3251
|
cwd: inv.cwd,
|
|
1850
3252
|
prompt,
|
|
@@ -1860,7 +3262,7 @@ Before you finish, check the file: run ${self.command} ${HandoffRuntime.outputRe
|
|
|
1860
3262
|
timeoutMs: this.h.timeoutMs ?? 30 * 6e4,
|
|
1861
3263
|
...signal ? { signal } : {}
|
|
1862
3264
|
});
|
|
1863
|
-
await
|
|
3265
|
+
await writeFile5(join5(dir, "claude-output.json"), res.stdout, "utf8");
|
|
1864
3266
|
let parsed;
|
|
1865
3267
|
try {
|
|
1866
3268
|
parsed = JSON.parse(res.stdout);
|
|
@@ -1870,8 +3272,8 @@ Before you finish, check the file: run ${self.command} ${HandoffRuntime.outputRe
|
|
|
1870
3272
|
const text = typeof parsed.result === "string" ? parsed.result : "";
|
|
1871
3273
|
if (res.exitCode !== 0 || parsed.is_error === true)
|
|
1872
3274
|
throw new RuntimeError(`Claude Code reported a failure: ${text || res.stderr.trim()}`);
|
|
1873
|
-
const after = (await
|
|
1874
|
-
const written = after !== void 0 && after !== before ? await
|
|
3275
|
+
const after = (await stat3(outputFile).catch(() => void 0))?.mtimeMs;
|
|
3276
|
+
const written = after !== void 0 && after !== before ? await readFile5(outputFile, "utf8") : void 0;
|
|
1875
3277
|
return {
|
|
1876
3278
|
output: written ?? text,
|
|
1877
3279
|
usage: {
|
|
@@ -1916,6 +3318,10 @@ var toolSource;
|
|
|
1916
3318
|
function registerToolSource(source) {
|
|
1917
3319
|
toolSource = source;
|
|
1918
3320
|
}
|
|
3321
|
+
var acpRunner;
|
|
3322
|
+
function registerCopilotAcp(o) {
|
|
3323
|
+
acpRunner = o;
|
|
3324
|
+
}
|
|
1919
3325
|
function classModels(rt, o) {
|
|
1920
3326
|
const by = modelsByClass(rt, o.env ?? {});
|
|
1921
3327
|
const out = {};
|
|
@@ -1937,17 +3343,19 @@ function classModels(rt, o) {
|
|
|
1937
3343
|
}
|
|
1938
3344
|
var told = /* @__PURE__ */ new WeakMap();
|
|
1939
3345
|
var nonEmpty = async (file, request) => {
|
|
1940
|
-
const text = await
|
|
3346
|
+
const text = await readFile5(file, "utf8").catch(() => "");
|
|
1941
3347
|
return text.trim() ? [] : [`${request.outputPath} is empty`];
|
|
1942
3348
|
};
|
|
1943
3349
|
async function createRuntime(orgPack, o) {
|
|
1944
3350
|
const rt = orgPack.runtime;
|
|
1945
|
-
const
|
|
3351
|
+
const asked = o.mode ?? rt.modes.default;
|
|
3352
|
+
const inSession = !!(o.env ?? process.env).ATTICUS_ACP_SESSION;
|
|
3353
|
+
const mode = asked === "copilot-acp" && inSession ? "hand-off" : asked;
|
|
1946
3354
|
if (!RUNTIME_MODES.includes(mode))
|
|
1947
3355
|
throw new RuntimeConfigError(`There is no AI mode called ${String(mode)}. Use one of: ${RUNTIME_MODES.join(", ")}.`);
|
|
1948
3356
|
if (rt.ai === "off" && CONFIGURED.includes(mode))
|
|
1949
3357
|
throw new AiOffError();
|
|
1950
|
-
if (CONFIGURED.includes(mode) && !rt.modes.allowed.includes(mode))
|
|
3358
|
+
if (CONFIGURED.includes(mode) && !rt.modes.allowed.includes(mode) && !(inSession && asked === "copilot-acp"))
|
|
1951
3359
|
throw new RuntimeConfigError(`runtime.yaml doesn't allow ${mode} mode (allowed: ${rt.modes.allowed.join(", ")}). Change modes.allowed in a pull request, or pick an allowed mode.`);
|
|
1952
3360
|
const one = { ...o.now ? { now: o.now } : {}, ...o.validate ? { validate: o.validate } : {} };
|
|
1953
3361
|
switch (mode) {
|
|
@@ -1955,7 +3363,9 @@ async function createRuntime(orgPack, o) {
|
|
|
1955
3363
|
return new HandoffRuntime({
|
|
1956
3364
|
root: o.root,
|
|
1957
3365
|
validate: o.handoffValidate ?? nonEmpty,
|
|
1958
|
-
...o.now ? { now: o.now } : {}
|
|
3366
|
+
...o.now ? { now: o.now } : {},
|
|
3367
|
+
// ADR 0185: a stage's critic lenses go to the person's assistant in one prompt.
|
|
3368
|
+
batch: true
|
|
1959
3369
|
});
|
|
1960
3370
|
case "model": {
|
|
1961
3371
|
const cfg = rt.adapters.model;
|
|
@@ -1995,6 +3405,22 @@ async function createRuntime(orgPack, o) {
|
|
|
1995
3405
|
...o.validate ? { validate: o.validate } : {}
|
|
1996
3406
|
});
|
|
1997
3407
|
}
|
|
3408
|
+
case "copilot-acp": {
|
|
3409
|
+
if (!rt.assistants.allowed.includes("copilot"))
|
|
3410
|
+
throw new RuntimeConfigError("copilot-acp drives GitHub Copilot, which runtime.yaml doesn't list under assistants.allowed.");
|
|
3411
|
+
const c = copilotAcpOf(rt);
|
|
3412
|
+
return new CopilotAcpRuntime({
|
|
3413
|
+
root: o.root,
|
|
3414
|
+
command: c.command,
|
|
3415
|
+
...c.model ? { model: c.model } : {},
|
|
3416
|
+
maxTurns: c.maxTurns,
|
|
3417
|
+
timeoutMs: c.timeoutMs,
|
|
3418
|
+
...acpRunner,
|
|
3419
|
+
...o.copilotAcp,
|
|
3420
|
+
...o.now ? { now: o.now } : {},
|
|
3421
|
+
...o.validate ? { validate: o.validate } : {}
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
1998
3424
|
case "replay":
|
|
1999
3425
|
if (o.recordings)
|
|
2000
3426
|
return new ReplayRuntime(o.recordings, one);
|
|
@@ -2011,13 +3437,13 @@ var sayOnce = (line) => process.stderr.write(`${line}
|
|
|
2011
3437
|
`);
|
|
2012
3438
|
async function runtimeForCli(root, o = {}) {
|
|
2013
3439
|
const env = o.env ?? process.env;
|
|
2014
|
-
const op = await loadOrgPack(
|
|
3440
|
+
const op = await loadOrgPack(join5(orgPackDirOf(root)));
|
|
2015
3441
|
const bad = op.problems.filter((p) => p.file === "runtime.yaml");
|
|
2016
3442
|
if (bad.length)
|
|
2017
3443
|
throw new RuntimeConfigError(`org-pack/runtime.yaml has problems: ${bad.map((p) => `${p.path ? `${p.path}: ` : ""}${p.message}`).join("; ")}`);
|
|
2018
3444
|
const asked = o.mode ?? env.ATTICUS_RUNTIME ?? op.runtime.modes.default;
|
|
2019
3445
|
if (asked === "mock")
|
|
2020
|
-
throw new RuntimeConfigError("Mock mode is for tests; use hand-off, model, headless-claude-code or replay.");
|
|
3446
|
+
throw new RuntimeConfigError("Mock mode is for tests; use hand-off, model, headless-claude-code, copilot-acp or replay.");
|
|
2021
3447
|
const mode = asked;
|
|
2022
3448
|
const runtime = await createRuntime(op, {
|
|
2023
3449
|
root,
|
|
@@ -2025,13 +3451,14 @@ async function runtimeForCli(root, o = {}) {
|
|
|
2025
3451
|
...env.ATTICUS_REPLAY_DIR ? { replayDir: env.ATTICUS_REPLAY_DIR } : {},
|
|
2026
3452
|
...o.providers ? { providers: o.providers } : {},
|
|
2027
3453
|
...o.headless ? { headless: o.headless } : {},
|
|
3454
|
+
...o.copilotAcp ? { copilotAcp: o.copilotAcp } : {},
|
|
2028
3455
|
...o.handoffValidate ? { handoffValidate: o.handoffValidate } : {},
|
|
2029
3456
|
// ADR 0121 addendum: the person's model per kind of step, and a model passed over said once.
|
|
2030
3457
|
env,
|
|
2031
3458
|
onNote: o.onNote ?? sayOnce
|
|
2032
3459
|
});
|
|
2033
|
-
const describe =
|
|
2034
|
-
return { runtime, mode, describe };
|
|
3460
|
+
const describe = runtime.kind === "hand-off" ? "AI steps are handed to your assistant: Atticus writes the prompt and waits for atticus handoff done." : mode === "model" ? `AI steps call the ${op.runtime.adapters.model?.type ?? "configured"} model directly.` : mode === "headless-claude-code" ? "AI steps run in headless Claude Code." : mode === "copilot-acp" ? "AI steps run in GitHub Copilot, unattended, one new session each (copilot-acp)." : "AI steps replay recorded answers.";
|
|
3461
|
+
return { runtime, mode: runtime.kind === "hand-off" ? "hand-off" : mode, describe };
|
|
2035
3462
|
}
|
|
2036
3463
|
|
|
2037
3464
|
// packages/ai/dist/attempts.js
|
|
@@ -2127,8 +3554,8 @@ var HashEmbedder = class {
|
|
|
2127
3554
|
return { i: h.readUInt32BE(0) % this.dim, sign: h[4] & 1 ? 1 : -1 };
|
|
2128
3555
|
};
|
|
2129
3556
|
for (const m of text.matchAll(/[A-Za-z0-9]+/g)) {
|
|
2130
|
-
const
|
|
2131
|
-
for (const w of new Set(
|
|
3557
|
+
const words2 = [m[0].toLowerCase(), ...identifierWords(m[0])];
|
|
3558
|
+
for (const w of new Set(words2)) {
|
|
2132
3559
|
if (w.length < 3)
|
|
2133
3560
|
continue;
|
|
2134
3561
|
const b = bucket(`w:${w}`);
|
|
@@ -2618,6 +4045,9 @@ function recordedJudge(byTexts, o = {}) {
|
|
|
2618
4045
|
}
|
|
2619
4046
|
|
|
2620
4047
|
export {
|
|
4048
|
+
personOnlyWhy,
|
|
4049
|
+
isPersonsFactsFile,
|
|
4050
|
+
windowsName,
|
|
2621
4051
|
DEFENCES_VERSION,
|
|
2622
4052
|
INSTRUCTION_RULES,
|
|
2623
4053
|
riskAtLeast,
|
|
@@ -2683,12 +4113,45 @@ export {
|
|
|
2683
4113
|
recordRun,
|
|
2684
4114
|
MockModelProvider,
|
|
2685
4115
|
ModelRuntime,
|
|
4116
|
+
BATCH_OUTPUT,
|
|
4117
|
+
DEFAULT_BATCH_MAX_TOKENS,
|
|
4118
|
+
batchSections,
|
|
2686
4119
|
HandoffRuntime,
|
|
2687
4120
|
pruneRuns,
|
|
4121
|
+
ACP_PROTOCOL_VERSION,
|
|
4122
|
+
ACP_METHODS,
|
|
4123
|
+
ACP_ERRORS,
|
|
4124
|
+
AcpRpcError,
|
|
4125
|
+
AcpClient,
|
|
4126
|
+
chunkText,
|
|
4127
|
+
UPDATE,
|
|
4128
|
+
usageOf,
|
|
4129
|
+
isAuthRequired,
|
|
4130
|
+
commandLineOf,
|
|
4131
|
+
atticusArgvOf,
|
|
4132
|
+
acpPathsOf,
|
|
4133
|
+
factsOnly,
|
|
4134
|
+
readRootsOf,
|
|
4135
|
+
needsShell,
|
|
4136
|
+
quoteForCmd,
|
|
4137
|
+
killTree,
|
|
4138
|
+
spawnAcpServer,
|
|
4139
|
+
atticusExec,
|
|
4140
|
+
AcpSignInError,
|
|
4141
|
+
AcpSetupError,
|
|
4142
|
+
MAX_NUDGES,
|
|
4143
|
+
carryOnPrompt,
|
|
4144
|
+
ACP_OFFERED,
|
|
4145
|
+
runAcpSession,
|
|
4146
|
+
copilotAcpProbe,
|
|
4147
|
+
CopilotAcpRuntime,
|
|
4148
|
+
keepAcpRun,
|
|
2688
4149
|
RUNTIME_MODES,
|
|
2689
4150
|
RuntimeConfigError,
|
|
2690
4151
|
AiOffError,
|
|
2691
4152
|
claudeCodeCliRunner,
|
|
4153
|
+
resolveWindowsCommand,
|
|
4154
|
+
quoteForCmdAlways,
|
|
2692
4155
|
collectChild,
|
|
2693
4156
|
posixPath,
|
|
2694
4157
|
HeadlessClaudeCodeRuntime,
|
|
@@ -2699,6 +4162,7 @@ export {
|
|
|
2699
4162
|
registeredModelProviders,
|
|
2700
4163
|
clearModelProviders,
|
|
2701
4164
|
registerToolSource,
|
|
4165
|
+
registerCopilotAcp,
|
|
2702
4166
|
createRuntime,
|
|
2703
4167
|
runtimeForCli,
|
|
2704
4168
|
resumedAttempt,
|