token-goat 2.8.6 → 2.9.2
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 +27 -717
- package/SECURITY.md +1 -1
- package/dist/token-goat-chunk-CGWACYYZ.mjs +79 -0
- package/dist/{token-goat-chunk-XAWIELXH.mjs → token-goat-chunk-FXAPKVRG.mjs} +545 -217
- package/dist/{token-goat-chunk-SHN4UTL4.mjs → token-goat-chunk-JMUBXBG7.mjs} +5 -5
- package/dist/{token-goat-chunk-U7M2LTGP.mjs → token-goat-chunk-KKIB7O3Z.mjs} +854 -283
- package/dist/{token-goat-chunk-ZIPBLIUZ.mjs → token-goat-chunk-LEBYARO3.mjs} +28 -4
- package/dist/{token-goat-chunk-DK4VLLYB.mjs → token-goat-chunk-LJ3CHCTT.mjs} +2061 -3612
- package/dist/{token-goat-chunk-L2XHDICZ.mjs → token-goat-chunk-NY4HYRSK.mjs} +28 -27
- package/dist/{token-goat-chunk-MA5237JN.mjs → token-goat-chunk-Q4LOQY44.mjs} +205 -60
- package/dist/{token-goat-chunk-GUNYAGOZ.mjs → token-goat-chunk-UZ2NFOOZ.mjs} +10103 -117
- package/dist/{token-goat-chunk-TELKICYU.mjs → token-goat-chunk-VCNW7BGU.mjs} +155 -519
- package/dist/{token-goat-chunk-LJEETTER.mjs → token-goat-chunk-ZVN5WHTO.mjs} +5 -5
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/docs/C4_RUNTIME_ARCHITECTURE.md +303 -0
- package/docs/cli.md +339 -0
- package/docs/install.md +331 -0
- package/docs/security.md +102 -0
- package/package.json +7 -2
- package/dist/token-goat-chunk-2JZ66BBE.mjs +0 -7157
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
deliveredOutputBytes,
|
|
5
|
+
wrappedShell
|
|
6
|
+
} from "./token-goat-chunk-CGWACYYZ.mjs";
|
|
3
7
|
import {
|
|
4
8
|
ToolFilter,
|
|
5
9
|
capTokens,
|
|
6
10
|
combineStreams,
|
|
7
11
|
compressOutput,
|
|
12
|
+
compressedTokensSaved,
|
|
13
|
+
dispatchArgv,
|
|
8
14
|
filterByName,
|
|
15
|
+
loadConfig,
|
|
16
|
+
recordStat,
|
|
9
17
|
resolveMinNetSavingsBytes,
|
|
10
18
|
selectFilter,
|
|
11
|
-
shlexSplit
|
|
12
|
-
|
|
13
|
-
} from "./token-goat-chunk-GUNYAGOZ.mjs";
|
|
14
|
-
import {
|
|
15
|
-
loadConfig,
|
|
16
|
-
recordStat
|
|
17
|
-
} from "./token-goat-chunk-2JZ66BBE.mjs";
|
|
19
|
+
shlexSplit
|
|
20
|
+
} from "./token-goat-chunk-UZ2NFOOZ.mjs";
|
|
18
21
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
19
22
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
20
23
|
|
|
@@ -28,17 +31,19 @@ var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
28
31
|
var MAX_CAPTURE_BYTES = 32 * 1024 * 1024;
|
|
29
32
|
var MIN_RECORD_STAT_BYTES = 32;
|
|
30
33
|
function resolveFilter(command, filterName, cwd) {
|
|
31
|
-
|
|
32
|
-
const named = filterByName(filterName);
|
|
33
|
-
if (named !== null) return named;
|
|
34
|
-
}
|
|
35
|
-
let argv;
|
|
34
|
+
let split;
|
|
36
35
|
try {
|
|
37
|
-
|
|
36
|
+
split = shlexSplit(command);
|
|
38
37
|
} catch {
|
|
39
|
-
|
|
38
|
+
split = null;
|
|
39
|
+
}
|
|
40
|
+
const argv = split === null ? [command] : dispatchArgv(split, cwd).argv;
|
|
41
|
+
if (filterName) {
|
|
42
|
+
const named = filterByName(filterName);
|
|
43
|
+
if (named !== null) return { filter: named, argv };
|
|
40
44
|
}
|
|
41
|
-
return
|
|
45
|
+
if (split === null) return { filter: null, argv };
|
|
46
|
+
return { filter: selectFilter(split, cwd), argv };
|
|
42
47
|
}
|
|
43
48
|
function baseSpawnOptions(timeout, cwd, env) {
|
|
44
49
|
return { shell: wrappedShell(), timeout: timeout * 1e3, cwd, env };
|
|
@@ -66,14 +71,14 @@ function resolveCompressLimits() {
|
|
|
66
71
|
}
|
|
67
72
|
function run(command, opts = {}) {
|
|
68
73
|
const timeout = opts.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
69
|
-
const filter = resolveFilter(command, opts.filterName, opts.cwd);
|
|
74
|
+
const { filter, argv } = resolveFilter(command, opts.filterName, opts.cwd);
|
|
70
75
|
if (filter === null) {
|
|
71
76
|
if ((opts.maxTokens ?? 0) > 0) {
|
|
72
|
-
return wrapAndCompress(command, new IdentityFilter(), timeout, resolveProfile(opts.compressionProfile), opts);
|
|
77
|
+
return wrapAndCompress(command, argv, new IdentityFilter(), timeout, resolveProfile(opts.compressionProfile), opts);
|
|
73
78
|
}
|
|
74
79
|
return passthrough(command, timeout, opts.cwd, opts.env);
|
|
75
80
|
}
|
|
76
|
-
return wrapAndCompress(command, filter, timeout, resolveProfile(opts.compressionProfile), opts);
|
|
81
|
+
return wrapAndCompress(command, argv, filter, timeout, resolveProfile(opts.compressionProfile), opts);
|
|
77
82
|
}
|
|
78
83
|
function runRaw(command, timeout = DEFAULT_TIMEOUT_SECONDS) {
|
|
79
84
|
return passthrough(command, timeout, void 0, void 0);
|
|
@@ -92,7 +97,7 @@ function decode(buf) {
|
|
|
92
97
|
if (buf == null) return "";
|
|
93
98
|
return typeof buf === "string" ? buf : buf.toString("utf8");
|
|
94
99
|
}
|
|
95
|
-
function wrapAndCompress(command, filter, timeout, profile, opts) {
|
|
100
|
+
function wrapAndCompress(command, argv, filter, timeout, profile, opts) {
|
|
96
101
|
const writeStdout = opts.writeStdout ?? ((s) => process.stdout.write(s));
|
|
97
102
|
const result = spawnSync(command, {
|
|
98
103
|
...baseSpawnOptions(timeout, opts.cwd, opts.env),
|
|
@@ -118,12 +123,6 @@ function wrapAndCompress(command, filter, timeout, profile, opts) {
|
|
|
118
123
|
} else {
|
|
119
124
|
exitCode = 0;
|
|
120
125
|
}
|
|
121
|
-
let argv;
|
|
122
|
-
try {
|
|
123
|
-
argv = shlexSplit(command);
|
|
124
|
-
} catch {
|
|
125
|
-
argv = [command];
|
|
126
|
-
}
|
|
127
126
|
const limits = resolveCompressLimits();
|
|
128
127
|
const compressed = compressOutput(filter, stdoutText, stderrText, exitCode, argv, {
|
|
129
128
|
compressionProfile: profile,
|
|
@@ -142,8 +141,10 @@ function wrapAndCompress(command, filter, timeout, profile, opts) {
|
|
|
142
141
|
return exitCode;
|
|
143
142
|
}
|
|
144
143
|
function recordSavings(result) {
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
const delivered = deliveredOutputBytes(result.originalBytes);
|
|
145
|
+
const bytesSaved = Math.max(0, delivered - result.compressedBytes);
|
|
146
|
+
if (bytesSaved < MIN_RECORD_STAT_BYTES) return;
|
|
147
|
+
recordStat(`bash_compress:${result.filterName}`, bytesSaved, compressedTokensSaved(bytesSaved));
|
|
147
148
|
}
|
|
148
149
|
export {
|
|
149
150
|
DEFAULT_TIMEOUT_SECONDS,
|
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
DEFAULT_RECONCILE_BUDGET_MS,
|
|
5
5
|
HOOK_EVENTS,
|
|
6
6
|
accumulateResidentLines,
|
|
7
7
|
buildCommandManifest,
|
|
8
8
|
checkSymbolBodySize,
|
|
9
|
-
commandHash,
|
|
10
9
|
flattenCommandNames,
|
|
11
|
-
getBashOutput,
|
|
12
10
|
getWebOutput,
|
|
13
|
-
|
|
14
|
-
isBashEntryStale,
|
|
15
|
-
isScopedGitStatusOrDiffStatCommand,
|
|
11
|
+
isReconcileClean,
|
|
16
12
|
lineMayCarryResidentSignal,
|
|
17
13
|
normalizePayload,
|
|
18
14
|
readStdinJson,
|
|
19
15
|
readTranscriptTail,
|
|
16
|
+
reconcileProject,
|
|
20
17
|
repeatedSkillBodyHint,
|
|
21
|
-
storeBashOutput,
|
|
22
18
|
storeWebOutput,
|
|
23
|
-
summarizeOutputDelta,
|
|
24
19
|
summarizeResidentContext,
|
|
25
20
|
taskListPruneHint
|
|
26
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-VCNW7BGU.mjs";
|
|
27
22
|
import {
|
|
28
|
-
|
|
29
|
-
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
23
|
+
BASH_OUTPUT_SUBDIR,
|
|
30
24
|
UNTRUSTED_TOOL_TAG,
|
|
31
25
|
WEB_FETCH_KEY_SEP,
|
|
32
26
|
appendDirtyPath,
|
|
@@ -36,51 +30,45 @@ import {
|
|
|
36
30
|
classifyBashHint,
|
|
37
31
|
classifyEditHint,
|
|
38
32
|
clearCurlDownload,
|
|
33
|
+
commandHash,
|
|
39
34
|
compactPathFor,
|
|
40
35
|
computeAdaptiveBudget,
|
|
41
|
-
contextOutput,
|
|
42
36
|
countSymbols,
|
|
43
|
-
denyOutput,
|
|
44
|
-
emitRewrite,
|
|
45
|
-
emitRewriteIfChanged,
|
|
46
37
|
enqueueDirtyPathSafe,
|
|
47
38
|
ensureWorkerAlive,
|
|
48
39
|
estimateTokens,
|
|
49
40
|
extractCompactFromMarker,
|
|
50
|
-
extractToolResponseField,
|
|
51
|
-
extractToolResultText,
|
|
52
41
|
fenceWithMatches,
|
|
53
42
|
formatProjectMap,
|
|
54
43
|
formatShrinkSummary,
|
|
44
|
+
getBashOutput,
|
|
55
45
|
getBashOutputId,
|
|
56
46
|
getContextPressure,
|
|
57
47
|
getCurlDownloadPath,
|
|
58
|
-
getCwd,
|
|
59
48
|
getFileLineRanges,
|
|
60
|
-
|
|
49
|
+
getFileServedOutputs,
|
|
61
50
|
getGlobMatchCount,
|
|
62
51
|
getGrepMatchCount,
|
|
63
|
-
getLastTabContext,
|
|
64
52
|
getMonitoringRecallHint,
|
|
65
53
|
getOutstandingAgentSpawns,
|
|
66
54
|
getSessionBashOutputs,
|
|
67
55
|
getSessionBashReruns,
|
|
68
56
|
getSessionFiles,
|
|
69
57
|
getSessionWebFetches,
|
|
70
|
-
getToolInput,
|
|
71
|
-
getToolName,
|
|
72
58
|
hasSeenImage,
|
|
73
59
|
hasSessionOutput,
|
|
74
60
|
imageQualifiesForShrink,
|
|
75
61
|
incrementSkillHit,
|
|
62
|
+
indexRecallEntry,
|
|
76
63
|
installedSkillPath,
|
|
64
|
+
isBashEntryStale,
|
|
77
65
|
isBuildCommand,
|
|
78
|
-
|
|
66
|
+
isScopedGitStatusOrDiffStatCommand,
|
|
79
67
|
isTestRunnerCommand,
|
|
68
|
+
lastTabContextMatches,
|
|
80
69
|
listSiblingSessionStates,
|
|
81
70
|
loadSessionCache,
|
|
82
71
|
loadSessionState,
|
|
83
|
-
makeDedupHintHandlers,
|
|
84
72
|
markCompactStale,
|
|
85
73
|
markCompacted,
|
|
86
74
|
markFileTruncated,
|
|
@@ -88,7 +76,7 @@ import {
|
|
|
88
76
|
matchesAllowPattern,
|
|
89
77
|
matchesDenyPattern,
|
|
90
78
|
meetsSavingsFloor,
|
|
91
|
-
|
|
79
|
+
metadataEndpointRefusal,
|
|
92
80
|
probeImageMeta,
|
|
93
81
|
recordBashOutput,
|
|
94
82
|
recordBashRerun,
|
|
@@ -97,6 +85,7 @@ import {
|
|
|
97
85
|
recordFileEdit,
|
|
98
86
|
recordFileLineRange,
|
|
99
87
|
recordFileRead,
|
|
88
|
+
recordFileServedOutput,
|
|
100
89
|
recordGlobQuery,
|
|
101
90
|
recordGrepQuery,
|
|
102
91
|
recordKnownRootThrottled,
|
|
@@ -105,58 +94,125 @@ import {
|
|
|
105
94
|
recordSeenImage,
|
|
106
95
|
recordSymbolRead,
|
|
107
96
|
recordWebFetch,
|
|
108
|
-
registerHook,
|
|
109
97
|
removeOutstandingAgentSpawn,
|
|
110
|
-
runHook,
|
|
111
98
|
saveSessionState,
|
|
112
99
|
scanAndRecord,
|
|
113
|
-
serializeOutput,
|
|
114
100
|
sessionOutputBodyBytes,
|
|
115
101
|
sessionSidecarPath,
|
|
116
102
|
setLastTabContext,
|
|
117
103
|
shrinkImage,
|
|
104
|
+
storeBashOutput,
|
|
118
105
|
storeBlob,
|
|
119
106
|
storeOutput,
|
|
107
|
+
summarizeOutputDelta,
|
|
120
108
|
takePendingLargeFileHint,
|
|
121
109
|
visionTokens,
|
|
122
110
|
visionTokensSaved,
|
|
123
111
|
wasCliReadThisSession,
|
|
124
112
|
wasFileReadThisSession,
|
|
125
113
|
wasHintShown
|
|
126
|
-
} from "./token-goat-chunk-
|
|
114
|
+
} from "./token-goat-chunk-LJ3CHCTT.mjs";
|
|
127
115
|
import {
|
|
128
116
|
canRunWrappedShell,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
detectFromCommand,
|
|
132
|
-
filterByName,
|
|
133
|
-
hasBareBackgroundOrNewline,
|
|
134
|
-
isRewriteWorthwhile,
|
|
135
|
-
resolveMinNetSavingsBytes,
|
|
136
|
-
shlexSplit
|
|
137
|
-
} from "./token-goat-chunk-GUNYAGOZ.mjs";
|
|
117
|
+
deliveredOutputBytes
|
|
118
|
+
} from "./token-goat-chunk-CGWACYYZ.mjs";
|
|
138
119
|
import {
|
|
120
|
+
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
121
|
+
ENV_KEYS,
|
|
122
|
+
IDENTICAL_READ_MIN_BODY_BYTES,
|
|
123
|
+
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
139
124
|
PER_FILE_COUNTERFACTUAL_CEILING,
|
|
140
125
|
VERSION,
|
|
126
|
+
compressOutput,
|
|
127
|
+
containsLineRun,
|
|
128
|
+
contextOutput,
|
|
129
|
+
countNoun,
|
|
130
|
+
dedupeConsecutive,
|
|
131
|
+
denyOutput,
|
|
132
|
+
detectFromCommand,
|
|
141
133
|
detectHarness,
|
|
142
134
|
detectLanguage,
|
|
143
135
|
displaySafePath,
|
|
136
|
+
emitRewrite,
|
|
137
|
+
emitRewriteIfChanged,
|
|
138
|
+
envBool,
|
|
139
|
+
envInt,
|
|
144
140
|
extractErrorMessage,
|
|
141
|
+
extractToolResponseField,
|
|
142
|
+
extractToolResultText,
|
|
143
|
+
filterByName,
|
|
145
144
|
foldPath,
|
|
145
|
+
getCwd,
|
|
146
146
|
getDb,
|
|
147
|
+
getFilePath,
|
|
147
148
|
getHarnessName,
|
|
149
|
+
getToolInput,
|
|
150
|
+
getToolName,
|
|
148
151
|
globalDbPath,
|
|
152
|
+
hasBareBackgroundOrNewline,
|
|
153
|
+
isMcpErrorResponse,
|
|
154
|
+
isRewriteWorthwhile,
|
|
149
155
|
isUnderSystemTemp,
|
|
150
156
|
loadConfig,
|
|
157
|
+
makeDedupHintHandlers,
|
|
151
158
|
normalizePath,
|
|
159
|
+
passOutput,
|
|
152
160
|
recordStat,
|
|
153
161
|
redactSecrets,
|
|
162
|
+
registerHook,
|
|
154
163
|
resolveIndexPath,
|
|
164
|
+
resolveMinNetSavingsBytes,
|
|
155
165
|
runGit,
|
|
166
|
+
runHook,
|
|
156
167
|
savedTokensFromBytes,
|
|
168
|
+
serializeOutput,
|
|
169
|
+
sessionStateKey,
|
|
170
|
+
shlexSplit,
|
|
157
171
|
shortFingerprint,
|
|
172
|
+
stripAnsiEscapes,
|
|
158
173
|
toKB
|
|
159
|
-
} from "./token-goat-chunk-
|
|
174
|
+
} from "./token-goat-chunk-UZ2NFOOZ.mjs";
|
|
175
|
+
|
|
176
|
+
// src/hint_suggestion_guard.ts
|
|
177
|
+
function looksLikeSuggestion(slice) {
|
|
178
|
+
return slice.includes('"');
|
|
179
|
+
}
|
|
180
|
+
var SAFE_OUTSIDE_QUOTES = /^[A-Za-z0-9 \t_./=:,@+-]*$/;
|
|
181
|
+
var CONTROL_OR_BIDI = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\u2066-\u2069\u061C\u200B-\u200D\u2028\u2029\uFE00-\uFE0F]|[\u{E0000}-\u{E007F}]/u;
|
|
182
|
+
function suggestionIsUnsafe(slice) {
|
|
183
|
+
if (slice.includes("$") || slice.includes("\n") || slice.includes("\r") || slice.includes("`")) return true;
|
|
184
|
+
if (CONTROL_OR_BIDI.test(slice)) return true;
|
|
185
|
+
const regions = slice.split('"');
|
|
186
|
+
if (regions.length % 2 === 0) return true;
|
|
187
|
+
for (let i = 2; i < regions.length; i += 2) {
|
|
188
|
+
if (!SAFE_OUTSIDE_QUOTES.test(regions[i] ?? "")) return true;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
var OMITTED = "token-goat (command omitted: the path contains shell metacharacters)";
|
|
193
|
+
function stripUnsafeSuggestions(text) {
|
|
194
|
+
if (!text.includes("token-goat ")) return text;
|
|
195
|
+
let out = "";
|
|
196
|
+
let at = 0;
|
|
197
|
+
for (; ; ) {
|
|
198
|
+
const start = text.indexOf("token-goat ", at);
|
|
199
|
+
if (start === -1) return out + text.slice(at);
|
|
200
|
+
const lineBreak = text.slice(start).search(/[\r\n]/);
|
|
201
|
+
const line = lineBreak === -1 ? text.slice(start) : text.slice(start, start + lineBreak);
|
|
202
|
+
const firstTick = line.indexOf("`");
|
|
203
|
+
const narrow = firstTick === -1 ? line : line.slice(0, firstTick);
|
|
204
|
+
out += text.slice(at, start);
|
|
205
|
+
if (!looksLikeSuggestion(narrow) || !suggestionIsUnsafe(narrow)) {
|
|
206
|
+
out += narrow;
|
|
207
|
+
at = start + narrow.length;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const lastTick = line.lastIndexOf("`");
|
|
211
|
+
const wide = lastTick === -1 ? line : line.slice(0, lastTick);
|
|
212
|
+
out += OMITTED;
|
|
213
|
+
at = start + wide.length;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
160
216
|
|
|
161
217
|
// src/hooks_grep.ts
|
|
162
218
|
function grepIntInput(toolInput, key) {
|
|
@@ -631,7 +687,7 @@ import crypto from "node:crypto";
|
|
|
631
687
|
var TRACKED_SKILL = "token-goat";
|
|
632
688
|
var MAX_COMMANDS_SHOWN = 8;
|
|
633
689
|
async function currentCommandNames() {
|
|
634
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
690
|
+
const { buildProgram } = await import("./token-goat-chunk-JMUBXBG7.mjs");
|
|
635
691
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
636
692
|
}
|
|
637
693
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -887,23 +943,47 @@ registerHook("post_tool_use", pendingContextHandler, { advisory: true, followsMa
|
|
|
887
943
|
// src/hooks_session_start.ts
|
|
888
944
|
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.';
|
|
889
945
|
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.';
|
|
890
|
-
function
|
|
891
|
-
if (cwd === void 0) return
|
|
892
|
-
let symbolCount;
|
|
946
|
+
function isIndexedProject(cwd) {
|
|
947
|
+
if (cwd === void 0) return false;
|
|
893
948
|
try {
|
|
894
|
-
|
|
949
|
+
return countSymbols({ rootDir: cwd }, globalDbPath()) > 0;
|
|
950
|
+
} catch {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function buildReminder(indexed) {
|
|
955
|
+
return indexed ? INDEXED_REMINDER : GENERIC_REMINDER;
|
|
956
|
+
}
|
|
957
|
+
function reconcileNote(cwd, indexed) {
|
|
958
|
+
if (!indexed) return null;
|
|
959
|
+
if (!envBool(ENV_KEYS.RECONCILE, true)) return null;
|
|
960
|
+
try {
|
|
961
|
+
const budgetMs = envInt(ENV_KEYS.RECONCILE_BUDGET_MS, DEFAULT_RECONCILE_BUDGET_MS, 0, 6e4);
|
|
962
|
+
const result = reconcileProject({ cwd, budgetMs });
|
|
963
|
+
if (isReconcileClean(result)) return null;
|
|
964
|
+
const parts = [];
|
|
965
|
+
if (result.changed.length > 0) parts.push(`${result.changed.length} changed`);
|
|
966
|
+
if (result.added.length > 0) parts.push(`${result.added.length} new`);
|
|
967
|
+
if (result.removed.length > 0) parts.push(`${result.removed.length} removed`);
|
|
968
|
+
const breakdown = parts.length > 1 ? ` (${parts.join(", ")})` : "";
|
|
969
|
+
const truncated = result.budgetExhausted ? ` (sweep stopped at its time budget with ${countNoun(result.unscanned, "file")} unchecked, so there may be more)` : "";
|
|
970
|
+
const total = result.changed.length + result.added.length + result.removed.length;
|
|
971
|
+
return `token-goat: reindexing ${countNoun(total, "file")} that changed outside this session${breakdown}${truncated}. Symbol lookups may be briefly stale.`;
|
|
895
972
|
} catch {
|
|
896
|
-
return
|
|
973
|
+
return null;
|
|
897
974
|
}
|
|
898
|
-
if (symbolCount <= 0) return GENERIC_REMINDER;
|
|
899
|
-
return INDEXED_REMINDER;
|
|
900
975
|
}
|
|
901
976
|
function sessionStartHandler(event) {
|
|
902
977
|
try {
|
|
903
978
|
if (!loadConfig().hints.session_start_reminder) return passOutput();
|
|
904
979
|
const cwd = getCwd(event);
|
|
905
|
-
|
|
980
|
+
const indexed = isIndexedProject(cwd);
|
|
981
|
+
let context = buildReminder(indexed);
|
|
906
982
|
if (cwd !== void 0) {
|
|
983
|
+
const drift = reconcileNote(cwd, indexed);
|
|
984
|
+
if (drift !== null) context += `
|
|
985
|
+
|
|
986
|
+
${drift}`;
|
|
907
987
|
const capsule = buildDeltaCapsule(cwd);
|
|
908
988
|
if (capsule !== null) context += `
|
|
909
989
|
|
|
@@ -5180,7 +5260,7 @@ function resolveCyclicReferences(value, utils, meta) {
|
|
|
5180
5260
|
}
|
|
5181
5261
|
const hierarchy = getMetaDataHierarchy(meta);
|
|
5182
5262
|
const depth = getCyclicReferenceDepth(value, hierarchy, 0);
|
|
5183
|
-
if (
|
|
5263
|
+
if (hierarchy !== void 0 && depth > 0) {
|
|
5184
5264
|
return hierarchy[hierarchy.length - depth]?.result;
|
|
5185
5265
|
}
|
|
5186
5266
|
const type = getObjectType(value);
|
|
@@ -6997,6 +7077,10 @@ function preFetchHandler(event) {
|
|
|
6997
7077
|
try {
|
|
6998
7078
|
const urlOnlyCtx = resolveWebFetchUrl(event);
|
|
6999
7079
|
if (urlOnlyCtx !== null) {
|
|
7080
|
+
const metadataRefusal = metadataEndpointRefusal(urlOnlyCtx.url);
|
|
7081
|
+
if (metadataRefusal !== null) {
|
|
7082
|
+
return denyOutput(`WebFetch blocked: ${metadataRefusal}.`);
|
|
7083
|
+
}
|
|
7000
7084
|
const wfCfg = loadConfig().webfetch;
|
|
7001
7085
|
if (wfCfg.deny.length > 0 && matchesDenyPattern(urlOnlyCtx.url, wfCfg.deny)) {
|
|
7002
7086
|
return denyOutput(`WebFetch blocked: URL matches a configured webfetch.deny pattern.`);
|
|
@@ -7130,7 +7214,7 @@ async function preSkillHandler(event) {
|
|
|
7130
7214
|
const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
|
|
7131
7215
|
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit));
|
|
7132
7216
|
return denyOutput(
|
|
7133
|
-
"Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-body " + skillName + " --compact` to recall the compact slice
|
|
7217
|
+
"Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-section " + skillName + " '<heading>'` 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."
|
|
7134
7218
|
);
|
|
7135
7219
|
}
|
|
7136
7220
|
const sourcePath = await installedSkillPath(skillName);
|
|
@@ -7145,12 +7229,12 @@ async function preSkillHandler(event) {
|
|
|
7145
7229
|
const savedBytes = bodyBytes - compactBytes;
|
|
7146
7230
|
recordStat("skill_compact_inlined", savedBytes, savedTokensFromBytes(savedBytes));
|
|
7147
7231
|
return denyOutput(
|
|
7148
|
-
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes); its compact slice (" + compactBytes + " bytes) is inlined below instead of the full body.
|
|
7232
|
+
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes); its compact slice (" + compactBytes + " bytes) is inlined below instead of the full body. For a specific section, run `token-goat skill-section " + skillName + " '<heading>'`, or `token-goat skill-body " + skillName + "` if you need the full body.\n\n" + compact
|
|
7149
7233
|
);
|
|
7150
7234
|
}
|
|
7151
7235
|
recordStat("skill_oversized_first_load");
|
|
7152
7236
|
return denyOutput(
|
|
7153
|
-
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes) and has a compact slice available. Use `token-goat skill-body " + skillName + " --compact` to load the compact slice
|
|
7237
|
+
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes) and has a compact slice available. Use `token-goat skill-section " + skillName + " '<heading>'` to load a specific section, `token-goat skill-body " + skillName + " --compact` to load the compact slice, or `token-goat skill-body " + skillName + "` for the full body."
|
|
7154
7238
|
);
|
|
7155
7239
|
}
|
|
7156
7240
|
} catch {
|
|
@@ -8383,6 +8467,40 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
8383
8467
|
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8384
8468
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8385
8469
|
}
|
|
8470
|
+
function pureFileReadPath(cmd) {
|
|
8471
|
+
return extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath ?? null;
|
|
8472
|
+
}
|
|
8473
|
+
async function maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8474
|
+
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8475
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
8476
|
+
const filePath = pureFileReadPath(cmd);
|
|
8477
|
+
if (filePath === null) return null;
|
|
8478
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8479
|
+
if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
|
|
8480
|
+
const fileKey = resolveIndexPath(filePath, cwd ?? process.cwd());
|
|
8481
|
+
const priorIds = getFileServedOutputs(fileKey);
|
|
8482
|
+
let containerId = null;
|
|
8483
|
+
let identical = false;
|
|
8484
|
+
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
8485
|
+
const id = priorIds[i];
|
|
8486
|
+
if (id === void 0) continue;
|
|
8487
|
+
const prior = getBashOutput(id);
|
|
8488
|
+
if (prior === null || !containsLineRun(prior.output, output)) continue;
|
|
8489
|
+
containerId = id;
|
|
8490
|
+
identical = prior.output === output;
|
|
8491
|
+
break;
|
|
8492
|
+
}
|
|
8493
|
+
const sessionKey = shortFingerprint(stripOutputPipeline(cmd));
|
|
8494
|
+
if (containerId === null) {
|
|
8495
|
+
const storedId = await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8496
|
+
recordBashOutput(sessionKey, storedId, originalBytes);
|
|
8497
|
+
recordFileServedOutput(fileKey, storedId);
|
|
8498
|
+
return null;
|
|
8499
|
+
}
|
|
8500
|
+
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 + "`." : "[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 + "`.";
|
|
8501
|
+
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8502
|
+
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) });
|
|
8503
|
+
}
|
|
8386
8504
|
async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8387
8505
|
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8388
8506
|
if (isCompressibleSingleCommand(cmd)) return null;
|
|
@@ -8415,7 +8533,28 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8415
8533
|
return null;
|
|
8416
8534
|
}
|
|
8417
8535
|
await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8418
|
-
return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: compressed.originalBytes });
|
|
8536
|
+
return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
8537
|
+
}
|
|
8538
|
+
function maybeStripAnsiOnly(output) {
|
|
8539
|
+
if (!output.includes("\x1B")) return null;
|
|
8540
|
+
let cfg;
|
|
8541
|
+
try {
|
|
8542
|
+
cfg = loadConfig().bash_compress;
|
|
8543
|
+
} catch {
|
|
8544
|
+
return null;
|
|
8545
|
+
}
|
|
8546
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("ansi")) return null;
|
|
8547
|
+
const stripped = stripAnsiEscapes(output);
|
|
8548
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8549
|
+
if (!isRewriteWorthwhile({
|
|
8550
|
+
originalBytes,
|
|
8551
|
+
rewrittenBytes: Buffer.byteLength(stripped, "utf-8"),
|
|
8552
|
+
noticeBytes: 0,
|
|
8553
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
8554
|
+
})) {
|
|
8555
|
+
return null;
|
|
8556
|
+
}
|
|
8557
|
+
return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
|
|
8419
8558
|
}
|
|
8420
8559
|
function unwrapCompressCommand(executed) {
|
|
8421
8560
|
const t = executed.trim();
|
|
@@ -9102,9 +9241,11 @@ async function postBashHandler(event) {
|
|
|
9102
9241
|
}
|
|
9103
9242
|
const isMonitoring = getMonitoringRecallHint(cmd) !== null;
|
|
9104
9243
|
if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
|
|
9244
|
+
const identical = await maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9245
|
+
if (identical !== null) return identical;
|
|
9105
9246
|
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9106
9247
|
if (compound !== null) return compound;
|
|
9107
|
-
return passOutput();
|
|
9248
|
+
return maybeStripAnsiOnly(output) ?? passOutput();
|
|
9108
9249
|
}
|
|
9109
9250
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
9110
9251
|
const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : stripOutputPipeline(cmd);
|
|
@@ -9121,6 +9262,8 @@ async function postBashHandler(event) {
|
|
|
9121
9262
|
return contextOutput(delta + " \u2014 full output: bash-output " + id);
|
|
9122
9263
|
}
|
|
9123
9264
|
}
|
|
9265
|
+
const ansiOnly = maybeStripAnsiOnly(output);
|
|
9266
|
+
if (ansiOnly !== null) return ansiOnly;
|
|
9124
9267
|
} catch {
|
|
9125
9268
|
}
|
|
9126
9269
|
return passOutput();
|
|
@@ -10007,7 +10150,7 @@ ${dataUrl}`, changed: true, savedBytes: saved, savedTokens };
|
|
|
10007
10150
|
var TAB_CONTEXT_UNCHANGED_NOTICE = "(tabs unchanged since last check)";
|
|
10008
10151
|
function dedupTabContext(text) {
|
|
10009
10152
|
if (!TAB_CONTEXT_RE.test(text)) return { text, changed: false };
|
|
10010
|
-
const isRepeat =
|
|
10153
|
+
const isRepeat = lastTabContextMatches(text);
|
|
10011
10154
|
setLastTabContext(text);
|
|
10012
10155
|
if (!isRepeat) return { text, changed: false };
|
|
10013
10156
|
const worthwhile = isRewriteWorthwhile({
|
|
@@ -10427,9 +10570,6 @@ function buildEvent(eventName, payload) {
|
|
|
10427
10570
|
const tracestate = typeof rawTracestate === "string" && rawTracestate.trim() !== "" ? rawTracestate.trim() : void 0;
|
|
10428
10571
|
return { eventName, toolName, toolInput, sessionId, agentId, traceparent, tracestate, raw: obj };
|
|
10429
10572
|
}
|
|
10430
|
-
function sessionStateKey(event) {
|
|
10431
|
-
return event.agentId !== void 0 ? `${event.sessionId}:agent:${event.agentId}` : event.sessionId;
|
|
10432
|
-
}
|
|
10433
10573
|
function harnessForNormalization() {
|
|
10434
10574
|
const detected = detectHarness();
|
|
10435
10575
|
if (detected === "codex") return "codex";
|
|
@@ -10440,6 +10580,11 @@ function harnessForNormalization() {
|
|
|
10440
10580
|
if (detected === "copilot_cli") return "copilot_cli";
|
|
10441
10581
|
return "claude";
|
|
10442
10582
|
}
|
|
10583
|
+
function safeSuggestions(output) {
|
|
10584
|
+
if (output.hookType === "deny") return { hookType: "deny", message: stripUnsafeSuggestions(output.message) };
|
|
10585
|
+
if (output.hookType === "context") return { hookType: "context", context: stripUnsafeSuggestions(output.context) };
|
|
10586
|
+
return output;
|
|
10587
|
+
}
|
|
10443
10588
|
async function relayInProcess(eventName, rawPayload) {
|
|
10444
10589
|
try {
|
|
10445
10590
|
if (!isHookEventName(eventName)) {
|
|
@@ -10462,12 +10607,12 @@ async function relayInProcess(eventName, rawPayload) {
|
|
|
10462
10607
|
loadSessionState(stateKey);
|
|
10463
10608
|
} catch {
|
|
10464
10609
|
}
|
|
10465
|
-
const output = await runHook(event);
|
|
10610
|
+
const output = safeSuggestions(await runHook(event));
|
|
10466
10611
|
try {
|
|
10467
10612
|
saveSessionState(stateKey);
|
|
10468
10613
|
} catch {
|
|
10469
10614
|
}
|
|
10470
|
-
return serializeOutput(output, event.eventName, harness);
|
|
10615
|
+
return serializeOutput(output, event.eventName, harness, event);
|
|
10471
10616
|
} catch {
|
|
10472
10617
|
return "{}";
|
|
10473
10618
|
}
|