token-goat 2.9.28 → 2.9.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -40
- package/SECURITY.md +28 -10
- package/THIRD_PARTY_NOTICES.md +40 -3
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/token-goat-chunk-3CHUNJ2O.mjs +53 -0
- package/dist/token-goat-chunk-3Z5S4KGM.mjs +918 -0
- package/dist/token-goat-chunk-4FVIAHSI.mjs +211 -0
- package/dist/{token-goat-chunk-66LODAUT.mjs → token-goat-chunk-4JJS54TR.mjs} +4 -3
- package/dist/token-goat-chunk-4LCCNRTG.mjs +313 -0
- package/dist/token-goat-chunk-54K3EUV3.mjs +35 -0
- package/dist/token-goat-chunk-56EIMS3E.mjs +159 -0
- package/dist/token-goat-chunk-5BCIJXMO.mjs +609 -0
- package/dist/token-goat-chunk-5KDMCMF2.mjs +362 -0
- package/dist/token-goat-chunk-5RG5SBL6.mjs +221 -0
- package/dist/{token-goat-chunk-RRNZMM3A.mjs → token-goat-chunk-5Y6ZYL6U.mjs} +21 -2
- package/dist/{token-goat-chunk-NILVK7XM.mjs → token-goat-chunk-64FBPDTK.mjs} +12 -6
- package/dist/{token-goat-chunk-LEG3JNJ6.mjs → token-goat-chunk-664RTTUV.mjs} +31 -15
- package/dist/token-goat-chunk-67TQJ76B.mjs +57 -0
- package/dist/token-goat-chunk-6QMAS7AB.mjs +42 -0
- package/dist/token-goat-chunk-7BXSNBY5.mjs +253 -0
- package/dist/{token-goat-chunk-F7QZRIWM.mjs → token-goat-chunk-7F2UBPD3.mjs} +42 -35
- package/dist/{token-goat-chunk-CC2PYJM7.mjs → token-goat-chunk-7IN2MKDS.mjs} +718 -595
- package/dist/token-goat-chunk-7NADEDKJ.mjs +124 -0
- package/dist/{token-goat-chunk-6SGLIPQB.mjs → token-goat-chunk-A3CK6N5Z.mjs} +621 -72
- package/dist/{token-goat-chunk-HXNAC4ZF.mjs → token-goat-chunk-AFLYLHDH.mjs} +48 -33
- package/dist/token-goat-chunk-AM5V6GSX.mjs +367 -0
- package/dist/{token-goat-chunk-I7CB3X4K.mjs → token-goat-chunk-APGZXSPS.mjs} +44 -148
- package/dist/{token-goat-chunk-BJBRGPBX.mjs → token-goat-chunk-ARDNEC2U.mjs} +11 -7
- package/dist/token-goat-chunk-ATCPY7EQ.mjs +134 -0
- package/dist/token-goat-chunk-B2RZZCSS.mjs +354 -0
- package/dist/{token-goat-chunk-QM76S6YS.mjs → token-goat-chunk-B45VMIIH.mjs} +6 -5
- package/dist/token-goat-chunk-BPGCUMR7.mjs +30 -0
- package/dist/{token-goat-chunk-GGLEE35R.mjs → token-goat-chunk-C63JQV5K.mjs} +49 -10
- package/dist/{token-goat-chunk-RJH64KQH.mjs → token-goat-chunk-COBQGL5S.mjs} +2661 -2440
- package/dist/token-goat-chunk-COTXZHTS.mjs +39 -0
- package/dist/token-goat-chunk-CYCEYAEO.mjs +265 -0
- package/dist/token-goat-chunk-CZ3LUXYZ.mjs +4115 -0
- package/dist/token-goat-chunk-DZX2YDPT.mjs +15 -0
- package/dist/token-goat-chunk-E3OQX2XT.mjs +824 -0
- package/dist/token-goat-chunk-EFHMMIGP.mjs +68 -0
- package/dist/token-goat-chunk-ETBHVDB6.mjs +328 -0
- package/dist/token-goat-chunk-EVMZJ27Z.mjs +147 -0
- package/dist/token-goat-chunk-EXKGNHEG.mjs +2096 -0
- package/dist/{token-goat-chunk-6Z5ULNE5.mjs → token-goat-chunk-FBNQNSJK.mjs} +2490 -4957
- package/dist/{token-goat-chunk-NMTKNYGF.mjs → token-goat-chunk-FNFBKLRD.mjs} +74 -9
- package/dist/{token-goat-chunk-XI2VFAV6.mjs → token-goat-chunk-FZ3NEW5O.mjs} +33 -19
- package/dist/token-goat-chunk-G5PJ7AGM.mjs +36 -0
- package/dist/{token-goat-chunk-VE27HPIF.mjs → token-goat-chunk-GFBSN7BD.mjs} +30 -398
- package/dist/{token-goat-chunk-L45ZQRT7.mjs → token-goat-chunk-GOLEOXZP.mjs} +780 -786
- package/dist/token-goat-chunk-H2K3QP6H.mjs +24 -0
- package/dist/token-goat-chunk-H2PHHGM5.mjs +188 -0
- package/dist/token-goat-chunk-HAVAOB3S.mjs +87 -0
- package/dist/token-goat-chunk-HG45AF2B.mjs +2228 -0
- package/dist/{token-goat-chunk-CBMY2FGI.mjs → token-goat-chunk-HOJLCGDP.mjs} +17113 -15554
- package/dist/{token-goat-chunk-HUDC5GS5.mjs → token-goat-chunk-HYBFA5X3.mjs} +120 -19
- package/dist/token-goat-chunk-IVALLT3S.mjs +48 -0
- package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
- package/dist/{token-goat-chunk-UBUKHVYW.mjs → token-goat-chunk-IVKIZWAB.mjs} +3 -3
- package/dist/{token-goat-chunk-4JMWPWCS.mjs → token-goat-chunk-JB6QN6VF.mjs} +6 -4
- package/dist/{token-goat-chunk-Q4S3UQGO.mjs → token-goat-chunk-JDHE26RP.mjs} +10 -3
- package/dist/{token-goat-chunk-7R2XYKC5.mjs → token-goat-chunk-JDU3EXSU.mjs} +14 -10
- package/dist/token-goat-chunk-JDWCRD6M.mjs +253 -0
- package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-JN4XMLSD.mjs} +7 -4
- package/dist/token-goat-chunk-JTQQGXZH.mjs +733 -0
- package/dist/token-goat-chunk-KKUFXAPD.mjs +25 -0
- package/dist/token-goat-chunk-KQNZDCBD.mjs +47 -0
- package/dist/{token-goat-chunk-4RN64ZND.mjs → token-goat-chunk-KWPNJBDH.mjs} +28 -3
- package/dist/token-goat-chunk-LOBGHXPB.mjs +242 -0
- package/dist/token-goat-chunk-MKPF4CL5.mjs +37 -0
- package/dist/token-goat-chunk-MNWKPRAL.mjs +13 -0
- package/dist/{token-goat-chunk-YWSP6IU7.mjs → token-goat-chunk-N2NMER56.mjs} +25 -15
- package/dist/token-goat-chunk-NYKZLXMK.mjs +218 -0
- package/dist/token-goat-chunk-OJBNYTJP.mjs +3087 -0
- package/dist/token-goat-chunk-P4QMM4WK.mjs +295 -0
- package/dist/token-goat-chunk-PD5VKH46.mjs +184 -0
- package/dist/token-goat-chunk-PEPTHRFF.mjs +129 -0
- package/dist/{token-goat-chunk-VWZKZPSR.mjs → token-goat-chunk-PU7V3FD6.mjs} +8752 -11098
- package/dist/token-goat-chunk-PY533T7F.mjs +46 -0
- package/dist/token-goat-chunk-Q5VTGRH7.mjs +761 -0
- package/dist/{token-goat-chunk-IHKZ3QNA.mjs → token-goat-chunk-QCLDAQTS.mjs} +33 -19
- package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-QSBRTGM2.mjs} +9 -2
- package/dist/{token-goat-chunk-4STDIGYC.mjs → token-goat-chunk-RC35RBUL.mjs} +7 -5
- package/dist/{token-goat-chunk-D5ICEPSB.mjs → token-goat-chunk-RHXKCZX3.mjs} +887 -507
- package/dist/{token-goat-chunk-RSS3TLXU.mjs → token-goat-chunk-RZRTDST7.mjs} +17 -7
- package/dist/{token-goat-chunk-MAMQPC43.mjs → token-goat-chunk-SJHWVNDQ.mjs} +15 -21
- package/dist/token-goat-chunk-SMVRN2HU.mjs +188 -0
- package/dist/{token-goat-chunk-EL7M26WP.mjs → token-goat-chunk-SQSSNQPE.mjs} +2100 -1442
- package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-T4FK7VOV.mjs} +3 -3
- package/dist/token-goat-chunk-U4N3BN2W.mjs +92 -0
- package/dist/token-goat-chunk-UA5V6P3P.mjs +122 -0
- package/dist/{token-goat-chunk-XKY4VKJ4.mjs → token-goat-chunk-UQ5UK2T7.mjs} +15 -4
- package/dist/token-goat-chunk-USILZ3T3.mjs +53 -0
- package/dist/token-goat-chunk-VES7EL7O.mjs +205 -0
- package/dist/{token-goat-chunk-M56BWPBN.mjs → token-goat-chunk-VL3YIFHZ.mjs} +550 -49
- package/dist/{token-goat-chunk-IZXKVT4A.mjs → token-goat-chunk-VPBBTSJR.mjs} +362 -128
- package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-W3QCJS6G.mjs} +10 -2
- package/dist/{token-goat-chunk-FSTWAOW6.mjs → token-goat-chunk-WY3L4DVT.mjs} +11 -5
- package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
- package/dist/token-goat-chunk-XKI4SEMX.mjs +66 -0
- package/dist/token-goat-chunk-XU4P6Z24.mjs +133 -0
- package/dist/{token-goat-chunk-6CCQDAYY.mjs → token-goat-chunk-Y776AENC.mjs} +138 -33
- package/dist/token-goat-chunk-YARZFCZS.mjs +359 -0
- package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
- package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-Z44YO3QZ.mjs} +12 -6
- package/dist/token-goat-chunk-Z57YODA2.mjs +35 -0
- package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-ZCDTCVIS.mjs} +49 -30
- package/dist/token-goat-chunk-ZEJVMUQH.mjs +48 -0
- package/dist/{token-goat-chunk-BGHSSJUA.mjs → token-goat-chunk-ZFOBKUWH.mjs} +1 -1
- package/dist/token-goat-chunk-ZMBL6UVV.mjs +110 -0
- package/dist/{token-goat-chunk-SJEKA7Y2.mjs → token-goat-chunk-ZQSJMO2R.mjs} +381 -201
- package/dist/token-goat-hook-client.cjs +539 -0
- package/dist/token-goat-hook-client.mjs +26 -0
- package/dist/token-goat-hook.mjs +48 -28
- package/dist/token-goat.core.mjs +10 -45
- package/docs/C4_RUNTIME_ARCHITECTURE.md +3 -3
- package/docs/cli.md +82 -47
- package/docs/install.md +58 -23
- package/docs/security.md +7 -7
- package/package.json +17 -7
- package/scripts/verify-native-dist.mjs +386 -0
- package/dist/token-goat-chunk-4JJBFT7V.mjs +0 -20
- package/dist/token-goat-chunk-5NNYIUGV.mjs +0 -143
- package/dist/token-goat-chunk-6DQNNFJ6.mjs +0 -294
- package/dist/token-goat-chunk-7MAFDRVR.mjs +0 -47
- package/dist/token-goat-chunk-BCN2YQC6.mjs +0 -32
- package/dist/token-goat-chunk-D7GHQKQD.mjs +0 -30
- package/dist/token-goat-chunk-GMOUBOX4.mjs +0 -386
- package/dist/token-goat-chunk-IFORDBGC.mjs +0 -192
- package/dist/token-goat-chunk-JXK2HQXS.mjs +0 -318
- package/dist/token-goat-chunk-L7SDVGBT.mjs +0 -430
- package/dist/token-goat-chunk-MMLMALWK.mjs +0 -1381
- package/dist/token-goat-chunk-MVSWBNMD.mjs +0 -1985
|
@@ -1,19 +1,159 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
applyProbeMarker
|
|
5
|
+
} from "./token-goat-chunk-ZEJVMUQH.mjs";
|
|
6
|
+
import {
|
|
7
|
+
MCP_TOOL_PATTERN,
|
|
8
|
+
buildCommandManifest,
|
|
9
|
+
buildReminder,
|
|
10
|
+
checkSymbolBodySize,
|
|
11
|
+
diagnoseSqlFailure,
|
|
12
|
+
flattenCommandNames,
|
|
13
|
+
isIndexedProject,
|
|
14
|
+
readStdinJson
|
|
15
|
+
} from "./token-goat-chunk-GFBSN7BD.mjs";
|
|
16
|
+
import {
|
|
17
|
+
commentSyntaxFor,
|
|
18
|
+
foldDetail,
|
|
19
|
+
mergeFolds,
|
|
20
|
+
planBodyFolds,
|
|
21
|
+
planCommentFolds,
|
|
22
|
+
planProseFolds
|
|
23
|
+
} from "./token-goat-chunk-5RG5SBL6.mjs";
|
|
24
|
+
import {
|
|
25
|
+
HOOK_EVENTS
|
|
26
|
+
} from "./token-goat-chunk-KKUFXAPD.mjs";
|
|
3
27
|
import {
|
|
4
28
|
MAX_CAPTURE_BYTES,
|
|
5
29
|
canRunPowerShell,
|
|
6
|
-
canRunWrappedShell
|
|
30
|
+
canRunWrappedShell
|
|
31
|
+
} from "./token-goat-chunk-7F2UBPD3.mjs";
|
|
32
|
+
import {
|
|
7
33
|
clipToDeliveryCap,
|
|
8
34
|
deliveredOutputBytes
|
|
9
|
-
} from "./token-goat-chunk-
|
|
35
|
+
} from "./token-goat-chunk-54K3EUV3.mjs";
|
|
36
|
+
import {
|
|
37
|
+
DEFAULT_RECONCILE_BUDGET_MS,
|
|
38
|
+
isReconcileClean,
|
|
39
|
+
reconcileProject
|
|
40
|
+
} from "./token-goat-chunk-7BXSNBY5.mjs";
|
|
41
|
+
import {
|
|
42
|
+
readCopilotHooksOwners
|
|
43
|
+
} from "./token-goat-chunk-G5PJ7AGM.mjs";
|
|
44
|
+
import {
|
|
45
|
+
matchesAllowPattern,
|
|
46
|
+
matchesDenyPattern,
|
|
47
|
+
metadataEndpointRefusal,
|
|
48
|
+
recordKnownRootThrottled,
|
|
49
|
+
resolveLineRegions
|
|
50
|
+
} from "./token-goat-chunk-YARZFCZS.mjs";
|
|
51
|
+
import {
|
|
52
|
+
SLICE_ESTIMATE_SCAN_CAP_BYTES,
|
|
53
|
+
applyHintTracking,
|
|
54
|
+
buildDeltaCapsule,
|
|
55
|
+
buildProjectMap,
|
|
56
|
+
classifyBashHint,
|
|
57
|
+
classifyEditHint,
|
|
58
|
+
classifyReadHint,
|
|
59
|
+
compactPathFor,
|
|
60
|
+
countTextLines,
|
|
61
|
+
formatProjectMap,
|
|
62
|
+
formatShrinkSummary,
|
|
63
|
+
harnessNumbersReadContent,
|
|
64
|
+
headingTreeParts,
|
|
65
|
+
hintTarget,
|
|
66
|
+
imageQualifiesForShrink,
|
|
67
|
+
indexMatchesDisk,
|
|
68
|
+
isDiffableSource,
|
|
69
|
+
isSessionArtifactFile,
|
|
70
|
+
isSourceExtension,
|
|
71
|
+
isTruncatedReadDelivery,
|
|
72
|
+
isVirtualIndexedPath,
|
|
73
|
+
logSuppressedDetection,
|
|
74
|
+
markCompactStale,
|
|
75
|
+
meetsSavingsFloor,
|
|
76
|
+
numberedRenderBytes,
|
|
77
|
+
parseReadDelivery,
|
|
78
|
+
probeImageMeta,
|
|
79
|
+
quietContextOutput,
|
|
80
|
+
readEvidenceFileText,
|
|
81
|
+
readRequestedSliceWindow,
|
|
82
|
+
readStartLine,
|
|
83
|
+
readWindowFromDisk,
|
|
84
|
+
recordActualSlice,
|
|
85
|
+
recordEvidence,
|
|
86
|
+
relPathWithinRoot,
|
|
87
|
+
settleSelfScoredHints,
|
|
88
|
+
sharpenRepeatedDeny,
|
|
89
|
+
shrinkImage,
|
|
90
|
+
sliceCommand,
|
|
91
|
+
sliceForPath,
|
|
92
|
+
store,
|
|
93
|
+
uncorrelatedHint,
|
|
94
|
+
visionTokens,
|
|
95
|
+
visionTokensSaved
|
|
96
|
+
} from "./token-goat-chunk-FBNQNSJK.mjs";
|
|
10
97
|
import {
|
|
11
|
-
GH_VIEW_BATCH_HINT_KEY,
|
|
12
98
|
SEARCH_COMMAND_RE,
|
|
99
|
+
classifyToolError
|
|
100
|
+
} from "./token-goat-chunk-PEPTHRFF.mjs";
|
|
101
|
+
import {
|
|
102
|
+
ORIG_HEAD_ELIGIBLE_GIT_RE,
|
|
103
|
+
ORIG_HEAD_REFLOG_MSG_RE,
|
|
104
|
+
PIPELINE_PASSTHROUGH_HEADS,
|
|
105
|
+
bashRecallKey,
|
|
106
|
+
cachedRunCoversCommand,
|
|
107
|
+
cdPrefixCwd,
|
|
108
|
+
commandRunDir,
|
|
109
|
+
deliveredLineNumbers,
|
|
110
|
+
detectUnbalancedShellSyntax,
|
|
111
|
+
enqueueNonHeadMovingRewrites,
|
|
112
|
+
extractCommand,
|
|
113
|
+
gitRepoRoot,
|
|
114
|
+
hasTestRunScopeOrBudget,
|
|
115
|
+
isDirectTestRunnerCommand,
|
|
116
|
+
isFullRecallCommand,
|
|
117
|
+
isHeadMovingGitCommand,
|
|
118
|
+
isRecallCommand,
|
|
119
|
+
isTokenGoatOwnCommand,
|
|
120
|
+
isWholeFileDump,
|
|
121
|
+
pipelineDivergenceNote,
|
|
122
|
+
pipelineShapeFilter,
|
|
123
|
+
pureFileReadPath,
|
|
124
|
+
resolveBashCall,
|
|
125
|
+
resolveCdHintPath,
|
|
126
|
+
stripCdPrefix,
|
|
127
|
+
stripCommandPrefix,
|
|
128
|
+
stripSubshellGroup,
|
|
129
|
+
stripTrailingStderrRedirect,
|
|
130
|
+
unwrapCompressCommand
|
|
131
|
+
} from "./token-goat-chunk-JTQQGXZH.mjs";
|
|
132
|
+
import {
|
|
133
|
+
fenceUntrusted,
|
|
134
|
+
fenceUntrustedSpans,
|
|
135
|
+
fenceWithMatches,
|
|
136
|
+
scanAndRecord
|
|
137
|
+
} from "./token-goat-chunk-67TQJ76B.mjs";
|
|
138
|
+
import {
|
|
139
|
+
appendDirtyPath,
|
|
140
|
+
enqueueDirtyPathSafe
|
|
141
|
+
} from "./token-goat-chunk-H2PHHGM5.mjs";
|
|
142
|
+
import {
|
|
143
|
+
registerHook,
|
|
144
|
+
runHook,
|
|
145
|
+
serializeOutput,
|
|
146
|
+
sessionStateKey
|
|
147
|
+
} from "./token-goat-chunk-P4QMM4WK.mjs";
|
|
148
|
+
import {
|
|
149
|
+
ensureWorkerAlive
|
|
150
|
+
} from "./token-goat-chunk-5KDMCMF2.mjs";
|
|
151
|
+
import {
|
|
152
|
+
GH_VIEW_BATCH_HINT_KEY,
|
|
13
153
|
buildGhViewBatchAdvisory,
|
|
14
154
|
buildRecallHint,
|
|
15
|
-
|
|
16
|
-
|
|
155
|
+
classifyCatPath,
|
|
156
|
+
compressionOptedOut,
|
|
17
157
|
extractCatFile,
|
|
18
158
|
extractCatFilesMulti,
|
|
19
159
|
extractCatJsonPipe,
|
|
@@ -49,7 +189,6 @@ import {
|
|
|
49
189
|
isCompressibleSingleCommand,
|
|
50
190
|
isCurlGetCommand,
|
|
51
191
|
isReadOnlyGhApi,
|
|
52
|
-
isTscCommand,
|
|
53
192
|
leadingLinesHint,
|
|
54
193
|
sedOverlapHint,
|
|
55
194
|
sedRangeHint,
|
|
@@ -60,142 +199,112 @@ import {
|
|
|
60
199
|
surgicalHintFor,
|
|
61
200
|
surgicalHintForConfigDoc,
|
|
62
201
|
taskOutputIsJsonlTranscript
|
|
63
|
-
} from "./token-goat-chunk-
|
|
202
|
+
} from "./token-goat-chunk-APGZXSPS.mjs";
|
|
64
203
|
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
getWebOutput,
|
|
74
|
-
isReconcileClean,
|
|
75
|
-
readStdinJson,
|
|
76
|
-
reconcileProject,
|
|
77
|
-
storeWebOutput
|
|
78
|
-
} from "./token-goat-chunk-VE27HPIF.mjs";
|
|
204
|
+
compressOutput,
|
|
205
|
+
detectFromCommand,
|
|
206
|
+
filterByName,
|
|
207
|
+
isRewriteWorthwhile,
|
|
208
|
+
looksLikePytestOutput,
|
|
209
|
+
pytestFilter,
|
|
210
|
+
resolveMinNetSavingsBytes
|
|
211
|
+
} from "./token-goat-chunk-COBQGL5S.mjs";
|
|
79
212
|
import {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
compactPathFor,
|
|
89
|
-
enqueueDirtyPathSafe,
|
|
90
|
-
ensureWorkerAlive,
|
|
91
|
-
fenceUntrusted,
|
|
92
|
-
fenceUntrustedSpans,
|
|
93
|
-
fenceWithMatches,
|
|
94
|
-
foldDelivery,
|
|
95
|
-
foldDetail,
|
|
96
|
-
foldingEnabled,
|
|
97
|
-
formatProjectMap,
|
|
98
|
-
formatShrinkSummary,
|
|
99
|
-
hintTarget,
|
|
100
|
-
imageQualifiesForShrink,
|
|
101
|
-
indexMatchesDisk,
|
|
102
|
-
indexServedBody,
|
|
103
|
-
isStructuralRewriteAccepted,
|
|
104
|
-
logSuppressedDetection,
|
|
105
|
-
markCompactStale,
|
|
106
|
-
matchesAllowPattern,
|
|
107
|
-
matchesDenyPattern,
|
|
108
|
-
meetsSavingsFloor,
|
|
109
|
-
metadataEndpointRefusal,
|
|
110
|
-
planMarkdownOutline,
|
|
111
|
-
planServedElisions,
|
|
112
|
-
planSourceSkeleton,
|
|
113
|
-
probeImageMeta,
|
|
114
|
-
recordKnownRootThrottled,
|
|
115
|
-
registerHook,
|
|
116
|
-
resolveLineRegions,
|
|
117
|
-
runHook,
|
|
118
|
-
scanAndRecord,
|
|
119
|
-
serializeOutput,
|
|
120
|
-
servedRunNotice,
|
|
121
|
-
sessionStateKey,
|
|
122
|
-
settleSelfScoredHints,
|
|
123
|
-
sharpenRepeatedDeny,
|
|
124
|
-
shrinkImage,
|
|
125
|
-
sliceCommand,
|
|
126
|
-
sliceForPath,
|
|
127
|
-
uncorrelatedHint,
|
|
128
|
-
visionTokens,
|
|
129
|
-
visionTokensSaved
|
|
130
|
-
} from "./token-goat-chunk-6Z5ULNE5.mjs";
|
|
213
|
+
commandPathIsTouchable,
|
|
214
|
+
docNavigation,
|
|
215
|
+
docSectionHint,
|
|
216
|
+
leadWithCommand,
|
|
217
|
+
preToolPathDeclined,
|
|
218
|
+
stripUnsafeSuggestions,
|
|
219
|
+
vscodePathAllowed
|
|
220
|
+
} from "./token-goat-chunk-ATCPY7EQ.mjs";
|
|
131
221
|
import {
|
|
132
222
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
133
223
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
134
|
-
commandPathIsTouchable,
|
|
135
224
|
contextOutput,
|
|
225
|
+
copilotCliUserRoot,
|
|
136
226
|
denyOutput,
|
|
137
227
|
emitRewrite,
|
|
138
228
|
emitRewriteIfChanged,
|
|
229
|
+
emitRewriteWithContext,
|
|
139
230
|
estimateResultCount,
|
|
140
231
|
extractToolResponseField,
|
|
141
232
|
extractToolResultText,
|
|
142
233
|
getCwd,
|
|
143
234
|
getFilePath,
|
|
235
|
+
getFilePaths,
|
|
144
236
|
getToolInput,
|
|
145
237
|
getToolName,
|
|
146
238
|
getTranscriptPath,
|
|
147
239
|
isMcpErrorResponse,
|
|
148
240
|
isTextualContentBlock,
|
|
149
|
-
leadWithCommand,
|
|
150
241
|
makeDedupHintHandlers,
|
|
151
242
|
mcpContentBlocks,
|
|
152
243
|
normalizePayload,
|
|
153
244
|
passOutput,
|
|
154
|
-
|
|
155
|
-
stripUnsafeSuggestions,
|
|
245
|
+
resolveEventPath,
|
|
156
246
|
structuredResultCount,
|
|
157
|
-
vscodePathAllowed,
|
|
158
247
|
withPreservedBlocks
|
|
159
|
-
} from "./token-goat-chunk-
|
|
160
|
-
import {
|
|
161
|
-
compressOutput,
|
|
162
|
-
detectFromCommand,
|
|
163
|
-
filterByName,
|
|
164
|
-
isRewriteWorthwhile,
|
|
165
|
-
looksLikePytestOutput,
|
|
166
|
-
pytestFilter,
|
|
167
|
-
resolveMinNetSavingsBytes
|
|
168
|
-
} from "./token-goat-chunk-RJH64KQH.mjs";
|
|
169
|
-
import {
|
|
170
|
-
projectTranscriptsDir
|
|
171
|
-
} from "./token-goat-chunk-6DQNNFJ6.mjs";
|
|
172
|
-
import {
|
|
173
|
-
readCopilotHooksOwners
|
|
174
|
-
} from "./token-goat-chunk-MMLMALWK.mjs";
|
|
248
|
+
} from "./token-goat-chunk-VPBBTSJR.mjs";
|
|
175
249
|
import {
|
|
176
250
|
BUDGET_ESCALATION_MARKER,
|
|
177
251
|
MANIFEST_PREAMBLE,
|
|
178
252
|
MANIFEST_RECOVERY_PREAMBLE,
|
|
179
253
|
buildManifest,
|
|
254
|
+
getWebOutput,
|
|
180
255
|
manifestPrintedPaths,
|
|
181
256
|
projectNotesFor,
|
|
257
|
+
recordCompactDroppedPaths,
|
|
258
|
+
storeWebOutput,
|
|
182
259
|
summaryBudgetDirective
|
|
183
|
-
} from "./token-goat-chunk-
|
|
260
|
+
} from "./token-goat-chunk-Q5VTGRH7.mjs";
|
|
184
261
|
import {
|
|
262
|
+
COMPACTED_AT_SUFFIX,
|
|
185
263
|
extractCompactFromMarker,
|
|
186
264
|
hasSessionOutput,
|
|
187
265
|
incrementSkillHit,
|
|
188
266
|
installedSkillPath,
|
|
189
267
|
loadSessionState,
|
|
268
|
+
safeSkillName,
|
|
190
269
|
saveSessionState,
|
|
191
270
|
sessionOutputBodyBytes,
|
|
192
271
|
sessionSidecarPath,
|
|
193
|
-
storeOutput
|
|
194
|
-
|
|
272
|
+
storeOutput,
|
|
273
|
+
writeSessionManifest
|
|
274
|
+
} from "./token-goat-chunk-Y776AENC.mjs";
|
|
275
|
+
import {
|
|
276
|
+
accumulateResidentLines,
|
|
277
|
+
lineMayCarryResidentSignal,
|
|
278
|
+
readTranscriptTail,
|
|
279
|
+
repeatedSkillBodyHint,
|
|
280
|
+
summarizeResidentContext,
|
|
281
|
+
taskListPruneHint
|
|
282
|
+
} from "./token-goat-chunk-SJHWVNDQ.mjs";
|
|
283
|
+
import {
|
|
284
|
+
BASH_OUTPUT_SUBDIR,
|
|
285
|
+
bashOutputIdSync,
|
|
286
|
+
commandHash,
|
|
287
|
+
getBashOutput,
|
|
288
|
+
getMonitoringRecallHint,
|
|
289
|
+
indexRecallEntry,
|
|
290
|
+
isBashEntryStale,
|
|
291
|
+
isBuildCommand,
|
|
292
|
+
isScopedGitStatusOrDiffStatCommand,
|
|
293
|
+
isTestRunnerCommand,
|
|
294
|
+
storeBashOutput,
|
|
295
|
+
storeBashOutputSync,
|
|
296
|
+
summarizeOutputDelta
|
|
297
|
+
} from "./token-goat-chunk-AFLYLHDH.mjs";
|
|
298
|
+
import {
|
|
299
|
+
claudeConfigDir,
|
|
300
|
+
projectTranscriptsDir
|
|
301
|
+
} from "./token-goat-chunk-BPGCUMR7.mjs";
|
|
195
302
|
import {
|
|
196
303
|
GENERIC_SERVED_OUTPUT_KEY,
|
|
197
304
|
clearCurlDownload,
|
|
305
|
+
exportSessionState,
|
|
198
306
|
getBashOutputId,
|
|
307
|
+
getCompactedAt,
|
|
199
308
|
getCurlDownloadPath,
|
|
200
309
|
getFileLineRanges,
|
|
201
310
|
getFileServedOutputs,
|
|
@@ -210,6 +319,7 @@ import {
|
|
|
210
319
|
markHintShown,
|
|
211
320
|
recordBashOutput,
|
|
212
321
|
recordBashRerun,
|
|
322
|
+
recordBashStartCwd,
|
|
213
323
|
recordCliRead,
|
|
214
324
|
recordCurlDownload,
|
|
215
325
|
recordFileEdit,
|
|
@@ -227,108 +337,114 @@ import {
|
|
|
227
337
|
resetFileLineRanges,
|
|
228
338
|
setLastTabContext,
|
|
229
339
|
setTranscriptPath,
|
|
340
|
+
takeBashStartCwd,
|
|
230
341
|
takePendingLargeFileHint,
|
|
231
342
|
wasCliReadThisSession,
|
|
232
343
|
wasFileReadThisSession,
|
|
233
344
|
wasHintShown
|
|
234
|
-
} from "./token-goat-chunk-
|
|
235
|
-
import {
|
|
236
|
-
accumulateResidentLines,
|
|
237
|
-
claudeConfigDir,
|
|
238
|
-
lineMayCarryResidentSignal,
|
|
239
|
-
readTranscriptTail,
|
|
240
|
-
repeatedSkillBodyHint,
|
|
241
|
-
summarizeResidentContext,
|
|
242
|
-
taskListPruneHint
|
|
243
|
-
} from "./token-goat-chunk-MAMQPC43.mjs";
|
|
244
|
-
import {
|
|
245
|
-
BASH_OUTPUT_SUBDIR,
|
|
246
|
-
bashOutputIdSync,
|
|
247
|
-
commandHash,
|
|
248
|
-
getBashOutput,
|
|
249
|
-
getMonitoringRecallHint,
|
|
250
|
-
indexRecallEntry,
|
|
251
|
-
isBashEntryStale,
|
|
252
|
-
isBuildCommand,
|
|
253
|
-
isScopedGitStatusOrDiffStatCommand,
|
|
254
|
-
isTestRunnerCommand,
|
|
255
|
-
storeBashOutput,
|
|
256
|
-
summarizeOutputDelta
|
|
257
|
-
} from "./token-goat-chunk-HXNAC4ZF.mjs";
|
|
258
|
-
import {
|
|
259
|
-
storeBlob
|
|
260
|
-
} from "./token-goat-chunk-BJBRGPBX.mjs";
|
|
345
|
+
} from "./token-goat-chunk-C63JQV5K.mjs";
|
|
261
346
|
import {
|
|
262
347
|
estimateTokens
|
|
263
|
-
} from "./token-goat-chunk-
|
|
264
|
-
import {
|
|
265
|
-
dedupeConsecutive,
|
|
266
|
-
hasBareBackground,
|
|
267
|
-
hasBareBackgroundOrNewline,
|
|
268
|
-
hasUnquotedOperator,
|
|
269
|
-
pathStem,
|
|
270
|
-
shlexSplit,
|
|
271
|
-
splitOwnTrailingNotices
|
|
272
|
-
} from "./token-goat-chunk-4RN64ZND.mjs";
|
|
273
|
-
import {
|
|
274
|
-
recordStat,
|
|
275
|
-
redactSecrets,
|
|
276
|
-
savedTokensFromBytes,
|
|
277
|
-
stripAnsiEscapes
|
|
278
|
-
} from "./token-goat-chunk-MVSWBNMD.mjs";
|
|
348
|
+
} from "./token-goat-chunk-JB6QN6VF.mjs";
|
|
279
349
|
import {
|
|
280
350
|
extractMarkdownHeadings,
|
|
281
|
-
|
|
282
|
-
|
|
351
|
+
findContainingSection,
|
|
352
|
+
formatHeadingTreeParts,
|
|
353
|
+
isImagePath,
|
|
354
|
+
isTreeSitterAvailable,
|
|
355
|
+
parseSourceSymbolsTreeSitterOnly,
|
|
356
|
+
parserFingerprintForLanguage
|
|
357
|
+
} from "./token-goat-chunk-RHXKCZX3.mjs";
|
|
283
358
|
import {
|
|
284
|
-
|
|
359
|
+
getFileEntry,
|
|
285
360
|
querySymbols
|
|
286
|
-
} from "./token-goat-chunk-
|
|
361
|
+
} from "./token-goat-chunk-HYBFA5X3.mjs";
|
|
362
|
+
import {
|
|
363
|
+
storeBlob
|
|
364
|
+
} from "./token-goat-chunk-ARDNEC2U.mjs";
|
|
365
|
+
import {
|
|
366
|
+
HOOK_PROBE_ENV,
|
|
367
|
+
claimFirstSavingsReceipt,
|
|
368
|
+
recordStat,
|
|
369
|
+
savedTokensFromBytes
|
|
370
|
+
} from "./token-goat-chunk-3Z5S4KGM.mjs";
|
|
287
371
|
import {
|
|
288
372
|
buildLineIndex,
|
|
289
373
|
detectLanguage,
|
|
290
374
|
getDb,
|
|
291
375
|
languageHasFlag,
|
|
292
376
|
offsetToLine
|
|
293
|
-
} from "./token-goat-chunk-
|
|
377
|
+
} from "./token-goat-chunk-VL3YIFHZ.mjs";
|
|
378
|
+
import {
|
|
379
|
+
hasPreciseSecret,
|
|
380
|
+
redactSecrets,
|
|
381
|
+
redactSerializedJson
|
|
382
|
+
} from "./token-goat-chunk-4FVIAHSI.mjs";
|
|
294
383
|
import {
|
|
295
|
-
detectHarness,
|
|
296
|
-
envBool,
|
|
297
|
-
envInt,
|
|
298
384
|
findProject,
|
|
299
|
-
|
|
385
|
+
fingerprintFile,
|
|
300
386
|
isUnderSystemTemp,
|
|
301
387
|
loadConfig,
|
|
388
|
+
makeProjectAt,
|
|
302
389
|
shortFingerprint
|
|
303
|
-
} from "./token-goat-chunk-
|
|
390
|
+
} from "./token-goat-chunk-7IN2MKDS.mjs";
|
|
391
|
+
import {
|
|
392
|
+
detectHarness,
|
|
393
|
+
getHarnessName,
|
|
394
|
+
relaySeededSessionId,
|
|
395
|
+
setRelaySeededSessionId
|
|
396
|
+
} from "./token-goat-chunk-U4N3BN2W.mjs";
|
|
397
|
+
import {
|
|
398
|
+
envBool,
|
|
399
|
+
envInt
|
|
400
|
+
} from "./token-goat-chunk-IVE6U7KV.mjs";
|
|
304
401
|
import {
|
|
305
|
-
ENV_KEYS,
|
|
306
402
|
IDENTICAL_READ_MIN_BODY_BYTES,
|
|
307
403
|
PER_FILE_COUNTERFACTUAL_CEILING,
|
|
308
|
-
VERSION,
|
|
309
404
|
atomicWriteText,
|
|
310
405
|
containsLineRun,
|
|
311
406
|
countNoun,
|
|
312
|
-
dataDir,
|
|
313
407
|
ensureDirSync,
|
|
314
408
|
extractErrorMessage,
|
|
315
|
-
foldPath,
|
|
316
|
-
globalDbPath,
|
|
317
409
|
hasCreatedConfig,
|
|
318
|
-
isInsideRoot,
|
|
319
410
|
runGit,
|
|
411
|
+
statSize,
|
|
320
412
|
toKB
|
|
321
|
-
} from "./token-goat-chunk-
|
|
413
|
+
} from "./token-goat-chunk-GOLEOXZP.mjs";
|
|
414
|
+
import {
|
|
415
|
+
foldPath,
|
|
416
|
+
isInsideRoot
|
|
417
|
+
} from "./token-goat-chunk-AM5V6GSX.mjs";
|
|
322
418
|
import {
|
|
323
419
|
UNTRUSTED_TOOL_TAG,
|
|
420
|
+
dirAtIndexKey,
|
|
324
421
|
displaySafePath,
|
|
325
422
|
displaySafeText,
|
|
423
|
+
fenceNumberedFileContent,
|
|
326
424
|
fenceUntrustedFileContent,
|
|
425
|
+
hostPathOfIndexKey,
|
|
426
|
+
hostPathOfTypedPath,
|
|
427
|
+
isFileAtIndexKey,
|
|
327
428
|
neutralizeSpokenMarkers,
|
|
328
429
|
normalizePath,
|
|
329
430
|
resolveIndexPath,
|
|
330
431
|
toDisplayPath
|
|
331
|
-
} from "./token-goat-chunk-
|
|
432
|
+
} from "./token-goat-chunk-FNFBKLRD.mjs";
|
|
433
|
+
import {
|
|
434
|
+
ENV_KEYS,
|
|
435
|
+
VERSION,
|
|
436
|
+
dataDir,
|
|
437
|
+
globalDbPath
|
|
438
|
+
} from "./token-goat-chunk-VES7EL7O.mjs";
|
|
439
|
+
import {
|
|
440
|
+
dedupeConsecutive,
|
|
441
|
+
hasBareBackground,
|
|
442
|
+
pathStem,
|
|
443
|
+
splitOwnTrailingNotices
|
|
444
|
+
} from "./token-goat-chunk-KWPNJBDH.mjs";
|
|
445
|
+
import {
|
|
446
|
+
stripAnsiEscapes
|
|
447
|
+
} from "./token-goat-chunk-HAVAOB3S.mjs";
|
|
332
448
|
import {
|
|
333
449
|
init_define_import_meta_env
|
|
334
450
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -340,535 +456,37 @@ init_define_import_meta_env();
|
|
|
340
456
|
init_define_import_meta_env();
|
|
341
457
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
342
458
|
import { dirname } from "node:path";
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
var
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
function isDirectTestRunnerCommand(cmd) {
|
|
365
|
-
return /^(?:pytest|(?:npx\s+)?(?:jest|vitest)|go\s+test|cargo\s+test)\b/i.test(cmd);
|
|
366
|
-
}
|
|
367
|
-
function extractCdPrefixDirs(rawCmd) {
|
|
368
|
-
const prefixMatch = rawCmd.match(CD_PREFIX_RE);
|
|
369
|
-
if (prefixMatch === null) return [];
|
|
370
|
-
const dirs = [];
|
|
371
|
-
const segmentPattern = /cd\s+(?:"([^"]*)"|'([^']*)'|(\S+))[ \t]*(?:&&|;|\r?\n)/g;
|
|
372
|
-
let match;
|
|
373
|
-
while ((match = segmentPattern.exec(prefixMatch[0])) !== null) {
|
|
374
|
-
const dir = match[1] ?? match[2] ?? match[3];
|
|
375
|
-
if (dir !== void 0) dirs.push(dir);
|
|
376
|
-
}
|
|
377
|
-
return dirs;
|
|
378
|
-
}
|
|
379
|
-
function resolveCdHintPath(rawCmd, filePath, cwd) {
|
|
380
|
-
if (extractCdPrefixDirs(rawCmd).length === 0) return filePath;
|
|
381
|
-
return resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd));
|
|
382
|
-
}
|
|
383
|
-
function cdPrefixCwd(rawCmd, cwd) {
|
|
384
|
-
let dir = cwd;
|
|
385
|
-
for (const target of extractCdPrefixDirs(rawCmd)) dir = resolveIndexPath(target, dir);
|
|
386
|
-
return dir;
|
|
387
|
-
}
|
|
388
|
-
function stripOutputPipeline(cmd) {
|
|
389
|
-
let inSingle = false;
|
|
390
|
-
let inDouble = false;
|
|
391
|
-
let cut = cmd.length;
|
|
392
|
-
let backslashes = 0;
|
|
393
|
-
for (let i = 0; i < cmd.length; i++) {
|
|
394
|
-
const ch = cmd[i];
|
|
395
|
-
if (ch === "\\") {
|
|
396
|
-
backslashes++;
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
const escaped = backslashes % 2 === 1;
|
|
400
|
-
backslashes = 0;
|
|
401
|
-
if (ch === "'" && !inDouble) {
|
|
402
|
-
if (!escaped) inSingle = !inSingle;
|
|
403
|
-
} else if (ch === '"' && !inSingle) {
|
|
404
|
-
if (!escaped) inDouble = !inDouble;
|
|
405
|
-
} else if (ch === "|" && !inSingle && !inDouble) {
|
|
406
|
-
if (cmd[i + 1] === "|") {
|
|
407
|
-
i++;
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
cut = i;
|
|
411
|
-
break;
|
|
459
|
+
var SERIAL_GAP_MS = 1e3;
|
|
460
|
+
var SERIAL_READS_BEFORE_HINT = 3;
|
|
461
|
+
var ZERO_HIT_SEARCHES_BEFORE_BRAKE = 3;
|
|
462
|
+
var MAX_BATCH_HINTS_PER_SESSION = 2;
|
|
463
|
+
var MAX_SEARCH_BRAKES_PER_SESSION = 2;
|
|
464
|
+
var STREAK_SUFFIX = ".call-streak";
|
|
465
|
+
var TG_SEARCH_RE = /^(?:token-goat|tg)\s+(?:answer|semantic|symbol|refs|locate)\b/;
|
|
466
|
+
var TG_MEANING_SEARCH_RE = /^(?:token-goat|tg)\s+(?:answer|semantic)\b/;
|
|
467
|
+
var TG_READ_RE = /^(?:token-goat|tg)\s+(?:read|section|outline|skeleton|brief)\b/;
|
|
468
|
+
var WRITE_REDIRECT_RE = /\d?>>?(?!\s*(?:&\d|\/dev\/null\b))/;
|
|
469
|
+
function freshState() {
|
|
470
|
+
return { promptId: "", lastReadPostAt: 0, inFlight: false, serialRun: 0, batchShownInRun: false, batchShown: 0, batchVerdict: "none", zeroHits: 0, brakeShownInRun: false, brakeShown: 0, brakeVerdict: false };
|
|
471
|
+
}
|
|
472
|
+
function readState(target) {
|
|
473
|
+
const state = freshState();
|
|
474
|
+
try {
|
|
475
|
+
const parsed = JSON.parse(readFileSync(target, "utf8"));
|
|
476
|
+
if (parsed === null || typeof parsed !== "object") return state;
|
|
477
|
+
const p = parsed;
|
|
478
|
+
for (const key of Object.keys(state)) {
|
|
479
|
+
if (typeof p[key] === typeof state[key]) state[key] = p[key];
|
|
412
480
|
}
|
|
481
|
+
if (!["none", "next_turn", "second_call"].includes(state.batchVerdict)) state.batchVerdict = "none";
|
|
482
|
+
} catch {
|
|
413
483
|
}
|
|
414
|
-
|
|
415
|
-
let prev;
|
|
416
|
-
do {
|
|
417
|
-
prev = base;
|
|
418
|
-
const masked = base.replace(/"((?:[^"\\]|\\.)*)"/g, (_m, inner) => '"' + " ".repeat(inner.length) + '"').replace(/'([^']*)'/g, (_m, inner) => "'" + " ".repeat(inner.length) + "'");
|
|
419
|
-
const newMasked = masked.replace(/\s*(?:[0-9]*>&[0-9]+|[0-9&]*>>?\s*(?:"[^"]*"|'[^']*'|[^\s|]+))\s*$/, "");
|
|
420
|
-
if (newMasked.length < masked.length) {
|
|
421
|
-
base = base.slice(0, newMasked.length);
|
|
422
|
-
}
|
|
423
|
-
} while (base !== prev);
|
|
424
|
-
return base.trim();
|
|
425
|
-
}
|
|
426
|
-
function pipelineDivergenceNote(cmd, entryCommand) {
|
|
427
|
-
if (entryCommand === cmd) return "";
|
|
428
|
-
const preview = entryCommand.length > 60 ? entryCommand.slice(0, 57) + "..." : entryCommand;
|
|
429
|
-
return " (cached from a differently-piped run, `" + preview + "` \u2014 verify it covers what you need before trusting it)";
|
|
430
|
-
}
|
|
431
|
-
function extractCommand(event) {
|
|
432
|
-
const cmd = event.toolInput["command"];
|
|
433
|
-
return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
|
|
434
|
-
}
|
|
435
|
-
var HEAD_MOVING_GIT_RE = /^\s*git\s+(?:checkout|switch|pull|merge|rebase|reset|cherry-pick)\b/i;
|
|
436
|
-
var PATH_SCOPED_CHECKOUT_RE = /^\s*git\s+checkout\s+(?:\S.*?\s)?--(?:\s|$)/i;
|
|
437
|
-
function isHeadMovingGitCommand(cmd) {
|
|
438
|
-
if (!HEAD_MOVING_GIT_RE.test(cmd)) return false;
|
|
439
|
-
if (PATH_SCOPED_CHECKOUT_RE.test(cmd)) return false;
|
|
440
|
-
return true;
|
|
484
|
+
return state;
|
|
441
485
|
}
|
|
442
|
-
|
|
443
|
-
var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
|
|
444
|
-
var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
|
|
445
|
-
var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
|
|
446
|
-
var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
|
|
447
|
-
var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
|
|
448
|
-
var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
|
|
449
|
-
function isFormatterWriteSegment(segment) {
|
|
450
|
-
return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
|
|
451
|
-
}
|
|
452
|
-
function safeShlexSplit(segment) {
|
|
486
|
+
function writeState(target, state) {
|
|
453
487
|
try {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
function segmentCommandIs(segment, name2) {
|
|
460
|
-
return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
|
|
461
|
-
}
|
|
462
|
-
function extractSedInPlaceFiles(segment) {
|
|
463
|
-
if (!segmentCommandIs(segment, "sed")) return [];
|
|
464
|
-
const tokens = safeShlexSplit(segment);
|
|
465
|
-
if (tokens === null) return [];
|
|
466
|
-
const rest = tokens.slice(1);
|
|
467
|
-
const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
|
|
468
|
-
if (inPlaceIdx === -1) return [];
|
|
469
|
-
const files = [];
|
|
470
|
-
let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
|
|
471
|
-
for (let i = 0; i < rest.length; i++) {
|
|
472
|
-
const t = rest[i];
|
|
473
|
-
if (i === inPlaceIdx) {
|
|
474
|
-
if (t === "-i" && rest[i + 1] === "") i++;
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
|
|
478
|
-
i++;
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
if (t.length > 1 && t.startsWith("-")) continue;
|
|
482
|
-
if (!scriptTaken) {
|
|
483
|
-
scriptTaken = true;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
files.push(t);
|
|
487
|
-
}
|
|
488
|
-
return files;
|
|
489
|
-
}
|
|
490
|
-
function extractTeeFiles(segment) {
|
|
491
|
-
if (!segmentCommandIs(segment, "tee")) return [];
|
|
492
|
-
const tokens = safeShlexSplit(segment);
|
|
493
|
-
if (tokens === null) return [];
|
|
494
|
-
return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
|
|
495
|
-
}
|
|
496
|
-
function extractRedirectTargets(segment) {
|
|
497
|
-
const targets = [];
|
|
498
|
-
const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
|
|
499
|
-
let m;
|
|
500
|
-
while ((m = re.exec(segment)) !== null) {
|
|
501
|
-
const raw = m[1];
|
|
502
|
-
targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
|
|
503
|
-
}
|
|
504
|
-
return targets;
|
|
505
|
-
}
|
|
506
|
-
function extractGitRestorePathspecs(cmd) {
|
|
507
|
-
const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
|
|
508
|
-
if (m === null) return [];
|
|
509
|
-
const tokens = safeShlexSplit(m[1] ?? "");
|
|
510
|
-
if (tokens === null) return [];
|
|
511
|
-
const dashDash = tokens.indexOf("--");
|
|
512
|
-
if (dashDash !== -1) return tokens.slice(dashDash + 1);
|
|
513
|
-
const specs = [];
|
|
514
|
-
let skipNext = false;
|
|
515
|
-
for (const t of tokens) {
|
|
516
|
-
if (skipNext) {
|
|
517
|
-
skipNext = false;
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
if (t === "-s" || t === "--source") {
|
|
521
|
-
skipNext = true;
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
if (t.startsWith("-")) continue;
|
|
525
|
-
specs.push(t);
|
|
526
|
-
}
|
|
527
|
-
return specs;
|
|
528
|
-
}
|
|
529
|
-
function gitRepoRoot(gitDir) {
|
|
530
|
-
const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
531
|
-
return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
|
|
532
|
-
}
|
|
533
|
-
function expandGitPathspecs(specs, gitDir) {
|
|
534
|
-
if (specs.length === 0) return [];
|
|
535
|
-
const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
|
|
536
|
-
if (listed.exitCode !== 0) return [];
|
|
537
|
-
const repoRoot = gitRepoRoot(gitDir);
|
|
538
|
-
return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
|
|
539
|
-
}
|
|
540
|
-
function workingTreeStatusPaths(gitDir) {
|
|
541
|
-
const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
542
|
-
if (status.exitCode !== 0) return [];
|
|
543
|
-
const repoRoot = gitRepoRoot(gitDir);
|
|
544
|
-
const out = [];
|
|
545
|
-
for (const line of status.stdout.split("\n")) {
|
|
546
|
-
if (line.length < 4) continue;
|
|
547
|
-
let rel = line.slice(3).trim();
|
|
548
|
-
if (rel.length === 0) continue;
|
|
549
|
-
const arrow = rel.lastIndexOf(" -> ");
|
|
550
|
-
if (arrow !== -1) rel = rel.slice(arrow + 4);
|
|
551
|
-
if (rel.startsWith('"')) {
|
|
552
|
-
try {
|
|
553
|
-
rel = JSON.parse(rel);
|
|
554
|
-
} catch {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
out.push(resolveIndexPath(rel, repoRoot));
|
|
559
|
-
}
|
|
560
|
-
return out;
|
|
561
|
-
}
|
|
562
|
-
function enqueueRewrittenPath(absPath) {
|
|
563
|
-
if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
|
|
564
|
-
try {
|
|
565
|
-
if (!statSync(absPath).isFile()) return;
|
|
566
|
-
} catch {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
|
|
570
|
-
}
|
|
571
|
-
function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
|
|
572
|
-
const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
|
|
573
|
-
const paths = [];
|
|
574
|
-
let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
|
|
575
|
-
if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
|
|
576
|
-
paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
|
|
577
|
-
}
|
|
578
|
-
for (const segment of splitShellSegments(cmd)) {
|
|
579
|
-
if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
|
|
580
|
-
if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
|
|
581
|
-
for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
|
|
582
|
-
for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
|
|
583
|
-
for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
|
|
584
|
-
}
|
|
585
|
-
if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
|
|
586
|
-
for (const p of paths) enqueueRewrittenPath(p);
|
|
587
|
-
}
|
|
588
|
-
var PIPELINE_PASSTHROUGH_HEADS = /* @__PURE__ */ new Set(["head", "tail", "cat", "tee", "less", "more"]);
|
|
589
|
-
var CI_FILTER_NAMES = /^(?:generic-ci|jest|vitest|pytest|go_test|cargo_test|cargo|go|make|cmake|gradle|maven|dotnet|turbo|nx|lerna|webpack|eslint|ruff|clippy|flake8|mypy|prettier|tsc)$/;
|
|
590
|
-
function isCiBuildTestSegment(cleaned, cwd) {
|
|
591
|
-
if (isTestRunnerCommand(cleaned) || isBuildCommand(cleaned) || isTscCommand(cleaned)) return true;
|
|
592
|
-
if (/^\s*(npm|pnpm|yarn|bun)\s+(run\s+)?(test|build|lint|typecheck|check|ci|guards)\b/i.test(cleaned)) return true;
|
|
593
|
-
if (/^\s*(cargo|go|dotnet|make|gradle|mvn|pytest|vitest|jest|eslint|ruff)\b/i.test(cleaned)) return true;
|
|
594
|
-
const detected = detectFromCommand(cleaned, cwd ?? void 0);
|
|
595
|
-
if (detected !== null && CI_FILTER_NAMES.test(detected.filter.name)) return true;
|
|
596
|
-
return false;
|
|
597
|
-
}
|
|
598
|
-
function pipelineShapeFilter(cmd, cwd) {
|
|
599
|
-
if (hasUnquotedOperator(cmd, ["&&", "||", ";"])) {
|
|
600
|
-
const forSplit2 = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
601
|
-
if (hasBareBackgroundOrNewline(forSplit2)) return null;
|
|
602
|
-
const segments2 = splitShellSegments(forSplit2);
|
|
603
|
-
if (segments2.length >= 2) {
|
|
604
|
-
let recognizedCiCount = 0;
|
|
605
|
-
for (const segment of segments2) {
|
|
606
|
-
const cleaned = stripOutputPipeline(segment.trim());
|
|
607
|
-
if (cleaned.length === 0) continue;
|
|
608
|
-
if (isCiBuildTestSegment(cleaned, cwd)) {
|
|
609
|
-
recognizedCiCount++;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (recognizedCiCount > 0) {
|
|
613
|
-
const ci = filterByName("generic-ci");
|
|
614
|
-
return ci === null ? null : { filter: ci, argv: [] };
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return null;
|
|
618
|
-
}
|
|
619
|
-
const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
620
|
-
if (hasBareBackgroundOrNewline(forSplit)) return null;
|
|
621
|
-
const segments = splitShellSegments(forSplit);
|
|
622
|
-
if (segments.length < 2) return null;
|
|
623
|
-
for (const segment of segments.slice(1)) {
|
|
624
|
-
const head = safeShlexSplit(segment)?.[0];
|
|
625
|
-
if (head === void 0) return null;
|
|
626
|
-
if (!PIPELINE_PASSTHROUGH_HEADS.has(head.replace(/^.*[/\\]/, ""))) return null;
|
|
627
|
-
}
|
|
628
|
-
const detected = detectFromCommand(stripOutputPipeline(cmd), cwd ?? void 0);
|
|
629
|
-
return detected === null ? null : { filter: detected.filter, argv: detected.argv };
|
|
630
|
-
}
|
|
631
|
-
function isFullRecallCommand(cmd) {
|
|
632
|
-
const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
633
|
-
if (hasBareBackgroundOrNewline(forSplit)) return false;
|
|
634
|
-
const first2 = splitShellSegments(forSplit)[0];
|
|
635
|
-
if (first2 === void 0 || !segmentCommandIs(first2, "token-goat")) return false;
|
|
636
|
-
const tokens = safeShlexSplit(stripOutputPipeline(first2));
|
|
637
|
-
if (tokens === null) return false;
|
|
638
|
-
return Object.values(RECALL_COMMAND).includes(tokens[1] ?? "") && tokens.includes("--full");
|
|
639
|
-
}
|
|
640
|
-
function pureFileReadPath(cmd) {
|
|
641
|
-
const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
|
|
642
|
-
if (single !== void 0) return single;
|
|
643
|
-
return singleFileCompoundReadPath(cmd);
|
|
644
|
-
}
|
|
645
|
-
function singleFileCompoundReadPath(cmd) {
|
|
646
|
-
const reads = extractLineRangeReadsCompound(cmd);
|
|
647
|
-
if (reads === null || reads.length !== 1) return null;
|
|
648
|
-
return reads[0]?.filePath ?? null;
|
|
649
|
-
}
|
|
650
|
-
function deliveredLineNumbers(cmd, lineCount) {
|
|
651
|
-
const ranged = extractLineRangeRead(cmd);
|
|
652
|
-
if (ranged !== null) {
|
|
653
|
-
const nums = [];
|
|
654
|
-
for (const [lo, hi] of ranged.ranges) for (let n = lo; n <= hi; n++) nums.push(n);
|
|
655
|
-
if (nums.length === 0) return null;
|
|
656
|
-
while (nums.length < lineCount) nums.push((nums[nums.length - 1] ?? 0) + 1);
|
|
657
|
-
return nums;
|
|
658
|
-
}
|
|
659
|
-
if (extractCatFile(cmd) !== null || extractHeadFile(cmd) !== null) return Array.from({ length: lineCount }, (_, i) => i + 1);
|
|
660
|
-
if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
|
|
661
|
-
return null;
|
|
662
|
-
}
|
|
663
|
-
var WHOLE_FILE_DUMP_RE = /^(?:(?:cat|type|Get-Content|gc)|(?:head|tail)[ \t]+-n[ \t]+\d+)[ \t]+(?:"([A-Za-z0-9._\-/\\:+@ ]+)"|'([A-Za-z0-9._\-/\\:+@ ]+)'|([A-Za-z0-9._\-/\\:+@]+))(?:[ \t]+2>(?:&1|\/dev\/null))?[ \t]*$/i;
|
|
664
|
-
function isWholeFileDump(cmd, extractedPath) {
|
|
665
|
-
const m = WHOLE_FILE_DUMP_RE.exec(cmd);
|
|
666
|
-
if (m === null) return false;
|
|
667
|
-
const shaped = m[1] ?? m[2] ?? m[3];
|
|
668
|
-
return shaped !== void 0 && shaped === extractedPath;
|
|
669
|
-
}
|
|
670
|
-
function unwrapCompressCommand(executed) {
|
|
671
|
-
const t = executed.trim();
|
|
672
|
-
if (!/^(?:token-goat|tg)\s+compress\b/.test(t)) return null;
|
|
673
|
-
let argv;
|
|
674
|
-
try {
|
|
675
|
-
argv = shlexSplit(t);
|
|
676
|
-
} catch {
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
|
-
for (let i = 0; i + 1 < argv.length; i++) {
|
|
680
|
-
const tok = argv[i];
|
|
681
|
-
if (tok === "-c" || tok === "--cmd") return argv[i + 1] ?? null;
|
|
682
|
-
if (tok === "--cmd-b64") {
|
|
683
|
-
try {
|
|
684
|
-
return Buffer.from(argv[i + 1] ?? "", "base64").toString("utf8");
|
|
685
|
-
} catch {
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
function detectUnbalancedShellSyntax(cmd) {
|
|
693
|
-
let inSingle = false;
|
|
694
|
-
let inDouble = false;
|
|
695
|
-
let i = 0;
|
|
696
|
-
while (i < cmd.length) {
|
|
697
|
-
const ch = cmd[i];
|
|
698
|
-
if (inSingle) {
|
|
699
|
-
if (ch === "'") {
|
|
700
|
-
inSingle = false;
|
|
701
|
-
}
|
|
702
|
-
i++;
|
|
703
|
-
continue;
|
|
704
|
-
}
|
|
705
|
-
if (inDouble) {
|
|
706
|
-
if (ch === "\\" && i + 1 < cmd.length) {
|
|
707
|
-
i += 2;
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
if (ch === '"') {
|
|
711
|
-
inDouble = false;
|
|
712
|
-
}
|
|
713
|
-
i++;
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
if (ch === "\\" && i + 1 < cmd.length) {
|
|
717
|
-
i += 2;
|
|
718
|
-
continue;
|
|
719
|
-
}
|
|
720
|
-
if (ch === "$" && cmd[i + 1] === "(" && cmd[i + 2] === "(") {
|
|
721
|
-
let depth = 2;
|
|
722
|
-
let j = i + 3;
|
|
723
|
-
while (j < cmd.length && depth > 0) {
|
|
724
|
-
if (cmd[j] === "(") depth++;
|
|
725
|
-
else if (cmd[j] === ")") depth--;
|
|
726
|
-
j++;
|
|
727
|
-
}
|
|
728
|
-
i = j;
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
if (ch === "$" && cmd[i + 1] === "'") {
|
|
732
|
-
let j = i + 2;
|
|
733
|
-
let closed = false;
|
|
734
|
-
while (j < cmd.length) {
|
|
735
|
-
if (cmd[j] === "\\") {
|
|
736
|
-
j += 2;
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
if (cmd[j] === "'") {
|
|
740
|
-
j++;
|
|
741
|
-
closed = true;
|
|
742
|
-
break;
|
|
743
|
-
}
|
|
744
|
-
j++;
|
|
745
|
-
}
|
|
746
|
-
if (!closed) {
|
|
747
|
-
inSingle = true;
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
i = j;
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
if (ch === "#" && (i === 0 || /\s/.test(cmd[i - 1] ?? ""))) {
|
|
754
|
-
const nl2 = cmd.indexOf("\n", i);
|
|
755
|
-
i = nl2 === -1 ? cmd.length : nl2 + 1;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (ch === "'") {
|
|
759
|
-
inSingle = true;
|
|
760
|
-
i++;
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
if (ch === '"') {
|
|
764
|
-
inDouble = true;
|
|
765
|
-
i++;
|
|
766
|
-
continue;
|
|
767
|
-
}
|
|
768
|
-
if (ch === "<" && i + 1 < cmd.length && cmd[i + 1] === "<") {
|
|
769
|
-
if (i + 2 < cmd.length && cmd[i + 2] === "<") {
|
|
770
|
-
i += 3;
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
773
|
-
let j = i + 2;
|
|
774
|
-
let hasIndentModifier = false;
|
|
775
|
-
if (j < cmd.length && (cmd[j] === "-" || cmd[j] === "~")) {
|
|
776
|
-
hasIndentModifier = true;
|
|
777
|
-
j++;
|
|
778
|
-
}
|
|
779
|
-
while (j < cmd.length && cmd[j] === " ") {
|
|
780
|
-
j++;
|
|
781
|
-
}
|
|
782
|
-
if (j < cmd.length) {
|
|
783
|
-
let delimStart = j;
|
|
784
|
-
if (cmd[j] === '"' || cmd[j] === "'") {
|
|
785
|
-
delimStart = j + 1;
|
|
786
|
-
const quoteChar = cmd[j];
|
|
787
|
-
let delimEnd = delimStart;
|
|
788
|
-
while (delimEnd < cmd.length && cmd[delimEnd] !== quoteChar) {
|
|
789
|
-
delimEnd++;
|
|
790
|
-
}
|
|
791
|
-
if (delimEnd >= cmd.length) {
|
|
792
|
-
return "an unclosed quote in a heredoc opener";
|
|
793
|
-
}
|
|
794
|
-
j = delimEnd + 1;
|
|
795
|
-
} else {
|
|
796
|
-
let delimEnd = delimStart;
|
|
797
|
-
while (delimEnd < cmd.length && /\w/.test(cmd.charAt(delimEnd))) {
|
|
798
|
-
delimEnd++;
|
|
799
|
-
}
|
|
800
|
-
j = delimEnd;
|
|
801
|
-
}
|
|
802
|
-
const delimiter = cmd.slice(delimStart, j).replace(/["']/g, "");
|
|
803
|
-
if (delimiter) {
|
|
804
|
-
let scanPos = j;
|
|
805
|
-
let terminatorEnd = -1;
|
|
806
|
-
while (scanPos <= cmd.length) {
|
|
807
|
-
const nl2 = cmd.indexOf("\n", scanPos);
|
|
808
|
-
const lineEnd = nl2 === -1 ? cmd.length : nl2;
|
|
809
|
-
let line = cmd.slice(scanPos, lineEnd);
|
|
810
|
-
if (line.endsWith("\r")) {
|
|
811
|
-
line = line.slice(0, -1);
|
|
812
|
-
}
|
|
813
|
-
const isMatch = hasIndentModifier ? line.trim() === delimiter : line === delimiter;
|
|
814
|
-
if (isMatch) {
|
|
815
|
-
terminatorEnd = nl2 === -1 ? cmd.length : nl2 + 1;
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
if (nl2 === -1) break;
|
|
819
|
-
scanPos = nl2 + 1;
|
|
820
|
-
}
|
|
821
|
-
if (terminatorEnd === -1) {
|
|
822
|
-
return `an unterminated heredoc (${delimiter} never appears on its own line)`;
|
|
823
|
-
}
|
|
824
|
-
i = terminatorEnd;
|
|
825
|
-
continue;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
i++;
|
|
830
|
-
}
|
|
831
|
-
if (inSingle) {
|
|
832
|
-
return "an unclosed single quote";
|
|
833
|
-
}
|
|
834
|
-
if (inDouble) {
|
|
835
|
-
return "an unclosed double quote";
|
|
836
|
-
}
|
|
837
|
-
return null;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// src/call_streak.ts
|
|
841
|
-
var SERIAL_GAP_MS = 1e3;
|
|
842
|
-
var SERIAL_READS_BEFORE_HINT = 3;
|
|
843
|
-
var ZERO_HIT_SEARCHES_BEFORE_BRAKE = 3;
|
|
844
|
-
var MAX_BATCH_HINTS_PER_SESSION = 2;
|
|
845
|
-
var MAX_SEARCH_BRAKES_PER_SESSION = 2;
|
|
846
|
-
var STREAK_SUFFIX = ".call-streak";
|
|
847
|
-
var TG_SEARCH_RE = /^(?:token-goat|tg)\s+(?:answer|semantic|symbol|refs|locate)\b/;
|
|
848
|
-
var TG_MEANING_SEARCH_RE = /^(?:token-goat|tg)\s+(?:answer|semantic)\b/;
|
|
849
|
-
var TG_READ_RE = /^(?:token-goat|tg)\s+(?:read|section|outline|skeleton|brief)\b/;
|
|
850
|
-
var WRITE_REDIRECT_RE = /\d?>>?(?!\s*(?:&\d|\/dev\/null\b))/;
|
|
851
|
-
function freshState() {
|
|
852
|
-
return { promptId: "", lastReadPostAt: 0, inFlight: false, serialRun: 0, batchShownInRun: false, batchShown: 0, batchVerdict: "none", zeroHits: 0, brakeShownInRun: false, brakeShown: 0, brakeVerdict: false };
|
|
853
|
-
}
|
|
854
|
-
function readState(target) {
|
|
855
|
-
const state = freshState();
|
|
856
|
-
try {
|
|
857
|
-
const parsed = JSON.parse(readFileSync(target, "utf8"));
|
|
858
|
-
if (parsed === null || typeof parsed !== "object") return state;
|
|
859
|
-
const p = parsed;
|
|
860
|
-
for (const key of Object.keys(state)) {
|
|
861
|
-
if (typeof p[key] === typeof state[key]) state[key] = p[key];
|
|
862
|
-
}
|
|
863
|
-
if (!["none", "next_turn", "second_call"].includes(state.batchVerdict)) state.batchVerdict = "none";
|
|
864
|
-
} catch {
|
|
865
|
-
}
|
|
866
|
-
return state;
|
|
867
|
-
}
|
|
868
|
-
function writeState(target, state) {
|
|
869
|
-
try {
|
|
870
|
-
ensureDirSync(dirname(target));
|
|
871
|
-
writeFileSync(target, JSON.stringify(state), "utf8");
|
|
488
|
+
ensureDirSync(dirname(target));
|
|
489
|
+
writeFileSync(target, JSON.stringify(state), "utf8");
|
|
872
490
|
} catch {
|
|
873
491
|
}
|
|
874
492
|
}
|
|
@@ -1044,15 +662,27 @@ function queuePendingContext(stateKey, text) {
|
|
|
1044
662
|
const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
|
|
1045
663
|
if (target === null) return;
|
|
1046
664
|
try {
|
|
1047
|
-
const
|
|
1048
|
-
const merged = existing === null ? trimmed : `${existing}
|
|
1049
|
-
${trimmed}`;
|
|
1050
|
-
const capped = merged.length <= MAX_PENDING_CONTEXT_BYTES ? merged : merged.slice(merged.length - MAX_PENDING_CONTEXT_BYTES);
|
|
665
|
+
const capped = fitPending(readPending(target), trimmed);
|
|
1051
666
|
ensureDirSync(dirname2(target));
|
|
1052
667
|
writeFileSync2(target, capped, "utf8");
|
|
1053
668
|
} catch {
|
|
1054
669
|
}
|
|
1055
670
|
}
|
|
671
|
+
function replacePendingContextBlock(stateKey, text, blockStart) {
|
|
672
|
+
const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
|
|
673
|
+
if (target === null) return;
|
|
674
|
+
try {
|
|
675
|
+
const existing = readPending(target);
|
|
676
|
+
const at = existing === null ? -1 : existing.indexOf(blockStart);
|
|
677
|
+
if (existing !== null && at >= 0) {
|
|
678
|
+
const kept = existing.slice(0, at).trim();
|
|
679
|
+
if (kept === "") rmSync(target, { force: true });
|
|
680
|
+
else writeFileSync2(target, kept, "utf8");
|
|
681
|
+
}
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
queuePendingContext(stateKey, text);
|
|
685
|
+
}
|
|
1056
686
|
function peekPendingContext(stateKey) {
|
|
1057
687
|
const target = sessionSidecarPath(stateKey, PENDING_SUFFIX);
|
|
1058
688
|
return target === null ? null : readPending(target);
|
|
@@ -1069,6 +699,18 @@ function commitPendingContext(stateKey, delivered) {
|
|
|
1069
699
|
} catch {
|
|
1070
700
|
}
|
|
1071
701
|
}
|
|
702
|
+
function fitPending(existing, text) {
|
|
703
|
+
if (existing !== null) {
|
|
704
|
+
const merged = `${existing}
|
|
705
|
+
${text}`;
|
|
706
|
+
if (merged.length <= MAX_PENDING_CONTEXT_BYTES) return merged;
|
|
707
|
+
}
|
|
708
|
+
if (text.length <= MAX_PENDING_CONTEXT_BYTES) return text;
|
|
709
|
+
const head = text.slice(0, MAX_PENDING_CONTEXT_BYTES);
|
|
710
|
+
const lineEnd = head.lastIndexOf("\n");
|
|
711
|
+
if (lineEnd > 0) return head.slice(0, lineEnd).trimEnd();
|
|
712
|
+
return /[\uD800-\uDBFF]$/.test(head) ? head.slice(0, -1) : head;
|
|
713
|
+
}
|
|
1072
714
|
function readPending(target) {
|
|
1073
715
|
try {
|
|
1074
716
|
const raw = readFileSync2(target, "utf8").trim();
|
|
@@ -1192,107 +834,847 @@ function shouldSuppressDuplicateVscodeHook(event, harness) {
|
|
|
1192
834
|
}
|
|
1193
835
|
}
|
|
1194
836
|
|
|
1195
|
-
// src/
|
|
837
|
+
// src/hooks_read_post.ts
|
|
1196
838
|
init_define_import_meta_env();
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
839
|
+
import * as fs2 from "node:fs";
|
|
840
|
+
import * as path2 from "node:path";
|
|
841
|
+
|
|
842
|
+
// src/served_lines.ts
|
|
843
|
+
init_define_import_meta_env();
|
|
844
|
+
var SERVED_RUN_COMPARISON_BUDGET = 4e5;
|
|
845
|
+
var SERVED_RUN_ANCHORS_PER_LINE = 32;
|
|
846
|
+
function indexServedBody(id, output) {
|
|
847
|
+
const lines = output.split("\n");
|
|
848
|
+
const positions = /* @__PURE__ */ new Map();
|
|
849
|
+
for (let j = 0; j < lines.length; j++) {
|
|
850
|
+
const key = lines[j] ?? "";
|
|
851
|
+
let at = positions.get(key);
|
|
852
|
+
if (at === void 0) {
|
|
853
|
+
at = [];
|
|
854
|
+
positions.set(key, at);
|
|
855
|
+
}
|
|
856
|
+
if (at.length < SERVED_RUN_ANCHORS_PER_LINE) at.push(j);
|
|
857
|
+
}
|
|
858
|
+
return { id, lines, positions };
|
|
859
|
+
}
|
|
860
|
+
function longestServedRun(rows, from, to, bodies, budget) {
|
|
861
|
+
let best = null;
|
|
862
|
+
for (const body of bodies) {
|
|
863
|
+
for (let i = from; i < to; i++) {
|
|
864
|
+
if (best !== null && to - i <= best.len) break;
|
|
865
|
+
const anchors = body.positions.get(rows[i]?.text ?? "");
|
|
866
|
+
if (anchors === void 0) continue;
|
|
867
|
+
for (const j of anchors) {
|
|
868
|
+
let k = 0;
|
|
869
|
+
while (i + k < to && j + k < body.lines.length && rows[i + k]?.text === body.lines[j + k]) {
|
|
870
|
+
k++;
|
|
871
|
+
if (--budget.left <= 0) return best !== null && best.len > 0 ? best : null;
|
|
872
|
+
}
|
|
873
|
+
if (best === null || k > best.len) best = { start: i, len: k, id: body.id };
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return best !== null && best.len > 0 ? best : null;
|
|
878
|
+
}
|
|
879
|
+
var MAX_SERVED_ELISIONS = 3;
|
|
880
|
+
function servedRunNotice(firstLine, lastLine, id, len) {
|
|
881
|
+
const which = firstLine !== null && lastLine !== null ? "lines " + firstLine + "-" + lastLine : len + " lines here";
|
|
882
|
+
return "[token-goat] " + which + " were already served verbatim in this session; withheld here. Recall them with `token-goat bash-output " + id + " --full`.";
|
|
883
|
+
}
|
|
884
|
+
function renderedRunBytes(rows, start, len) {
|
|
885
|
+
let bytes = 0;
|
|
886
|
+
for (let i = start; i < start + len; i++) bytes += Buffer.byteLength(rows[i]?.raw ?? "", "utf-8") + 1;
|
|
887
|
+
return bytes;
|
|
888
|
+
}
|
|
889
|
+
function planServedElisions(rows, bodies) {
|
|
890
|
+
if (rows.length === 0 || bodies.length === 0) return [];
|
|
891
|
+
const budget = { left: SERVED_RUN_COMPARISON_BUDGET };
|
|
892
|
+
let spans = [[0, rows.length]];
|
|
893
|
+
const cuts = [];
|
|
894
|
+
for (let pass = 0; pass < MAX_SERVED_ELISIONS; pass++) {
|
|
895
|
+
let bestRun = null;
|
|
896
|
+
let bestSpan = -1;
|
|
897
|
+
for (let s = 0; s < spans.length; s++) {
|
|
898
|
+
const span = spans[s];
|
|
899
|
+
if (span === void 0) continue;
|
|
900
|
+
const run = longestServedRun(rows, span[0], span[1], bodies, budget);
|
|
901
|
+
if (run !== null && (bestRun === null || run.len > bestRun.len)) {
|
|
902
|
+
bestRun = run;
|
|
903
|
+
bestSpan = s;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (bestRun === null || bestSpan < 0) break;
|
|
907
|
+
const first2 = rows[bestRun.start];
|
|
908
|
+
const last = rows[bestRun.start + bestRun.len - 1];
|
|
909
|
+
if (first2 === void 0 || last === void 0) break;
|
|
910
|
+
const noticeBytes = Buffer.byteLength(servedRunNotice(first2.no, last.no, bestRun.id, bestRun.len), "utf-8") + 1;
|
|
911
|
+
if (renderedRunBytes(rows, bestRun.start, bestRun.len) <= noticeBytes) break;
|
|
912
|
+
cuts.push(bestRun);
|
|
913
|
+
const chosen = spans[bestSpan];
|
|
914
|
+
if (chosen === void 0) break;
|
|
915
|
+
const rebuilt = [];
|
|
916
|
+
for (let s = 0; s < spans.length; s++) {
|
|
917
|
+
const span = spans[s];
|
|
918
|
+
if (span === void 0) continue;
|
|
919
|
+
if (s !== bestSpan) {
|
|
920
|
+
rebuilt.push(span);
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (bestRun.start > chosen[0]) rebuilt.push([chosen[0], bestRun.start]);
|
|
924
|
+
if (bestRun.start + bestRun.len < chosen[1]) rebuilt.push([bestRun.start + bestRun.len, chosen[1]]);
|
|
925
|
+
}
|
|
926
|
+
spans = rebuilt;
|
|
1203
927
|
}
|
|
1204
|
-
|
|
928
|
+
cuts.sort((a, b) => a.start - b.start);
|
|
929
|
+
return cuts;
|
|
1205
930
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
const offset = grepIntInput(toolInput, "offset");
|
|
1222
|
-
return JSON.stringify([
|
|
1223
|
-
pattern,
|
|
1224
|
-
path6,
|
|
1225
|
-
outputMode,
|
|
1226
|
-
glob,
|
|
1227
|
-
type,
|
|
1228
|
-
caseInsensitive,
|
|
1229
|
-
onlyMatching,
|
|
1230
|
-
multiline,
|
|
1231
|
-
lineNumbers,
|
|
1232
|
-
contextA,
|
|
1233
|
-
contextB,
|
|
1234
|
-
contextC,
|
|
1235
|
-
headLimit,
|
|
1236
|
-
offset
|
|
1237
|
-
]);
|
|
931
|
+
|
|
932
|
+
// src/read_shape.ts
|
|
933
|
+
init_define_import_meta_env();
|
|
934
|
+
import { readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
935
|
+
import { dirname as dirname3 } from "node:path";
|
|
936
|
+
var SHAPES = /* @__PURE__ */ new Set(["outline", "skeleton", "fold", "truncated", "partial"]);
|
|
937
|
+
var SHAPE_SUFFIX = ".read-shape";
|
|
938
|
+
var MAX_ENTRIES = 512;
|
|
939
|
+
function load(target) {
|
|
940
|
+
try {
|
|
941
|
+
const parsed = JSON.parse(readFileSync3(target, "utf8"));
|
|
942
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
943
|
+
} catch {
|
|
944
|
+
return {};
|
|
945
|
+
}
|
|
1238
946
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
recordQuery: recordGrepQuery,
|
|
1243
|
-
getMatchCount: getGrepMatchCount,
|
|
1244
|
-
minMatchesConfigKey: "grep_dedup_min_matches",
|
|
1245
|
-
statName: "grep_dedup_hint"
|
|
1246
|
-
});
|
|
1247
|
-
var CONTENT_LINE_RE = /^(.+?):(\d+):(.*)$/s;
|
|
1248
|
-
function foldGrepContentHandler(event) {
|
|
947
|
+
function recordReadShape(stateKey, file, shape) {
|
|
948
|
+
const target = sessionSidecarPath(stateKey, SHAPE_SUFFIX);
|
|
949
|
+
if (target === null) return;
|
|
1249
950
|
try {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (toolInput["-n"] === false) return passOutput();
|
|
1258
|
-
const rawText = extractToolResponseField(event.raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
|
|
1259
|
-
if (!rawText) return passOutput();
|
|
1260
|
-
const text = redactSecrets(rawText).text;
|
|
1261
|
-
const rawLines = text.split(/\r\n|\r|\n/);
|
|
1262
|
-
const parsed = [];
|
|
1263
|
-
for (const line of rawLines) {
|
|
1264
|
-
if (line.trim() === "") continue;
|
|
1265
|
-
const m = CONTENT_LINE_RE.exec(line);
|
|
1266
|
-
if (!m) return passOutput();
|
|
1267
|
-
parsed.push({ file: m[1], lineNo: m[2], rest: m[3] });
|
|
1268
|
-
}
|
|
1269
|
-
if (parsed.length === 0) return passOutput();
|
|
1270
|
-
const order = [];
|
|
1271
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1272
|
-
for (const p of parsed) {
|
|
1273
|
-
let group = groups.get(p.file);
|
|
1274
|
-
if (group === void 0) {
|
|
1275
|
-
group = [];
|
|
1276
|
-
groups.set(p.file, group);
|
|
1277
|
-
order.push(p.file);
|
|
1278
|
-
}
|
|
1279
|
-
group.push({ lineNo: p.lineNo, rest: p.rest });
|
|
951
|
+
const shapes = load(target);
|
|
952
|
+
if (shape === null) {
|
|
953
|
+
if (!Object.hasOwn(shapes, file)) return;
|
|
954
|
+
delete shapes[file];
|
|
955
|
+
} else {
|
|
956
|
+
delete shapes[file];
|
|
957
|
+
shapes[file] = shape;
|
|
1280
958
|
}
|
|
1281
|
-
const
|
|
1282
|
-
if (
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
foldedLines.push(f);
|
|
1286
|
-
for (const { lineNo, rest } of groups.get(f)) {
|
|
1287
|
-
foldedLines.push(` ${lineNo}: ${rest}`);
|
|
1288
|
-
}
|
|
959
|
+
const keys = Object.keys(shapes);
|
|
960
|
+
if (keys.length === 0) {
|
|
961
|
+
rmSync3(target, { force: true });
|
|
962
|
+
return;
|
|
1289
963
|
}
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
964
|
+
for (const k of keys.slice(0, Math.max(0, keys.length - MAX_ENTRIES))) delete shapes[k];
|
|
965
|
+
ensureDirSync(dirname3(target));
|
|
966
|
+
writeFileSync3(target, JSON.stringify(shapes), "utf8");
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function readShapeOf(stateKey, file) {
|
|
971
|
+
const target = sessionSidecarPath(stateKey, SHAPE_SUFFIX);
|
|
972
|
+
if (target === null) return null;
|
|
973
|
+
const shapes = load(target);
|
|
974
|
+
const shape = Object.hasOwn(shapes, file) ? shapes[file] : void 0;
|
|
975
|
+
return shape !== void 0 && SHAPES.has(shape) ? shape : null;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/fold_delivery.ts
|
|
979
|
+
init_define_import_meta_env();
|
|
980
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
981
|
+
var BODY_FOLD_KEEP_LINES = 8;
|
|
982
|
+
var BODY_FOLD_MIN_SPAN = 20;
|
|
983
|
+
var COMMENT_FOLD_KEEP_LINES = 2;
|
|
984
|
+
var COMMENT_FOLD_MIN_BLOCK = 12;
|
|
985
|
+
var BODY_FOLD_SYMBOL_LIMIT = 1e4;
|
|
986
|
+
function bodyFoldNotice(name2, firstLine, lastLine, shownPath, declLine) {
|
|
987
|
+
const n = lastLine - firstLine + 1;
|
|
988
|
+
const anchor = declLine === void 0 ? "" : `@${declLine}`;
|
|
989
|
+
const safeName = displaySafeText(name2);
|
|
990
|
+
return `... ${n} more lines of ${safeName} (${firstLine}-${lastLine}) folded -- token-goat read "${shownPath}::${safeName}${anchor}"`;
|
|
991
|
+
}
|
|
992
|
+
function foldingEnabled() {
|
|
993
|
+
const hints = loadConfig().hints;
|
|
994
|
+
return hints.fold_code_bodies || hints.fold_comment_blocks || hints.fold_prose_paragraphs;
|
|
995
|
+
}
|
|
996
|
+
var PROSE_FOLDABLE_EXT_RE = /\.(?:md|mdx|markdown)$/i;
|
|
997
|
+
function isProseFoldablePath(normalizedPath) {
|
|
998
|
+
return PROSE_FOLDABLE_EXT_RE.test(normalizedPath);
|
|
999
|
+
}
|
|
1000
|
+
function proseFoldNotice(keep, line, shownPath, normalizedPath) {
|
|
1001
|
+
const section = findContainingSection(normalizedPath, line, line);
|
|
1002
|
+
if (section === null) return null;
|
|
1003
|
+
return `${displaySafeText(keep)} ... rest of paragraph folded (line ${line}) -- token-goat section "${shownPath}::${displaySafeText(section.heading)}"`;
|
|
1004
|
+
}
|
|
1005
|
+
function commentFoldNotice(firstLine, lastLine, shownPath) {
|
|
1006
|
+
const n = lastLine - firstLine + 1;
|
|
1007
|
+
return `... ${n} more comment lines (${firstLine}-${lastLine}) folded -- Read "${shownPath}" with offset=${firstLine}, limit=${n}`;
|
|
1008
|
+
}
|
|
1009
|
+
var ALIGNED_LAYOUT_NOTE = "Every line shows its real line number: each withheld run opens with one notice line on the run's first line (a `...` pointer, or a `[token-goat] lines ... were already served` notice), and the rest of the run is left as empty lines.";
|
|
1010
|
+
function pushWithheld(out, notice, len, layout) {
|
|
1011
|
+
if (layout === "compact") {
|
|
1012
|
+
out.push(notice);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
out.push(notice.replace(/\r?\n/g, " "));
|
|
1016
|
+
for (let i = 1; i < len; i++) out.push("");
|
|
1017
|
+
}
|
|
1018
|
+
function alignedPaddingBytes(len, layout) {
|
|
1019
|
+
return layout === "aligned" ? Math.max(0, len - 1) : 0;
|
|
1020
|
+
}
|
|
1021
|
+
var FOLD_SPAN_PARSE_MAX_BYTES = 4e5;
|
|
1022
|
+
function parseFoldSpansFromDisk(normalizedPath, rows) {
|
|
1023
|
+
try {
|
|
1024
|
+
const onDisk = hostPathOfIndexKey(normalizedPath);
|
|
1025
|
+
if (statSync2(onDisk).size > FOLD_SPAN_PARSE_MAX_BYTES) return [];
|
|
1026
|
+
const language = detectLanguage(normalizedPath);
|
|
1027
|
+
if (!isTreeSitterAvailable(language)) return [];
|
|
1028
|
+
const fileText = readFileSync4(onDisk, "utf-8");
|
|
1029
|
+
const diskLines = fileText.split("\n");
|
|
1030
|
+
for (const row of rows) {
|
|
1031
|
+
const disk = diskLines[row.no - 1];
|
|
1032
|
+
if (disk === void 0 || disk.replace(/\r$/, "") !== row.text.replace(/\r$/, "")) return [];
|
|
1033
|
+
}
|
|
1034
|
+
const symbols = parseSourceSymbolsTreeSitterOnly(fileText, normalizedPath, language);
|
|
1035
|
+
if (symbols === null) return [];
|
|
1036
|
+
return symbols.slice(0, BODY_FOLD_SYMBOL_LIMIT).map((s) => ({ name: s.name, kind: s.kind, lineStart: s.lineStart, lineEnd: s.lineEnd }));
|
|
1037
|
+
} catch {
|
|
1038
|
+
return [];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function resolveFoldSpans(normalizedPath, hasCommentSyntax, rows) {
|
|
1042
|
+
try {
|
|
1043
|
+
const entry = getFileEntry(normalizedPath);
|
|
1044
|
+
if (entry !== null && entry.sha !== "" && !isVirtualIndexedPath(normalizedPath) && entry.sha === fingerprintFile(hostPathOfIndexKey(normalizedPath)) && entry.parserSha === parserFingerprintForLanguage(entry.language)) {
|
|
1045
|
+
return querySymbols({ filePath: normalizedPath, limit: BODY_FOLD_SYMBOL_LIMIT });
|
|
1046
|
+
}
|
|
1047
|
+
if (hasCommentSyntax) enqueueDirtyPathSafe(normalizedPath);
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
return parseFoldSpansFromDisk(normalizedPath, rows);
|
|
1051
|
+
}
|
|
1052
|
+
function strictlyInteriorOnWindow(folds, rowCount, windowed) {
|
|
1053
|
+
if (!windowed) return [...folds];
|
|
1054
|
+
return folds.filter((f) => f.startIdx > 0 && f.startIdx + f.len < rowCount);
|
|
1055
|
+
}
|
|
1056
|
+
function foldDelivery(rows, normalizedPath, shownPath, windowed = false, layout = "compact") {
|
|
1057
|
+
const syntax = commentSyntaxFor(normalizedPath);
|
|
1058
|
+
const foldBodies = loadConfig().hints.fold_code_bodies;
|
|
1059
|
+
const spans = foldBodies ? resolveFoldSpans(normalizedPath, syntax !== null, rows) : [];
|
|
1060
|
+
const bodyFolds = foldBodies ? planBodyFolds(rows, spans, BODY_FOLD_KEEP_LINES, BODY_FOLD_MIN_SPAN) : [];
|
|
1061
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1062
|
+
for (const fold of bodyFolds) for (let i = fold.startIdx; i < fold.startIdx + fold.len; i++) claimed.add(i);
|
|
1063
|
+
const commentFolds = loadConfig().hints.fold_comment_blocks ? strictlyInteriorOnWindow(planCommentFolds(rows, syntax, COMMENT_FOLD_KEEP_LINES, COMMENT_FOLD_MIN_BLOCK, claimed), rows.length, windowed) : [];
|
|
1064
|
+
for (const fold of commentFolds) for (let i = fold.startIdx; i < fold.startIdx + fold.len; i++) claimed.add(i);
|
|
1065
|
+
const proseFolds = isProseFoldablePath(normalizedPath) && loadConfig().hints.fold_prose_paragraphs ? strictlyInteriorOnWindow(planProseFolds(rows, claimed), rows.length, windowed).filter((f) => findContainingSection(normalizedPath, f.firstLine, f.firstLine) !== null) : [];
|
|
1066
|
+
const folds = mergeFolds(mergeFolds(bodyFolds, commentFolds), proseFolds);
|
|
1067
|
+
if (folds.length === 0) return null;
|
|
1068
|
+
const numbered = [];
|
|
1069
|
+
const raw = [];
|
|
1070
|
+
let at = 0;
|
|
1071
|
+
for (const fold of folds) {
|
|
1072
|
+
for (let i = at; i < fold.startIdx; i++) {
|
|
1073
|
+
numbered.push(rows[i]?.raw ?? "");
|
|
1074
|
+
raw.push(rows[i]?.text ?? "");
|
|
1075
|
+
}
|
|
1076
|
+
let notice;
|
|
1077
|
+
switch (fold.kind) {
|
|
1078
|
+
case "comment":
|
|
1079
|
+
notice = commentFoldNotice(fold.firstLine, fold.lastLine, shownPath);
|
|
1080
|
+
break;
|
|
1081
|
+
case "prose":
|
|
1082
|
+
notice = proseFoldNotice(fold.keep ?? "", fold.firstLine, shownPath, normalizedPath);
|
|
1083
|
+
break;
|
|
1084
|
+
case "body":
|
|
1085
|
+
notice = bodyFoldNotice(fold.name, fold.firstLine, fold.lastLine, shownPath, fold.declLine);
|
|
1086
|
+
break;
|
|
1087
|
+
default: {
|
|
1088
|
+
const unreachable = fold.kind;
|
|
1089
|
+
throw new Error(`unhandled fold kind: ${String(unreachable)}`);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (notice === null) {
|
|
1093
|
+
for (let i = fold.startIdx; i < fold.startIdx + fold.len; i++) {
|
|
1094
|
+
numbered.push(rows[i]?.raw ?? "");
|
|
1095
|
+
raw.push(rows[i]?.text ?? "");
|
|
1096
|
+
}
|
|
1097
|
+
at = fold.startIdx + fold.len;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
pushWithheld(numbered, notice, fold.len, layout);
|
|
1101
|
+
at = fold.startIdx + fold.len;
|
|
1102
|
+
}
|
|
1103
|
+
for (let i = at; i < rows.length; i++) {
|
|
1104
|
+
numbered.push(rows[i]?.raw ?? "");
|
|
1105
|
+
raw.push(rows[i]?.text ?? "");
|
|
1106
|
+
}
|
|
1107
|
+
return { numbered, raw, folds };
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/fold_structure.ts
|
|
1111
|
+
init_define_import_meta_env();
|
|
1112
|
+
var OUTLINE_MIN_BODY_BYTES = 8e3;
|
|
1113
|
+
var OUTLINE_MIN_HEADINGS = 6;
|
|
1114
|
+
var OUTLINE_MAX_REPLACEMENT_RATIO = 0.4;
|
|
1115
|
+
var SKELETON_MIN_BODY_BYTES = 12e3;
|
|
1116
|
+
var SKELETON_MIN_SYMBOLS = 8;
|
|
1117
|
+
var SKELETON_MAX_REPLACEMENT_RATIO = 0.4;
|
|
1118
|
+
function outlineLeadInRows(rows, headings) {
|
|
1119
|
+
const opensWithH1 = headings[0]?.level === 1;
|
|
1120
|
+
const boundary = opensWithH1 ? headings.find((h, i) => i > 0 && h.level === 2) : headings[0];
|
|
1121
|
+
if (boundary === void 0) return [];
|
|
1122
|
+
const boundaryIdx = rows.findIndex((r) => r.no === boundary.lineNumber);
|
|
1123
|
+
return boundaryIdx > 0 ? rows.slice(0, boundaryIdx) : [];
|
|
1124
|
+
}
|
|
1125
|
+
function capLeadIn(rows) {
|
|
1126
|
+
return { rows: [...rows], notice: null };
|
|
1127
|
+
}
|
|
1128
|
+
function planMarkdownOutline(rows, normalizedPath, shownPath, originalBytes, layout = "compact") {
|
|
1129
|
+
if (!loadConfig().hints.outline_large_documents) return null;
|
|
1130
|
+
if (!isProseFoldablePath(normalizedPath)) return null;
|
|
1131
|
+
if (originalBytes < OUTLINE_MIN_BODY_BYTES) return null;
|
|
1132
|
+
const fileText = rows.map((r) => r.text).join("\n");
|
|
1133
|
+
const headings = extractMarkdownHeadings(fileText);
|
|
1134
|
+
if (headings.length < OUTLINE_MIN_HEADINGS) return null;
|
|
1135
|
+
const { guidance, sectionsList } = headingTreeParts(headings, shownPath, fileText);
|
|
1136
|
+
const leadInRows = outlineLeadInRows(rows, headings);
|
|
1137
|
+
const { rows: cappedLeadIn, notice: capNotice } = capLeadIn(leadInRows);
|
|
1138
|
+
const leadInFolded = foldDelivery(cappedLeadIn, normalizedPath, shownPath, false, layout);
|
|
1139
|
+
const leadInNumbered = leadInFolded !== null ? leadInFolded.numbered : cappedLeadIn.map((r) => r.raw);
|
|
1140
|
+
const leadInRaw = leadInFolded !== null ? leadInFolded.raw : cappedLeadIn.map((r) => r.text);
|
|
1141
|
+
const headingCount = `at least ${headings.length} heading${headings.length === 1 ? "" : "s"} found`;
|
|
1142
|
+
const heading = hintTarget(normalizedPath, "section", { headings, content: fileText, placeholder: "<Heading>" }).name;
|
|
1143
|
+
if (layout === "aligned") {
|
|
1144
|
+
const body = fenceNumberedFileContent([...leadInNumbered, ...planOutlineAlignedRows(rows, headings, leadInNumbered.length, shownPath, normalizedPath)].join("\n"), ALIGNED_LAYOUT_NOTE);
|
|
1145
|
+
const alignedNotice = `Partial view: this ${originalBytes.toLocaleString("en-US")} B document was cut down to ${leadInRows.length > 0 ? "its lead-in and " : ""}its headings (${headingCount}), each on its real line, with the text under each heading withheld behind a pointer. Run token-goat section "${shownPath}::${heading}" to read one section verbatim.`;
|
|
1146
|
+
return { numbered: [body.body], raw: leadInRaw, kind: "read:markdown_outline", detail: shownPath, ratioCap: OUTLINE_MAX_REPLACEMENT_RATIO, context: `${body.preamble}
|
|
1147
|
+
${alignedNotice}` };
|
|
1148
|
+
}
|
|
1149
|
+
const notice = leadInRows.length > 0 ? `Partial view: this ${originalBytes.toLocaleString("en-US")} B document was replaced with its lead-in (the content before its first section) and a heading tree (${headingCount}). Run token-goat section "${shownPath}::${heading}" to read one section verbatim.` : `Partial view: this ${originalBytes.toLocaleString("en-US")} B document has no lead-in before its first section, so it was replaced with a heading tree alone (${headingCount}). Run token-goat section "${shownPath}::${heading}" to read one section verbatim.`;
|
|
1150
|
+
return {
|
|
1151
|
+
// The notice leads so that token-goat speaks first: emitting the lead-in above it let a document open with forged `[token-goat: ...]` or `[tg]` lines that read as this rewrite's own preamble. The lead-in is file bytes like the heading tree, so it gets its own fence rather than riding in the tree's; both fences are part of `numbered`, so their cost is priced by the ratioCap gate below along with everything else.
|
|
1152
|
+
numbered: [
|
|
1153
|
+
notice,
|
|
1154
|
+
...leadInNumbered.length > 0 ? [fenceUntrustedFileContent(leadInNumbered.join("\n"))] : [],
|
|
1155
|
+
...capNotice !== null ? [capNotice] : [],
|
|
1156
|
+
guidance,
|
|
1157
|
+
fenceUntrustedFileContent(sectionsList)
|
|
1158
|
+
],
|
|
1159
|
+
raw: leadInRaw,
|
|
1160
|
+
kind: "read:markdown_outline",
|
|
1161
|
+
detail: shownPath,
|
|
1162
|
+
ratioCap: OUTLINE_MAX_REPLACEMENT_RATIO
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function planOutlineAlignedRows(rows, headings, startIdx, shownPath, normalizedPath) {
|
|
1166
|
+
const listedLines = new Set(headings.map((h) => h.lineNumber));
|
|
1167
|
+
const out = [];
|
|
1168
|
+
let i = startIdx;
|
|
1169
|
+
while (i < rows.length) {
|
|
1170
|
+
const row = rows[i];
|
|
1171
|
+
if (row === void 0) break;
|
|
1172
|
+
if (listedLines.has(row.no)) {
|
|
1173
|
+
out.push(row.raw);
|
|
1174
|
+
i++;
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
let j = i;
|
|
1178
|
+
while (j < rows.length && !listedLines.has(rows[j]?.no ?? -1)) j++;
|
|
1179
|
+
const firstLine = row.no;
|
|
1180
|
+
const lastLine = rows[j - 1]?.no ?? firstLine;
|
|
1181
|
+
const n = j - i;
|
|
1182
|
+
const under = findContainingSection(normalizedPath, firstLine, lastLine)?.heading ?? null;
|
|
1183
|
+
const notice = under !== null && !/["`$\\]/.test(under) && displaySafeText(under) === under ? `... ${n} line${n === 1 ? "" : "s"} (${firstLine}-${lastLine}) under "${under}" withheld -- token-goat section "${shownPath}::${under}"` : null;
|
|
1184
|
+
let runBytes = 0;
|
|
1185
|
+
for (let k = i; k < j; k++) runBytes += Buffer.byteLength(rows[k]?.raw ?? "", "utf-8") + 1;
|
|
1186
|
+
if (notice === null || Buffer.byteLength(notice, "utf-8") + alignedPaddingBytes(n, "aligned") >= runBytes) {
|
|
1187
|
+
for (let k = i; k < j; k++) out.push(rows[k]?.raw ?? "");
|
|
1188
|
+
} else {
|
|
1189
|
+
pushWithheld(out, notice, n, "aligned");
|
|
1190
|
+
}
|
|
1191
|
+
i = j;
|
|
1192
|
+
}
|
|
1193
|
+
return out;
|
|
1194
|
+
}
|
|
1195
|
+
function skeletonGapNotice(firstLine, lastLine, shownPath) {
|
|
1196
|
+
const n = lastLine - firstLine + 1;
|
|
1197
|
+
return `... ${n} line${n === 1 ? "" : "s"} (${firstLine}-${lastLine}) withheld from the skeleton -- Read "${shownPath}" with offset=${firstLine}, limit=${n}`;
|
|
1198
|
+
}
|
|
1199
|
+
function planSourceSkeletonRuns(rows, symbols, shownPath, layout) {
|
|
1200
|
+
const base = rows[0]?.no ?? 1;
|
|
1201
|
+
const keep = /* @__PURE__ */ new Set();
|
|
1202
|
+
const firstDeclLine = Math.min(...symbols.map((s) => s.lineStart));
|
|
1203
|
+
for (let n = base; n < firstDeclLine; n++) keep.add(n);
|
|
1204
|
+
for (const sym of symbols) keep.add(sym.lineStart);
|
|
1205
|
+
const bodyRunStart = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const sym of symbols) if (sym.lineEnd > sym.lineStart) bodyRunStart.set(sym.lineStart + 1, sym);
|
|
1207
|
+
const runEndsAfter = /* @__PURE__ */ new Set();
|
|
1208
|
+
for (const sym of symbols) if (sym.lineEnd > sym.lineStart) runEndsAfter.add(sym.lineEnd);
|
|
1209
|
+
const numbered = [];
|
|
1210
|
+
const raw = [];
|
|
1211
|
+
let withheldLines = 0;
|
|
1212
|
+
let i = 0;
|
|
1213
|
+
while (i < rows.length) {
|
|
1214
|
+
const row = rows[i];
|
|
1215
|
+
if (row === void 0) break;
|
|
1216
|
+
if (keep.has(row.no)) {
|
|
1217
|
+
numbered.push(row.raw);
|
|
1218
|
+
raw.push(row.text);
|
|
1219
|
+
i++;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
let j = i;
|
|
1223
|
+
while (j < rows.length && !keep.has(rows[j]?.no ?? -1)) {
|
|
1224
|
+
const stop = runEndsAfter.has(rows[j]?.no ?? -1);
|
|
1225
|
+
j++;
|
|
1226
|
+
if (stop) break;
|
|
1227
|
+
}
|
|
1228
|
+
const firstLine = row.no;
|
|
1229
|
+
const lastLine = rows[j - 1]?.no ?? firstLine;
|
|
1230
|
+
const sym = bodyRunStart.get(firstLine);
|
|
1231
|
+
const notice = sym !== void 0 && sym.lineEnd === lastLine ? bodyFoldNotice(sym.name, firstLine, lastLine, shownPath, sym.lineStart) : skeletonGapNotice(firstLine, lastLine, shownPath);
|
|
1232
|
+
let runBytes = 0;
|
|
1233
|
+
for (let k = i; k < j; k++) runBytes += Buffer.byteLength(rows[k]?.raw ?? "", "utf-8") + 1;
|
|
1234
|
+
if (Buffer.byteLength(notice, "utf-8") + alignedPaddingBytes(j - i, layout) >= runBytes) {
|
|
1235
|
+
for (let k = i; k < j; k++) {
|
|
1236
|
+
const kept = rows[k];
|
|
1237
|
+
if (kept === void 0) continue;
|
|
1238
|
+
numbered.push(kept.raw);
|
|
1239
|
+
raw.push(kept.text);
|
|
1240
|
+
}
|
|
1241
|
+
} else {
|
|
1242
|
+
pushWithheld(numbered, notice, j - i, layout);
|
|
1243
|
+
withheldLines += lastLine - firstLine + 1;
|
|
1244
|
+
}
|
|
1245
|
+
i = j;
|
|
1246
|
+
}
|
|
1247
|
+
return withheldLines === 0 ? null : { numbered, raw, withheldLines };
|
|
1248
|
+
}
|
|
1249
|
+
function planSourceSkeleton(rows, normalizedPath, shownPath, originalBytes, layout = "compact") {
|
|
1250
|
+
if (!loadConfig().hints.skeleton_large_sources) return null;
|
|
1251
|
+
if (originalBytes < SKELETON_MIN_BODY_BYTES) return null;
|
|
1252
|
+
const language = detectLanguage(normalizedPath);
|
|
1253
|
+
if (!isTreeSitterAvailable(language)) return null;
|
|
1254
|
+
const fileText = rows.map((r) => r.text).join("\n");
|
|
1255
|
+
const symbols = parseSourceSymbolsTreeSitterOnly(fileText, normalizedPath, language);
|
|
1256
|
+
if (symbols === null) return null;
|
|
1257
|
+
if (symbols.length < SKELETON_MIN_SYMBOLS) return null;
|
|
1258
|
+
const plan = planSourceSkeletonRuns(rows, symbols, shownPath, layout);
|
|
1259
|
+
if (plan === null) return null;
|
|
1260
|
+
const notice = `Partial view: this ${originalBytes.toLocaleString("en-US")} B source file was replaced with its structural skeleton, its preamble and one line per declaration, with ${plan.withheldLines.toLocaleString("en-US")} line${plan.withheldLines === 1 ? "" : "s"} of bodies withheld (at least ${symbols.length} declaration${symbols.length === 1 ? "" : "s"} found). Run token-goat read "${shownPath}::SymbolName" for one body verbatim, or Read "${shownPath}" with offset=1, limit=${rows.length} for the whole file.`;
|
|
1261
|
+
if (layout === "aligned") {
|
|
1262
|
+
const body = fenceNumberedFileContent(plan.numbered.join("\n"), ALIGNED_LAYOUT_NOTE);
|
|
1263
|
+
return { numbered: [body.body], raw: plan.raw, kind: "read:source_skeleton", detail: shownPath, ratioCap: SKELETON_MAX_REPLACEMENT_RATIO, context: `${body.preamble}
|
|
1264
|
+
${notice}` };
|
|
1265
|
+
}
|
|
1266
|
+
return { numbered: [notice, fenceUntrustedFileContent(plan.numbered.join("\n"))], raw: plan.raw, kind: "read:source_skeleton", detail: shownPath, ratioCap: SKELETON_MAX_REPLACEMENT_RATIO };
|
|
1267
|
+
}
|
|
1268
|
+
function isStructuralRewriteAccepted(originalBytes, rewrittenBytes, ratioCap) {
|
|
1269
|
+
if (rewrittenBytes > originalBytes * ratioCap) return false;
|
|
1270
|
+
return isRewriteWorthwhile({ originalBytes, rewrittenBytes, noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() });
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/hooks_read_post.ts
|
|
1274
|
+
function extractReadOutput(raw) {
|
|
1275
|
+
return extractToolResponseField(raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
|
|
1276
|
+
}
|
|
1277
|
+
function readLayout(event) {
|
|
1278
|
+
return harnessNumbersReadContent(event) ? "aligned" : "compact";
|
|
1279
|
+
}
|
|
1280
|
+
function keepsLineNumbers(original, rewritten) {
|
|
1281
|
+
return original.split("\n").length === rewritten.split("\n").length;
|
|
1282
|
+
}
|
|
1283
|
+
function postReadHandlerInner(event, suppressStructuralHint) {
|
|
1284
|
+
const filePath = getFilePath(event);
|
|
1285
|
+
if (filePath === void 0) return passOutput();
|
|
1286
|
+
const normalized = normalizePath(filePath);
|
|
1287
|
+
const shown = displaySafePath(normalized);
|
|
1288
|
+
const respText = extractReadOutput(event.raw);
|
|
1289
|
+
if (isTruncatedReadDelivery(event, respText)) {
|
|
1290
|
+
markFileTruncated(normalized, !readRequestedSliceWindow(event).isExplicitSlice);
|
|
1291
|
+
}
|
|
1292
|
+
const postBasename = path2.basename(normalized);
|
|
1293
|
+
const diffSourcesEnabled = loadConfig().hints.serve_diff_on_reread;
|
|
1294
|
+
if (/\.(md|mdx|markdown|rst|txt)$/i.test(postBasename) || isSessionArtifactFile(normalized) || diffSourcesEnabled && isDiffableSource(postBasename)) {
|
|
1295
|
+
try {
|
|
1296
|
+
const onDisk = hostPathOfIndexKey(normalized);
|
|
1297
|
+
const sz = statSize(onDisk);
|
|
1298
|
+
if (sz !== null && sz <= 256 * 1024) {
|
|
1299
|
+
const content = fs2.readFileSync(onDisk);
|
|
1300
|
+
store(sessionStateKey(event), normalized, content);
|
|
1301
|
+
}
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (loadConfig().hints.cross_session_read_dedup) {
|
|
1306
|
+
try {
|
|
1307
|
+
const cwd = getCwd(event) ?? process.cwd();
|
|
1308
|
+
const project = findProject(cwd) ?? makeProjectAt(cwd);
|
|
1309
|
+
const source = readEvidenceFileText(normalized);
|
|
1310
|
+
if (source !== null) recordEvidence({ projectRoot: project.root, source: normalized, representation: "file", text: source });
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (loadConfig().hints.cross_session_read_dedup) {
|
|
1315
|
+
try {
|
|
1316
|
+
const cwd = getCwd(event) ?? process.cwd();
|
|
1317
|
+
let project = findProject(cwd);
|
|
1318
|
+
if (!project) {
|
|
1319
|
+
project = makeProjectAt(cwd);
|
|
1320
|
+
}
|
|
1321
|
+
const sessionState = exportSessionState();
|
|
1322
|
+
const mappedFiles = [];
|
|
1323
|
+
for (const fileEntry of sessionState.files) {
|
|
1324
|
+
const relPath = relPathWithinRoot(project.root, fileEntry.path);
|
|
1325
|
+
if (relPath !== null) {
|
|
1326
|
+
mappedFiles.push({
|
|
1327
|
+
rel_path: relPath,
|
|
1328
|
+
hit_count: fileEntry.readCount
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
writeSessionManifest(project.hash, sessionStateKey(event), { files: mappedFiles });
|
|
1333
|
+
} catch {
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (isSourceExtension(postBasename)) {
|
|
1337
|
+
try {
|
|
1338
|
+
const onDisk = hostPathOfIndexKey(normalized);
|
|
1339
|
+
const sz = statSize(onDisk);
|
|
1340
|
+
if (sz !== null && sz <= SLICE_ESTIMATE_SCAN_CAP_BYTES) {
|
|
1341
|
+
const lineCount = countTextLines(fs2.readFileSync(onDisk, "utf8"));
|
|
1342
|
+
const minLines = loadConfig().post_read_code_compress.min_lines;
|
|
1343
|
+
if (lineCount >= minLines && !suppressStructuralHint) {
|
|
1344
|
+
if (!meetsSavingsFloor(sz)) {
|
|
1345
|
+
logSuppressedDetection("read_structural_nav", event.sessionId, normalized);
|
|
1346
|
+
} else {
|
|
1347
|
+
recordStat("session_hint", 0, 0);
|
|
1348
|
+
return quietContextOutput(
|
|
1349
|
+
shown + " is " + lineCount + ' lines. Use `token-goat skeleton "' + shown + '"` or `token-goat outline "' + shown + '"` for structural navigation instead of a future full re-read.'
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return passOutput();
|
|
1358
|
+
}
|
|
1359
|
+
function recordReadAsServedOutput(event, deliveredRaw = null) {
|
|
1360
|
+
try {
|
|
1361
|
+
const cfg = loadConfig().bash_compress;
|
|
1362
|
+
if (!cfg.enabled) return;
|
|
1363
|
+
const filePath = getFilePath(event);
|
|
1364
|
+
if (filePath === void 0) return;
|
|
1365
|
+
const normalized = normalizePath(filePath);
|
|
1366
|
+
if (isImagePath(normalized)) return;
|
|
1367
|
+
const respText = extractReadOutput(event.raw);
|
|
1368
|
+
if (isTruncatedReadDelivery(event, respText)) return;
|
|
1369
|
+
if (deliveredRaw === null) recordActualSlice(event, normalized);
|
|
1370
|
+
const served = deliveredRaw ?? readWindowFromDisk(event, normalized);
|
|
1371
|
+
if (served === null) return;
|
|
1372
|
+
if (Buffer.byteLength(served, "utf-8") < Math.max(cfg.cache_min_bytes, IDENTICAL_READ_MIN_BODY_BYTES)) return;
|
|
1373
|
+
const window = readRequestedSliceWindow(event);
|
|
1374
|
+
let syntheticCommand = "Read " + normalized;
|
|
1375
|
+
if (window.isExplicitSlice) {
|
|
1376
|
+
syntheticCommand += " --offset " + window.offset;
|
|
1377
|
+
if (window.limit !== void 0) syntheticCommand += " --limit " + window.limit;
|
|
1378
|
+
}
|
|
1379
|
+
const id = storeBashOutputSync(syntheticCommand, served, 0, getCwd(event) ?? process.cwd());
|
|
1380
|
+
recordFileServedOutput(normalized, id);
|
|
1381
|
+
} catch {
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function elideAlreadyServedLines(event, respText) {
|
|
1385
|
+
if (!loadConfig().hints.elide_served_lines) return null;
|
|
1386
|
+
const filePath = getFilePath(event);
|
|
1387
|
+
if (filePath === void 0) return null;
|
|
1388
|
+
const normalized = normalizePath(filePath);
|
|
1389
|
+
if (isImagePath(normalized)) return null;
|
|
1390
|
+
if (isTruncatedReadDelivery(event, respText)) return null;
|
|
1391
|
+
const ids = getFileServedOutputs(normalized);
|
|
1392
|
+
if (ids.length === 0) return null;
|
|
1393
|
+
const parsed = parseReadDelivery(event, respText);
|
|
1394
|
+
if (parsed === null) return null;
|
|
1395
|
+
if (hasPreciseSecret(respText)) return null;
|
|
1396
|
+
const bodies = [];
|
|
1397
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
1398
|
+
const id = ids[i];
|
|
1399
|
+
if (id === void 0) continue;
|
|
1400
|
+
const prior = getBashOutput(id);
|
|
1401
|
+
if (prior !== null) bodies.push(indexServedBody(id, prior.output));
|
|
1402
|
+
}
|
|
1403
|
+
if (bodies.length === 0) return null;
|
|
1404
|
+
const cuts = planServedElisions(parsed.rows, bodies);
|
|
1405
|
+
if (cuts.length === 0) return null;
|
|
1406
|
+
const layout = readLayout(event);
|
|
1407
|
+
const bodyRows = [];
|
|
1408
|
+
let at = 0;
|
|
1409
|
+
for (const cut of cuts) {
|
|
1410
|
+
for (let i = at; i < cut.start; i++) bodyRows.push(parsed.rows[i]?.raw ?? "");
|
|
1411
|
+
const first2 = parsed.rows[cut.start];
|
|
1412
|
+
const last = parsed.rows[cut.start + cut.len - 1];
|
|
1413
|
+
if (first2 === void 0 || last === void 0) return null;
|
|
1414
|
+
pushWithheld(bodyRows, servedRunNotice(first2.no, last.no, cut.id, cut.len), cut.len, layout);
|
|
1415
|
+
at = cut.start + cut.len;
|
|
1416
|
+
}
|
|
1417
|
+
for (let i = at; i < parsed.rows.length; i++) bodyRows.push(parsed.rows[i]?.raw ?? "");
|
|
1418
|
+
const fenced = fenceNumberedFileContent(bodyRows.join("\n"), ALIGNED_LAYOUT_NOTE);
|
|
1419
|
+
const rewritten = [...parsed.header, fenced.body, ...parsed.trailer].join("\n");
|
|
1420
|
+
if (layout === "aligned" && !keepsLineNumbers(respText, rewritten)) return null;
|
|
1421
|
+
const originalBytes = Buffer.byteLength(respText, "utf-8");
|
|
1422
|
+
if (layout === "aligned") {
|
|
1423
|
+
if (!isRewriteWorthwhile({
|
|
1424
|
+
originalBytes,
|
|
1425
|
+
rewrittenBytes: Buffer.byteLength(rewritten, "utf-8") + Buffer.byteLength(fenced.preamble, "utf-8"),
|
|
1426
|
+
noticeBytes: 0,
|
|
1427
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
1428
|
+
})) {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
return emitRewriteWithContext(rewritten, fenced.preamble, "read", { kind: "read:served_elide", originalBytes });
|
|
1432
|
+
}
|
|
1433
|
+
if (!isRewriteWorthwhile({
|
|
1434
|
+
originalBytes,
|
|
1435
|
+
rewrittenBytes: Buffer.byteLength(rewritten, "utf-8"),
|
|
1436
|
+
noticeBytes: 0,
|
|
1437
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
1438
|
+
})) {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
return emitRewrite(rewritten, "read", { kind: "read:served_elide", originalBytes });
|
|
1442
|
+
}
|
|
1443
|
+
function structuralFoldInputs(event, respText) {
|
|
1444
|
+
const filePath = getFilePath(event);
|
|
1445
|
+
if (filePath === void 0) return null;
|
|
1446
|
+
if (readRequestedSliceWindow(event).isExplicitSlice) return null;
|
|
1447
|
+
if (isTruncatedReadDelivery(event, respText)) return null;
|
|
1448
|
+
if (hasPreciseSecret(respText)) return null;
|
|
1449
|
+
const parsed = parseReadDelivery(event, respText);
|
|
1450
|
+
if (parsed === null) return null;
|
|
1451
|
+
const normalized = normalizePath(filePath);
|
|
1452
|
+
const shown = displaySafePath(toDisplayPath(findProject(getCwd(event) ?? process.cwd())?.root, normalized));
|
|
1453
|
+
return { rows: parsed.rows, header: parsed.header, trailer: parsed.trailer, normalized, shown, originalBytes: Buffer.byteLength(respText, "utf-8"), respText, layout: readLayout(event), startLine: readStartLine(event) };
|
|
1454
|
+
}
|
|
1455
|
+
function emitStructuralFold(inputs, fold) {
|
|
1456
|
+
const rewritten = [...inputs.header, ...fold.numbered, ...inputs.trailer].join("\n");
|
|
1457
|
+
if (fold.context !== void 0) {
|
|
1458
|
+
if (!keepsLineNumbers(inputs.respText, rewritten)) return null;
|
|
1459
|
+
const renderedOriginal = numberedRenderBytes(inputs.respText, inputs.startLine);
|
|
1460
|
+
const renderedRewrite = numberedRenderBytes(rewritten, inputs.startLine) + Buffer.byteLength(fold.context, "utf-8");
|
|
1461
|
+
if (!isStructuralRewriteAccepted(renderedOriginal, renderedRewrite, fold.ratioCap)) return null;
|
|
1462
|
+
return {
|
|
1463
|
+
output: emitRewriteWithContext(rewritten, fold.context, "read", { kind: fold.kind, originalBytes: inputs.originalBytes, detail: fold.detail }),
|
|
1464
|
+
deliveredRaw: fold.raw.join("\n")
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
if (!isStructuralRewriteAccepted(inputs.originalBytes, Buffer.byteLength(rewritten, "utf-8"), fold.ratioCap)) return null;
|
|
1468
|
+
return {
|
|
1469
|
+
output: emitRewrite(rewritten, "read", { kind: fold.kind, originalBytes: inputs.originalBytes, detail: fold.detail }),
|
|
1470
|
+
deliveredRaw: fold.raw.join("\n")
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
function foldMarkdownOutline(event, respText) {
|
|
1474
|
+
const inputs = structuralFoldInputs(event, respText);
|
|
1475
|
+
if (inputs === null) return null;
|
|
1476
|
+
const fold = planMarkdownOutline(inputs.rows, inputs.normalized, inputs.shown, inputs.originalBytes, inputs.layout);
|
|
1477
|
+
return fold === null ? null : emitStructuralFold(inputs, fold);
|
|
1478
|
+
}
|
|
1479
|
+
function foldSourceSkeleton(event, respText) {
|
|
1480
|
+
const inputs = structuralFoldInputs(event, respText);
|
|
1481
|
+
if (inputs === null) return null;
|
|
1482
|
+
const fold = planSourceSkeleton(inputs.rows, inputs.normalized, inputs.shown, inputs.originalBytes, inputs.layout);
|
|
1483
|
+
return fold === null ? null : emitStructuralFold(inputs, fold);
|
|
1484
|
+
}
|
|
1485
|
+
function foldCodeBodies(event, respText) {
|
|
1486
|
+
if (!foldingEnabled()) return null;
|
|
1487
|
+
const filePath = getFilePath(event);
|
|
1488
|
+
if (filePath === void 0) return null;
|
|
1489
|
+
const normalized = normalizePath(filePath);
|
|
1490
|
+
if (isImagePath(normalized)) return null;
|
|
1491
|
+
const sliceWin = readRequestedSliceWindow(event);
|
|
1492
|
+
const requestedOffset = sliceWin.offset;
|
|
1493
|
+
if (requestedOffset !== void 0 && readStartLine(event) !== requestedOffset) return null;
|
|
1494
|
+
if (isTruncatedReadDelivery(event, respText)) return null;
|
|
1495
|
+
if (hasPreciseSecret(respText)) return null;
|
|
1496
|
+
const parsed = parseReadDelivery(event, respText);
|
|
1497
|
+
if (parsed === null) return null;
|
|
1498
|
+
const shown = displaySafePath(toDisplayPath(findProject(getCwd(event) ?? process.cwd())?.root, normalized));
|
|
1499
|
+
const layout = readLayout(event);
|
|
1500
|
+
const folded = foldDelivery(parsed.rows, normalized, shown, sliceWin.isExplicitSlice, layout);
|
|
1501
|
+
if (folded === null) return null;
|
|
1502
|
+
const originalBytes = Buffer.byteLength(respText, "utf-8");
|
|
1503
|
+
if (layout === "aligned") {
|
|
1504
|
+
const fenced = fenceNumberedFileContent(folded.numbered.join("\n"), ALIGNED_LAYOUT_NOTE);
|
|
1505
|
+
const rewritten2 = [...parsed.header, fenced.body, ...parsed.trailer].join("\n");
|
|
1506
|
+
if (!keepsLineNumbers(respText, rewritten2)) return null;
|
|
1507
|
+
if (!isRewriteWorthwhile({
|
|
1508
|
+
originalBytes,
|
|
1509
|
+
rewrittenBytes: Buffer.byteLength(rewritten2, "utf-8") + Buffer.byteLength(fenced.preamble, "utf-8"),
|
|
1510
|
+
noticeBytes: 0,
|
|
1511
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
1512
|
+
})) {
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
return {
|
|
1516
|
+
output: emitRewriteWithContext(rewritten2, fenced.preamble, "read", { kind: "read:body_fold", originalBytes, detail: foldDetail(normalized, folded.folds) }),
|
|
1517
|
+
deliveredRaw: folded.raw.join("\n")
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
const rewritten = [...parsed.header, fenceUntrustedFileContent(folded.numbered.join("\n")), ...parsed.trailer].join("\n");
|
|
1521
|
+
if (!isRewriteWorthwhile({
|
|
1522
|
+
originalBytes,
|
|
1523
|
+
rewrittenBytes: Buffer.byteLength(rewritten, "utf-8"),
|
|
1524
|
+
noticeBytes: 0,
|
|
1525
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
1526
|
+
})) {
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1529
|
+
return {
|
|
1530
|
+
output: emitRewrite(rewritten, "read", { kind: "read:body_fold", originalBytes, detail: foldDetail(normalized, folded.folds) }),
|
|
1531
|
+
deliveredRaw: folded.raw.join("\n")
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function postReadHandler(event) {
|
|
1535
|
+
const respText = extractReadOutput(event.raw);
|
|
1536
|
+
const elided = elideAlreadyServedLines(event, respText);
|
|
1537
|
+
const outlined = elided === null ? foldMarkdownOutline(event, respText) : null;
|
|
1538
|
+
const skeletoned = elided === null && outlined === null ? foldSourceSkeleton(event, respText) : null;
|
|
1539
|
+
const folded = elided === null && outlined === null && skeletoned === null ? foldCodeBodies(event, respText) : null;
|
|
1540
|
+
const rewrite = elided ?? outlined?.output ?? skeletoned?.output ?? folded?.output ?? null;
|
|
1541
|
+
const out = applyHintTracking(event, postReadHandlerInner(event, rewrite !== null), classifyReadHint);
|
|
1542
|
+
const deliveredRaw = outlined?.deliveredRaw ?? skeletoned?.deliveredRaw ?? folded?.deliveredRaw ?? null;
|
|
1543
|
+
if (deliveredRaw !== null) forgetFoldedWindow(event);
|
|
1544
|
+
recordReadAsServedOutput(event, deliveredRaw);
|
|
1545
|
+
recordLastReadShape(event, respText, outlined !== null ? "outline" : skeletoned !== null ? "skeleton" : folded !== null ? "fold" : null);
|
|
1546
|
+
return rewrite ?? out;
|
|
1547
|
+
}
|
|
1548
|
+
function recordLastReadShape(event, respText, rewrite) {
|
|
1549
|
+
try {
|
|
1550
|
+
const filePath = getFilePath(event);
|
|
1551
|
+
if (filePath === void 0) return;
|
|
1552
|
+
const normalized = normalizePath(filePath);
|
|
1553
|
+
if (isImagePath(normalized)) return;
|
|
1554
|
+
let shape = rewrite;
|
|
1555
|
+
if (shape === null && isTruncatedReadDelivery(event, respText)) shape = "truncated";
|
|
1556
|
+
if (shape === null && readRequestedSliceWindow(event).isExplicitSlice && windowCoversFile(event) !== true) shape = "partial";
|
|
1557
|
+
recordReadShape(sessionStateKey(event), normalized, shape);
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function windowCoversFile(event) {
|
|
1562
|
+
const resp = event.raw["tool_response"];
|
|
1563
|
+
const file = resp?.["file"];
|
|
1564
|
+
const start = file?.["startLine"];
|
|
1565
|
+
const num = file?.["numLines"];
|
|
1566
|
+
const total = file?.["totalLines"];
|
|
1567
|
+
if (typeof start !== "number" || typeof num !== "number" || typeof total !== "number") return void 0;
|
|
1568
|
+
return start <= 1 && start + num - 1 >= total;
|
|
1569
|
+
}
|
|
1570
|
+
function forgetFoldedWindow(event) {
|
|
1571
|
+
if (!readRequestedSliceWindow(event).isExplicitSlice) return;
|
|
1572
|
+
const filePath = getFilePath(event);
|
|
1573
|
+
if (filePath !== void 0) resetFileLineRanges(normalizePath(filePath));
|
|
1574
|
+
}
|
|
1575
|
+
registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
|
|
1576
|
+
|
|
1577
|
+
// src/hooks_grep.ts
|
|
1578
|
+
init_define_import_meta_env();
|
|
1579
|
+
function grepIntInput(toolInput, key) {
|
|
1580
|
+
const value = toolInput[key];
|
|
1581
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1582
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
1583
|
+
const n = Number(value);
|
|
1584
|
+
if (Number.isFinite(n)) return n;
|
|
1585
|
+
}
|
|
1586
|
+
return void 0;
|
|
1587
|
+
}
|
|
1588
|
+
function grepSignature(toolInput) {
|
|
1589
|
+
const pattern = toolInput["pattern"];
|
|
1590
|
+
if (typeof pattern !== "string" || pattern === "") return null;
|
|
1591
|
+
const path8 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
|
|
1592
|
+
const outputMode = typeof toolInput["output_mode"] === "string" ? toolInput["output_mode"] : "files_with_matches";
|
|
1593
|
+
const glob = typeof toolInput["glob"] === "string" ? toolInput["glob"] : "";
|
|
1594
|
+
const type = typeof toolInput["type"] === "string" ? toolInput["type"] : "";
|
|
1595
|
+
const caseInsensitive = toolInput["-i"] === true;
|
|
1596
|
+
const onlyMatching = toolInput["-o"] === true;
|
|
1597
|
+
const multiline = toolInput["multiline"] === true;
|
|
1598
|
+
const lineNumbers = toolInput["-n"] !== false;
|
|
1599
|
+
const contextA = grepIntInput(toolInput, "-A");
|
|
1600
|
+
const contextB = grepIntInput(toolInput, "-B");
|
|
1601
|
+
const contextC = grepIntInput(toolInput, "-C") ?? grepIntInput(toolInput, "context");
|
|
1602
|
+
const headLimit = grepIntInput(toolInput, "head_limit");
|
|
1603
|
+
const offset = grepIntInput(toolInput, "offset");
|
|
1604
|
+
return JSON.stringify([
|
|
1605
|
+
pattern,
|
|
1606
|
+
path8,
|
|
1607
|
+
outputMode,
|
|
1608
|
+
glob,
|
|
1609
|
+
type,
|
|
1610
|
+
caseInsensitive,
|
|
1611
|
+
onlyMatching,
|
|
1612
|
+
multiline,
|
|
1613
|
+
lineNumbers,
|
|
1614
|
+
contextA,
|
|
1615
|
+
contextB,
|
|
1616
|
+
contextC,
|
|
1617
|
+
headLimit,
|
|
1618
|
+
offset
|
|
1619
|
+
]);
|
|
1620
|
+
}
|
|
1621
|
+
var { post: dedupPostHandler, pre: preGrepDedupHandler } = makeDedupHintHandlers({
|
|
1622
|
+
toolName: "Grep",
|
|
1623
|
+
buildSignature: grepSignature,
|
|
1624
|
+
recordQuery: recordGrepQuery,
|
|
1625
|
+
getMatchCount: getGrepMatchCount,
|
|
1626
|
+
minMatchesConfigKey: "grep_dedup_min_matches",
|
|
1627
|
+
statName: "grep_dedup_hint"
|
|
1628
|
+
});
|
|
1629
|
+
var CONTENT_LINE_RE = /^(.+?):(\d+):(.*)$/s;
|
|
1630
|
+
function foldGrepContentHandler(event) {
|
|
1631
|
+
try {
|
|
1632
|
+
if (getToolName(event) !== "Grep") return passOutput();
|
|
1633
|
+
const toolInput = getToolInput(event);
|
|
1634
|
+
if (toolInput["output_mode"] !== "content") return passOutput();
|
|
1635
|
+
if (toolInput["multiline"] === true) return passOutput();
|
|
1636
|
+
if (toolInput["-A"] !== void 0 || toolInput["-B"] !== void 0 || toolInput["-C"] !== void 0 || toolInput["context"] !== void 0) {
|
|
1637
|
+
return passOutput();
|
|
1638
|
+
}
|
|
1639
|
+
if (toolInput["-n"] === false) return passOutput();
|
|
1640
|
+
const rawText = extractToolResponseField(event.raw, OUTPUT_FIRST_TOOL_RESPONSE_KEYS);
|
|
1641
|
+
if (!rawText) return passOutput();
|
|
1642
|
+
const text = redactSecrets(rawText).text;
|
|
1643
|
+
const rawLines = text.split(/\r\n|\r|\n/);
|
|
1644
|
+
const parsed = [];
|
|
1645
|
+
for (const line of rawLines) {
|
|
1646
|
+
if (line.trim() === "") continue;
|
|
1647
|
+
const m = CONTENT_LINE_RE.exec(line);
|
|
1648
|
+
if (!m) return passOutput();
|
|
1649
|
+
parsed.push({ file: m[1], lineNo: m[2], rest: m[3] });
|
|
1650
|
+
}
|
|
1651
|
+
if (parsed.length === 0) return passOutput();
|
|
1652
|
+
const order = [];
|
|
1653
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1654
|
+
for (const p of parsed) {
|
|
1655
|
+
let group = groups.get(p.file);
|
|
1656
|
+
if (group === void 0) {
|
|
1657
|
+
group = [];
|
|
1658
|
+
groups.set(p.file, group);
|
|
1659
|
+
order.push(p.file);
|
|
1660
|
+
}
|
|
1661
|
+
group.push({ lineNo: p.lineNo, rest: p.rest });
|
|
1662
|
+
}
|
|
1663
|
+
const hasSharedFile = order.some((f) => groups.get(f).length >= 2);
|
|
1664
|
+
if (!hasSharedFile) return passOutput();
|
|
1665
|
+
const foldedLines = [];
|
|
1666
|
+
for (const f of order) {
|
|
1667
|
+
foldedLines.push(f);
|
|
1668
|
+
for (const { lineNo, rest } of groups.get(f)) {
|
|
1669
|
+
foldedLines.push(` ${lineNo}: ${rest}`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const rewritten = foldedLines.join("\n");
|
|
1673
|
+
const originalBytes = Buffer.byteLength(text, "utf-8");
|
|
1674
|
+
const rewrittenBytes = Buffer.byteLength(rewritten, "utf-8");
|
|
1675
|
+
if (!isRewriteWorthwhile({
|
|
1676
|
+
originalBytes,
|
|
1677
|
+
rewrittenBytes,
|
|
1296
1678
|
noticeBytes: 0,
|
|
1297
1679
|
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
1298
1680
|
})) {
|
|
@@ -1332,12 +1714,12 @@ function preGrepHandler(event) {
|
|
|
1332
1714
|
if (getToolName(event) !== "Grep") return passOutput();
|
|
1333
1715
|
const toolInput = getToolInput(event);
|
|
1334
1716
|
const structSearch = extractGrepStructuralSearch(toolInput);
|
|
1335
|
-
if (structSearch !== null) {
|
|
1717
|
+
if (structSearch !== null && (!structSearch.isDoc || docNavigation(structSearch.filePath).section)) {
|
|
1336
1718
|
recordStat("session_hint", 0, 0);
|
|
1337
1719
|
const { isDoc } = structSearch;
|
|
1338
1720
|
const filePath = displaySafePath(structSearch.filePath);
|
|
1339
|
-
const target = hintTarget(structSearch.filePath, isDoc ? "section" : "symbol", { cwd: getCwd(event) ?? process.cwd(), event });
|
|
1340
|
-
const hint = isDoc ?
|
|
1721
|
+
const target = hintTarget(structSearch.filePath, isDoc ? "section" : "symbol", { cwd: getCwd(event) ?? process.cwd(), event, pattern: String(toolInput["pattern"] ?? "") });
|
|
1722
|
+
const hint = isDoc ? docSectionHint(filePath, target.name, "Scanning a document for headings loads large match output.") : target.real ? leadWithCommand('token-goat read "' + filePath + "::" + target.name + '"', 'to inspect one symbol, or `token-goat skeleton "' + filePath + '"` to see the file structure', "Scanning a source file for symbols loads large match output.") : leadWithCommand('token-goat skeleton "' + filePath + '"', 'to see the file structure, or `token-goat read "' + filePath + '::SymbolName"` to inspect a specific symbol', "Scanning a source file for symbols loads large match output.");
|
|
1341
1723
|
return contextOutput(hint);
|
|
1342
1724
|
}
|
|
1343
1725
|
return applyHintTracking(event, preGrepDedupHandler(event), uncorrelatedHint("grep_dedup_hint"));
|
|
@@ -1356,18 +1738,23 @@ registerHook("post_tool_use", postGrepHandler, { toolName: "Grep" });
|
|
|
1356
1738
|
|
|
1357
1739
|
// src/hooks_glob.ts
|
|
1358
1740
|
init_define_import_meta_env();
|
|
1359
|
-
function
|
|
1741
|
+
function isSameDirAsCwd(target, cwd) {
|
|
1742
|
+
const norm = (p) => foldPath(normalizePath(p).replace(/\/+$/, ""));
|
|
1743
|
+
return norm(target) === norm(cwd);
|
|
1744
|
+
}
|
|
1745
|
+
function isBroadCatchAllGlob(pattern, pathArg, cwd) {
|
|
1360
1746
|
const p = pattern.trim();
|
|
1361
1747
|
const isBroad = p === "*" || p === "**/*" || p === "**" || p === "*.*" || p === "**/*.*" || p === "**/*.**";
|
|
1362
1748
|
if (!isBroad) return false;
|
|
1363
1749
|
const target = (pathArg ?? "").trim().replace(/[\\/]+$/, "");
|
|
1364
|
-
|
|
1750
|
+
if (target === "" || target === "." || target === "./" || target === ".\\" || !target.includes("/") && !target.includes("\\")) return true;
|
|
1751
|
+
return cwd !== void 0 && isSameDirAsCwd(target, cwd);
|
|
1365
1752
|
}
|
|
1366
1753
|
function globSignature(toolInput) {
|
|
1367
1754
|
const pattern = toolInput["pattern"];
|
|
1368
1755
|
if (typeof pattern !== "string" || pattern === "") return null;
|
|
1369
|
-
const
|
|
1370
|
-
return JSON.stringify([pattern,
|
|
1756
|
+
const path8 = typeof toolInput["path"] === "string" ? toolInput["path"] : "";
|
|
1757
|
+
return JSON.stringify([pattern, path8]);
|
|
1371
1758
|
}
|
|
1372
1759
|
var { post: postGlobHandler, pre: preGlobDedupHandler } = makeDedupHintHandlers({
|
|
1373
1760
|
toolName: "Glob",
|
|
@@ -1383,7 +1770,7 @@ function preGlobHandler(event) {
|
|
|
1383
1770
|
const toolInput = getToolInput(event);
|
|
1384
1771
|
const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"] : "";
|
|
1385
1772
|
const pathArg = typeof toolInput["path"] === "string" ? toolInput["path"] : typeof toolInput["paths"] === "string" ? toolInput["paths"] : Array.isArray(toolInput["paths"]) && toolInput["paths"].length === 1 && typeof toolInput["paths"][0] === "string" ? toolInput["paths"][0] : void 0;
|
|
1386
|
-
if (pattern && isBroadCatchAllGlob(pattern, pathArg)) {
|
|
1773
|
+
if (pattern && isBroadCatchAllGlob(pattern, pathArg, getCwd(event))) {
|
|
1387
1774
|
recordStat("session_hint", 0, 0);
|
|
1388
1775
|
return contextOutput(
|
|
1389
1776
|
'Broad recursive glob pattern "' + displaySafeText(pattern) + '" traverses entire directory trees and may dump thousands of paths into context. Use `token-goat map --compact` to inspect project directory structure efficiently, or narrow the glob path.'
|
|
@@ -1399,17 +1786,25 @@ registerHook("post_tool_use", postGlobHandler, { toolName: "Glob" });
|
|
|
1399
1786
|
|
|
1400
1787
|
// src/hooks_edit.ts
|
|
1401
1788
|
init_define_import_meta_env();
|
|
1402
|
-
import * as
|
|
1789
|
+
import * as path3 from "node:path";
|
|
1403
1790
|
import { statSync as statSync3 } from "node:fs";
|
|
1404
1791
|
function postEditHandlerInner(event) {
|
|
1405
|
-
const
|
|
1406
|
-
if (
|
|
1792
|
+
const filePaths = getFilePaths(event);
|
|
1793
|
+
if (filePaths.length === 0) return passOutput();
|
|
1794
|
+
const normalizedPaths = filePaths.map((p) => recordEditedFile(event, p));
|
|
1795
|
+
if (normalizedPaths.length !== 1) return passOutput();
|
|
1796
|
+
const normalized = normalizedPaths[0];
|
|
1797
|
+
return markdownSectionHint(event, normalized);
|
|
1798
|
+
}
|
|
1799
|
+
function recordEditedFile(event, filePath) {
|
|
1407
1800
|
const normalized = normalizePath(filePath);
|
|
1408
1801
|
recordFileEdit(normalized);
|
|
1409
|
-
|
|
1802
|
+
bookEditAfterPartialRead(event, normalized);
|
|
1803
|
+
const indexPath = resolveIndexPath(filePath, getCwd(event) ?? process.cwd());
|
|
1804
|
+
const underSystemTemp = isUnderSystemTemp(indexPath);
|
|
1410
1805
|
if (!underSystemTemp) {
|
|
1411
1806
|
try {
|
|
1412
|
-
appendDirtyPath(
|
|
1807
|
+
appendDirtyPath(indexPath);
|
|
1413
1808
|
} catch (e) {
|
|
1414
1809
|
recordStat("dirty_queue_append_failed", 0, 0, void 0, extractErrorMessage(e));
|
|
1415
1810
|
}
|
|
@@ -1419,7 +1814,7 @@ function postEditHandlerInner(event) {
|
|
|
1419
1814
|
recordStat("worker_healthcheck_failed", 0, 0, void 0, extractErrorMessage(e));
|
|
1420
1815
|
}
|
|
1421
1816
|
try {
|
|
1422
|
-
recordKnownRootThrottled(
|
|
1817
|
+
recordKnownRootThrottled(indexPath);
|
|
1423
1818
|
} catch (e) {
|
|
1424
1819
|
recordStat("known_root_record_failed", 0, 0, void 0, extractErrorMessage(e));
|
|
1425
1820
|
}
|
|
@@ -1427,18 +1822,24 @@ function postEditHandlerInner(event) {
|
|
|
1427
1822
|
if (loadConfig().hints.stable_doc_compacts) {
|
|
1428
1823
|
markCompactStale(compactPathFor(normalized));
|
|
1429
1824
|
}
|
|
1430
|
-
|
|
1825
|
+
return normalized;
|
|
1826
|
+
}
|
|
1827
|
+
function markdownSectionHint(event, normalized) {
|
|
1828
|
+
const editedBasename = path3.basename(normalized);
|
|
1431
1829
|
if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
|
|
1432
1830
|
let editedSize = Infinity;
|
|
1433
1831
|
try {
|
|
1434
|
-
editedSize = statSync3(normalized).size;
|
|
1832
|
+
editedSize = statSync3(hostPathOfIndexKey(normalized)).size;
|
|
1435
1833
|
} catch {
|
|
1436
1834
|
}
|
|
1835
|
+
const repeatKey = `edit-section-hint:${normalized}:${getCompactedAt()}`;
|
|
1836
|
+
if (wasHintShown(repeatKey)) return passOutput();
|
|
1437
1837
|
if (!meetsSavingsFloor(editedSize)) {
|
|
1438
1838
|
logSuppressedDetection("edit_reread_suggest", event.sessionId, normalized);
|
|
1439
1839
|
} else {
|
|
1440
1840
|
const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
|
|
1441
1841
|
const heading = hintTarget(normalized, "section", { placeholder: "HeadingName" });
|
|
1842
|
+
markHintShown(repeatKey);
|
|
1442
1843
|
return contextOutput(
|
|
1443
1844
|
leadWithCommand('token-goat section "' + escapedPath + "::" + heading.name + '"', "to re-read a specific section rather than the full file", displaySafePath(editedBasename) + " was edited."),
|
|
1444
1845
|
[escapedPath]
|
|
@@ -1447,6 +1848,16 @@ function postEditHandlerInner(event) {
|
|
|
1447
1848
|
}
|
|
1448
1849
|
return passOutput();
|
|
1449
1850
|
}
|
|
1851
|
+
function bookEditAfterPartialRead(event, normalized) {
|
|
1852
|
+
try {
|
|
1853
|
+
const stateKey = sessionStateKey(event);
|
|
1854
|
+
const shape = readShapeOf(stateKey, normalized);
|
|
1855
|
+
if (shape === null) return;
|
|
1856
|
+
recordStat("edit_after_fold", 0, 0, void 0, `last_read=${shape} tool=${event.toolName ?? "unknown"}`);
|
|
1857
|
+
if (event.toolName === "Write") recordReadShape(stateKey, normalized, null);
|
|
1858
|
+
} catch {
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1450
1861
|
function postEditHandler(event) {
|
|
1451
1862
|
return applyHintTracking(event, postEditHandlerInner(event), classifyEditHint);
|
|
1452
1863
|
}
|
|
@@ -1457,7 +1868,7 @@ registerHook("post_tool_use", postEditHandler, { toolName: "NotebookEdit" });
|
|
|
1457
1868
|
|
|
1458
1869
|
// src/hooks_write.ts
|
|
1459
1870
|
init_define_import_meta_env();
|
|
1460
|
-
import { readFileSync as
|
|
1871
|
+
import { readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
|
|
1461
1872
|
var MAX_LINES_FOR_DIFF = 4e3;
|
|
1462
1873
|
var MAX_OLD_FILE_BYTES_FOR_DIFF = 4 * 1024 * 1024;
|
|
1463
1874
|
function splitLines(text) {
|
|
@@ -1498,7 +1909,7 @@ function preWriteRewriteHandler(event) {
|
|
|
1498
1909
|
if (stat.size > MAX_OLD_FILE_BYTES_FOR_DIFF) return passOutput();
|
|
1499
1910
|
let oldContent;
|
|
1500
1911
|
try {
|
|
1501
|
-
oldContent =
|
|
1912
|
+
oldContent = readFileSync6(filePath, "utf-8");
|
|
1502
1913
|
} catch {
|
|
1503
1914
|
return passOutput();
|
|
1504
1915
|
}
|
|
@@ -1524,7 +1935,8 @@ registerHook("pre_tool_use", preWriteRewriteHandler, { toolName: "Write" });
|
|
|
1524
1935
|
|
|
1525
1936
|
// src/hooks_compact.ts
|
|
1526
1937
|
init_define_import_meta_env();
|
|
1527
|
-
import * as
|
|
1938
|
+
import * as fs3 from "node:fs";
|
|
1939
|
+
import * as path4 from "node:path";
|
|
1528
1940
|
|
|
1529
1941
|
// src/harness_channels.ts
|
|
1530
1942
|
init_define_import_meta_env();
|
|
@@ -1536,8 +1948,21 @@ var PRE_COMPACT_CONTEXT_DROPPED = /* @__PURE__ */ new Set(["copilot_cli", "codex
|
|
|
1536
1948
|
function dropsPreCompactContext() {
|
|
1537
1949
|
return PRE_COMPACT_CONTEXT_DROPPED.has(getHarnessName());
|
|
1538
1950
|
}
|
|
1951
|
+
var PRE_COMPACT_RESUMES_ON_PROMPT = /* @__PURE__ */ new Set(["copilot_cli"]);
|
|
1952
|
+
function resumesCompactionOnPrompt() {
|
|
1953
|
+
return PRE_COMPACT_RESUMES_ON_PROMPT.has(getHarnessName()) && !dropsPromptSubmitContext();
|
|
1954
|
+
}
|
|
1539
1955
|
|
|
1540
1956
|
// src/hooks_compact.ts
|
|
1957
|
+
function recordSessionCompaction(sessionId, now) {
|
|
1958
|
+
const p = sessionSidecarPath(sessionId ?? "", COMPACTED_AT_SUFFIX);
|
|
1959
|
+
if (!p) return;
|
|
1960
|
+
try {
|
|
1961
|
+
ensureDirSync(path4.dirname(p));
|
|
1962
|
+
fs3.writeFileSync(p, String(now), "utf8");
|
|
1963
|
+
} catch {
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1541
1966
|
function preCompactHandler(event) {
|
|
1542
1967
|
const cfg = loadConfig().compact_assist;
|
|
1543
1968
|
let out = passOutput();
|
|
@@ -1548,12 +1973,14 @@ function preCompactHandler(event) {
|
|
|
1548
1973
|
|
|
1549
1974
|
${manifest}`);
|
|
1550
1975
|
} else if (event.sessionId) {
|
|
1551
|
-
|
|
1976
|
+
replacePendingContextBlock(sessionStateKey(event), `${MANIFEST_RECOVERY_PREAMBLE}
|
|
1552
1977
|
|
|
1553
|
-
${manifest.slice(0, MAX_PENDING_CONTEXT_BYTES - MANIFEST_RECOVERY_PREAMBLE.length - 2)}
|
|
1978
|
+
${manifest.slice(0, MAX_PENDING_CONTEXT_BYTES - MANIFEST_RECOVERY_PREAMBLE.length - 2)}`, MANIFEST_RECOVERY_PREAMBLE);
|
|
1554
1979
|
}
|
|
1555
1980
|
}
|
|
1556
|
-
|
|
1981
|
+
const now = Date.now();
|
|
1982
|
+
markCompacted(now);
|
|
1983
|
+
recordSessionCompaction(event.sessionId, now);
|
|
1557
1984
|
return out;
|
|
1558
1985
|
}
|
|
1559
1986
|
registerHook("pre_compact", preCompactHandler);
|
|
@@ -1569,11 +1996,13 @@ function postCompactHandler(event) {
|
|
|
1569
1996
|
const cwd = normalizePath(getCwd(event) ?? process.cwd());
|
|
1570
1997
|
const spell = (p) => foldPath(p.replaceAll("\\", "/"));
|
|
1571
1998
|
const haystack = spell(summary);
|
|
1572
|
-
const
|
|
1573
|
-
const rel =
|
|
1574
|
-
const forms = rel !== "" && !rel.startsWith("..") && !
|
|
1999
|
+
const kept = sample.filter((p) => {
|
|
2000
|
+
const rel = path4.relative(cwd, p);
|
|
2001
|
+
const forms = rel !== "" && !rel.startsWith("..") && !path4.isAbsolute(rel) ? [p, rel] : [p];
|
|
1575
2002
|
return forms.some((f) => haystack.includes(spell(f)));
|
|
1576
|
-
})
|
|
2003
|
+
});
|
|
2004
|
+
const survived = kept.length;
|
|
2005
|
+
recordCompactDroppedPaths(sessionStateKey(event), typeof raw === "string" ? sample.filter((p) => !kept.includes(p)) : null);
|
|
1577
2006
|
const trigger = typeof event.raw["trigger"] === "string" ? event.raw["trigger"] : "unknown";
|
|
1578
2007
|
const budget = loadConfig().compact_assist.summary_budget_chars;
|
|
1579
2008
|
const over = budget > 0 && summary.length > budget ? 1 : 0;
|
|
@@ -1598,7 +2027,7 @@ init_define_import_meta_env();
|
|
|
1598
2027
|
var TRACKED_SKILL = "token-goat";
|
|
1599
2028
|
var MAX_COMMANDS_SHOWN = 8;
|
|
1600
2029
|
async function currentCommandNames() {
|
|
1601
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
2030
|
+
const { buildProgram } = await import("./token-goat-chunk-HOJLCGDP.mjs");
|
|
1602
2031
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
1603
2032
|
}
|
|
1604
2033
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -1747,10 +2176,14 @@ async function userPromptSubmitHandler(event) {
|
|
|
1747
2176
|
}
|
|
1748
2177
|
const driftNudge = await checkSkillVersionDrift(event.sessionId);
|
|
1749
2178
|
const residentHints = residentContextHints(event);
|
|
1750
|
-
|
|
2179
|
+
const resumed = resumesCompactionOnPrompt() ? peekPendingContext(sessionStateKey(event)) : null;
|
|
2180
|
+
if (parts.length === 0 && !driftNudge && residentHints.length === 0 && resumed === null) {
|
|
1751
2181
|
return passOutput();
|
|
1752
2182
|
}
|
|
1753
2183
|
const lines = [];
|
|
2184
|
+
if (resumed !== null) {
|
|
2185
|
+
lines.push(resumed);
|
|
2186
|
+
}
|
|
1754
2187
|
if (parts.length > 0) {
|
|
1755
2188
|
lines.push("[" + parts.join(" | ") + "]");
|
|
1756
2189
|
}
|
|
@@ -1823,20 +2256,6 @@ registerHook("post_tool_use", pendingContextHandler, { advisory: true, followsMa
|
|
|
1823
2256
|
|
|
1824
2257
|
// src/hooks_session_start.ts
|
|
1825
2258
|
init_define_import_meta_env();
|
|
1826
|
-
var GENERIC_REMINDER = 'token-goat: prefer surgical reads over the Read/Grep tools on this codebase; shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are just commands, not tool names -- `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::Heading"`, `token-goat semantic "description"`, `token-goat outline <file>`. Run `token-goat index .` if this project is not indexed yet.';
|
|
1827
|
-
var INDEXED_REMINDER = 'token-goat: this project is indexed. Prefer `symbol <name>`, `read "file::symbol"`, `section "file::Heading"`, `semantic "description"`, or `outline <file>` over a full Read/Grep tool call; for JSON/YAML use `json-query file \'a.b.c\'` or `yaml-query` (nested keys are not symbols); shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are still just commands.';
|
|
1828
|
-
var NOTE_REMINDER = ' Record a finding that must outlive a compaction with `token-goat note set <key> "<finding>"`; notes come back at every session start.';
|
|
1829
|
-
function isIndexedProject(cwd) {
|
|
1830
|
-
if (cwd === void 0) return false;
|
|
1831
|
-
try {
|
|
1832
|
-
return countSymbols({ rootDir: cwd }, globalDbPath()) > 0;
|
|
1833
|
-
} catch {
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
function buildReminder(indexed) {
|
|
1838
|
-
return (indexed ? INDEXED_REMINDER : GENERIC_REMINDER) + NOTE_REMINDER;
|
|
1839
|
-
}
|
|
1840
2259
|
function reconcileNote(cwd, indexed) {
|
|
1841
2260
|
if (!indexed) return null;
|
|
1842
2261
|
if (!envBool(ENV_KEYS.RECONCILE, true)) return null;
|
|
@@ -1860,6 +2279,11 @@ function reconcileNote(cwd, indexed) {
|
|
|
1860
2279
|
const clauses = [];
|
|
1861
2280
|
if (drifted > 0) clauses.push(`${countNoun(drifted, "file")} that changed outside this session${breakdown}`);
|
|
1862
2281
|
if (upgraded > 0) clauses.push(`${countNoun(upgraded, "file")} unchanged on disk but indexed by an older version of token-goat`);
|
|
2282
|
+
const unqueued = total - result.enqueued;
|
|
2283
|
+
if (unqueued > 0) {
|
|
2284
|
+
const refused = unqueued < total ? `${unqueued} of them` : total === 1 ? "it" : "they";
|
|
2285
|
+
return `token-goat: found ${clauses.join(" and ")}${truncated}, but ${refused} could not be queued for reindexing, so symbol lookups may stay stale until token-goat index runs here.`;
|
|
2286
|
+
}
|
|
1863
2287
|
return `token-goat: reindexing ${clauses.join(" and ")}${truncated}. Symbol lookups may be briefly stale.`;
|
|
1864
2288
|
} catch (e) {
|
|
1865
2289
|
recordStat("reconcile_note_failed", 0, 0, void 0, extractErrorMessage(e));
|
|
@@ -1870,16 +2294,29 @@ async function postCompactRecovery(event) {
|
|
|
1870
2294
|
if (event.raw["source"] !== "compact" || event.sessionId === void 0) return null;
|
|
1871
2295
|
if (!loadConfig().compact_assist.enabled) return null;
|
|
1872
2296
|
try {
|
|
1873
|
-
const { buildResumePacket } = await import("./token-goat-chunk-
|
|
2297
|
+
const { buildResumePacket } = await import("./token-goat-chunk-MKPF4CL5.mjs");
|
|
1874
2298
|
return await buildResumePacket(event.sessionId);
|
|
1875
2299
|
} catch {
|
|
1876
2300
|
return null;
|
|
1877
2301
|
}
|
|
1878
2302
|
}
|
|
1879
2303
|
async function sessionStartHandler(event) {
|
|
2304
|
+
return withSavingsReceipt(await sessionStartOutput(event));
|
|
2305
|
+
}
|
|
2306
|
+
function savingsReceiptText(tokens) {
|
|
2307
|
+
return `token-goat has saved about ${tokens.toLocaleString("en-US")} tokens so far. Run \`token-goat stats\` for the detail.`;
|
|
2308
|
+
}
|
|
2309
|
+
function withSavingsReceipt(output, homeDir) {
|
|
2310
|
+
if (output.hookType !== "pass" && output.hookType !== "context") return output;
|
|
2311
|
+
if (detectHarness() !== "claudecode") return output;
|
|
2312
|
+
const saved = claimFirstSavingsReceipt(void 0, homeDir);
|
|
2313
|
+
return saved === null ? output : { ...output, notice: savingsReceiptText(saved) };
|
|
2314
|
+
}
|
|
2315
|
+
async function sessionStartOutput(event) {
|
|
1880
2316
|
const recovery = await postCompactRecovery(event);
|
|
1881
2317
|
const cwd = getCwd(event);
|
|
1882
|
-
const
|
|
2318
|
+
const { anchorStatus } = await import("./token-goat-chunk-USILZ3T3.mjs");
|
|
2319
|
+
const tail = [projectNotesFor(cwd, anchorStatus), recovery].filter((part) => part !== null).join("\n\n");
|
|
1883
2320
|
const tailOnly = () => tail === "" ? passOutput() : contextOutput(tail);
|
|
1884
2321
|
try {
|
|
1885
2322
|
if (!loadConfig().hints.session_start_reminder) return tailOnly();
|
|
@@ -1902,6 +2339,16 @@ ${capsule}`;
|
|
|
1902
2339
|
}
|
|
1903
2340
|
} catch {
|
|
1904
2341
|
}
|
|
2342
|
+
try {
|
|
2343
|
+
const { currentUpgradeDecision, getCachedUpdateStatus, updateAdvice } = await import("./token-goat-chunk-3CHUNJ2O.mjs");
|
|
2344
|
+
const update = getCachedUpdateStatus();
|
|
2345
|
+
const advice = update === null ? null : updateAdvice(currentUpgradeDecision(update));
|
|
2346
|
+
if (update?.latest && advice !== null) {
|
|
2347
|
+
context += `
|
|
2348
|
+
[token-goat update available: v${update.current} -> v${update.latest}. ${advice}]`;
|
|
2349
|
+
}
|
|
2350
|
+
} catch {
|
|
2351
|
+
}
|
|
1905
2352
|
if (tail !== "") context += `
|
|
1906
2353
|
|
|
1907
2354
|
${tail}`;
|
|
@@ -6470,8 +6917,8 @@ function trimCharacterEnd(str, char) {
|
|
|
6470
6917
|
function unicodeEscape(str) {
|
|
6471
6918
|
return str.replace(/[\s\S]/g, (c) => "\\u" + c.charCodeAt().toString(16).padStart(4, "0"));
|
|
6472
6919
|
}
|
|
6473
|
-
function get(obj,
|
|
6474
|
-
for (const key of
|
|
6920
|
+
function get(obj, path8) {
|
|
6921
|
+
for (const key of path8) {
|
|
6475
6922
|
if (!obj) {
|
|
6476
6923
|
return void 0;
|
|
6477
6924
|
}
|
|
@@ -7672,8 +8119,8 @@ function withBrackets(str, brackets) {
|
|
|
7672
8119
|
const rbr = typeof brackets[1] === "string" ? brackets[1] : "]";
|
|
7673
8120
|
return lbr + str + rbr;
|
|
7674
8121
|
}
|
|
7675
|
-
function pathRewrite(
|
|
7676
|
-
const modifiedPath = typeof rewriter === "function" ? rewriter(
|
|
8122
|
+
function pathRewrite(path8, rewriter, baseUrl, metadata, elem) {
|
|
8123
|
+
const modifiedPath = typeof rewriter === "function" ? rewriter(path8, metadata, elem) : path8;
|
|
7677
8124
|
return modifiedPath[0] === "/" && baseUrl ? trimCharacterEnd(baseUrl, "/") + modifiedPath : modifiedPath;
|
|
7678
8125
|
}
|
|
7679
8126
|
function formatImage(elem, walk, builder, formatOptions) {
|
|
@@ -7997,9 +8444,9 @@ function handleDeprecatedOptions(options) {
|
|
|
7997
8444
|
options.selectors.push(...tagDefinitions);
|
|
7998
8445
|
options.selectors = mergeDuplicatesPreferLast(options.selectors, ((s) => s.selector));
|
|
7999
8446
|
}
|
|
8000
|
-
function set(obj,
|
|
8001
|
-
const valueKey =
|
|
8002
|
-
for (const key of
|
|
8447
|
+
function set(obj, path8, value) {
|
|
8448
|
+
const valueKey = path8.pop();
|
|
8449
|
+
for (const key of path8) {
|
|
8003
8450
|
let nested = obj[key];
|
|
8004
8451
|
if (!nested) {
|
|
8005
8452
|
nested = {};
|
|
@@ -8067,19 +8514,30 @@ function resolveWebFetchContext(event) {
|
|
|
8067
8514
|
}
|
|
8068
8515
|
return resolveWebFetchUrl(event);
|
|
8069
8516
|
}
|
|
8517
|
+
function webFetchPolicyUrls(event) {
|
|
8518
|
+
if (getToolName(event) !== "WebFetch") return [];
|
|
8519
|
+
const toolInput = getToolInput(event);
|
|
8520
|
+
const out = [];
|
|
8521
|
+
const single = toolInput["url"];
|
|
8522
|
+
if (typeof single === "string" && single !== "") out.push(single);
|
|
8523
|
+
const list = toolInput["urls"];
|
|
8524
|
+
if (Array.isArray(list)) {
|
|
8525
|
+
for (const entry of list) if (typeof entry === "string" && entry !== "" && !out.includes(entry)) out.push(entry);
|
|
8526
|
+
}
|
|
8527
|
+
return out;
|
|
8528
|
+
}
|
|
8070
8529
|
function preFetchHandler(event) {
|
|
8071
8530
|
try {
|
|
8072
|
-
const
|
|
8073
|
-
|
|
8074
|
-
const metadataRefusal = metadataEndpointRefusal(urlOnlyCtx.url);
|
|
8531
|
+
for (const url2 of webFetchPolicyUrls(event)) {
|
|
8532
|
+
const metadataRefusal = metadataEndpointRefusal(url2);
|
|
8075
8533
|
if (metadataRefusal !== null) {
|
|
8076
8534
|
return denyOutput(`WebFetch blocked: ${metadataRefusal}.`);
|
|
8077
8535
|
}
|
|
8078
8536
|
const wfCfg = loadConfig().webfetch;
|
|
8079
|
-
if (wfCfg.deny.length > 0 && matchesDenyPattern(
|
|
8537
|
+
if (wfCfg.deny.length > 0 && matchesDenyPattern(url2, wfCfg.deny)) {
|
|
8080
8538
|
return denyOutput(`WebFetch blocked: URL matches a configured webfetch.deny pattern.`);
|
|
8081
8539
|
}
|
|
8082
|
-
if (wfCfg.allow.length > 0 && !matchesAllowPattern(
|
|
8540
|
+
if (wfCfg.allow.length > 0 && !matchesAllowPattern(url2, wfCfg.allow)) {
|
|
8083
8541
|
return denyOutput(`WebFetch blocked: URL does not match any configured webfetch.allow pattern.`);
|
|
8084
8542
|
}
|
|
8085
8543
|
}
|
|
@@ -8163,6 +8621,33 @@ registerHook("post_tool_use", postFetchHandler, { toolName: "WebFetch" });
|
|
|
8163
8621
|
// src/hooks_skill.ts
|
|
8164
8622
|
init_define_import_meta_env();
|
|
8165
8623
|
import { readFile } from "node:fs/promises";
|
|
8624
|
+
|
|
8625
|
+
// src/copilot_skill_path.ts
|
|
8626
|
+
init_define_import_meta_env();
|
|
8627
|
+
import { access } from "node:fs/promises";
|
|
8628
|
+
import { homedir as homedir2 } from "node:os";
|
|
8629
|
+
import { resolve } from "node:path";
|
|
8630
|
+
async function copilotSkillPath(skillName, cwd) {
|
|
8631
|
+
const name2 = safeSkillName(skillName);
|
|
8632
|
+
if (!name2 || name2.includes(":")) return null;
|
|
8633
|
+
const roots = [];
|
|
8634
|
+
if (cwd) {
|
|
8635
|
+
roots.push(resolve(cwd, ".github", "skills"), resolve(cwd, ".agents", "skills"), resolve(cwd, ".claude", "skills"));
|
|
8636
|
+
}
|
|
8637
|
+
roots.push(resolve(copilotCliUserRoot(), "skills"), resolve(homedir2(), ".agents", "skills"));
|
|
8638
|
+
for (const root of roots) {
|
|
8639
|
+
const diskPath = resolve(root, name2, "SKILL.md");
|
|
8640
|
+
try {
|
|
8641
|
+
await access(diskPath);
|
|
8642
|
+
return diskPath;
|
|
8643
|
+
} catch {
|
|
8644
|
+
continue;
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
return null;
|
|
8648
|
+
}
|
|
8649
|
+
|
|
8650
|
+
// src/hooks_skill.ts
|
|
8166
8651
|
var OVERSIZED_FIRST_LOAD_THRESHOLD_BYTES = 6e3;
|
|
8167
8652
|
var COMPACT_INLINE_MAX_BYTES = 24e3;
|
|
8168
8653
|
function skillSectionArg(skillPath, body) {
|
|
@@ -8184,6 +8669,13 @@ function extractSkillName(toolInput) {
|
|
|
8184
8669
|
function extractSkillBody(raw) {
|
|
8185
8670
|
return extractToolResponseField(raw, BODY_FIRST_TOOL_RESPONSE_KEYS);
|
|
8186
8671
|
}
|
|
8672
|
+
function skillResultIsConfirmationOnly() {
|
|
8673
|
+
return getHarnessName() === "copilot_cli";
|
|
8674
|
+
}
|
|
8675
|
+
function harnessSkillPath(event, skillName) {
|
|
8676
|
+
if (skillResultIsConfirmationOnly()) return copilotSkillPath(skillName, getCwd(event) ?? null);
|
|
8677
|
+
return installedSkillPath(skillName);
|
|
8678
|
+
}
|
|
8187
8679
|
function resolveSkillContext(event) {
|
|
8188
8680
|
const toolName = getToolName(event);
|
|
8189
8681
|
if (toolName !== "Skill") {
|
|
@@ -8199,14 +8691,14 @@ function resolveSkillContext(event) {
|
|
|
8199
8691
|
}
|
|
8200
8692
|
return { skillName };
|
|
8201
8693
|
}
|
|
8202
|
-
function planHeadingTree(body, bodyBytes, skillName) {
|
|
8694
|
+
function planHeadingTree(body, bodyBytes, skillName, sourcePath) {
|
|
8203
8695
|
const headings = extractMarkdownHeadings(body);
|
|
8204
8696
|
if (headings.length < OUTLINE_MIN_HEADINGS) return null;
|
|
8205
8697
|
const { sectionsList } = formatHeadingTreeParts(headings, skillName);
|
|
8206
8698
|
const treeBytes = Buffer.byteLength(sectionsList, "utf-8");
|
|
8207
8699
|
if (treeBytes > bodyBytes * OUTLINE_MAX_REPLACEMENT_RATIO) return null;
|
|
8208
8700
|
const totalHeadings = extractMarkdownHeadings(body, Number.MAX_SAFE_INTEGER).length;
|
|
8209
|
-
const phrase = totalHeadings > headings.length ? "its heading tree shows " + headings.length + " of " + totalHeadings + " headings below instead of the full body; the
|
|
8701
|
+
const phrase = totalHeadings > headings.length ? "its heading tree shows " + headings.length + " of " + totalHeadings + " headings below instead of the full body; the other " + (totalHeadings - headings.length) + ' load by name just the same, and `token-goat outline "' + displaySafeText(sourcePath) + '"` lists all ' + totalHeadings + "." : "its heading tree (" + headings.length + " headings) is inlined below instead of the full body.";
|
|
8210
8702
|
return { sectionsList, treeBytes, phrase };
|
|
8211
8703
|
}
|
|
8212
8704
|
async function preSkillHandler(event) {
|
|
@@ -8224,10 +8716,10 @@ async function preSkillHandler(event) {
|
|
|
8224
8716
|
const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
|
|
8225
8717
|
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit), void 0, "skill-reload-deny");
|
|
8226
8718
|
return denyOutput(
|
|
8227
|
-
"Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-section " + skillName + " " + skillSectionArg(await
|
|
8719
|
+
"Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-section " + skillName + " " + skillSectionArg(await harnessSkillPath(event, skillName)) + "` to recall a section, `token-goat skill-body " + skillName + " --compact` to recall the compact slice, or `token-goat skill-body " + skillName + "` for the full body instead of re-loading it."
|
|
8228
8720
|
);
|
|
8229
8721
|
}
|
|
8230
|
-
const sourcePath = await
|
|
8722
|
+
const sourcePath = await harnessSkillPath(event, skillName);
|
|
8231
8723
|
if (sourcePath) {
|
|
8232
8724
|
try {
|
|
8233
8725
|
const body = await readFile(sourcePath, "utf-8");
|
|
@@ -8244,7 +8736,7 @@ async function preSkillHandler(event) {
|
|
|
8244
8736
|
);
|
|
8245
8737
|
}
|
|
8246
8738
|
}
|
|
8247
|
-
const tree = planHeadingTree(body, bodyBytes, skillName);
|
|
8739
|
+
const tree = planHeadingTree(body, bodyBytes, skillName, sourcePath);
|
|
8248
8740
|
if (tree !== null) {
|
|
8249
8741
|
const replaced = compact !== null ? Buffer.byteLength(compact, "utf-8") : bodyBytes;
|
|
8250
8742
|
const savedBytes = Math.max(0, replaced - tree.treeBytes);
|
|
@@ -8274,13 +8766,23 @@ async function postSkillHandler(event) {
|
|
|
8274
8766
|
if (ctx === null) {
|
|
8275
8767
|
return passOutput();
|
|
8276
8768
|
}
|
|
8277
|
-
const { skillName } = ctx;
|
|
8278
|
-
recordStat("skill_load");
|
|
8279
|
-
|
|
8769
|
+
const { skillName } = ctx;
|
|
8770
|
+
recordStat("skill_load");
|
|
8771
|
+
let body;
|
|
8772
|
+
let sourcePath;
|
|
8773
|
+
if (skillResultIsConfirmationOnly()) {
|
|
8774
|
+
sourcePath = await harnessSkillPath(event, skillName);
|
|
8775
|
+
if (sourcePath === null) {
|
|
8776
|
+
return passOutput();
|
|
8777
|
+
}
|
|
8778
|
+
body = await readFile(sourcePath, "utf-8");
|
|
8779
|
+
} else {
|
|
8780
|
+
body = extractSkillBody(event.raw);
|
|
8781
|
+
sourcePath = await installedSkillPath(skillName);
|
|
8782
|
+
}
|
|
8280
8783
|
if (!body) {
|
|
8281
8784
|
return passOutput();
|
|
8282
8785
|
}
|
|
8283
|
-
const sourcePath = await installedSkillPath(skillName);
|
|
8284
8786
|
await storeOutput(event.sessionId, skillName, body, sourcePath ? { sourcePath } : void 0);
|
|
8285
8787
|
await incrementSkillHit(skillName);
|
|
8286
8788
|
await recordSkillVersionSnapshot(event.sessionId, skillName);
|
|
@@ -8309,7 +8811,7 @@ function dualShellArg(s) {
|
|
|
8309
8811
|
if (SHELL_UNSAFE_IN_EITHER_QUOTING_RE.test(s)) return null;
|
|
8310
8812
|
return `"${s}"`;
|
|
8311
8813
|
}
|
|
8312
|
-
function detectStructuralIndexRewrite(rawCmd, cwd) {
|
|
8814
|
+
function detectStructuralIndexRewrite(rawCmd, cwd, event) {
|
|
8313
8815
|
try {
|
|
8314
8816
|
const detected = detectFromCommand(rawCmd, cwd);
|
|
8315
8817
|
if (detected === null || detected.filter.name !== "grep") return null;
|
|
@@ -8319,10 +8821,11 @@ function detectStructuralIndexRewrite(rawCmd, cwd) {
|
|
|
8319
8821
|
if (bin === void 0 || pattern === void 0 || rawPath === void 0) return null;
|
|
8320
8822
|
if (!ALLOWED_BINARIES.has(pathStem(bin).toLowerCase())) return null;
|
|
8321
8823
|
if (/[*?[\]{}]/.test(rawPath)) return null;
|
|
8824
|
+
if (!commandPathIsTouchable(rawPath, event)) return null;
|
|
8322
8825
|
const resolved = resolveIndexPath(rawPath, cwd);
|
|
8323
8826
|
let stat;
|
|
8324
8827
|
try {
|
|
8325
|
-
stat = statSync5(resolved);
|
|
8828
|
+
stat = statSync5(hostPathOfIndexKey(resolved));
|
|
8326
8829
|
} catch {
|
|
8327
8830
|
return null;
|
|
8328
8831
|
}
|
|
@@ -8367,16 +8870,17 @@ function detectStructuralIndexRewrite(rawCmd, cwd) {
|
|
|
8367
8870
|
|
|
8368
8871
|
// src/bash_range_savings.ts
|
|
8369
8872
|
init_define_import_meta_env();
|
|
8370
|
-
import { statSync as statSync6, readFileSync as
|
|
8873
|
+
import { statSync as statSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
8371
8874
|
var ALL_SYMBOLS_IN_FILE = -1;
|
|
8372
8875
|
var REGION_HEADER_BYTES = 56;
|
|
8373
8876
|
function rangeSubstituteFor(hintPath, cwd, ranges) {
|
|
8374
8877
|
if (ranges.length === 0) return null;
|
|
8375
8878
|
try {
|
|
8376
8879
|
const resolved = resolveIndexPath(hintPath, cwd);
|
|
8377
|
-
|
|
8880
|
+
const onDisk = hostPathOfIndexKey(resolved);
|
|
8881
|
+
if (!statSync6(onDisk).isFile()) return null;
|
|
8378
8882
|
if (!indexMatchesDisk(resolved)) return null;
|
|
8379
|
-
const lines =
|
|
8883
|
+
const lines = readFileSync7(onDisk, "utf8").split("\n");
|
|
8380
8884
|
const symbols = querySymbols({ filePath: resolved, limit: ALL_SYMBOLS_IN_FILE });
|
|
8381
8885
|
if (symbols.length === 0) return null;
|
|
8382
8886
|
const bytesOf = (start, end) => Buffer.byteLength(lines.slice(start - 1, Math.min(end, lines.length)).join("\n"), "utf8");
|
|
@@ -8400,335 +8904,103 @@ function rangeSubstituteFor(hintPath, cwd, ranges) {
|
|
|
8400
8904
|
const regions = [...regionBySpan.values()].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
8401
8905
|
const replacementBytes = regions.reduce((t, r) => t + bytesOf(r.start, r.end) + REGION_HEADER_BYTES, 0);
|
|
8402
8906
|
const commands = regions.map(
|
|
8403
|
-
(r) => r.name === null ? `token-goat read "${hintPath}:${r.start}-${r.end}"` : `token-goat read "${hintPath}::${r.name}"`
|
|
8404
|
-
);
|
|
8405
|
-
return { requestedBytes, replacementBytes, commands };
|
|
8406
|
-
} catch {
|
|
8407
|
-
return null;
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
|
|
8411
|
-
// src/hooks_bash.ts
|
|
8412
|
-
import { statSync as statSync7, existsSync, readFileSync as readFileSync5 } from "node:fs";
|
|
8413
|
-
import * as path4 from "node:path";
|
|
8414
|
-
function compressionOptedOut(rawCmd) {
|
|
8415
|
-
return process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0" || commandOptsOutOfCompression(rawCmd);
|
|
8416
|
-
}
|
|
8417
|
-
var INTERPRETER_READ_TOKEN_CAP = 2e3;
|
|
8418
|
-
function cappedInterpreterRead(event, rawCmd, cmd, hint, denial) {
|
|
8419
|
-
return maybeCompressRewrite(event, rawCmd, cmd, { maxTokens: INTERPRETER_READ_TOKEN_CAP, hint: stripUnsafeSuggestions(hint) }) ?? denyOutput(denial);
|
|
8420
|
-
}
|
|
8421
|
-
function maybeCompressRewrite(event, rawCmd, cmd, cap) {
|
|
8422
|
-
if (compressionOptedOut(rawCmd)) return null;
|
|
8423
|
-
if (event.raw["_tg_harness"] === "vscode") return null;
|
|
8424
|
-
const isPwshHarness = process.platform === "win32" && (event.raw["_tg_harness"] === "codex" || event.raw["_tg_harness"] === "copilot_cli" || event.raw["tool_name"] === "powershell");
|
|
8425
|
-
if (isPwshHarness) {
|
|
8426
|
-
if (!canRunPowerShell()) return null;
|
|
8427
|
-
} else {
|
|
8428
|
-
if (!canRunWrappedShell()) return null;
|
|
8429
|
-
}
|
|
8430
|
-
let cfg;
|
|
8431
|
-
try {
|
|
8432
|
-
cfg = loadConfig().bash_compress;
|
|
8433
|
-
} catch {
|
|
8434
|
-
return null;
|
|
8435
|
-
}
|
|
8436
|
-
if (!cfg.enabled) return null;
|
|
8437
|
-
let filterName;
|
|
8438
|
-
if (cap !== void 0) {
|
|
8439
|
-
if (hasBareBackground(cmd)) return null;
|
|
8440
|
-
filterName = "passthrough";
|
|
8441
|
-
} else {
|
|
8442
|
-
const gateCmd = stripTrailingStderrRedirect(cmd);
|
|
8443
|
-
const detected = detectFromCommand(gateCmd, getCwd(event));
|
|
8444
|
-
if (detected !== null) {
|
|
8445
|
-
filterName = detected.filter.name;
|
|
8446
|
-
} else if (isCompressibleSingleCommand(gateCmd)) {
|
|
8447
|
-
filterName = "generic";
|
|
8448
|
-
} else {
|
|
8449
|
-
return null;
|
|
8450
|
-
}
|
|
8451
|
-
}
|
|
8452
|
-
if (cfg.disabled_filters.includes(filterName)) return null;
|
|
8453
|
-
const capArgs = cap === void 0 ? "" : ` --max-tokens ${cap.maxTokens} --cap-hint-b64 ${Buffer.from(cap.hint, "utf8").toString("base64")}`;
|
|
8454
|
-
if (isPwshHarness) {
|
|
8455
|
-
const b64 = Buffer.from(rawCmd, "utf8").toString("base64");
|
|
8456
|
-
if (Buffer.from(b64, "base64").toString("utf8") !== rawCmd) return null;
|
|
8457
|
-
const wrapped2 = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds}${capArgs} --shell pwsh --cmd-b64 ${b64}`;
|
|
8458
|
-
if (wrapped2.length > 24e3) return null;
|
|
8459
|
-
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped2 } };
|
|
8460
|
-
}
|
|
8461
|
-
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds}${capArgs} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8462
|
-
if (cap !== void 0 && wrapped.length > 24e3) return null;
|
|
8463
|
-
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8464
|
-
}
|
|
8465
|
-
function elideServedShellLines(cmd, output, priorIds, unknownLineNumbers = false) {
|
|
8466
|
-
if (priorIds.length === 0) return null;
|
|
8467
|
-
const lines = output.split("\n");
|
|
8468
|
-
const numbers = unknownLineNumbers ? Array.from({ length: lines.length }, () => null) : deliveredLineNumbers(cmd, lines.length);
|
|
8469
|
-
if (numbers === null) return null;
|
|
8470
|
-
const rows = lines.map((text, i) => ({ no: numbers[i] ?? null, text, raw: text }));
|
|
8471
|
-
const bodies = [];
|
|
8472
|
-
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
8473
|
-
const id = priorIds[i];
|
|
8474
|
-
if (id === void 0) continue;
|
|
8475
|
-
const prior = getBashOutput(id);
|
|
8476
|
-
if (prior !== null) bodies.push(indexServedBody(id, prior.output));
|
|
8477
|
-
}
|
|
8478
|
-
const cuts = planServedElisions(rows, bodies);
|
|
8479
|
-
if (cuts.length === 0) return null;
|
|
8480
|
-
const spans = [];
|
|
8481
|
-
const push = (text, own) => {
|
|
8482
|
-
const sep = spans.length === 0 ? "" : "\n";
|
|
8483
|
-
const prev = spans[spans.length - 1];
|
|
8484
|
-
if (prev !== void 0 && prev.own === true === own) spans[spans.length - 1] = { text: prev.text + sep + text, own };
|
|
8485
|
-
else spans.push({ text: sep + text, own });
|
|
8486
|
-
};
|
|
8487
|
-
let at = 0;
|
|
8488
|
-
for (const cut of cuts) {
|
|
8489
|
-
for (let i = at; i < cut.start; i++) push(rows[i]?.raw ?? "", false);
|
|
8490
|
-
const first2 = rows[cut.start];
|
|
8491
|
-
const last = rows[cut.start + cut.len - 1];
|
|
8492
|
-
if (first2 === void 0 || last === void 0) return null;
|
|
8493
|
-
push(servedRunNotice(first2.no, last.no, cut.id, cut.len), true);
|
|
8494
|
-
at = cut.start + cut.len;
|
|
8495
|
-
}
|
|
8496
|
-
for (let i = at; i < rows.length; i++) push(rows[i]?.raw ?? "", false);
|
|
8497
|
-
return spans;
|
|
8498
|
-
}
|
|
8499
|
-
function foldShellReadBodies(cmd, output, fileKey, cwd) {
|
|
8500
|
-
if (!foldingEnabled()) return null;
|
|
8501
|
-
if (redactSecrets(output).count > 0) return null;
|
|
8502
|
-
const lines = output.split("\n");
|
|
8503
|
-
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
8504
|
-
if (numbers === null) return null;
|
|
8505
|
-
const rows = [];
|
|
8506
|
-
for (let i = 0; i < lines.length; i++) {
|
|
8507
|
-
const no = numbers[i];
|
|
8508
|
-
if (no === void 0 || no === null) return null;
|
|
8509
|
-
rows.push({ no, text: lines[i] ?? "", raw: lines[i] ?? "" });
|
|
8510
|
-
}
|
|
8511
|
-
const headRead = extractHeadFile(cmd) ?? extractGetContentHead(cmd);
|
|
8512
|
-
const windowed = extractLineRangeRead(cmd) !== null || headRead !== null && lines.length >= headRead.n;
|
|
8513
|
-
const folded = foldDelivery(rows, fileKey, displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey)), windowed);
|
|
8514
|
-
if (folded === null) return null;
|
|
8515
|
-
return { text: folded.numbered.join("\n"), folds: folded.folds };
|
|
8516
|
-
}
|
|
8517
|
-
function foldShellReadStructure(cmd, filePath, output, fileKey, cwd) {
|
|
8518
|
-
if (!isWholeFileDump(cmd, filePath)) return null;
|
|
8519
|
-
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8520
|
-
let onDisk;
|
|
8521
|
-
try {
|
|
8522
|
-
onDisk = statSync7(fileKey).size;
|
|
8523
|
-
} catch {
|
|
8524
|
-
return null;
|
|
8525
|
-
}
|
|
8526
|
-
if (onDisk !== originalBytes) return null;
|
|
8527
|
-
if (redactSecrets(output).count > 0) return null;
|
|
8528
|
-
const lines = output.split("\n");
|
|
8529
|
-
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
8530
|
-
const rows = lines.map((text2, i) => ({ no: i + 1, text: text2, raw: text2 }));
|
|
8531
|
-
const shown = displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey));
|
|
8532
|
-
const fold = planMarkdownOutline(rows, fileKey, shown, originalBytes) ?? planSourceSkeleton(rows, fileKey, shown, originalBytes);
|
|
8533
|
-
if (fold === null) return null;
|
|
8534
|
-
const text = fold.numbered.join("\n");
|
|
8535
|
-
if (!isStructuralRewriteAccepted(originalBytes, Buffer.byteLength(text, "utf-8"), fold.ratioCap)) return null;
|
|
8536
|
-
return { text, kind: fold.kind === "read:markdown_outline" ? "bash_compress:markdown-outline" : "bash_compress:source-skeleton", detail: fold.detail };
|
|
8537
|
-
}
|
|
8538
|
-
var REWRITE_CLIP_NOTE = "\n[token-goat: rewrite clipped to the harness delivery cap; the pointer above recalls the untouched output]";
|
|
8539
|
-
function fenceRewriteWithinCap(spans) {
|
|
8540
|
-
const joined = spans.map((s) => s.text).join("");
|
|
8541
|
-
const fenced = fenceUntrustedSpans(spans, UNTRUSTED_TOOL_TAG);
|
|
8542
|
-
if (fenced === joined) return joined;
|
|
8543
|
-
const overhead = Buffer.byteLength(fenced, "utf-8") - Buffer.byteLength(joined, "utf-8") + Buffer.byteLength(REWRITE_CLIP_NOTE, "utf-8");
|
|
8544
|
-
const cut = clipToDeliveryCap(joined, overhead);
|
|
8545
|
-
if (!cut.clipped) return fenced;
|
|
8546
|
-
const kept = [];
|
|
8547
|
-
let left2 = cut.text.length;
|
|
8548
|
-
for (const s of spans) {
|
|
8549
|
-
if (left2 <= 0) break;
|
|
8550
|
-
kept.push(s.text.length <= left2 ? s : { ...s, text: s.text.slice(0, left2) });
|
|
8551
|
-
left2 -= s.text.length;
|
|
8552
|
-
}
|
|
8553
|
-
return fenceUntrustedSpans(kept, UNTRUSTED_TOOL_TAG) + REWRITE_CLIP_NOTE;
|
|
8554
|
-
}
|
|
8555
|
-
async function maybeCollapseIdenticalRead(cmd, rawCmd, optedOut, output, exitCode, cwd, cacheMinBytes, persisted = false) {
|
|
8556
|
-
if (optedOut) return null;
|
|
8557
|
-
if (exitCode !== null && exitCode !== 0) return null;
|
|
8558
|
-
const filePath = pureFileReadPath(cmd);
|
|
8559
|
-
if (filePath === null) return null;
|
|
8560
|
-
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8561
|
-
if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
|
|
8562
|
-
const fileKey = resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd ?? process.cwd()));
|
|
8563
|
-
const priorIds = getFileServedOutputs(fileKey);
|
|
8564
|
-
let containerId = null;
|
|
8565
|
-
let identical = false;
|
|
8566
|
-
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
8567
|
-
const id = priorIds[i];
|
|
8568
|
-
if (id === void 0) continue;
|
|
8569
|
-
const prior = getBashOutput(id);
|
|
8570
|
-
if (prior === null || !containsLineRun(prior.output, output)) continue;
|
|
8571
|
-
containerId = id;
|
|
8572
|
-
identical = prior.output === output;
|
|
8573
|
-
break;
|
|
8574
|
-
}
|
|
8575
|
-
const sessionKey = shortFingerprint(stripOutputPipeline(cmd));
|
|
8576
|
-
if (containerId === null) {
|
|
8577
|
-
const elided = elideServedShellLines(cmd, output, priorIds);
|
|
8578
|
-
const priced = (text) => isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(text, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() });
|
|
8579
|
-
let rewrite = null;
|
|
8580
|
-
if (elided !== null) {
|
|
8581
|
-
const fenced = fenceRewriteWithinCap(elided);
|
|
8582
|
-
if (priced(fenced)) rewrite = { text: fenced, reason: "already-served file lines withheld", kind: "bash_compress:served-elide" };
|
|
8583
|
-
} else {
|
|
8584
|
-
const structural = foldShellReadStructure(cmd, filePath, output, fileKey, cwd);
|
|
8585
|
-
if (structural !== null && priced(structural.text)) {
|
|
8586
|
-
rewrite = { text: structural.text, reason: structural.kind === "bash_compress:markdown-outline" ? "document replaced with its heading tree" : "source replaced with its structural skeleton", kind: structural.kind, detail: structural.detail };
|
|
8587
|
-
}
|
|
8588
|
-
if (rewrite === null) {
|
|
8589
|
-
const folded = foldShellReadBodies(cmd, output, fileKey, cwd);
|
|
8590
|
-
if (folded !== null) {
|
|
8591
|
-
const fenced = fenceRewriteWithinCap([{ text: folded.text }]);
|
|
8592
|
-
if (priced(fenced)) rewrite = { text: fenced, reason: "code bodies folded", kind: "bash_compress:body-fold", detail: foldDetail(fileKey, folded.folds) };
|
|
8593
|
-
}
|
|
8594
|
-
}
|
|
8595
|
-
}
|
|
8596
|
-
const storedId = await storeBashOutput(cmd, rewrite?.text ?? output, exitCode ?? 0, cwd);
|
|
8597
|
-
recordBashOutput(sessionKey, storedId, originalBytes);
|
|
8598
|
-
if (!persisted) recordFileServedOutput(fileKey, storedId);
|
|
8599
|
-
if (rewrite === null) return null;
|
|
8600
|
-
return emitRewrite(rewrite.text, rewrite.reason, { kind: rewrite.kind, originalBytes: deliveredOutputBytes(originalBytes), ...rewrite.detail === void 0 ? {} : { detail: rewrite.detail } });
|
|
8601
|
-
}
|
|
8602
|
-
const pointer = identical ? "[token-goat] Identical to an earlier run of this command in this session; the file has not changed since. " + originalBytes + " bytes withheld -- recall them with `token-goat bash-output " + containerId + " --full`." : "[token-goat] These " + originalBytes + " bytes already appear verbatim inside a wider read of " + filePath + " served earlier in this session. Withheld -- recall the full earlier output with `token-goat bash-output " + containerId + " --full`.";
|
|
8603
|
-
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8604
|
-
return emitRewrite(pointer, identical ? "identical file re-read collapsed" : "already-served file lines collapsed", { kind: identical ? "bash_compress:identical-reread" : "bash_compress:contained-reread", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
8605
|
-
}
|
|
8606
|
-
async function maybeElideServedGenericOutput(cmd, optedOut, output, exitCode, cwd, cacheMinBytes, persisted = false) {
|
|
8607
|
-
if (optedOut) return null;
|
|
8608
|
-
if (!loadConfig().bash_compress.elide_served_shell_output) return null;
|
|
8609
|
-
if (exitCode !== null && exitCode !== 0) return null;
|
|
8610
|
-
if (redactSecrets(output).count > 0) return null;
|
|
8611
|
-
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8612
|
-
if (originalBytes < cacheMinBytes) return null;
|
|
8613
|
-
const priorIds = getFileServedOutputs(GENERIC_SERVED_OUTPUT_KEY).filter((id) => getBashOutput(id)?.command !== cmd);
|
|
8614
|
-
let rewrittenText = null;
|
|
8615
|
-
if (priorIds.length > 0) {
|
|
8616
|
-
const elided = elideServedShellLines(cmd, output, priorIds, true);
|
|
8617
|
-
const fenced = elided === null ? null : fenceRewriteWithinCap(elided);
|
|
8618
|
-
if (fenced !== null && isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(fenced, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) {
|
|
8619
|
-
rewrittenText = fenced;
|
|
8620
|
-
}
|
|
8621
|
-
}
|
|
8622
|
-
if (!persisted) recordFileServedOutput(GENERIC_SERVED_OUTPUT_KEY, await storeBashOutput(cmd, rewrittenText ?? output, exitCode ?? 0, cwd));
|
|
8623
|
-
if (rewrittenText === null) return null;
|
|
8624
|
-
return emitRewrite(rewrittenText, "already-served shell output collapsed", { kind: "bash_compress:generic-served-elision", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
8625
|
-
}
|
|
8626
|
-
async function maybeCompressCompoundOutput(cmd, optedOut, output, exitCode, cwd, cacheMinBytes, isUnwrapped = false) {
|
|
8627
|
-
if (optedOut) return null;
|
|
8628
|
-
if (!isUnwrapped) return null;
|
|
8629
|
-
if (isFullRecallCommand(cmd)) return null;
|
|
8630
|
-
if (exitCode !== null && exitCode !== 0) return null;
|
|
8631
|
-
let cfg;
|
|
8632
|
-
try {
|
|
8633
|
-
cfg = loadConfig().bash_compress;
|
|
8634
|
-
} catch {
|
|
8635
|
-
return null;
|
|
8636
|
-
}
|
|
8637
|
-
if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
|
|
8638
|
-
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
|
|
8639
|
-
const shaped = pipelineShapeFilter(cmd, cwd) ?? (isUnwrapped ? detectFromCommand(cmd, cwd ?? void 0) ?? null : null);
|
|
8640
|
-
const useShaped = shaped !== null && !cfg.disabled_filters.includes(shaped.filter.name);
|
|
8641
|
-
const filter3 = useShaped ? shaped.filter : filterByName("generic");
|
|
8642
|
-
if (filter3 === null) return null;
|
|
8643
|
-
const filterArgv = useShaped ? shaped.argv : [];
|
|
8644
|
-
const compressed = compressOutput(filter3, output, "", exitCode ?? 0, filterArgv, {
|
|
8645
|
-
maxLines: cfg.max_lines,
|
|
8646
|
-
maxBytes: cfg.max_bytes
|
|
8647
|
-
});
|
|
8648
|
-
const minNet = resolveMinNetSavingsBytes();
|
|
8649
|
-
if (!compressed.worthApplying(minNet)) return null;
|
|
8650
|
-
const id = bashOutputIdSync(cmd, output, cwd);
|
|
8651
|
-
const { body: untrusted, notices } = splitOwnTrailingNotices(compressed.text);
|
|
8652
|
-
const marker = compressed.withMarker(minNet).slice(compressed.text.length);
|
|
8653
|
-
const body = fenceUntrusted(untrusted, UNTRUSTED_TOOL_TAG) + notices + marker + "\n[token-goat] full output: bash-output " + id + " --full";
|
|
8654
|
-
const emittedBytes = Buffer.byteLength(body, "utf-8");
|
|
8655
|
-
if (!isRewriteWorthwhile({
|
|
8656
|
-
originalBytes: compressed.originalBytes,
|
|
8657
|
-
rewrittenBytes: emittedBytes,
|
|
8658
|
-
noticeBytes: 0,
|
|
8659
|
-
minNetSavingsBytes: minNet
|
|
8660
|
-
})) {
|
|
8661
|
-
return null;
|
|
8662
|
-
}
|
|
8663
|
-
await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8664
|
-
return emitRewrite(body, "bash", { kind: `bash_compress:${filter3.name}`, originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
8665
|
-
}
|
|
8666
|
-
function maybeStripAnsiOnly(output) {
|
|
8667
|
-
if (!output.includes("\x1B")) return null;
|
|
8668
|
-
let cfg;
|
|
8669
|
-
try {
|
|
8670
|
-
cfg = loadConfig().bash_compress;
|
|
8907
|
+
(r) => r.name === null ? `token-goat read "${hintPath}:${r.start}-${r.end}"` : `token-goat read "${hintPath}::${r.name}"`
|
|
8908
|
+
);
|
|
8909
|
+
return { requestedBytes, replacementBytes, commands };
|
|
8671
8910
|
} catch {
|
|
8672
8911
|
return null;
|
|
8673
8912
|
}
|
|
8674
|
-
if (!cfg.enabled || cfg.disabled_filters.includes("ansi")) return null;
|
|
8675
|
-
const stripped = stripAnsiEscapes(output);
|
|
8676
|
-
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8677
|
-
if (!isRewriteWorthwhile({
|
|
8678
|
-
originalBytes,
|
|
8679
|
-
rewrittenBytes: Buffer.byteLength(stripped, "utf-8"),
|
|
8680
|
-
noticeBytes: 0,
|
|
8681
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
8682
|
-
})) {
|
|
8683
|
-
return null;
|
|
8684
|
-
}
|
|
8685
|
-
return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
|
|
8686
8913
|
}
|
|
8687
|
-
|
|
8688
|
-
|
|
8914
|
+
|
|
8915
|
+
// src/hooks_bash.ts
|
|
8916
|
+
import { statSync as statSync7, existsSync, readFileSync as readFileSync8 } from "node:fs";
|
|
8917
|
+
import * as path5 from "node:path";
|
|
8918
|
+
var INTERPRETER_READ_TOKEN_CAP = 2e3;
|
|
8919
|
+
function cappedInterpreterRead(event, rawCmd, cmd, hint, denial) {
|
|
8920
|
+
return maybeCompressRewrite(event, rawCmd, cmd, { maxTokens: INTERPRETER_READ_TOKEN_CAP, hint: stripUnsafeSuggestions(hint) }) ?? denyOutput(denial);
|
|
8921
|
+
}
|
|
8922
|
+
function maybeCompressRewrite(event, rawCmd, cmd, cap) {
|
|
8923
|
+
if (/^(?:token-goat|tg|npx\s+token-goat)(?:\s|$)/.test(cmd.trimStart())) return null;
|
|
8924
|
+
if (compressionOptedOut(rawCmd)) return null;
|
|
8925
|
+
if (event.raw["_tg_harness"] === "vscode") return null;
|
|
8926
|
+
const isPwshHarness = process.platform === "win32" && (event.raw["_tg_harness"] === "codex" || event.raw["_tg_harness"] === "copilot_cli" || event.raw["tool_name"] === "powershell");
|
|
8927
|
+
if (isPwshHarness) {
|
|
8928
|
+
if (!canRunPowerShell()) return null;
|
|
8929
|
+
} else {
|
|
8930
|
+
if (!canRunWrappedShell()) return null;
|
|
8931
|
+
}
|
|
8689
8932
|
let cfg;
|
|
8690
8933
|
try {
|
|
8691
8934
|
cfg = loadConfig().bash_compress;
|
|
8692
8935
|
} catch {
|
|
8693
8936
|
return null;
|
|
8694
8937
|
}
|
|
8695
|
-
if (!cfg.enabled
|
|
8696
|
-
let
|
|
8938
|
+
if (!cfg.enabled) return null;
|
|
8939
|
+
let filterName;
|
|
8940
|
+
if (cap !== void 0) {
|
|
8941
|
+
if (hasBareBackground(cmd)) return null;
|
|
8942
|
+
filterName = "passthrough";
|
|
8943
|
+
} else {
|
|
8944
|
+
const gateCmd = stripTrailingStderrRedirect(cmd);
|
|
8945
|
+
const detected = detectFromCommand(gateCmd, commandRunDir(rawCmd, getCwd(event) ?? null) ?? void 0);
|
|
8946
|
+
if (detected !== null) {
|
|
8947
|
+
filterName = detected.filter.name;
|
|
8948
|
+
} else if (isCompressibleSingleCommand(gateCmd)) {
|
|
8949
|
+
filterName = "generic";
|
|
8950
|
+
} else {
|
|
8951
|
+
return null;
|
|
8952
|
+
}
|
|
8953
|
+
}
|
|
8954
|
+
if (cfg.disabled_filters.includes(filterName)) return null;
|
|
8955
|
+
const capArgs = cap === void 0 ? "" : ` --max-tokens ${cap.maxTokens} --cap-hint-b64 ${Buffer.from(cap.hint, "utf8").toString("base64")}`;
|
|
8956
|
+
if (isPwshHarness) {
|
|
8957
|
+
const b64 = Buffer.from(rawCmd, "utf8").toString("base64");
|
|
8958
|
+
if (Buffer.from(b64, "base64").toString("utf8") !== rawCmd) return null;
|
|
8959
|
+
const wrapped2 = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds}${capArgs} --shell pwsh --cmd-b64 ${b64}`;
|
|
8960
|
+
if (wrapped2.length > 24e3) return null;
|
|
8961
|
+
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped2 } };
|
|
8962
|
+
}
|
|
8963
|
+
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds}${capArgs} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8964
|
+
if (cap !== void 0 && wrapped.length > 24e3) return null;
|
|
8965
|
+
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8966
|
+
}
|
|
8967
|
+
function wholeFileRange(filePath, hintPath, cwd, ranges, tool) {
|
|
8968
|
+
const [range] = ranges;
|
|
8969
|
+
if (ranges.length !== 1 || range === void 0 || range[0] > 1) return null;
|
|
8970
|
+
const cat = classifyCatPath(filePath, tool);
|
|
8971
|
+
if (cat === null) return null;
|
|
8697
8972
|
try {
|
|
8698
|
-
|
|
8973
|
+
const onDisk = hostPathOfTypedPath(hintPath, cwd);
|
|
8974
|
+
const st = statSync7(onDisk);
|
|
8975
|
+
if (!st.isFile() || st.size > SLICE_ESTIMATE_SCAN_CAP_BYTES) return null;
|
|
8976
|
+
const total = countTextLines(readFileSync8(onDisk, "utf8"));
|
|
8977
|
+
if (total === 0 || range[1] < total) return null;
|
|
8978
|
+
return { start: range[0], end: range[1], cat, reason: "`" + tool + "` over lines " + range[0] + "-" + range[1] + " is the whole file (" + total + " lines), and loads all of it into context." };
|
|
8699
8979
|
} catch {
|
|
8700
8980
|
return null;
|
|
8701
8981
|
}
|
|
8702
|
-
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8703
|
-
const notice = `[token-goat: curl HTML body cleaned via extractCleanText; use \`token-goat bash-output ${id} --full\` to recall the raw markup]
|
|
8704
|
-
`;
|
|
8705
|
-
const noticeBytes = Buffer.byteLength(notice, "utf-8");
|
|
8706
|
-
const clipNote = "\n[token-goat: cleaned text clipped to the harness delivery cap; the notice at the top of this output recalls the full raw markup]";
|
|
8707
|
-
const fenceOverhead = Buffer.byteLength(fenceUntrusted(cleaned, UNTRUSTED_TOOL_TAG), "utf-8") - Buffer.byteLength(cleaned, "utf-8");
|
|
8708
|
-
const cut = clipToDeliveryCap(cleaned, noticeBytes + fenceOverhead + Buffer.byteLength(clipNote, "utf-8"));
|
|
8709
|
-
const body = cut.text;
|
|
8710
|
-
const clipped = cut.clipped ? clipNote : "";
|
|
8711
|
-
const fenced = fenceUntrusted(body, UNTRUSTED_TOOL_TAG) + clipped;
|
|
8712
|
-
if (!isRewriteWorthwhile({
|
|
8713
|
-
originalBytes,
|
|
8714
|
-
rewrittenBytes: Buffer.byteLength(fenced, "utf-8"),
|
|
8715
|
-
noticeBytes,
|
|
8716
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
8717
|
-
})) {
|
|
8718
|
-
return null;
|
|
8719
|
-
}
|
|
8720
|
-
return emitRewrite(notice + fenced, "curl HTML body cleaned", { kind: "bash_compress:curl-html", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
8721
8982
|
}
|
|
8722
8983
|
function preBashHandlerInner(event) {
|
|
8723
8984
|
const rawCmd = extractCommand(event);
|
|
8724
8985
|
if (rawCmd === void 0) return passOutput();
|
|
8725
8986
|
const cmd = stripCommandPrefix(rawCmd);
|
|
8726
|
-
const
|
|
8987
|
+
const grouped = stripSubshellGroup(rawCmd);
|
|
8988
|
+
const cdStripped = stripCdPrefix(grouped) !== grouped;
|
|
8727
8989
|
const preHookCwd = getCwd(event) ?? null;
|
|
8990
|
+
const toolUseId = event.raw["tool_use_id"];
|
|
8991
|
+
if (typeof toolUseId === "string" && toolUseId !== "" && preHookCwd !== null) recordBashStartCwd(toolUseId, preHookCwd);
|
|
8728
8992
|
const hintCwd = preHookCwd ?? process.cwd();
|
|
8993
|
+
const runDir = commandRunDir(rawCmd, preHookCwd);
|
|
8729
8994
|
const pathHint = (paths, text) => contextOutput(text, typeof paths === "string" ? [paths] : paths);
|
|
8730
8995
|
const targetFor = (hintPath, slice = sliceForPath(hintPath)) => hintTarget(hintPath, slice, { cwd: hintCwd, event });
|
|
8996
|
+
const taskOutputPath = (typed) => {
|
|
8997
|
+
if (!commandPathIsTouchable(typed, event)) return null;
|
|
8998
|
+
if (path5.isAbsolute(typed) || path5.win32.isAbsolute(typed)) return { probe: typed, shown: displaySafePath(typed) };
|
|
8999
|
+
const probe = resolveIndexPath(typed, cdPrefixCwd(rawCmd, hintCwd));
|
|
9000
|
+
return { probe, shown: displaySafePath(probe) };
|
|
9001
|
+
};
|
|
8731
9002
|
const pricedSubstitute = (hintPath, start, end) => {
|
|
9003
|
+
if (!commandPathIsTouchable(hintPath, event)) return null;
|
|
8732
9004
|
const sub = rangeSubstituteFor(hintPath, hintCwd, [[start, end]]);
|
|
8733
9005
|
return sub !== null && meetsSavingsFloor(sub.requestedBytes - sub.replacementBytes) ? sub : null;
|
|
8734
9006
|
};
|
|
@@ -8743,6 +9015,12 @@ function preBashHandlerInner(event) {
|
|
|
8743
9015
|
return false;
|
|
8744
9016
|
};
|
|
8745
9017
|
const cfg = loadConfig();
|
|
9018
|
+
if (cfg.hints.deny_bash_double_backslash && process.platform === "win32" && event.raw["_tg_harness"] === "claude" && rawCmd.includes("\\\\") && detectHarness() === "claudecode") {
|
|
9019
|
+
recordStat("session_hint", 0, 0);
|
|
9020
|
+
return denyOutput(
|
|
9021
|
+
"This command has two backslashes in a row. Claude Code's Bash tool on Windows halves every run of two or more backslashes before Git Bash sees the command, so it would run with different text than you wrote, with no error (anthropics/claude-code#85856). Write text containing backslashes to a file with the Write tool and run it by path, or use the PowerShell tool, which passes the command unchanged. For paths, use forward slashes: C:/Users/me works in Git Bash. To turn this check off, set hints.deny_bash_double_backslash = false or TOKEN_GOAT_DENY_BASH_DOUBLE_BACKSLASH=0."
|
|
9022
|
+
);
|
|
9023
|
+
}
|
|
8746
9024
|
if (cfg.hints.warn_unbalanced_shell_quoting) {
|
|
8747
9025
|
const quoteError = detectUnbalancedShellSyntax(cmd);
|
|
8748
9026
|
if (quoteError !== null) {
|
|
@@ -8754,9 +9032,11 @@ function preBashHandlerInner(event) {
|
|
|
8754
9032
|
}
|
|
8755
9033
|
const taskOutput = extractTasksOutput(cmd);
|
|
8756
9034
|
if (taskOutput !== null) {
|
|
8757
|
-
const { id,
|
|
9035
|
+
const { id, n } = taskOutput;
|
|
9036
|
+
const outPaths = taskOutputPath(taskOutput.path);
|
|
8758
9037
|
recordStat("session_hint", 0, 0);
|
|
8759
|
-
if (taskOutputIsJsonlTranscript(
|
|
9038
|
+
if (outPaths !== null && taskOutputIsJsonlTranscript(outPaths.probe)) {
|
|
9039
|
+
const outPath = outPaths.shown;
|
|
8760
9040
|
const tail = n ?? 50;
|
|
8761
9041
|
return denyOutput(
|
|
8762
9042
|
"Task output " + id + ' is a JSONL agent transcript on disk. Use `token-goat bash-output --file "' + outPath + '" --transcript` to read the assistant text, then narrow with `--grep PATTERN` or `--tail ' + tail + '`, or read a specific line range (the only way to reach the MIDDLE of a large artifact) with `token-goat read "' + outPath + '@START-END"`, instead of reading the whole file.'
|
|
@@ -8814,13 +9094,22 @@ function preBashHandlerInner(event) {
|
|
|
8814
9094
|
const hintPaths = [];
|
|
8815
9095
|
for (const { filePath, ranges, tool } of sedReads) {
|
|
8816
9096
|
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9097
|
+
if (!commandPathIsTouchable(hintPath, event)) continue;
|
|
8817
9098
|
hintPaths.push(hintPath);
|
|
8818
9099
|
const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
9100
|
+
const whole = singleLineRangeRead === null ? null : wholeFileRange(filePath, hintPath, hintCwd, ranges, tool);
|
|
9101
|
+
if (whole !== null && findRangeOverlap(getFileLineRanges(sedDedupKey), whole.start, whole.end) === null) {
|
|
9102
|
+
recordStat("session_hint", 0, 0);
|
|
9103
|
+
if (whole.cat.isSql) return pathHint(hintPath, sqlTableHint(hintPath, targetFor(hintPath, "table"), whole.reason));
|
|
9104
|
+
const hint = surgicalHintFor(hintPath, whole.cat.isEnv, whole.cat.isConfig, whole.cat.isDoc, whole.cat.isXml, targetFor(hintPath, whole.cat.isEnv ? "key" : void 0), whole.reason);
|
|
9105
|
+
return cdStripped ? pathHint(hintPath, hint) : denyOutput(hint);
|
|
9106
|
+
}
|
|
8819
9107
|
const overlapHints = [];
|
|
8820
9108
|
const freshRanges = [];
|
|
9109
|
+
const onDisk = isFileAtIndexKey(sedDedupKey);
|
|
8821
9110
|
for (const [start, end] of ranges) {
|
|
8822
9111
|
const priorOverlap = findRangeOverlap(getFileLineRanges(sedDedupKey), start, end);
|
|
8823
|
-
recordFileLineRange(sedDedupKey, start, end);
|
|
9112
|
+
if (onDisk) recordFileLineRange(sedDedupKey, start, end);
|
|
8824
9113
|
if (priorOverlap !== null) {
|
|
8825
9114
|
overlapHints.push(sedOverlapHint(hintPath, priorOverlap, start, end));
|
|
8826
9115
|
} else {
|
|
@@ -8933,8 +9222,9 @@ function preBashHandlerInner(event) {
|
|
|
8933
9222
|
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
8934
9223
|
recordStat("session_hint", 0, 0);
|
|
8935
9224
|
if (isOutputFile) {
|
|
8936
|
-
|
|
8937
|
-
|
|
9225
|
+
const outPaths = taskOutputPath(filePath);
|
|
9226
|
+
if (outPaths !== null && taskOutputIsJsonlTranscript(outPaths.probe)) {
|
|
9227
|
+
const tHint = 'This `.output` file is a JSONL agent transcript. Use `token-goat bash-output --file "' + outPaths.shown + '" --transcript` to read the assistant text, then narrow with `--grep PATTERN` or `--tail N`, instead of hand-parsing the JSONL.';
|
|
8938
9228
|
return cdStripped ? pathHint(hintPath, tHint) : denyOutput(tHint);
|
|
8939
9229
|
}
|
|
8940
9230
|
return pathHint(
|
|
@@ -8995,7 +9285,7 @@ function preBashHandlerInner(event) {
|
|
|
8995
9285
|
recordStat("session_hint", 0, 0);
|
|
8996
9286
|
return cdStripped ? pathHint(hintPath, denial) : cappedInterpreterRead(event, rawCmd, cmd, hint, denial);
|
|
8997
9287
|
}
|
|
8998
|
-
const structuralRewrite = detectStructuralIndexRewrite(rawCmd, hintCwd);
|
|
9288
|
+
const structuralRewrite = detectStructuralIndexRewrite(rawCmd, hintCwd, event);
|
|
8999
9289
|
if (structuralRewrite !== null) {
|
|
9000
9290
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: structuralRewrite.command } };
|
|
9001
9291
|
}
|
|
@@ -9010,7 +9300,7 @@ function preBashHandlerInner(event) {
|
|
|
9010
9300
|
const { filePath } = mdHeadingGrep;
|
|
9011
9301
|
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9012
9302
|
recordStat("session_hint", 0, 0);
|
|
9013
|
-
return pathHint(hintPath,
|
|
9303
|
+
return pathHint(hintPath, docSectionHint(hintPath, targetFor(hintPath, "section").name));
|
|
9014
9304
|
}
|
|
9015
9305
|
const rgSymbol = extractRgSymbolSearch(cmd);
|
|
9016
9306
|
if (rgSymbol !== null) {
|
|
@@ -9033,11 +9323,11 @@ function preBashHandlerInner(event) {
|
|
|
9033
9323
|
}
|
|
9034
9324
|
const monitoringHint = getMonitoringRecallHint(cmd);
|
|
9035
9325
|
if (monitoringHint !== null) {
|
|
9036
|
-
const monCmdHash =
|
|
9326
|
+
const monCmdHash = bashRecallKey(cmd, runDir);
|
|
9037
9327
|
const monOutputId = getBashOutputId(monCmdHash);
|
|
9038
9328
|
const monEntryRaw = monOutputId !== null ? getBashOutput(monOutputId) : null;
|
|
9039
|
-
const monEntry = monEntryRaw !== null && !isBashEntryStale(monEntryRaw, cmd,
|
|
9040
|
-
if (monOutputId !== null && monEntry !== null && recallWorthShowing(monOutputId, monEntry)) {
|
|
9329
|
+
const monEntry = monEntryRaw !== null && !isBashEntryStale(monEntryRaw, cmd, runDir) ? monEntryRaw : null;
|
|
9330
|
+
if (monOutputId !== null && monEntry !== null && cachedRunCoversCommand(monEntry.command, cmd) && recallWorthShowing(monOutputId, monEntry)) {
|
|
9041
9331
|
const monBytes = monEntry.sizeBytes;
|
|
9042
9332
|
const catFile = extractCatSourceFile(cmd);
|
|
9043
9333
|
if (catFile !== null) {
|
|
@@ -9046,7 +9336,7 @@ function preBashHandlerInner(event) {
|
|
|
9046
9336
|
"Prior output from `" + cmd + "`" + pipelineDivergenceNote(cmd, monEntry.command) + " is cached. Use `token-goat bash-output " + monOutputId + "` to recall the full file, or `" + sliceCommand(displaySafePath(catFile), targetFor(displaySafePath(catFile))) + "` to extract only the part you need."
|
|
9047
9337
|
);
|
|
9048
9338
|
}
|
|
9049
|
-
const cmdSummary = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
|
|
9339
|
+
const cmdSummary = cmd.length > 60 ? cmd.slice(0, 57).trimEnd() + "..." : cmd;
|
|
9050
9340
|
recordStat("bash_compress:recall", monBytes, savedTokensFromBytes(monBytes));
|
|
9051
9341
|
return contextOutput(
|
|
9052
9342
|
"Prior output from `" + cmdSummary + "`" + pipelineDivergenceNote(cmd, monEntry.command) + " is cached.\nUse `token-goat bash-output " + monOutputId + " " + monitoringHint + "` to re-inspect without re-running."
|
|
@@ -9056,10 +9346,10 @@ function preBashHandlerInner(event) {
|
|
|
9056
9346
|
const curlDl = extractCurlDownload(cmd);
|
|
9057
9347
|
if (curlDl !== null) {
|
|
9058
9348
|
const prevPath = getCurlDownloadPath(curlDl.url);
|
|
9059
|
-
const
|
|
9060
|
-
if (prevPath !== null &&
|
|
9349
|
+
const prevOnDisk = prevPath !== null && commandPathIsTouchable(prevPath, event) ? hostPathOfTypedPath(prevPath, preHookCwd ?? process.cwd()) : null;
|
|
9350
|
+
if (prevPath !== null && prevOnDisk !== null && !existsSync(prevOnDisk)) {
|
|
9061
9351
|
clearCurlDownload(curlDl.url);
|
|
9062
|
-
} else if (prevPath !== null &&
|
|
9352
|
+
} else if (prevPath !== null && prevOnDisk !== null && statSync7(prevOnDisk).size >= loadConfig().hints.bash_dedup_min_bytes) {
|
|
9063
9353
|
recordStat("session_hint", 0, 0);
|
|
9064
9354
|
return denyOutput(leadWithCommand(sliceCommand(prevPath, targetFor(prevPath)), "to read a part of it, or `rg '<pattern>' " + prevPath + "` to search it", "Already downloaded to " + prevPath + " earlier this session."));
|
|
9065
9355
|
}
|
|
@@ -9069,7 +9359,7 @@ function preBashHandlerInner(event) {
|
|
|
9069
9359
|
const curlHash = shortFingerprint(curlCacheKey);
|
|
9070
9360
|
const curlOutputId = getBashOutputId(curlHash);
|
|
9071
9361
|
const curlEntryRaw = curlOutputId !== null ? getBashOutput(curlOutputId) : null;
|
|
9072
|
-
const curlEntry = curlEntryRaw !== null && !isBashEntryStale(curlEntryRaw, cmd,
|
|
9362
|
+
const curlEntry = curlEntryRaw !== null && !isBashEntryStale(curlEntryRaw, cmd, runDir) ? curlEntryRaw : null;
|
|
9073
9363
|
if (curlOutputId !== null && curlEntry !== null && recallWorthShowing(curlOutputId, curlEntry)) {
|
|
9074
9364
|
const curlBytes = curlEntry.sizeBytes;
|
|
9075
9365
|
recordStat("bash_compress:recall", curlBytes, savedTokensFromBytes(curlBytes));
|
|
@@ -9081,10 +9371,10 @@ function preBashHandlerInner(event) {
|
|
|
9081
9371
|
}
|
|
9082
9372
|
}
|
|
9083
9373
|
if (isReadOnlyGhApi(cmd)) {
|
|
9084
|
-
const ghHash =
|
|
9374
|
+
const ghHash = bashRecallKey(cmd, runDir);
|
|
9085
9375
|
const ghOutputId = getBashOutputId(ghHash);
|
|
9086
9376
|
const ghEntryRaw = ghOutputId !== null ? getBashOutput(ghOutputId) : null;
|
|
9087
|
-
const ghEntry = ghEntryRaw !== null && !isBashEntryStale(ghEntryRaw, cmd,
|
|
9377
|
+
const ghEntry = ghEntryRaw !== null && !isBashEntryStale(ghEntryRaw, cmd, runDir) ? ghEntryRaw : null;
|
|
9088
9378
|
if (ghOutputId !== null && ghEntry !== null && recallWorthShowing(ghOutputId, ghEntry)) {
|
|
9089
9379
|
const ghBytes = ghEntry.sizeBytes;
|
|
9090
9380
|
recordStat("bash_compress:recall", ghBytes, savedTokensFromBytes(ghBytes));
|
|
@@ -9095,10 +9385,10 @@ function preBashHandlerInner(event) {
|
|
|
9095
9385
|
}
|
|
9096
9386
|
}
|
|
9097
9387
|
if (isScopedGitStatusOrDiffStatCommand(cmd)) {
|
|
9098
|
-
const gitScopedHash =
|
|
9388
|
+
const gitScopedHash = bashRecallKey(cmd, runDir);
|
|
9099
9389
|
const gitScopedOutputId = getBashOutputId(gitScopedHash);
|
|
9100
9390
|
const gitScopedEntryRaw = gitScopedOutputId !== null ? getBashOutput(gitScopedOutputId) : null;
|
|
9101
|
-
const gitScopedEntry = gitScopedEntryRaw !== null && !isBashEntryStale(gitScopedEntryRaw, cmd,
|
|
9391
|
+
const gitScopedEntry = gitScopedEntryRaw !== null && !isBashEntryStale(gitScopedEntryRaw, cmd, runDir) ? gitScopedEntryRaw : null;
|
|
9102
9392
|
if (gitScopedOutputId !== null && gitScopedEntry !== null && recallWorthShowing(gitScopedOutputId, gitScopedEntry)) {
|
|
9103
9393
|
const gitScopedBytes = gitScopedEntry.sizeBytes;
|
|
9104
9394
|
recordStat("bash_compress:recall", gitScopedBytes, savedTokensFromBytes(gitScopedBytes));
|
|
@@ -9108,61 +9398,326 @@ function preBashHandlerInner(event) {
|
|
|
9108
9398
|
);
|
|
9109
9399
|
}
|
|
9110
9400
|
}
|
|
9111
|
-
const tgRead = extractTgSurgicalRead(cmd,
|
|
9112
|
-
if (tgRead !== null) {
|
|
9113
|
-
const cliKey = tgRead.sub + "::" + tgRead.spec;
|
|
9114
|
-
const notes = [];
|
|
9115
|
-
if (wasCliReadThisSession(cliKey)) {
|
|
9116
|
-
notes.push("You already ran this exact `token-goat " + tgRead.sub + "` query earlier this session \u2014 check your context above before re-running it.");
|
|
9117
|
-
}
|
|
9118
|
-
if (tgRead.filePath !== null && wasFileReadThisSession(tgRead.filePath)) {
|
|
9119
|
-
notes.push("`" + tgRead.filePath + "` was already fully read via the Read tool this session \u2014 that content may already cover this.");
|
|
9120
|
-
}
|
|
9121
|
-
if (notes.length > 0) {
|
|
9122
|
-
recordStat("session_hint", 0, 0);
|
|
9123
|
-
return pathHint(tgRead.filePath !== null ? [tgRead.filePath] : [], notes.join(" "));
|
|
9401
|
+
const tgRead = extractTgSurgicalRead(cmd, runDir);
|
|
9402
|
+
if (tgRead !== null) {
|
|
9403
|
+
const cliKey = tgRead.sub + "::" + tgRead.spec;
|
|
9404
|
+
const notes = [];
|
|
9405
|
+
if (wasCliReadThisSession(cliKey)) {
|
|
9406
|
+
notes.push("You already ran this exact `token-goat " + tgRead.sub + "` query earlier this session \u2014 check your context above before re-running it.");
|
|
9407
|
+
}
|
|
9408
|
+
if (tgRead.filePath !== null && wasFileReadThisSession(tgRead.filePath)) {
|
|
9409
|
+
notes.push("`" + tgRead.filePath + "` was already fully read via the Read tool this session \u2014 that content may already cover this.");
|
|
9410
|
+
}
|
|
9411
|
+
if (notes.length > 0) {
|
|
9412
|
+
recordStat("session_hint", 0, 0);
|
|
9413
|
+
return pathHint(tgRead.filePath !== null ? [tgRead.filePath] : [], notes.join(" "));
|
|
9414
|
+
}
|
|
9415
|
+
return passOutput();
|
|
9416
|
+
}
|
|
9417
|
+
if (isTestRunnerCommand(cmd) && isDirectTestRunnerCommand(cmd) && !hasTestRunScopeOrBudget(cmd)) {
|
|
9418
|
+
const compression = maybeCompressRewrite(event, rawCmd, cmd);
|
|
9419
|
+
if (compression !== null) return compression;
|
|
9420
|
+
const key = `test-run-budget:${event.sessionId}:${shortFingerprint(cmd)}`;
|
|
9421
|
+
if (!wasHintShown(key)) {
|
|
9422
|
+
markHintShown(key);
|
|
9423
|
+
recordStat("session_hint", 0, 0);
|
|
9424
|
+
return contextOutput(
|
|
9425
|
+
"This test command has no targeted selector or explicit timeout. Prefer a focused test path/name or the runner\u2019s supported timeout before starting a potentially unbounded suite; run the full suite deliberately when it is needed."
|
|
9426
|
+
);
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
if (!isBuildCommand(cmd) && detectFromCommand(stripTrailingStderrRedirect(cmd), runDir ?? void 0) === null) return passOutput();
|
|
9430
|
+
const cmdHash = bashRecallKey(cmd, runDir);
|
|
9431
|
+
const outputId = getBashOutputId(cmdHash);
|
|
9432
|
+
const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
|
|
9433
|
+
const entry = entryRaw !== null && !isBashEntryStale(entryRaw, cmd, runDir) ? entryRaw : null;
|
|
9434
|
+
if (outputId !== null && entry !== null && recallWorthShowing(outputId, entry)) {
|
|
9435
|
+
const bytes = entry.sizeBytes;
|
|
9436
|
+
recordStat("bash_compress:recall", bytes, savedTokensFromBytes(bytes));
|
|
9437
|
+
return contextOutput(buildRecallHint(cmd, outputId));
|
|
9438
|
+
}
|
|
9439
|
+
return maybeCompressRewrite(event, rawCmd, cmd) ?? passOutput();
|
|
9440
|
+
}
|
|
9441
|
+
function preBashHandler(event) {
|
|
9442
|
+
return applyHintTracking(event, preBashHandlerInner(event), classifyBashHint);
|
|
9443
|
+
}
|
|
9444
|
+
registerHook("pre_tool_use", preBashHandler, { toolName: "Bash" });
|
|
9445
|
+
|
|
9446
|
+
// src/hooks_bash_post.ts
|
|
9447
|
+
init_define_import_meta_env();
|
|
9448
|
+
import { statSync as statSync8, existsSync as existsSync2, readFileSync as readFileSync9 } from "node:fs";
|
|
9449
|
+
import * as path6 from "node:path";
|
|
9450
|
+
function elideServedShellLines(cmd, output, priorIds, unknownLineNumbers = false) {
|
|
9451
|
+
if (priorIds.length === 0) return null;
|
|
9452
|
+
const lines = output.split("\n");
|
|
9453
|
+
const numbers = unknownLineNumbers ? Array.from({ length: lines.length }, () => null) : deliveredLineNumbers(cmd, lines.length);
|
|
9454
|
+
if (numbers === null) return null;
|
|
9455
|
+
const rows = lines.map((text, i) => ({ no: numbers[i] ?? null, text, raw: text }));
|
|
9456
|
+
const bodies = [];
|
|
9457
|
+
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
9458
|
+
const id = priorIds[i];
|
|
9459
|
+
if (id === void 0) continue;
|
|
9460
|
+
const prior = getBashOutput(id);
|
|
9461
|
+
if (prior !== null) bodies.push(indexServedBody(id, prior.output));
|
|
9462
|
+
}
|
|
9463
|
+
const cuts = planServedElisions(rows, bodies);
|
|
9464
|
+
if (cuts.length === 0) return null;
|
|
9465
|
+
const spans = [];
|
|
9466
|
+
const push = (text, own) => {
|
|
9467
|
+
const sep = spans.length === 0 ? "" : "\n";
|
|
9468
|
+
const prev = spans[spans.length - 1];
|
|
9469
|
+
if (prev !== void 0 && prev.own === true === own) spans[spans.length - 1] = { text: prev.text + sep + text, own };
|
|
9470
|
+
else spans.push({ text: sep + text, own });
|
|
9471
|
+
};
|
|
9472
|
+
let at = 0;
|
|
9473
|
+
for (const cut of cuts) {
|
|
9474
|
+
for (let i = at; i < cut.start; i++) push(rows[i]?.raw ?? "", false);
|
|
9475
|
+
const first2 = rows[cut.start];
|
|
9476
|
+
const last = rows[cut.start + cut.len - 1];
|
|
9477
|
+
if (first2 === void 0 || last === void 0) return null;
|
|
9478
|
+
push(servedRunNotice(first2.no, last.no, cut.id, cut.len), true);
|
|
9479
|
+
at = cut.start + cut.len;
|
|
9480
|
+
}
|
|
9481
|
+
for (let i = at; i < rows.length; i++) push(rows[i]?.raw ?? "", false);
|
|
9482
|
+
return spans;
|
|
9483
|
+
}
|
|
9484
|
+
function foldShellReadBodies(cmd, output, fileKey, cwd) {
|
|
9485
|
+
if (!foldingEnabled()) return null;
|
|
9486
|
+
if (redactSecrets(output).count > 0) return null;
|
|
9487
|
+
const lines = output.split("\n");
|
|
9488
|
+
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
9489
|
+
if (numbers === null) return null;
|
|
9490
|
+
const rows = [];
|
|
9491
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9492
|
+
const no = numbers[i];
|
|
9493
|
+
if (no === void 0 || no === null) return null;
|
|
9494
|
+
rows.push({ no, text: lines[i] ?? "", raw: lines[i] ?? "" });
|
|
9495
|
+
}
|
|
9496
|
+
const headRead = extractHeadFile(cmd) ?? extractGetContentHead(cmd);
|
|
9497
|
+
const windowed = extractLineRangeRead(cmd) !== null || headRead !== null && lines.length >= headRead.n;
|
|
9498
|
+
const folded = foldDelivery(rows, fileKey, displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey)), windowed);
|
|
9499
|
+
if (folded === null) return null;
|
|
9500
|
+
return { text: folded.numbered.join("\n"), folds: folded.folds };
|
|
9501
|
+
}
|
|
9502
|
+
function foldShellReadStructure(cmd, filePath, output, fileKey, cwd) {
|
|
9503
|
+
if (!isWholeFileDump(cmd, filePath)) return null;
|
|
9504
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
9505
|
+
let onDisk;
|
|
9506
|
+
try {
|
|
9507
|
+
onDisk = statSync8(hostPathOfIndexKey(fileKey)).size;
|
|
9508
|
+
} catch {
|
|
9509
|
+
return null;
|
|
9510
|
+
}
|
|
9511
|
+
if (onDisk !== originalBytes) return null;
|
|
9512
|
+
if (redactSecrets(output).count > 0) return null;
|
|
9513
|
+
const lines = output.split("\n");
|
|
9514
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
9515
|
+
const rows = lines.map((text2, i) => ({ no: i + 1, text: text2, raw: text2 }));
|
|
9516
|
+
const shown = displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey));
|
|
9517
|
+
const fold = planMarkdownOutline(rows, fileKey, shown, originalBytes) ?? planSourceSkeleton(rows, fileKey, shown, originalBytes);
|
|
9518
|
+
if (fold === null) return null;
|
|
9519
|
+
const text = fold.numbered.join("\n");
|
|
9520
|
+
if (!isStructuralRewriteAccepted(originalBytes, Buffer.byteLength(text, "utf-8"), fold.ratioCap)) return null;
|
|
9521
|
+
return { text, kind: fold.kind === "read:markdown_outline" ? "bash_compress:markdown-outline" : "bash_compress:source-skeleton", detail: fold.detail };
|
|
9522
|
+
}
|
|
9523
|
+
var REWRITE_CLIP_NOTE = "\n[token-goat: rewrite clipped to the harness delivery cap; the pointer above recalls the untouched output]";
|
|
9524
|
+
function fenceRewriteWithinCap(spans) {
|
|
9525
|
+
const joined = spans.map((s) => s.text).join("");
|
|
9526
|
+
const fenced = fenceUntrustedSpans(spans, UNTRUSTED_TOOL_TAG);
|
|
9527
|
+
if (fenced === joined) return joined;
|
|
9528
|
+
const overhead = Buffer.byteLength(fenced, "utf-8") - Buffer.byteLength(joined, "utf-8") + Buffer.byteLength(REWRITE_CLIP_NOTE, "utf-8");
|
|
9529
|
+
const cut = clipToDeliveryCap(joined, overhead);
|
|
9530
|
+
if (!cut.clipped) return fenced;
|
|
9531
|
+
const kept = [];
|
|
9532
|
+
let left2 = cut.text.length;
|
|
9533
|
+
for (const s of spans) {
|
|
9534
|
+
if (left2 <= 0) break;
|
|
9535
|
+
kept.push(s.text.length <= left2 ? s : { ...s, text: s.text.slice(0, left2) });
|
|
9536
|
+
left2 -= s.text.length;
|
|
9537
|
+
}
|
|
9538
|
+
return fenceUntrustedSpans(kept, UNTRUSTED_TOOL_TAG) + REWRITE_CLIP_NOTE;
|
|
9539
|
+
}
|
|
9540
|
+
async function maybeCollapseIdenticalRead(cmd, runDir, optedOut, output, exitCode, cwd, cacheMinBytes, persisted = false) {
|
|
9541
|
+
if (optedOut) return null;
|
|
9542
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
9543
|
+
const filePath = pureFileReadPath(cmd);
|
|
9544
|
+
if (filePath === null) return null;
|
|
9545
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
9546
|
+
if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
|
|
9547
|
+
const fileKey = resolveIndexPath(filePath, runDir ?? process.cwd());
|
|
9548
|
+
const priorIds = getFileServedOutputs(fileKey);
|
|
9549
|
+
let containerId = null;
|
|
9550
|
+
let identical = false;
|
|
9551
|
+
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
9552
|
+
const id = priorIds[i];
|
|
9553
|
+
if (id === void 0) continue;
|
|
9554
|
+
const prior = getBashOutput(id);
|
|
9555
|
+
if (prior === null || !containsLineRun(prior.output, output)) continue;
|
|
9556
|
+
containerId = id;
|
|
9557
|
+
identical = prior.output === output;
|
|
9558
|
+
break;
|
|
9559
|
+
}
|
|
9560
|
+
const sessionKey = bashRecallKey(cmd, runDir);
|
|
9561
|
+
if (containerId === null) {
|
|
9562
|
+
const elided = elideServedShellLines(cmd, output, priorIds);
|
|
9563
|
+
const priced = (text) => isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(text, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() });
|
|
9564
|
+
let rewrite = null;
|
|
9565
|
+
if (elided !== null) {
|
|
9566
|
+
const fenced = fenceRewriteWithinCap(elided);
|
|
9567
|
+
if (priced(fenced)) rewrite = { text: fenced, reason: "already-served file lines withheld", kind: "bash_compress:served-elide" };
|
|
9568
|
+
} else {
|
|
9569
|
+
const structural = foldShellReadStructure(cmd, filePath, output, fileKey, cwd);
|
|
9570
|
+
if (structural !== null && priced(structural.text)) {
|
|
9571
|
+
rewrite = { text: structural.text, reason: structural.kind === "bash_compress:markdown-outline" ? "document replaced with its heading tree" : "source replaced with its structural skeleton", kind: structural.kind, detail: structural.detail };
|
|
9572
|
+
}
|
|
9573
|
+
if (rewrite === null) {
|
|
9574
|
+
const folded = foldShellReadBodies(cmd, output, fileKey, cwd);
|
|
9575
|
+
if (folded !== null) {
|
|
9576
|
+
const fenced = fenceRewriteWithinCap([{ text: folded.text }]);
|
|
9577
|
+
if (priced(fenced)) rewrite = { text: fenced, reason: "code bodies folded", kind: "bash_compress:body-fold", detail: foldDetail(fileKey, folded.folds) };
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
if (rewrite !== null && (extractLineRangeRead(cmd) !== null || extractHeadFile(cmd) !== null)) resetFileLineRanges(fileKey);
|
|
9581
|
+
}
|
|
9582
|
+
const storedId = await storeBashOutput(cmd, rewrite?.text ?? output, exitCode ?? 0, runDir);
|
|
9583
|
+
recordBashOutput(sessionKey, storedId, originalBytes);
|
|
9584
|
+
if (!persisted) recordFileServedOutput(fileKey, storedId);
|
|
9585
|
+
if (rewrite === null) return null;
|
|
9586
|
+
return emitRewrite(rewrite.text, rewrite.reason, { kind: rewrite.kind, originalBytes: deliveredOutputBytes(originalBytes), ...rewrite.detail === void 0 ? {} : { detail: rewrite.detail } });
|
|
9587
|
+
}
|
|
9588
|
+
const pointer = identical ? "[token-goat] Identical to an earlier run of this command in this session; the file has not changed since. " + originalBytes + " bytes withheld -- recall them with `token-goat bash-output " + containerId + " --full`." : "[token-goat] These " + originalBytes + " bytes already appear verbatim inside a wider read of " + filePath + " served earlier in this session. Withheld -- recall the full earlier output with `token-goat bash-output " + containerId + " --full`.";
|
|
9589
|
+
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
9590
|
+
return emitRewrite(pointer, identical ? "identical file re-read collapsed" : "already-served file lines collapsed", { kind: identical ? "bash_compress:identical-reread" : "bash_compress:contained-reread", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
9591
|
+
}
|
|
9592
|
+
async function maybeElideServedGenericOutput(cmd, optedOut, output, exitCode, runDir, cacheMinBytes, persisted = false) {
|
|
9593
|
+
if (optedOut) return null;
|
|
9594
|
+
if (isRecallCommand(cmd)) return null;
|
|
9595
|
+
if (!loadConfig().bash_compress.elide_served_shell_output) return null;
|
|
9596
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
9597
|
+
if (redactSecrets(output).count > 0) return null;
|
|
9598
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
9599
|
+
if (originalBytes < cacheMinBytes) return null;
|
|
9600
|
+
const priorIds = getFileServedOutputs(GENERIC_SERVED_OUTPUT_KEY).filter((id) => getBashOutput(id)?.command !== cmd);
|
|
9601
|
+
let rewrittenText = null;
|
|
9602
|
+
if (priorIds.length > 0) {
|
|
9603
|
+
const elided = elideServedShellLines(cmd, output, priorIds, true);
|
|
9604
|
+
const fenced = elided === null ? null : fenceRewriteWithinCap(elided);
|
|
9605
|
+
if (fenced !== null && isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(fenced, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) {
|
|
9606
|
+
rewrittenText = fenced;
|
|
9124
9607
|
}
|
|
9125
|
-
return passOutput();
|
|
9126
9608
|
}
|
|
9127
|
-
if (
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9609
|
+
if (!persisted) recordFileServedOutput(GENERIC_SERVED_OUTPUT_KEY, await storeBashOutput(cmd, rewrittenText ?? output, exitCode ?? 0, runDir));
|
|
9610
|
+
if (rewrittenText === null) return null;
|
|
9611
|
+
return emitRewrite(rewrittenText, "already-served shell output collapsed", { kind: "bash_compress:generic-served-elision", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
9612
|
+
}
|
|
9613
|
+
async function maybeCompressCompoundOutput(cmd, optedOut, output, exitCode, runDir, cacheMinBytes, isUnwrapped = false) {
|
|
9614
|
+
if (optedOut) return null;
|
|
9615
|
+
if (!isUnwrapped) return null;
|
|
9616
|
+
if (isFullRecallCommand(cmd)) return null;
|
|
9617
|
+
if (isTokenGoatOwnCommand(cmd)) return null;
|
|
9618
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
9619
|
+
let cfg;
|
|
9620
|
+
try {
|
|
9621
|
+
cfg = loadConfig().bash_compress;
|
|
9622
|
+
} catch {
|
|
9623
|
+
return null;
|
|
9138
9624
|
}
|
|
9139
|
-
if (!
|
|
9140
|
-
|
|
9141
|
-
const
|
|
9142
|
-
const
|
|
9143
|
-
const
|
|
9144
|
-
if (
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9625
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
|
|
9626
|
+
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
|
|
9627
|
+
const shaped = pipelineShapeFilter(cmd, runDir) ?? (isUnwrapped ? detectFromCommand(cmd, runDir ?? void 0) ?? null : null);
|
|
9628
|
+
const useShaped = shaped !== null && !cfg.disabled_filters.includes(shaped.filter.name);
|
|
9629
|
+
const filter3 = useShaped ? shaped.filter : filterByName("generic");
|
|
9630
|
+
if (filter3 === null) return null;
|
|
9631
|
+
const filterArgv = useShaped ? shaped.argv : [];
|
|
9632
|
+
const compressed = compressOutput(filter3, output, "", exitCode ?? 0, filterArgv, {
|
|
9633
|
+
maxLines: cfg.max_lines,
|
|
9634
|
+
maxBytes: cfg.max_bytes
|
|
9635
|
+
});
|
|
9636
|
+
const minNet = resolveMinNetSavingsBytes();
|
|
9637
|
+
if (!compressed.worthApplying(minNet)) return null;
|
|
9638
|
+
const id = bashOutputIdSync(cmd, output, runDir);
|
|
9639
|
+
const { body: untrusted, notices } = splitOwnTrailingNotices(compressed.text);
|
|
9640
|
+
const marker = compressed.withMarker(minNet).slice(compressed.text.length);
|
|
9641
|
+
const body = fenceUntrusted(untrusted, UNTRUSTED_TOOL_TAG) + notices + marker + "\n[token-goat] full output: bash-output " + id + " --full";
|
|
9642
|
+
const emittedBytes = Buffer.byteLength(body, "utf-8");
|
|
9643
|
+
if (!isRewriteWorthwhile({
|
|
9644
|
+
originalBytes: compressed.originalBytes,
|
|
9645
|
+
rewrittenBytes: emittedBytes,
|
|
9646
|
+
noticeBytes: 0,
|
|
9647
|
+
minNetSavingsBytes: minNet
|
|
9648
|
+
})) {
|
|
9649
|
+
return null;
|
|
9148
9650
|
}
|
|
9149
|
-
|
|
9651
|
+
await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9652
|
+
return emitRewrite(body, "bash", { kind: `bash_compress:${filter3.name}`, originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
9150
9653
|
}
|
|
9151
|
-
function
|
|
9152
|
-
|
|
9654
|
+
function maybeStripAnsiOnly(output) {
|
|
9655
|
+
if (!output.includes("\x1B")) return null;
|
|
9656
|
+
let cfg;
|
|
9657
|
+
try {
|
|
9658
|
+
cfg = loadConfig().bash_compress;
|
|
9659
|
+
} catch {
|
|
9660
|
+
return null;
|
|
9661
|
+
}
|
|
9662
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("ansi")) return null;
|
|
9663
|
+
const stripped = stripAnsiEscapes(output);
|
|
9664
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
9665
|
+
if (!isRewriteWorthwhile({
|
|
9666
|
+
originalBytes,
|
|
9667
|
+
rewrittenBytes: Buffer.byteLength(stripped, "utf-8"),
|
|
9668
|
+
noticeBytes: 0,
|
|
9669
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9670
|
+
})) {
|
|
9671
|
+
return null;
|
|
9672
|
+
}
|
|
9673
|
+
return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
|
|
9674
|
+
}
|
|
9675
|
+
function maybeFoldCurlHtml(cmd, output, id) {
|
|
9676
|
+
if (!isCurlGetCommand(cmd) || !looksLikeHtml(output)) return null;
|
|
9677
|
+
let cfg;
|
|
9678
|
+
try {
|
|
9679
|
+
cfg = loadConfig().bash_compress;
|
|
9680
|
+
} catch {
|
|
9681
|
+
return null;
|
|
9682
|
+
}
|
|
9683
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("curl-html")) return null;
|
|
9684
|
+
let cleaned;
|
|
9685
|
+
try {
|
|
9686
|
+
cleaned = extractCleanText(output);
|
|
9687
|
+
} catch {
|
|
9688
|
+
return null;
|
|
9689
|
+
}
|
|
9690
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
9691
|
+
const notice = `[token-goat: curl HTML body cleaned via extractCleanText; use \`token-goat bash-output ${id} --full\` to recall the raw markup]
|
|
9692
|
+
`;
|
|
9693
|
+
const noticeBytes = Buffer.byteLength(notice, "utf-8");
|
|
9694
|
+
const clipNote = "\n[token-goat: cleaned text clipped to the harness delivery cap; the notice at the top of this output recalls the full raw markup]";
|
|
9695
|
+
const fenceOverhead = Buffer.byteLength(fenceUntrusted(cleaned, UNTRUSTED_TOOL_TAG), "utf-8") - Buffer.byteLength(cleaned, "utf-8");
|
|
9696
|
+
const cut = clipToDeliveryCap(cleaned, noticeBytes + fenceOverhead + Buffer.byteLength(clipNote, "utf-8"));
|
|
9697
|
+
const body = cut.text;
|
|
9698
|
+
const clipped = cut.clipped ? clipNote : "";
|
|
9699
|
+
const fenced = fenceUntrusted(body, UNTRUSTED_TOOL_TAG) + clipped;
|
|
9700
|
+
if (!isRewriteWorthwhile({
|
|
9701
|
+
originalBytes,
|
|
9702
|
+
rewrittenBytes: Buffer.byteLength(fenced, "utf-8"),
|
|
9703
|
+
noticeBytes,
|
|
9704
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9705
|
+
})) {
|
|
9706
|
+
return null;
|
|
9707
|
+
}
|
|
9708
|
+
return emitRewrite(notice + fenced, "curl HTML body cleaned", { kind: "bash_compress:curl-html", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
9153
9709
|
}
|
|
9154
|
-
registerHook("pre_tool_use", preBashHandler, { toolName: "Bash" });
|
|
9155
9710
|
function readPersistedBashOutput(resp, cwd, sessionId) {
|
|
9156
9711
|
const persistedPath = resp["persistedOutputPath"];
|
|
9157
9712
|
if (typeof persistedPath !== "string" || persistedPath === "" || sessionId === "") return null;
|
|
9158
|
-
const root =
|
|
9713
|
+
const root = path6.join(projectTranscriptsDir(cwd ?? process.cwd()), sessionId, "tool-results");
|
|
9159
9714
|
if (!isInsideRoot(persistedPath, root)) return null;
|
|
9160
9715
|
try {
|
|
9161
|
-
const stat =
|
|
9716
|
+
const stat = statSync8(persistedPath);
|
|
9162
9717
|
if (!stat.isFile() || stat.size > MAX_CAPTURE_BYTES) return null;
|
|
9163
9718
|
const persistedSize = resp["persistedOutputSize"];
|
|
9164
9719
|
if (typeof persistedSize === "number" && Math.abs(stat.size - persistedSize) > 8) return null;
|
|
9165
|
-
return
|
|
9720
|
+
return readFileSync9(persistedPath, "utf-8");
|
|
9166
9721
|
} catch {
|
|
9167
9722
|
return null;
|
|
9168
9723
|
}
|
|
@@ -9230,63 +9785,71 @@ function forgetPersistedLineRangeReads(rawCmd, cmd, cwd) {
|
|
|
9230
9785
|
resetFileLineRanges(resolveIndexPath(hintPath, hintCwd));
|
|
9231
9786
|
}
|
|
9232
9787
|
}
|
|
9233
|
-
function recordBashFileReadsForSessionCache(cmd,
|
|
9234
|
-
const
|
|
9788
|
+
function recordBashFileReadsForSessionCache(cmd, runDir) {
|
|
9789
|
+
const base = runDir ?? process.cwd();
|
|
9790
|
+
const record = (p, put) => {
|
|
9791
|
+
const file = resolveIndexPath(p, base);
|
|
9792
|
+
if (isFileAtIndexKey(file)) put(file);
|
|
9793
|
+
};
|
|
9235
9794
|
const gcTail = extractGetContentTail(cmd);
|
|
9236
9795
|
if (gcTail !== null) {
|
|
9237
|
-
|
|
9796
|
+
record(gcTail.filePath, markFileTruncated);
|
|
9238
9797
|
return;
|
|
9239
9798
|
}
|
|
9240
9799
|
const tail = extractTailFile(cmd);
|
|
9241
9800
|
if (tail !== null) {
|
|
9242
|
-
|
|
9801
|
+
record(tail.filePath, markFileTruncated);
|
|
9243
9802
|
return;
|
|
9244
9803
|
}
|
|
9245
9804
|
const gcSelect = extractGetContentSelectFirst(cmd);
|
|
9246
9805
|
if (gcSelect !== null) {
|
|
9247
|
-
|
|
9806
|
+
record(gcSelect.filePath, (file) => recordFileLineRange(file, 1, gcSelect.n));
|
|
9248
9807
|
return;
|
|
9249
9808
|
}
|
|
9250
9809
|
const gcHead = extractGetContentHead(cmd);
|
|
9251
9810
|
if (gcHead !== null) {
|
|
9252
|
-
|
|
9811
|
+
record(gcHead.filePath, (file) => recordFileLineRange(file, 1, gcHead.n));
|
|
9253
9812
|
return;
|
|
9254
9813
|
}
|
|
9255
9814
|
const head = extractHeadFile(cmd);
|
|
9256
9815
|
if (head !== null) {
|
|
9257
|
-
|
|
9816
|
+
record(head.filePath, (file) => recordFileLineRange(file, 1, head.n));
|
|
9258
9817
|
return;
|
|
9259
9818
|
}
|
|
9260
9819
|
const cat = extractCatFile(cmd);
|
|
9261
9820
|
if (cat !== null) {
|
|
9262
|
-
|
|
9821
|
+
record(cat.filePath, recordFileRead);
|
|
9263
9822
|
return;
|
|
9264
9823
|
}
|
|
9265
9824
|
const catMulti = extractCatFilesMulti(cmd);
|
|
9266
9825
|
if (catMulti !== null) {
|
|
9267
|
-
for (const r of catMulti)
|
|
9826
|
+
for (const r of catMulti) record(r.filePath, recordFileRead);
|
|
9268
9827
|
return;
|
|
9269
9828
|
}
|
|
9270
9829
|
const wslCat = extractWslCatFile(cmd);
|
|
9271
9830
|
if (wslCat !== null) {
|
|
9272
|
-
|
|
9831
|
+
record(wslCat.filePath, recordFileRead);
|
|
9273
9832
|
return;
|
|
9274
9833
|
}
|
|
9275
9834
|
}
|
|
9276
|
-
async function maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode,
|
|
9835
|
+
async function maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode, runDir, isUnwrapped, event, ansiResult) {
|
|
9277
9836
|
const outputBytes = Buffer.byteLength(output, "utf-8");
|
|
9278
9837
|
if (outputBytes < 4096 || optedOut || exitCode !== null && exitCode !== 0 || // token-goat's own output is already the narrow form; telling the model to compress it is noise.
|
|
9279
|
-
|
|
9838
|
+
isTokenGoatOwnCommand(cmd) || // a line-range read, piped or repeated, is already the narrow slice; compressing it saves nothing.
|
|
9839
|
+
extractLineRangeReadsCompound(cmd) !== null || event.raw["_tg_harness"] !== "vscode" && (isCompressibleSingleCommand(cmd) || !isUnwrapped)) {
|
|
9280
9840
|
return null;
|
|
9281
9841
|
}
|
|
9282
9842
|
const key = `bash-uncompressed-4k:${event.sessionId ?? ""}:${shortFingerprint(cmd)}`;
|
|
9283
9843
|
if (wasHintShown(key)) return null;
|
|
9284
9844
|
markHintShown(key);
|
|
9285
9845
|
recordStat("session_hint", 0, 0);
|
|
9286
|
-
const id = await storeBashOutput(cmd, output, exitCode ?? 0,
|
|
9846
|
+
const id = await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9287
9847
|
const kb = Math.round(outputBytes / 1024);
|
|
9288
|
-
const
|
|
9289
|
-
const
|
|
9848
|
+
const bare = stripCdPrefix(rawCmd);
|
|
9849
|
+
const cdDir = bare === rawCmd ? null : runDir;
|
|
9850
|
+
const cdHost = cdDir === null ? null : dirAtIndexKey(cdDir);
|
|
9851
|
+
const runCmd = cdHost === null ? bare : `cd '${cdHost}' && ${bare}`;
|
|
9852
|
+
const compressable = !/["`$\r\n]/.test(runCmd) && (cdDir === null || cdHost !== null && !cdHost.includes("'"));
|
|
9290
9853
|
const msg = `[tg] Output was ${kb}KB uncompressed; \`token-goat bash-output ${id}\` recalls it` + (compressable ? `, and \`token-goat compress -c "${displaySafeText(runCmd)}"\` compresses the next run.` : ".");
|
|
9291
9854
|
if (ansiResult !== null && ansiResult.hookType === "rewriteOutput") {
|
|
9292
9855
|
return emitRewrite(msg + "\n" + fenceUntrusted(ansiResult.updatedOutput, UNTRUSTED_TOOL_TAG), "large uncompressed output hint", void 0, "counted-elsewhere");
|
|
@@ -9298,17 +9861,19 @@ async function postBashHandler(event) {
|
|
|
9298
9861
|
const rawCmdRaw = extractCommand(event);
|
|
9299
9862
|
if (rawCmdRaw === void 0) return passOutput();
|
|
9300
9863
|
const isUnwrapped = unwrapCompressCommand(rawCmdRaw) === null;
|
|
9301
|
-
const rawCmd = unwrapCompressCommand(rawCmdRaw) ?? rawCmdRaw;
|
|
9302
|
-
const cmd = stripCommandPrefix(rawCmd);
|
|
9303
9864
|
const output = extractBashOutput(event);
|
|
9304
9865
|
const exitCode = extractExitCode(event.raw);
|
|
9305
9866
|
const cwd = getCwd(event) ?? null;
|
|
9867
|
+
const toolUseId = event.raw["tool_use_id"];
|
|
9868
|
+
const heldStartCwd = typeof toolUseId === "string" && toolUseId !== "" ? takeBashStartCwd(toolUseId) : null;
|
|
9869
|
+
const startCwd = heldStartCwd ?? cwd;
|
|
9870
|
+
const { rawCmd, cmd, runDir } = resolveBashCall(rawCmdRaw, startCwd);
|
|
9306
9871
|
const cacheMinBytes = loadConfig().bash_compress.cache_min_bytes;
|
|
9307
9872
|
const resp = event.raw["tool_response"];
|
|
9308
9873
|
const persisted = resp !== null && typeof resp === "object" && typeof resp["persistedOutputPath"] === "string" && resp["persistedOutputPath"] !== "";
|
|
9309
9874
|
const optedOut = compressionOptedOut(rawCmd);
|
|
9310
9875
|
if (isHeadMovingGitCommand(cmd) && (exitCode === null || exitCode === 0)) {
|
|
9311
|
-
const gitDir =
|
|
9876
|
+
const gitDir = runDir ?? process.cwd();
|
|
9312
9877
|
let diffBase = "HEAD@{1}";
|
|
9313
9878
|
if (ORIG_HEAD_ELIGIBLE_GIT_RE.test(cmd)) {
|
|
9314
9879
|
const reflogTop = runGit(["reflog", "-1", "--format=%gs", "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
@@ -9328,18 +9893,19 @@ async function postBashHandler(event) {
|
|
|
9328
9893
|
}
|
|
9329
9894
|
}
|
|
9330
9895
|
if (exitCode === null || exitCode === 0) {
|
|
9331
|
-
enqueueNonHeadMovingRewrites(cmd,
|
|
9896
|
+
enqueueNonHeadMovingRewrites(cmd, runDir ?? process.cwd());
|
|
9332
9897
|
}
|
|
9333
9898
|
const curlDl = extractCurlDownload(cmd);
|
|
9334
9899
|
if (curlDl !== null && (exitCode === null || exitCode === 0)) {
|
|
9335
|
-
const
|
|
9336
|
-
if (
|
|
9337
|
-
recordCurlDownload(curlDl.url,
|
|
9900
|
+
const outputOnDisk = hostPathOfTypedPath(curlDl.outputPath, runDir ?? process.cwd());
|
|
9901
|
+
if (existsSync2(outputOnDisk)) {
|
|
9902
|
+
recordCurlDownload(curlDl.url, outputOnDisk);
|
|
9338
9903
|
}
|
|
9339
9904
|
}
|
|
9340
|
-
if (persisted) forgetPersistedLineRangeReads(rawCmd, cmd,
|
|
9341
|
-
else if (exitCode === null || exitCode === 0) recordBashFileReadsForSessionCache(cmd,
|
|
9342
|
-
|
|
9905
|
+
if (persisted) forgetPersistedLineRangeReads(rawCmd, cmd, startCwd);
|
|
9906
|
+
else if (exitCode === null || exitCode === 0) recordBashFileReadsForSessionCache(cmd, runDir);
|
|
9907
|
+
let tgRead = extractTgSurgicalRead(cmd, runDir);
|
|
9908
|
+
if (heldStartCwd === null && cwd !== null && tgRead?.filePath != null && !isFileAtIndexKey(tgRead.filePath)) tgRead = extractTgSurgicalRead(cmd, cwd);
|
|
9343
9909
|
if (tgRead !== null && (exitCode === null || exitCode === 0)) {
|
|
9344
9910
|
recordCliRead(tgRead.sub + "::" + tgRead.spec);
|
|
9345
9911
|
if (tgRead.filePath !== null) {
|
|
@@ -9354,8 +9920,8 @@ async function postBashHandler(event) {
|
|
|
9354
9920
|
}
|
|
9355
9921
|
}
|
|
9356
9922
|
if (isReadOnlyGhApi(cmd) && (exitCode === null || exitCode === 0) && Buffer.byteLength(output, "utf-8") >= cacheMinBytes) {
|
|
9357
|
-
const ghCacheHash =
|
|
9358
|
-
const ghCacheId = await storeBashOutput(cmd, output, exitCode ?? 0,
|
|
9923
|
+
const ghCacheHash = bashRecallKey(cmd, runDir);
|
|
9924
|
+
const ghCacheId = await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9359
9925
|
recordBashOutput(ghCacheHash, ghCacheId, Buffer.byteLength(output, "utf-8"));
|
|
9360
9926
|
}
|
|
9361
9927
|
const ghHint = buildGhApiHint(cmd, output, exitCode);
|
|
@@ -9367,52 +9933,54 @@ async function postBashHandler(event) {
|
|
|
9367
9933
|
if (ghView !== null && (exitCode === null || exitCode === 0) && !wasHintShown(GH_VIEW_BATCH_HINT_KEY)) {
|
|
9368
9934
|
markHintShown(GH_VIEW_BATCH_HINT_KEY);
|
|
9369
9935
|
if (Buffer.byteLength(output, "utf-8") >= cacheMinBytes) {
|
|
9370
|
-
const ghViewId = await storeBashOutput(cmd, output, exitCode ?? 0,
|
|
9371
|
-
recordBashOutput(
|
|
9936
|
+
const ghViewId = await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9937
|
+
recordBashOutput(bashRecallKey(cmd, runDir), ghViewId, Buffer.byteLength(output, "utf-8"));
|
|
9372
9938
|
}
|
|
9373
9939
|
recordStat("session_hint", 0, 0);
|
|
9374
9940
|
return contextOutput(buildGhViewBatchAdvisory(ghView.sub, ghView.ref));
|
|
9375
9941
|
}
|
|
9376
9942
|
if (isTestRunnerCommand(cmd) && exitCode !== null && exitCode !== 0 && Buffer.byteLength(output, "utf-8") >= cacheMinBytes) {
|
|
9377
|
-
const testFailHash =
|
|
9378
|
-
const testFailId = await storeBashOutput(cmd, output, exitCode,
|
|
9943
|
+
const testFailHash = bashRecallKey(cmd, runDir);
|
|
9944
|
+
const testFailId = await storeBashOutput(cmd, output, exitCode, runDir);
|
|
9379
9945
|
recordBashOutput(testFailHash, testFailId, Buffer.byteLength(output, "utf-8"));
|
|
9380
9946
|
recordStat("session_hint", 0, 0);
|
|
9381
9947
|
return contextOutput(`[token-goat] Tests failed. Run \`token-goat bash-output ${testFailId} | token-goat failures\` to see just the failing blocks instead of the full output.`);
|
|
9382
9948
|
}
|
|
9383
9949
|
if (isScopedGitStatusOrDiffStatCommand(cmd) && (exitCode === null || exitCode === 0) && Buffer.byteLength(output, "utf-8") >= cacheMinBytes) {
|
|
9384
|
-
const gitScopedCacheHash =
|
|
9385
|
-
const gitScopedCacheId = await storeBashOutput(cmd, output, exitCode ?? 0,
|
|
9950
|
+
const gitScopedCacheHash = bashRecallKey(cmd, runDir);
|
|
9951
|
+
const gitScopedCacheId = await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9386
9952
|
recordBashOutput(gitScopedCacheHash, gitScopedCacheId, Buffer.byteLength(output, "utf-8"));
|
|
9387
9953
|
}
|
|
9388
|
-
if (isUnwrapped && /^git(?:\s+-[^\s]+|\s+--[^\s]+)*\s+diff\b/i.test(cmd)) {
|
|
9389
|
-
const unwrappedCompressed = await maybeCompressCompoundOutput(cmd, optedOut, output, exitCode,
|
|
9390
|
-
if (unwrappedCompressed !== null)
|
|
9954
|
+
if (isUnwrapped && (isTestRunnerCommand(cmd) && detectFromCommand(cmd, runDir ?? void 0) !== null || /^git(?:\s+-[^\s]+|\s+--[^\s]+)*\s+diff\b/i.test(cmd))) {
|
|
9955
|
+
const unwrappedCompressed = await maybeCompressCompoundOutput(cmd, optedOut, output, exitCode, runDir, cacheMinBytes, isUnwrapped);
|
|
9956
|
+
if (unwrappedCompressed !== null) {
|
|
9957
|
+
recordBashOutput(bashRecallKey(cmd, runDir), bashOutputIdSync(cmd, output, runDir), Buffer.byteLength(output, "utf-8"));
|
|
9958
|
+
return unwrappedCompressed;
|
|
9959
|
+
}
|
|
9391
9960
|
}
|
|
9392
9961
|
const isMonitoring = getMonitoringRecallHint(cmd) !== null;
|
|
9393
9962
|
const isFileRead = pureFileReadPath(cmd) !== null;
|
|
9394
9963
|
if (isFileRead || !isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
|
|
9395
|
-
const identical = await maybeCollapseIdenticalRead(cmd,
|
|
9964
|
+
const identical = await maybeCollapseIdenticalRead(cmd, runDir, optedOut, output, exitCode, cwd, cacheMinBytes, persisted);
|
|
9396
9965
|
if (identical !== null) return identical;
|
|
9397
9966
|
if (!isFileRead && (!isUnwrapped || !isCompressibleSingleCommand(cmd))) {
|
|
9398
|
-
const compound = await maybeCompressCompoundOutput(cmd, optedOut, output, exitCode,
|
|
9967
|
+
const compound = await maybeCompressCompoundOutput(cmd, optedOut, output, exitCode, runDir, cacheMinBytes, isUnwrapped);
|
|
9399
9968
|
if (compound !== null) return compound;
|
|
9400
9969
|
}
|
|
9401
9970
|
if (!isFileRead && extractLineRangeReadsCompound(cmd) === null) {
|
|
9402
|
-
const genericElision = await maybeElideServedGenericOutput(cmd, optedOut, output, exitCode,
|
|
9971
|
+
const genericElision = await maybeElideServedGenericOutput(cmd, optedOut, output, exitCode, runDir, cacheMinBytes, persisted);
|
|
9403
9972
|
if (genericElision !== null) return genericElision;
|
|
9404
9973
|
}
|
|
9405
9974
|
const ansiStripped = optedOut ? null : maybeStripAnsiOnly(output);
|
|
9406
|
-
const largeHint2 = await maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode,
|
|
9975
|
+
const largeHint2 = await maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode, runDir, isUnwrapped, event, ansiStripped);
|
|
9407
9976
|
if (largeHint2 !== null) return largeHint2;
|
|
9408
9977
|
return ansiStripped ?? passOutput();
|
|
9409
9978
|
}
|
|
9410
9979
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
9411
|
-
const
|
|
9412
|
-
const
|
|
9413
|
-
const priorId = await commandHash(cmd, cwd);
|
|
9980
|
+
const simpleHash = isCurlGetCommand(cmd) ? shortFingerprint(extractCurlUrl(cmd) ?? cmd) : bashRecallKey(cmd, runDir);
|
|
9981
|
+
const priorId = await commandHash(cmd, runDir);
|
|
9414
9982
|
const priorEntry = getBashOutput(priorId);
|
|
9415
|
-
const id = await storeBashOutput(cmd, output, exitCode ?? 0,
|
|
9983
|
+
const id = await storeBashOutput(cmd, output, exitCode ?? 0, runDir);
|
|
9416
9984
|
recordBashOutput(simpleHash, id, Buffer.byteLength(output, "utf-8"));
|
|
9417
9985
|
if (priorEntry !== null) {
|
|
9418
9986
|
recordBashRerun(simpleHash);
|
|
@@ -9425,7 +9993,7 @@ async function postBashHandler(event) {
|
|
|
9425
9993
|
const curlHtmlFold = optedOut ? null : maybeFoldCurlHtml(cmd, output, id);
|
|
9426
9994
|
if (curlHtmlFold !== null) return curlHtmlFold;
|
|
9427
9995
|
const ansiOnly = optedOut ? null : maybeStripAnsiOnly(output);
|
|
9428
|
-
const largeHint = await maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode,
|
|
9996
|
+
const largeHint = await maybeEmitLargeUncompressedHint(cmd, rawCmd, optedOut, output, exitCode, runDir, isUnwrapped, event, ansiOnly);
|
|
9429
9997
|
if (largeHint !== null) return largeHint;
|
|
9430
9998
|
if (ansiOnly !== null) return ansiOnly;
|
|
9431
9999
|
} catch {
|
|
@@ -9436,14 +10004,13 @@ registerHook("post_tool_use", postBashHandler, { toolName: "Bash" });
|
|
|
9436
10004
|
|
|
9437
10005
|
// src/hooks_bashoutput.ts
|
|
9438
10006
|
init_define_import_meta_env();
|
|
9439
|
-
function pollCacheId(sessionId,
|
|
9440
|
-
return
|
|
10007
|
+
function pollCacheId(tool, sessionId, pollId) {
|
|
10008
|
+
return `${tool.blobPrefix}_${shortFingerprint(`${sessionId}\0${tool.channel}\0${pollId}`)}`;
|
|
9441
10009
|
}
|
|
9442
|
-
function storePollSnapshot(
|
|
9443
|
-
const id = pollCacheId(sessionId, bashId);
|
|
10010
|
+
function storePollSnapshot(tool, id, pollId, output) {
|
|
9444
10011
|
const entry = {
|
|
9445
10012
|
id,
|
|
9446
|
-
command:
|
|
10013
|
+
command: `${tool.channel}:${pollId}`,
|
|
9447
10014
|
output,
|
|
9448
10015
|
exitCode: 0,
|
|
9449
10016
|
storedAt: Date.now(),
|
|
@@ -9451,61 +10018,55 @@ function storePollSnapshot(sessionId, bashId, output) {
|
|
|
9451
10018
|
};
|
|
9452
10019
|
storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
|
|
9453
10020
|
}
|
|
10021
|
+
function rewritePoll(tool, sessionId, pollId, rawOutput) {
|
|
10022
|
+
if (!rawOutput) return passOutput();
|
|
10023
|
+
const output = redactSecrets(rawOutput).text;
|
|
10024
|
+
if (!output) return passOutput();
|
|
10025
|
+
const originalBytes = Buffer.byteLength(rawOutput, "utf-8");
|
|
10026
|
+
const outputBytes = Buffer.byteLength(output, "utf-8");
|
|
10027
|
+
const id = pollCacheId(tool, sessionId, pollId);
|
|
10028
|
+
const prior = getBashOutput(id);
|
|
10029
|
+
const minBytes = loadConfig().bash_compress.cache_min_bytes;
|
|
10030
|
+
storePollSnapshot(tool, id, pollId, output);
|
|
10031
|
+
if (prior === null) {
|
|
10032
|
+
if (tool.channel === "bashoutput") return passOutput();
|
|
10033
|
+
const collapsed = tool.compactFirst(output);
|
|
10034
|
+
if (collapsed === output) return passOutput();
|
|
10035
|
+
const collapsedBytes = Buffer.byteLength(collapsed, "utf-8");
|
|
10036
|
+
if (outputBytes - collapsedBytes < minBytes) return passOutput();
|
|
10037
|
+
if (!isRewriteWorthwhile({ originalBytes: outputBytes, rewrittenBytes: collapsedBytes, noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return passOutput();
|
|
10038
|
+
return emitRewrite(collapsed, "taskoutput", { kind: "taskoutput:collapse", originalBytes });
|
|
10039
|
+
}
|
|
10040
|
+
if (outputBytes < minBytes) return passOutput();
|
|
10041
|
+
if (output === prior.output) {
|
|
10042
|
+
const unchangedNotice = `[token-goat: ${tool.idArg} ${pollId} unchanged since last poll -- no new output]`;
|
|
10043
|
+
if (!isRewriteWorthwhile({ originalBytes: outputBytes, rewrittenBytes: 0, noticeBytes: Buffer.byteLength(unchangedNotice, "utf-8"), minNetSavingsBytes: resolveMinNetSavingsBytes() })) return passOutput();
|
|
10044
|
+
return tool.channel === "bashoutput" ? emitRewrite(unchangedNotice, "bashoutput", { kind: "bashoutput:unchanged", originalBytes }) : emitRewrite(unchangedNotice, "taskoutput", { kind: "taskoutput:unchanged", originalBytes });
|
|
10045
|
+
}
|
|
10046
|
+
if (!output.startsWith(prior.output)) return passOutput();
|
|
10047
|
+
const deltaNotice = `[token-goat: ${tool.idArg} ${pollId} delta since last poll]
|
|
10048
|
+
`;
|
|
10049
|
+
const fenced = fenceUntrusted(tool.compactDelta(output.slice(prior.output.length)), UNTRUSTED_TOOL_TAG);
|
|
10050
|
+
const body = `${deltaNotice}${fenced}`;
|
|
10051
|
+
if (!isRewriteWorthwhile({ originalBytes: outputBytes, rewrittenBytes: Buffer.byteLength(body, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return passOutput();
|
|
10052
|
+
return tool.channel === "bashoutput" ? emitRewrite(body, "bashoutput", { kind: "bashoutput:delta", originalBytes }) : emitRewrite(body, "taskoutput", { kind: "taskoutput:delta", originalBytes });
|
|
10053
|
+
}
|
|
9454
10054
|
function getBashId(toolInput) {
|
|
9455
10055
|
const value = toolInput["bash_id"];
|
|
9456
10056
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
9457
10057
|
}
|
|
10058
|
+
function compactPytestDelta(delta) {
|
|
10059
|
+
if (!looksLikePytestOutput(delta)) return delta;
|
|
10060
|
+
const compressed = pytestFilter.apply(delta, "", 0, []);
|
|
10061
|
+
return compressed.worthApplying(resolveMinNetSavingsBytes()) ? compressed.withMarker(resolveMinNetSavingsBytes()) : delta;
|
|
10062
|
+
}
|
|
10063
|
+
var BASH_OUTPUT_POLL = { channel: "bashoutput", blobPrefix: "bgpoll", idArg: "bash_id", compactDelta: compactPytestDelta };
|
|
9458
10064
|
function postBashOutputHandler(event) {
|
|
9459
10065
|
try {
|
|
9460
10066
|
if (getToolName(event) !== "BashOutput" || !event.sessionId) return passOutput();
|
|
9461
10067
|
const bashId = getBashId(getToolInput(event));
|
|
9462
10068
|
if (bashId === void 0) return passOutput();
|
|
9463
|
-
|
|
9464
|
-
if (!rawOutput) return passOutput();
|
|
9465
|
-
const output = redactSecrets(rawOutput).text;
|
|
9466
|
-
if (!output) return passOutput();
|
|
9467
|
-
const prior = getBashOutput(pollCacheId(event.sessionId, bashId));
|
|
9468
|
-
const minBytes = loadConfig().bash_compress.cache_min_bytes;
|
|
9469
|
-
if (prior === null) {
|
|
9470
|
-
storePollSnapshot(event.sessionId, bashId, output);
|
|
9471
|
-
return passOutput();
|
|
9472
|
-
}
|
|
9473
|
-
if (output === prior.output) {
|
|
9474
|
-
storePollSnapshot(event.sessionId, bashId, output);
|
|
9475
|
-
if (Buffer.byteLength(output, "utf-8") < minBytes) return passOutput();
|
|
9476
|
-
const unchangedNotice = `[token-goat: bash_id ${bashId} unchanged since last poll -- no new output]`;
|
|
9477
|
-
if (!isRewriteWorthwhile({
|
|
9478
|
-
originalBytes: Buffer.byteLength(output, "utf-8"),
|
|
9479
|
-
rewrittenBytes: 0,
|
|
9480
|
-
noticeBytes: Buffer.byteLength(unchangedNotice, "utf-8"),
|
|
9481
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9482
|
-
})) {
|
|
9483
|
-
return passOutput();
|
|
9484
|
-
}
|
|
9485
|
-
return emitRewrite(unchangedNotice, "bashoutput", { kind: "bashoutput:unchanged", originalBytes: Buffer.byteLength(rawOutput, "utf-8") });
|
|
9486
|
-
}
|
|
9487
|
-
if (!output.startsWith(prior.output)) {
|
|
9488
|
-
storePollSnapshot(event.sessionId, bashId, output);
|
|
9489
|
-
return passOutput();
|
|
9490
|
-
}
|
|
9491
|
-
const delta = output.slice(prior.output.length);
|
|
9492
|
-
storePollSnapshot(event.sessionId, bashId, output);
|
|
9493
|
-
if (Buffer.byteLength(delta, "utf-8") < minBytes) return passOutput();
|
|
9494
|
-
const compressedDelta = looksLikePytestOutput(delta) ? pytestFilter.apply(delta, "", 0, []) : null;
|
|
9495
|
-
const deltaBody = compressedDelta !== null && compressedDelta.worthApplying(resolveMinNetSavingsBytes()) ? compressedDelta.withMarker(resolveMinNetSavingsBytes()) : delta;
|
|
9496
|
-
const deltaNotice = `[token-goat: bash_id ${bashId} delta since last poll]
|
|
9497
|
-
`;
|
|
9498
|
-
const fenced = fenceUntrusted(deltaBody, UNTRUSTED_TOOL_TAG);
|
|
9499
|
-
const body = `${deltaNotice}${fenced}`;
|
|
9500
|
-
if (!isRewriteWorthwhile({
|
|
9501
|
-
originalBytes: Buffer.byteLength(output, "utf-8"),
|
|
9502
|
-
rewrittenBytes: Buffer.byteLength(body, "utf-8"),
|
|
9503
|
-
noticeBytes: 0,
|
|
9504
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9505
|
-
})) {
|
|
9506
|
-
return passOutput();
|
|
9507
|
-
}
|
|
9508
|
-
return emitRewrite(body, "bashoutput", { kind: "bashoutput:delta", originalBytes: Buffer.byteLength(rawOutput, "utf-8") });
|
|
10069
|
+
return rewritePoll(BASH_OUTPUT_POLL, event.sessionId, bashId, extractToolResultText(event.raw));
|
|
9509
10070
|
} catch {
|
|
9510
10071
|
return passOutput();
|
|
9511
10072
|
}
|
|
@@ -9514,21 +10075,6 @@ registerHook("post_tool_use", postBashOutputHandler, { toolName: "BashOutput" })
|
|
|
9514
10075
|
|
|
9515
10076
|
// src/hooks_taskoutput.ts
|
|
9516
10077
|
init_define_import_meta_env();
|
|
9517
|
-
function pollCacheId2(sessionId, taskId) {
|
|
9518
|
-
return `taskpoll_${shortFingerprint(`${sessionId}\0taskoutput\0${taskId}`)}`;
|
|
9519
|
-
}
|
|
9520
|
-
function storePollSnapshot2(sessionId, taskId, output) {
|
|
9521
|
-
const id = pollCacheId2(sessionId, taskId);
|
|
9522
|
-
const entry = {
|
|
9523
|
-
id,
|
|
9524
|
-
command: `taskoutput:${taskId}`,
|
|
9525
|
-
output,
|
|
9526
|
-
exitCode: 0,
|
|
9527
|
-
storedAt: Date.now(),
|
|
9528
|
-
sizeBytes: Buffer.byteLength(output, "utf-8")
|
|
9529
|
-
};
|
|
9530
|
-
storeBlob(BASH_OUTPUT_SUBDIR, id, entry);
|
|
9531
|
-
}
|
|
9532
10078
|
function collapseRepeatedLines(text) {
|
|
9533
10079
|
return dedupeConsecutive(text.split("\n")).join("\n");
|
|
9534
10080
|
}
|
|
@@ -9547,68 +10093,13 @@ function extractTaskOutputText(raw) {
|
|
|
9547
10093
|
}
|
|
9548
10094
|
return extractToolResultText(raw);
|
|
9549
10095
|
}
|
|
10096
|
+
var TASK_OUTPUT_POLL = { channel: "taskoutput", blobPrefix: "taskpoll", idArg: "task_id", compactDelta: collapseRepeatedLines, compactFirst: collapseRepeatedLines };
|
|
9550
10097
|
function postTaskOutputHandler(event) {
|
|
9551
10098
|
try {
|
|
9552
10099
|
if (getToolName(event) !== "TaskOutput" || !event.sessionId) return passOutput();
|
|
9553
10100
|
const taskId = getTaskId(getToolInput(event));
|
|
9554
10101
|
if (taskId === void 0) return passOutput();
|
|
9555
|
-
|
|
9556
|
-
if (!rawOutput) return passOutput();
|
|
9557
|
-
const output = redactSecrets(rawOutput).text;
|
|
9558
|
-
if (!output) return passOutput();
|
|
9559
|
-
const prior = getBashOutput(pollCacheId2(event.sessionId, taskId));
|
|
9560
|
-
const minBytes = loadConfig().bash_compress.cache_min_bytes;
|
|
9561
|
-
if (prior === null) {
|
|
9562
|
-
storePollSnapshot2(event.sessionId, taskId, output);
|
|
9563
|
-
const collapsed = collapseRepeatedLines(output);
|
|
9564
|
-
if (collapsed === output) return passOutput();
|
|
9565
|
-
const savings = Buffer.byteLength(output, "utf-8") - Buffer.byteLength(collapsed, "utf-8");
|
|
9566
|
-
if (savings < minBytes) return passOutput();
|
|
9567
|
-
if (!isRewriteWorthwhile({
|
|
9568
|
-
originalBytes: Buffer.byteLength(output, "utf-8"),
|
|
9569
|
-
rewrittenBytes: Buffer.byteLength(collapsed, "utf-8"),
|
|
9570
|
-
noticeBytes: 0,
|
|
9571
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9572
|
-
})) {
|
|
9573
|
-
return passOutput();
|
|
9574
|
-
}
|
|
9575
|
-
return emitRewrite(collapsed, "taskoutput", { kind: "taskoutput:collapse", originalBytes: Buffer.byteLength(rawOutput, "utf-8") });
|
|
9576
|
-
}
|
|
9577
|
-
if (output === prior.output) {
|
|
9578
|
-
storePollSnapshot2(event.sessionId, taskId, output);
|
|
9579
|
-
if (Buffer.byteLength(output, "utf-8") < minBytes) return passOutput();
|
|
9580
|
-
const unchangedNotice = `[token-goat: task_id ${taskId} unchanged since last poll -- no new output]`;
|
|
9581
|
-
if (!isRewriteWorthwhile({
|
|
9582
|
-
originalBytes: Buffer.byteLength(output, "utf-8"),
|
|
9583
|
-
rewrittenBytes: 0,
|
|
9584
|
-
noticeBytes: Buffer.byteLength(unchangedNotice, "utf-8"),
|
|
9585
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9586
|
-
})) {
|
|
9587
|
-
return passOutput();
|
|
9588
|
-
}
|
|
9589
|
-
return emitRewrite(unchangedNotice, "taskoutput", { kind: "taskoutput:unchanged", originalBytes: Buffer.byteLength(rawOutput, "utf-8") });
|
|
9590
|
-
}
|
|
9591
|
-
if (!output.startsWith(prior.output)) {
|
|
9592
|
-
storePollSnapshot2(event.sessionId, taskId, output);
|
|
9593
|
-
return passOutput();
|
|
9594
|
-
}
|
|
9595
|
-
const delta = output.slice(prior.output.length);
|
|
9596
|
-
storePollSnapshot2(event.sessionId, taskId, output);
|
|
9597
|
-
if (Buffer.byteLength(delta, "utf-8") < minBytes) return passOutput();
|
|
9598
|
-
const collapsedDelta = collapseRepeatedLines(delta);
|
|
9599
|
-
const deltaNotice = `[token-goat: task_id ${taskId} delta since last poll]
|
|
9600
|
-
`;
|
|
9601
|
-
const fenced = fenceUntrusted(collapsedDelta, UNTRUSTED_TOOL_TAG);
|
|
9602
|
-
const body = `${deltaNotice}${fenced}`;
|
|
9603
|
-
if (!isRewriteWorthwhile({
|
|
9604
|
-
originalBytes: Buffer.byteLength(output, "utf-8"),
|
|
9605
|
-
rewrittenBytes: Buffer.byteLength(body, "utf-8"),
|
|
9606
|
-
noticeBytes: 0,
|
|
9607
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9608
|
-
})) {
|
|
9609
|
-
return passOutput();
|
|
9610
|
-
}
|
|
9611
|
-
return emitRewrite(body, "taskoutput", { kind: "taskoutput:delta", originalBytes: Buffer.byteLength(rawOutput, "utf-8") });
|
|
10102
|
+
return rewritePoll(TASK_OUTPUT_POLL, event.sessionId, taskId, extractTaskOutputText(event.raw));
|
|
9612
10103
|
} catch {
|
|
9613
10104
|
return passOutput();
|
|
9614
10105
|
}
|
|
@@ -9617,8 +10108,8 @@ registerHook("post_tool_use", postTaskOutputHandler, { toolName: "TaskOutput" })
|
|
|
9617
10108
|
|
|
9618
10109
|
// src/hooks_tool_failure.ts
|
|
9619
10110
|
init_define_import_meta_env();
|
|
9620
|
-
import { existsSync as
|
|
9621
|
-
import { dirname as
|
|
10111
|
+
import { existsSync as existsSync3, readFileSync as readFileSync10, statSync as statSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
10112
|
+
import { dirname as dirname5, relative as relative2 } from "node:path";
|
|
9622
10113
|
var FAILURE_SUFFIX = ".tool-failures";
|
|
9623
10114
|
var MAX_TRACKED_FAILURES = 64;
|
|
9624
10115
|
var SIGNATURE_ERROR_CHARS = 200;
|
|
@@ -9645,7 +10136,7 @@ function ledgerPath(sessionId) {
|
|
|
9645
10136
|
}
|
|
9646
10137
|
function readLedger(target) {
|
|
9647
10138
|
try {
|
|
9648
|
-
const parsed = JSON.parse(
|
|
10139
|
+
const parsed = JSON.parse(readFileSync10(target, "utf8"));
|
|
9649
10140
|
if (parsed !== null && typeof parsed === "object") {
|
|
9650
10141
|
const seen = parsed.seen;
|
|
9651
10142
|
const order = parsed.order;
|
|
@@ -9659,8 +10150,8 @@ function readLedger(target) {
|
|
|
9659
10150
|
}
|
|
9660
10151
|
function writeLedger(target, ledger) {
|
|
9661
10152
|
try {
|
|
9662
|
-
ensureDirSync(
|
|
9663
|
-
|
|
10153
|
+
ensureDirSync(dirname5(target));
|
|
10154
|
+
writeFileSync5(target, JSON.stringify(ledger), "utf8");
|
|
9664
10155
|
} catch {
|
|
9665
10156
|
}
|
|
9666
10157
|
}
|
|
@@ -9703,16 +10194,16 @@ function diagnoseEditFailure(event, errorText) {
|
|
|
9703
10194
|
if (!isMultiple && !isNotFound) {
|
|
9704
10195
|
return null;
|
|
9705
10196
|
}
|
|
9706
|
-
const filePath = getFilePath(event) ?? (typeof event.toolInput["path"] === "string" ? event.toolInput["path"] : void 0);
|
|
10197
|
+
const filePath = getFilePath(event) ?? (typeof event.toolInput["path"] === "string" && event.toolInput["path"] !== "" ? resolveEventPath(event, event.toolInput["path"]) : void 0);
|
|
9707
10198
|
if (!filePath) return null;
|
|
9708
10199
|
const oldString = typeof event.toolInput["old_string"] === "string" ? event.toolInput["old_string"] : typeof event.toolInput["old_str"] === "string" ? event.toolInput["old_str"] : typeof event.toolInput["target"] === "string" ? event.toolInput["target"] : void 0;
|
|
9709
10200
|
if (oldString === void 0 || oldString === "") return null;
|
|
9710
|
-
const absPath = normalizePath(filePath);
|
|
9711
|
-
if (!
|
|
10201
|
+
const absPath = hostPathOfIndexKey(normalizePath(filePath));
|
|
10202
|
+
if (!existsSync3(absPath)) return null;
|
|
9712
10203
|
try {
|
|
9713
|
-
const stat =
|
|
10204
|
+
const stat = statSync9(absPath);
|
|
9714
10205
|
if (stat.size > MAX_EDIT_DIAGNOSE_BYTES) return null;
|
|
9715
|
-
const fileContent =
|
|
10206
|
+
const fileContent = readFileSync10(absPath, "utf8");
|
|
9716
10207
|
const relDisplay = displaySafeText(relative2(process.cwd(), absPath).replace(/\\/g, "/") || absPath);
|
|
9717
10208
|
if (isMultiple) {
|
|
9718
10209
|
const matchLines = [];
|
|
@@ -9884,7 +10375,7 @@ function mcpOutputId(sessionId, hash) {
|
|
|
9884
10375
|
}
|
|
9885
10376
|
function mcpInputPreview(toolInput) {
|
|
9886
10377
|
try {
|
|
9887
|
-
return
|
|
10378
|
+
return redactSerializedJson(JSON.stringify(toolInput)).text.slice(0, 120);
|
|
9888
10379
|
} catch {
|
|
9889
10380
|
return "";
|
|
9890
10381
|
}
|
|
@@ -9903,12 +10394,7 @@ function storeMcpOutput(sessionId, toolName, toolInput, resultText) {
|
|
|
9903
10394
|
output: redactedOutput,
|
|
9904
10395
|
exitCode: 0,
|
|
9905
10396
|
storedAt: Date.now(),
|
|
9906
|
-
// Sized off the redacted output, not the raw pre-redaction resultText (rawSizeBytes is only
|
|
9907
|
-
// the cache-eligibility gate above) -- mirrors bash_output_cache.ts's storeBashOutput, whose
|
|
9908
|
-
// sizeBytes is likewise computed from redactedOutput. Sizing off the raw text here left
|
|
9909
|
-
// mcp-audit's per-call token estimate and `mcp-history`'s byte column reporting a stale byte
|
|
9910
|
-
// count for any entry a secret was actually stripped from, out of sync with the stored/served
|
|
9911
|
-
// (redacted, shorter) output.
|
|
10397
|
+
// Sized off the redacted output, not the raw pre-redaction resultText (rawSizeBytes is only the cache-eligibility gate above) -- mirrors bash_output_cache.ts's storeBashOutput, whose sizeBytes is likewise computed from redactedOutput. Sizing off the raw text here left mcp-audit's per-call token estimate and `mcp-history`'s byte column reporting a stale byte count for any entry a secret was actually stripped from, out of sync with the stored/served (redacted, shorter) output.
|
|
9912
10398
|
sizeBytes: Buffer.byteLength(redactedOutput, "utf-8")
|
|
9913
10399
|
};
|
|
9914
10400
|
if (!storeBlob(BASH_OUTPUT_SUBDIR, id, entry)) return null;
|
|
@@ -10267,9 +10753,58 @@ function compressMcpResultWithPacks(toolName, resultText) {
|
|
|
10267
10753
|
|
|
10268
10754
|
// src/hooks_mcp.ts
|
|
10269
10755
|
var MCP_OVERSIZED_THRESHOLD_BYTES = 25e3;
|
|
10756
|
+
var TOKEN_GOAT_MCP_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
10757
|
+
"read",
|
|
10758
|
+
"symbol",
|
|
10759
|
+
"section",
|
|
10760
|
+
"outline",
|
|
10761
|
+
"skeleton",
|
|
10762
|
+
"refs",
|
|
10763
|
+
"brief",
|
|
10764
|
+
"map",
|
|
10765
|
+
"changed",
|
|
10766
|
+
"grep",
|
|
10767
|
+
"imports",
|
|
10768
|
+
"exports",
|
|
10769
|
+
"semantic",
|
|
10770
|
+
"index_status",
|
|
10771
|
+
"handoff_create",
|
|
10772
|
+
"handoff_apply",
|
|
10773
|
+
"retrieve_text"
|
|
10774
|
+
]);
|
|
10775
|
+
function isTokenGoatMcpTool(toolName) {
|
|
10776
|
+
const lower = toolName.toLowerCase();
|
|
10777
|
+
let rest = lower;
|
|
10778
|
+
if (rest.startsWith("mcp__")) {
|
|
10779
|
+
rest = rest.slice(5);
|
|
10780
|
+
} else if (rest.startsWith("mcp_")) {
|
|
10781
|
+
rest = rest.slice(4);
|
|
10782
|
+
} else {
|
|
10783
|
+
if (lower.startsWith("token_goat_") || lower.startsWith("token-goat-") || lower.startsWith("tg_")) {
|
|
10784
|
+
const tool2 = lower.replace(/^(token_goat_|token-goat-|tg_)/, "");
|
|
10785
|
+
return TOKEN_GOAT_MCP_TOOL_NAMES.has(tool2);
|
|
10786
|
+
}
|
|
10787
|
+
return false;
|
|
10788
|
+
}
|
|
10789
|
+
const sepIdx = rest.indexOf("__");
|
|
10790
|
+
let server = "";
|
|
10791
|
+
let tool = "";
|
|
10792
|
+
if (sepIdx !== -1) {
|
|
10793
|
+
server = rest.slice(0, sepIdx);
|
|
10794
|
+
tool = rest.slice(sepIdx + 2);
|
|
10795
|
+
} else {
|
|
10796
|
+
const singleIdx = rest.indexOf("_");
|
|
10797
|
+
if (singleIdx !== -1) {
|
|
10798
|
+
server = rest.slice(0, singleIdx);
|
|
10799
|
+
tool = rest.slice(singleIdx + 1);
|
|
10800
|
+
}
|
|
10801
|
+
}
|
|
10802
|
+
const isTgServer = server === "token_goat" || server === "token-goat" || server === "tg" || server === "token_goat_mcp" || server === "token-goat-mcp";
|
|
10803
|
+
return isTgServer && TOKEN_GOAT_MCP_TOOL_NAMES.has(tool);
|
|
10804
|
+
}
|
|
10270
10805
|
function preMcpHandler(event) {
|
|
10271
10806
|
const toolName = getToolName(event);
|
|
10272
|
-
if (!toolName || !event.sessionId) return passOutput();
|
|
10807
|
+
if (!toolName || !event.sessionId || isTokenGoatMcpTool(toolName)) return passOutput();
|
|
10273
10808
|
const toolInput = getToolInput(event);
|
|
10274
10809
|
if (!isMcpReadOnly(toolName, toolInput)) return passOutput();
|
|
10275
10810
|
const ttlMs = loadConfig().hints.mcp_dedup_ttl_secs * 1e3;
|
|
@@ -10283,7 +10818,7 @@ function preMcpHandler(event) {
|
|
|
10283
10818
|
}
|
|
10284
10819
|
function postMcpHandler(event) {
|
|
10285
10820
|
const toolName = getToolName(event);
|
|
10286
|
-
if (!toolName || !toolName.startsWith("mcp__")) return passOutput();
|
|
10821
|
+
if (!toolName || !toolName.startsWith("mcp__") || isTokenGoatMcpTool(toolName)) return passOutput();
|
|
10287
10822
|
const toolInput = getToolInput(event);
|
|
10288
10823
|
const resultText = extractToolResultText(event.raw);
|
|
10289
10824
|
if (!resultText) return passOutput();
|
|
@@ -10568,16 +11103,17 @@ registerHook("post_tool_use", postBrowserImageHandler, { toolPattern: "^mcp__" }
|
|
|
10568
11103
|
// src/hooks_agent_spawn.ts
|
|
10569
11104
|
init_define_import_meta_env();
|
|
10570
11105
|
import { createHash as createHash2 } from "node:crypto";
|
|
10571
|
-
import * as
|
|
10572
|
-
import * as
|
|
11106
|
+
import * as fs4 from "node:fs";
|
|
11107
|
+
import * as path7 from "node:path";
|
|
10573
11108
|
var BRIEFING_TARGET_TOKENS = 550;
|
|
10574
|
-
function buildSubagentBriefing() {
|
|
11109
|
+
function buildSubagentBriefing(projectRoot) {
|
|
10575
11110
|
try {
|
|
10576
11111
|
const head = [];
|
|
10577
11112
|
head.push("");
|
|
10578
11113
|
head.push("## Session briefing (for context)");
|
|
10579
11114
|
try {
|
|
10580
|
-
|
|
11115
|
+
if (projectRoot === null) throw new Error("no project root");
|
|
11116
|
+
const map2 = buildProjectMap(projectRoot, { compact: true });
|
|
10581
11117
|
const mapText = formatProjectMap(map2, map2.compact);
|
|
10582
11118
|
head.push(mapText);
|
|
10583
11119
|
} catch {
|
|
@@ -10641,6 +11177,32 @@ function findDuplicateOutstandingPrompt(prompt) {
|
|
|
10641
11177
|
function truncateForWarning(text, max) {
|
|
10642
11178
|
return text.length > max ? text.slice(0, max) + "..." : text;
|
|
10643
11179
|
}
|
|
11180
|
+
function briefingRoot(event) {
|
|
11181
|
+
if (getHarnessName() === "vscode") return getCwd(event) ?? null;
|
|
11182
|
+
return process.cwd();
|
|
11183
|
+
}
|
|
11184
|
+
function subagentStartWired() {
|
|
11185
|
+
const harness = getHarnessName();
|
|
11186
|
+
const dirVar = harness === "copilot_cli" ? "TOKEN_GOAT_COPILOT_HOOKS_DIR" : harness === "vscode" ? "TOKEN_GOAT_VSCODE_HOOKS_DIR" : void 0;
|
|
11187
|
+
const dir = dirVar === void 0 ? void 0 : process.env[dirVar];
|
|
11188
|
+
if (dir === void 0 || dir === "") return false;
|
|
11189
|
+
try {
|
|
11190
|
+
const config = JSON.parse(fs4.readFileSync(path7.join(dir, "token-goat.json"), "utf8"));
|
|
11191
|
+
const entries = config.hooks?.[harness === "vscode" ? "SubagentStart" : "subagentStart"];
|
|
11192
|
+
return Array.isArray(entries) && entries.length > 0;
|
|
11193
|
+
} catch {
|
|
11194
|
+
return false;
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11197
|
+
function subagentStartHandler(event) {
|
|
11198
|
+
try {
|
|
11199
|
+
const root = getCwd(event) ?? (getHarnessName() === "vscode" ? null : process.cwd());
|
|
11200
|
+
const briefing = buildSubagentBriefing(root).trim();
|
|
11201
|
+
return briefing ? contextOutput(briefing) : passOutput();
|
|
11202
|
+
} catch {
|
|
11203
|
+
return passOutput();
|
|
11204
|
+
}
|
|
11205
|
+
}
|
|
10644
11206
|
function isAgentTool(toolName) {
|
|
10645
11207
|
return toolName === "Agent" || toolName === "task" || toolName === "Task";
|
|
10646
11208
|
}
|
|
@@ -10650,9 +11212,11 @@ function preAgentHandler(event) {
|
|
|
10650
11212
|
const prompt = toolInput["prompt"];
|
|
10651
11213
|
if (typeof prompt !== "string" || prompt.trim() === "") return passOutput();
|
|
10652
11214
|
try {
|
|
11215
|
+
const scopedDeny = buildScopedSpawnDeny(toolInput);
|
|
11216
|
+
if (scopedDeny) return scopedDeny;
|
|
10653
11217
|
const duplicateOf = findDuplicateOutstandingPrompt(prompt);
|
|
10654
11218
|
recordOutstandingAgentSpawn(prompt);
|
|
10655
|
-
const briefing = buildSubagentBriefing();
|
|
11219
|
+
const briefing = subagentStartWired() ? "" : buildSubagentBriefing(briefingRoot(event));
|
|
10656
11220
|
const advisory = duplicateOf ? `
|
|
10657
11221
|
|
|
10658
11222
|
[token-goat] A similar subagent spawn already appears to be outstanding this session (prompt starts: "${neutralizeSpokenMarkers(truncateForWarning(duplicateOf, 80))}"). This is advisory only -- proceeding is fine if intentional.` : "";
|
|
@@ -10794,8 +11358,8 @@ function parseAgentDefinition(text, fallbackName) {
|
|
|
10794
11358
|
return { name: name2, restricted: false };
|
|
10795
11359
|
}
|
|
10796
11360
|
function findRestrictedAgentNames(roots) {
|
|
10797
|
-
const projectAgentsRoot =
|
|
10798
|
-
const scanRoots = roots ?? [
|
|
11361
|
+
const projectAgentsRoot = path7.join(process.cwd(), ".claude", "agents");
|
|
11362
|
+
const scanRoots = roots ?? [path7.join(claudeConfigDir(), "agents"), projectAgentsRoot];
|
|
10799
11363
|
const names = /* @__PURE__ */ new Set();
|
|
10800
11364
|
const visited = /* @__PURE__ */ new Set();
|
|
10801
11365
|
let filesSeen = 0;
|
|
@@ -10803,7 +11367,7 @@ function findRestrictedAgentNames(roots) {
|
|
|
10803
11367
|
if (depth > ROSTER_WALK_MAX_DEPTH || filesSeen >= ROSTER_WALK_MAX_FILES) return;
|
|
10804
11368
|
let real;
|
|
10805
11369
|
try {
|
|
10806
|
-
real =
|
|
11370
|
+
real = fs4.realpathSync(dir);
|
|
10807
11371
|
} catch {
|
|
10808
11372
|
return;
|
|
10809
11373
|
}
|
|
@@ -10813,16 +11377,16 @@ function findRestrictedAgentNames(roots) {
|
|
|
10813
11377
|
visited.add(key);
|
|
10814
11378
|
let entries;
|
|
10815
11379
|
try {
|
|
10816
|
-
entries =
|
|
11380
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
10817
11381
|
} catch {
|
|
10818
11382
|
return;
|
|
10819
11383
|
}
|
|
10820
11384
|
for (const entry of entries) {
|
|
10821
11385
|
if (filesSeen >= ROSTER_WALK_MAX_FILES) return;
|
|
10822
|
-
const full =
|
|
11386
|
+
const full = path7.join(dir, entry.name);
|
|
10823
11387
|
let stat;
|
|
10824
11388
|
try {
|
|
10825
|
-
stat =
|
|
11389
|
+
stat = fs4.statSync(full);
|
|
10826
11390
|
} catch {
|
|
10827
11391
|
continue;
|
|
10828
11392
|
}
|
|
@@ -10834,30 +11398,30 @@ function findRestrictedAgentNames(roots) {
|
|
|
10834
11398
|
filesSeen++;
|
|
10835
11399
|
let text;
|
|
10836
11400
|
try {
|
|
10837
|
-
text =
|
|
11401
|
+
text = fs4.readFileSync(full, "utf-8");
|
|
10838
11402
|
} catch {
|
|
10839
11403
|
continue;
|
|
10840
11404
|
}
|
|
10841
|
-
const parsed = parseAgentDefinition(text,
|
|
11405
|
+
const parsed = parseAgentDefinition(text, path7.basename(entry.name, path7.extname(entry.name)));
|
|
10842
11406
|
if (parsed !== null && parsed.restricted) names.add(parsed.name);
|
|
10843
11407
|
}
|
|
10844
11408
|
};
|
|
10845
11409
|
for (const root of scanRoots) {
|
|
10846
|
-
const isProjectRoot = roots === void 0 &&
|
|
11410
|
+
const isProjectRoot = roots === void 0 && path7.resolve(root) === path7.resolve(projectAgentsRoot);
|
|
10847
11411
|
if (!isProjectRoot) {
|
|
10848
11412
|
walk(root, 0, null);
|
|
10849
11413
|
continue;
|
|
10850
11414
|
}
|
|
10851
11415
|
let rootIsLink;
|
|
10852
11416
|
try {
|
|
10853
|
-
rootIsLink =
|
|
11417
|
+
rootIsLink = fs4.lstatSync(root).isSymbolicLink();
|
|
10854
11418
|
} catch {
|
|
10855
11419
|
continue;
|
|
10856
11420
|
}
|
|
10857
11421
|
if (rootIsLink) continue;
|
|
10858
11422
|
let confineTo;
|
|
10859
11423
|
try {
|
|
10860
|
-
confineTo =
|
|
11424
|
+
confineTo = fs4.realpathSync(root);
|
|
10861
11425
|
} catch {
|
|
10862
11426
|
continue;
|
|
10863
11427
|
}
|
|
@@ -10867,7 +11431,8 @@ function findRestrictedAgentNames(roots) {
|
|
|
10867
11431
|
}
|
|
10868
11432
|
function buildUnrestrictedSpawnAdvisory(toolInput) {
|
|
10869
11433
|
try {
|
|
10870
|
-
|
|
11434
|
+
const harness = getHarnessName();
|
|
11435
|
+
if (harness === "copilot_cli" || harness === "vscode") return "";
|
|
10871
11436
|
const rawType = toolInput["subagent_type"];
|
|
10872
11437
|
const spawnType = typeof rawType === "string" ? rawType.trim() : "";
|
|
10873
11438
|
if (spawnType !== "" && spawnType !== "general-purpose") return "";
|
|
@@ -10883,6 +11448,71 @@ function buildUnrestrictedSpawnAdvisory(toolInput) {
|
|
|
10883
11448
|
return "";
|
|
10884
11449
|
}
|
|
10885
11450
|
}
|
|
11451
|
+
var SCOPED_SPAWN_DENY_KEY = "agent-scoped-spawn-deny";
|
|
11452
|
+
function buildScopedSpawnDeny(toolInput) {
|
|
11453
|
+
if (!loadConfig().hints.agent_scoped_spawn_deny) return null;
|
|
11454
|
+
const harness = getHarnessName();
|
|
11455
|
+
if (harness === "copilot_cli" || harness === "vscode") return null;
|
|
11456
|
+
const rawType = toolInput["subagent_type"];
|
|
11457
|
+
if (typeof rawType === "string" && rawType.trim() !== "") return null;
|
|
11458
|
+
if (wasHintShown(SCOPED_SPAWN_DENY_KEY)) return null;
|
|
11459
|
+
const names = findRestrictedAgentNames();
|
|
11460
|
+
if (names.length === 0) return null;
|
|
11461
|
+
markHintShown(SCOPED_SPAWN_DENY_KEY);
|
|
11462
|
+
markHintShown(SPAWN_RESTRICT_HINT_KEY);
|
|
11463
|
+
recordStat("agent_scoped_spawn_deny", 0, 0);
|
|
11464
|
+
const shown = neutralizeSpokenMarkers(names.slice(0, SPAWN_RESTRICT_MAX_NAMES).join(", "));
|
|
11465
|
+
const more = names.length > SPAWN_RESTRICT_MAX_NAMES ? ` and ${names.length - SPAWN_RESTRICT_MAX_NAMES} more` : "";
|
|
11466
|
+
return denyOutput(`This Agent call omits subagent_type, so it would run as general-purpose, which is unrestricted: its lane starts by paying for every tool and MCP schema on the machine. Tools-restricted agent definitions exist here: ${shown}${more}. Re-issue the call with one of them as subagent_type if it fits the task. If none fits, re-issue it unchanged and it will run: this refusal fires once per session.`);
|
|
11467
|
+
}
|
|
11468
|
+
function reportNotice(resultText, recallHint, spawnAdvisory) {
|
|
11469
|
+
return `[token-goat] This subagent report (${toKB(resultText.length)}KB) is cached for later recall: ${recallHint}${spawnAdvisory === "" ? "" : `
|
|
11470
|
+
${spawnAdvisory}`}`;
|
|
11471
|
+
}
|
|
11472
|
+
var REWRITTEN_REPORT_RE = /\n\n\[token-goat\] This subagent report \([0-9.]+KB\) is cached for later recall: token-goat mcp-output mcp_[0-9a-f]{16} --full$/;
|
|
11473
|
+
function planReportCompaction(resultText, recallHint, notice) {
|
|
11474
|
+
const cfg = loadConfig().agent_report;
|
|
11475
|
+
const collapsed = collapseFencedBlocks(resultText, recallHint, cfg.fence_collapse_min_lines, cfg.fence_collapse_keep_lines);
|
|
11476
|
+
const deduped = dedupeFencedBlocks(collapsed, resultText, recallHint);
|
|
11477
|
+
const final = collapseBlankRunsInFences(deduped);
|
|
11478
|
+
if (final === resultText) return { final, worthwhile: false };
|
|
11479
|
+
const worthwhile = isRewriteWorthwhile({
|
|
11480
|
+
originalBytes: Buffer.byteLength(resultText, "utf-8"),
|
|
11481
|
+
rewrittenBytes: Buffer.byteLength(final, "utf-8"),
|
|
11482
|
+
noticeBytes: Buffer.byteLength(notice, "utf-8"),
|
|
11483
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
11484
|
+
});
|
|
11485
|
+
return { final, worthwhile };
|
|
11486
|
+
}
|
|
11487
|
+
function recordReportCompaction(resultText, updatedOutput) {
|
|
11488
|
+
const savedBytes = Buffer.byteLength(resultText, "utf-8") - Buffer.byteLength(updatedOutput, "utf-8");
|
|
11489
|
+
if (savedBytes > 0) recordStat("agent_report_compact", savedBytes, savedTokensFromBytes(savedBytes));
|
|
11490
|
+
}
|
|
11491
|
+
function subagentReportRewriteHandler(event) {
|
|
11492
|
+
try {
|
|
11493
|
+
if (getHarnessName() !== "copilot_cli" || !event.sessionId) return passOutput();
|
|
11494
|
+
const raw = event.raw["last_assistant_message"];
|
|
11495
|
+
if (typeof raw !== "string" || raw === "") return passOutput();
|
|
11496
|
+
const redactedReport = redactSecrets(raw);
|
|
11497
|
+
const resultText = redactedReport.text;
|
|
11498
|
+
if (resultText.length < loadConfig().agent_report.min_bytes) return passOutput();
|
|
11499
|
+
const toolInput = { agent_id: event.agentId ?? "" };
|
|
11500
|
+
const id = mcpOutputId(event.sessionId, mcpHash("task", toolInput));
|
|
11501
|
+
const recallHint = `token-goat mcp-output ${id} --full`;
|
|
11502
|
+
const notice = reportNotice(resultText, recallHint, "");
|
|
11503
|
+
const { final, worthwhile } = planReportCompaction(resultText, recallHint, notice);
|
|
11504
|
+
if (!worthwhile) return passOutput();
|
|
11505
|
+
if (storeMcpOutput(event.sessionId, "task", toolInput, resultText) !== id) return passOutput();
|
|
11506
|
+
if (redactedReport.count > 0) recordStat("secret_redacted", 0, redactedReport.count, void 0, "agent");
|
|
11507
|
+
const updatedOutput = `${final}
|
|
11508
|
+
|
|
11509
|
+
${notice}`;
|
|
11510
|
+
recordReportCompaction(resultText, updatedOutput);
|
|
11511
|
+
return emitRewrite(updatedOutput, "agent", void 0, "counted-elsewhere");
|
|
11512
|
+
} catch {
|
|
11513
|
+
return passOutput();
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
10886
11516
|
function postAgentHandler(event) {
|
|
10887
11517
|
try {
|
|
10888
11518
|
if (!isAgentTool(event.toolName) || !event.sessionId) return passOutput();
|
|
@@ -10892,6 +11522,7 @@ function postAgentHandler(event) {
|
|
|
10892
11522
|
}
|
|
10893
11523
|
const redactedReport = redactSecrets(extractToolResultText(event.raw));
|
|
10894
11524
|
const resultText = redactedReport.text;
|
|
11525
|
+
if (REWRITTEN_REPORT_RE.test(resultText)) return passOutput();
|
|
10895
11526
|
const agentReportCfg = loadConfig().agent_report;
|
|
10896
11527
|
const spawnAdvisory = buildUnrestrictedSpawnAdvisory(event.toolInput);
|
|
10897
11528
|
if (!resultText || resultText.length < agentReportCfg.min_bytes) {
|
|
@@ -10902,25 +11533,14 @@ function postAgentHandler(event) {
|
|
|
10902
11533
|
if (redactedReport.count > 0) recordStat("secret_redacted", 0, redactedReport.count, void 0, "agent");
|
|
10903
11534
|
recordStat("session_hint", 0, 0);
|
|
10904
11535
|
const recallHint = `token-goat mcp-output ${id} --full`;
|
|
10905
|
-
const notice =
|
|
10906
|
-
|
|
10907
|
-
const collapsed = collapseFencedBlocks(resultText, recallHint, agentReportCfg.fence_collapse_min_lines, agentReportCfg.fence_collapse_keep_lines);
|
|
10908
|
-
const deduped = dedupeFencedBlocks(collapsed, resultText, recallHint);
|
|
10909
|
-
const final = collapseBlankRunsInFences(deduped);
|
|
10910
|
-
const originalBytes = Buffer.byteLength(resultText, "utf-8");
|
|
11536
|
+
const notice = reportNotice(resultText, recallHint, spawnAdvisory);
|
|
11537
|
+
const { final, worthwhile } = planReportCompaction(resultText, recallHint, notice);
|
|
10911
11538
|
if (final !== resultText) {
|
|
10912
|
-
const worthwhile = isRewriteWorthwhile({
|
|
10913
|
-
originalBytes,
|
|
10914
|
-
rewrittenBytes: Buffer.byteLength(final, "utf-8"),
|
|
10915
|
-
noticeBytes: Buffer.byteLength(notice, "utf-8"),
|
|
10916
|
-
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
10917
|
-
});
|
|
10918
11539
|
if (worthwhile) {
|
|
10919
11540
|
const updatedOutput = `${final}
|
|
10920
11541
|
|
|
10921
11542
|
${notice}`;
|
|
10922
|
-
|
|
10923
|
-
if (savedBytes > 0) recordStat("agent_report_compact", savedBytes, savedTokensFromBytes(savedBytes));
|
|
11543
|
+
recordReportCompaction(resultText, updatedOutput);
|
|
10924
11544
|
return emitRewrite(updatedOutput, "agent", void 0, "counted-elsewhere");
|
|
10925
11545
|
}
|
|
10926
11546
|
recordStat("agent_report_compact_declined", 0, 0);
|
|
@@ -10930,6 +11550,8 @@ ${notice}`;
|
|
|
10930
11550
|
return passOutput();
|
|
10931
11551
|
}
|
|
10932
11552
|
}
|
|
11553
|
+
registerHook("subagent_start", subagentStartHandler);
|
|
11554
|
+
registerHook("subagent_stop", subagentReportRewriteHandler, { advisory: true });
|
|
10933
11555
|
registerHook("pre_tool_use", preAgentHandler, { toolName: "Agent" });
|
|
10934
11556
|
registerHook("pre_tool_use", preAgentHandler, { toolName: "task" });
|
|
10935
11557
|
registerHook("pre_tool_use", preAgentHandler, { toolName: "Task" });
|
|
@@ -10950,7 +11572,7 @@ function buildEvent(eventName, payload) {
|
|
|
10950
11572
|
const rawSession = obj["session_id"] ?? obj["sessionId"];
|
|
10951
11573
|
const sessionId = typeof rawSession === "string" ? rawSession : "";
|
|
10952
11574
|
const rawAgentId = obj["agent_id"] ?? obj["agentId"];
|
|
10953
|
-
const agentId = typeof rawAgentId === "string" && rawAgentId !== "" ? rawAgentId : void 0;
|
|
11575
|
+
const agentId = eventName !== "subagent_start" && typeof rawAgentId === "string" && rawAgentId !== "" ? rawAgentId : void 0;
|
|
10954
11576
|
const rawTraceparent = obj["traceparent"] ?? obj["traceParent"] ?? process.env["TRACEPARENT"] ?? process.env["traceparent"];
|
|
10955
11577
|
const traceparent = typeof rawTraceparent === "string" && rawTraceparent.trim() !== "" ? rawTraceparent.trim() : void 0;
|
|
10956
11578
|
const rawTracestate = obj["tracestate"] ?? obj["traceState"] ?? process.env["TRACESTATE"] ?? process.env["tracestate"];
|
|
@@ -10966,21 +11588,41 @@ function harnessForNormalization() {
|
|
|
10966
11588
|
if (detected === "qwen") return "qwen";
|
|
10967
11589
|
if (detected === "copilot_cli") return "copilot_cli";
|
|
10968
11590
|
if (detected === "vscode") return "vscode";
|
|
11591
|
+
if (detected === "antigravity") return "antigravity";
|
|
10969
11592
|
return "claude";
|
|
10970
11593
|
}
|
|
10971
11594
|
function safeSuggestions(output) {
|
|
10972
11595
|
if (output.hookType === "deny") return { hookType: "deny", message: stripUnsafeSuggestions(output.message) };
|
|
10973
|
-
if (output.hookType === "context") return { hookType: "context", context: stripUnsafeSuggestions(output.context) };
|
|
11596
|
+
if (output.hookType === "context") return { hookType: "context", context: stripUnsafeSuggestions(output.context), ...output.notice !== void 0 ? { notice: output.notice } : {} };
|
|
11597
|
+
if (output.hookType === "rewriteOutput" && output.context !== void 0) return { ...output, context: stripUnsafeSuggestions(output.context) };
|
|
10974
11598
|
return output;
|
|
10975
11599
|
}
|
|
10976
11600
|
var relayQueue = Promise.resolve();
|
|
10977
|
-
|
|
11601
|
+
var RELAY_QUEUE_WAIT_MS = 12e4;
|
|
11602
|
+
function afterPredecessor(previous) {
|
|
11603
|
+
return new Promise((resolve3) => {
|
|
11604
|
+
const timer = setTimeout(resolve3, RELAY_QUEUE_WAIT_MS);
|
|
11605
|
+
timer.unref();
|
|
11606
|
+
void previous.then(() => {
|
|
11607
|
+
clearTimeout(timer);
|
|
11608
|
+
resolve3();
|
|
11609
|
+
});
|
|
11610
|
+
});
|
|
11611
|
+
}
|
|
11612
|
+
var NATIVE_FALLBACK_ENV = "TOKEN_GOAT_NATIVE_FALLBACK";
|
|
11613
|
+
function nativeFallbackDetail(env = process.env) {
|
|
11614
|
+
const reason = env[NATIVE_FALLBACK_ENV];
|
|
11615
|
+
return reason !== void 0 && /^[a-z0-9-]{1,40}$/.test(reason) ? `native-fallback:${reason}` : void 0;
|
|
11616
|
+
}
|
|
11617
|
+
function relayInProcess(eventName, rawPayload, harnessWaitMs, opts = {}) {
|
|
10978
11618
|
const receivedAt = Date.now();
|
|
10979
|
-
const
|
|
11619
|
+
const detail = opts.detail ?? nativeFallbackDetail();
|
|
11620
|
+
const probe = process.env[HOOK_PROBE_ENV] === "1";
|
|
11621
|
+
const run = afterPredecessor(relayQueue).then(() => relayOne(eventName, rawPayload, harnessWaitMs, receivedAt, opts.elapsedMs ?? (() => performance.now()), opts.afterReply, detail, probe));
|
|
10980
11622
|
relayQueue = run.catch(() => void 0);
|
|
10981
11623
|
return run;
|
|
10982
11624
|
}
|
|
10983
|
-
async function relayOne(eventName, rawPayload, harnessWaitMs, receivedAt) {
|
|
11625
|
+
async function relayOne(eventName, rawPayload, harnessWaitMs, receivedAt, elapsedMs, afterReply, detail, probe = false) {
|
|
10984
11626
|
if (!isHookEventName(eventName)) {
|
|
10985
11627
|
return "{}";
|
|
10986
11628
|
}
|
|
@@ -10989,8 +11631,16 @@ async function relayOne(eventName, rawPayload, harnessWaitMs, receivedAt) {
|
|
|
10989
11631
|
const payload = eventName === "pre_tool_use" || eventName === "post_tool_use" ? normalizePayload(rawPayload, harnessForNormalization()) : rawPayload;
|
|
10990
11632
|
const event = buildEvent(eventName, payload);
|
|
10991
11633
|
if (shouldSuppressDuplicateVscodeHook(event, harness)) return "{}";
|
|
10992
|
-
|
|
10993
|
-
|
|
11634
|
+
const envSessionId = process.env["CLAUDE_CODE_SESSION_ID"];
|
|
11635
|
+
const seededSessionId = relaySeededSessionId();
|
|
11636
|
+
if (!envSessionId || envSessionId === seededSessionId) {
|
|
11637
|
+
if (event.sessionId) {
|
|
11638
|
+
process.env["CLAUDE_CODE_SESSION_ID"] = event.sessionId;
|
|
11639
|
+
setRelaySeededSessionId(event.sessionId);
|
|
11640
|
+
} else if (seededSessionId !== void 0) {
|
|
11641
|
+
delete process.env["CLAUDE_CODE_SESSION_ID"];
|
|
11642
|
+
setRelaySeededSessionId(void 0);
|
|
11643
|
+
}
|
|
10994
11644
|
}
|
|
10995
11645
|
const rawTranscriptPath = event.raw["transcript_path"];
|
|
10996
11646
|
if (typeof rawTranscriptPath === "string" && rawTranscriptPath !== "") {
|
|
@@ -11007,7 +11657,7 @@ async function relayOne(eventName, rawPayload, harnessWaitMs, receivedAt) {
|
|
|
11007
11657
|
loadSessionState(stateKey);
|
|
11008
11658
|
} catch {
|
|
11009
11659
|
}
|
|
11010
|
-
const output = safeSuggestions(sharpenRepeatedDeny(event, applyCallStreak(event, await runHook(event), receivedAt)));
|
|
11660
|
+
const output = applyProbeMarker(event.eventName, safeSuggestions(sharpenRepeatedDeny(event, applyCallStreak(event, await runHook(event), receivedAt))));
|
|
11011
11661
|
if (event.sessionId) {
|
|
11012
11662
|
commitPendingContext(stateKey, output.hookType === "context" ? output.context : null);
|
|
11013
11663
|
}
|
|
@@ -11019,7 +11669,12 @@ async function relayOne(eventName, rawPayload, harnessWaitMs, receivedAt) {
|
|
|
11019
11669
|
} catch {
|
|
11020
11670
|
return "{}";
|
|
11021
11671
|
} finally {
|
|
11022
|
-
|
|
11672
|
+
const durationMs = harnessWaitMs ?? elapsedMs();
|
|
11673
|
+
const record = () => {
|
|
11674
|
+
if (!probe) recordStat(`hook:${eventName}`, 0, 0, void 0, detail, void 0, durationMs);
|
|
11675
|
+
};
|
|
11676
|
+
if (afterReply) afterReply(record);
|
|
11677
|
+
else record();
|
|
11023
11678
|
}
|
|
11024
11679
|
}
|
|
11025
11680
|
async function relay(eventName) {
|
|
@@ -11045,6 +11700,9 @@ async function relay(eventName) {
|
|
|
11045
11700
|
|
|
11046
11701
|
export {
|
|
11047
11702
|
buildEvent,
|
|
11703
|
+
RELAY_QUEUE_WAIT_MS,
|
|
11704
|
+
NATIVE_FALLBACK_ENV,
|
|
11705
|
+
nativeFallbackDetail,
|
|
11048
11706
|
relayInProcess,
|
|
11049
11707
|
relay
|
|
11050
11708
|
};
|