token-goat 2.8.4 → 2.9.1
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 +20 -8
- package/dist/{token-goat-chunk-NZFTWBGV.mjs → token-goat-chunk-4KD2N46D.mjs} +28 -27
- package/dist/{token-goat-chunk-CZALRRGN.mjs → token-goat-chunk-4ZDRTNGV.mjs} +9942 -95
- package/dist/{token-goat-chunk-L6YP6FKQ.mjs → token-goat-chunk-AUM22UQ2.mjs} +5 -5
- package/dist/{token-goat-chunk-LZOAPGWR.mjs → token-goat-chunk-CD6U5OD6.mjs} +28 -4
- package/dist/{token-goat-chunk-4OTIB7SB.mjs → token-goat-chunk-FDXINYK4.mjs} +2120 -3658
- package/dist/token-goat-chunk-LHMC5ENL.mjs +79 -0
- package/dist/{token-goat-chunk-IZRXU64B.mjs → token-goat-chunk-MR7MNSDR.mjs} +155 -517
- package/dist/{token-goat-chunk-ZQ3PUOP3.mjs → token-goat-chunk-RRNRPCLX.mjs} +167 -116
- package/dist/{token-goat-chunk-U4UI5F3S.mjs → token-goat-chunk-SQSB5P5P.mjs} +842 -368
- package/dist/{token-goat-chunk-RE7S7H26.mjs → token-goat-chunk-SXSWQMNP.mjs} +512 -206
- package/dist/{token-goat-chunk-HNODKNWO.mjs → token-goat-chunk-V5T3YNYR.mjs} +5 -5
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +1 -1
- package/dist/token-goat-chunk-E76UNTVK.mjs +0 -7133
|
@@ -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-MR7MNSDR.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,27 +30,23 @@ 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
37
|
enqueueDirtyPathSafe,
|
|
46
38
|
ensureWorkerAlive,
|
|
47
39
|
estimateTokens,
|
|
48
40
|
extractCompactFromMarker,
|
|
49
|
-
|
|
50
|
-
extractToolResultText,
|
|
51
|
-
fenceUntrustedContent,
|
|
41
|
+
fenceWithMatches,
|
|
52
42
|
formatProjectMap,
|
|
53
43
|
formatShrinkSummary,
|
|
44
|
+
getBashOutput,
|
|
54
45
|
getBashOutputId,
|
|
55
46
|
getContextPressure,
|
|
56
47
|
getCurlDownloadPath,
|
|
57
|
-
getCwd,
|
|
58
48
|
getFileLineRanges,
|
|
59
|
-
|
|
49
|
+
getFileServedOutputs,
|
|
60
50
|
getGlobMatchCount,
|
|
61
51
|
getGrepMatchCount,
|
|
62
52
|
getLastTabContext,
|
|
@@ -66,20 +56,19 @@ import {
|
|
|
66
56
|
getSessionBashReruns,
|
|
67
57
|
getSessionFiles,
|
|
68
58
|
getSessionWebFetches,
|
|
69
|
-
getToolInput,
|
|
70
|
-
getToolName,
|
|
71
59
|
hasSeenImage,
|
|
72
60
|
hasSessionOutput,
|
|
73
61
|
imageQualifiesForShrink,
|
|
74
62
|
incrementSkillHit,
|
|
63
|
+
indexRecallEntry,
|
|
75
64
|
installedSkillPath,
|
|
65
|
+
isBashEntryStale,
|
|
76
66
|
isBuildCommand,
|
|
77
|
-
|
|
67
|
+
isScopedGitStatusOrDiffStatCommand,
|
|
78
68
|
isTestRunnerCommand,
|
|
79
69
|
listSiblingSessionStates,
|
|
80
70
|
loadSessionCache,
|
|
81
71
|
loadSessionState,
|
|
82
|
-
makeDedupHintHandlers,
|
|
83
72
|
markCompactStale,
|
|
84
73
|
markCompacted,
|
|
85
74
|
markFileTruncated,
|
|
@@ -87,7 +76,6 @@ import {
|
|
|
87
76
|
matchesAllowPattern,
|
|
88
77
|
matchesDenyPattern,
|
|
89
78
|
meetsSavingsFloor,
|
|
90
|
-
passOutput,
|
|
91
79
|
probeImageMeta,
|
|
92
80
|
recordBashOutput,
|
|
93
81
|
recordBashRerun,
|
|
@@ -96,6 +84,7 @@ import {
|
|
|
96
84
|
recordFileEdit,
|
|
97
85
|
recordFileLineRange,
|
|
98
86
|
recordFileRead,
|
|
87
|
+
recordFileServedOutput,
|
|
99
88
|
recordGlobQuery,
|
|
100
89
|
recordGrepQuery,
|
|
101
90
|
recordKnownRootThrottled,
|
|
@@ -104,58 +93,84 @@ import {
|
|
|
104
93
|
recordSeenImage,
|
|
105
94
|
recordSymbolRead,
|
|
106
95
|
recordWebFetch,
|
|
107
|
-
registerHook,
|
|
108
96
|
removeOutstandingAgentSpawn,
|
|
109
|
-
runHook,
|
|
110
97
|
saveSessionState,
|
|
111
|
-
|
|
112
|
-
serializeOutput,
|
|
98
|
+
scanAndRecord,
|
|
113
99
|
sessionOutputBodyBytes,
|
|
114
100
|
sessionSidecarPath,
|
|
115
101
|
setLastTabContext,
|
|
116
102
|
shrinkImage,
|
|
103
|
+
storeBashOutput,
|
|
117
104
|
storeBlob,
|
|
118
105
|
storeOutput,
|
|
106
|
+
summarizeOutputDelta,
|
|
119
107
|
takePendingLargeFileHint,
|
|
120
108
|
visionTokens,
|
|
121
109
|
visionTokensSaved,
|
|
122
110
|
wasCliReadThisSession,
|
|
123
111
|
wasFileReadThisSession,
|
|
124
112
|
wasHintShown
|
|
125
|
-
} from "./token-goat-chunk-
|
|
113
|
+
} from "./token-goat-chunk-FDXINYK4.mjs";
|
|
126
114
|
import {
|
|
127
115
|
canRunWrappedShell,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
detectFromCommand,
|
|
131
|
-
filterByName,
|
|
132
|
-
hasBareBackgroundOrNewline,
|
|
133
|
-
isRewriteWorthwhile,
|
|
134
|
-
resolveMinNetSavingsBytes,
|
|
135
|
-
shlexSplit
|
|
136
|
-
} from "./token-goat-chunk-CZALRRGN.mjs";
|
|
116
|
+
deliveredOutputBytes
|
|
117
|
+
} from "./token-goat-chunk-LHMC5ENL.mjs";
|
|
137
118
|
import {
|
|
119
|
+
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
120
|
+
ENV_KEYS,
|
|
121
|
+
IDENTICAL_READ_MIN_BODY_BYTES,
|
|
122
|
+
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
138
123
|
PER_FILE_COUNTERFACTUAL_CEILING,
|
|
139
124
|
VERSION,
|
|
125
|
+
compressOutput,
|
|
126
|
+
containsLineRun,
|
|
127
|
+
contextOutput,
|
|
128
|
+
countNoun,
|
|
129
|
+
dedupeConsecutive,
|
|
130
|
+
denyOutput,
|
|
131
|
+
detectFromCommand,
|
|
140
132
|
detectHarness,
|
|
141
133
|
detectLanguage,
|
|
142
134
|
displaySafePath,
|
|
135
|
+
emitRewrite,
|
|
136
|
+
emitRewriteIfChanged,
|
|
137
|
+
envBool,
|
|
138
|
+
envInt,
|
|
143
139
|
extractErrorMessage,
|
|
140
|
+
extractToolResponseField,
|
|
141
|
+
extractToolResultText,
|
|
142
|
+
filterByName,
|
|
144
143
|
foldPath,
|
|
144
|
+
getCwd,
|
|
145
145
|
getDb,
|
|
146
|
+
getFilePath,
|
|
146
147
|
getHarnessName,
|
|
148
|
+
getToolInput,
|
|
149
|
+
getToolName,
|
|
147
150
|
globalDbPath,
|
|
151
|
+
hasBareBackgroundOrNewline,
|
|
152
|
+
isMcpErrorResponse,
|
|
153
|
+
isRewriteWorthwhile,
|
|
148
154
|
isUnderSystemTemp,
|
|
149
155
|
loadConfig,
|
|
156
|
+
makeDedupHintHandlers,
|
|
150
157
|
normalizePath,
|
|
158
|
+
passOutput,
|
|
151
159
|
recordStat,
|
|
152
160
|
redactSecrets,
|
|
161
|
+
registerHook,
|
|
153
162
|
resolveIndexPath,
|
|
163
|
+
resolveMinNetSavingsBytes,
|
|
154
164
|
runGit,
|
|
165
|
+
runHook,
|
|
155
166
|
savedTokensFromBytes,
|
|
167
|
+
serializeOutput,
|
|
168
|
+
sessionStateKey,
|
|
169
|
+
shlexSplit,
|
|
156
170
|
shortFingerprint,
|
|
171
|
+
stripAnsiEscapes,
|
|
157
172
|
toKB
|
|
158
|
-
} from "./token-goat-chunk-
|
|
173
|
+
} from "./token-goat-chunk-4ZDRTNGV.mjs";
|
|
159
174
|
|
|
160
175
|
// src/hooks_grep.ts
|
|
161
176
|
function grepIntInput(toolInput, key) {
|
|
@@ -630,7 +645,7 @@ import crypto from "node:crypto";
|
|
|
630
645
|
var TRACKED_SKILL = "token-goat";
|
|
631
646
|
var MAX_COMMANDS_SHOWN = 8;
|
|
632
647
|
async function currentCommandNames() {
|
|
633
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
648
|
+
const { buildProgram } = await import("./token-goat-chunk-V5T3YNYR.mjs");
|
|
634
649
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
635
650
|
}
|
|
636
651
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -886,23 +901,47 @@ registerHook("post_tool_use", pendingContextHandler, { advisory: true, followsMa
|
|
|
886
901
|
// src/hooks_session_start.ts
|
|
887
902
|
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.';
|
|
888
903
|
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.';
|
|
889
|
-
function
|
|
890
|
-
if (cwd === void 0) return
|
|
891
|
-
let symbolCount;
|
|
904
|
+
function isIndexedProject(cwd) {
|
|
905
|
+
if (cwd === void 0) return false;
|
|
892
906
|
try {
|
|
893
|
-
|
|
907
|
+
return countSymbols({ rootDir: cwd }, globalDbPath()) > 0;
|
|
894
908
|
} catch {
|
|
895
|
-
return
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function buildReminder(indexed) {
|
|
913
|
+
return indexed ? INDEXED_REMINDER : GENERIC_REMINDER;
|
|
914
|
+
}
|
|
915
|
+
function reconcileNote(cwd, indexed) {
|
|
916
|
+
if (!indexed) return null;
|
|
917
|
+
if (!envBool(ENV_KEYS.RECONCILE, true)) return null;
|
|
918
|
+
try {
|
|
919
|
+
const budgetMs = envInt(ENV_KEYS.RECONCILE_BUDGET_MS, DEFAULT_RECONCILE_BUDGET_MS, 0, 6e4);
|
|
920
|
+
const result = reconcileProject({ cwd, budgetMs });
|
|
921
|
+
if (isReconcileClean(result)) return null;
|
|
922
|
+
const parts = [];
|
|
923
|
+
if (result.changed.length > 0) parts.push(`${result.changed.length} changed`);
|
|
924
|
+
if (result.added.length > 0) parts.push(`${result.added.length} new`);
|
|
925
|
+
if (result.removed.length > 0) parts.push(`${result.removed.length} removed`);
|
|
926
|
+
const breakdown = parts.length > 1 ? ` (${parts.join(", ")})` : "";
|
|
927
|
+
const truncated = result.budgetExhausted ? ` (sweep stopped at its time budget with ${countNoun(result.unscanned, "file")} unchecked, so there may be more)` : "";
|
|
928
|
+
const total = result.changed.length + result.added.length + result.removed.length;
|
|
929
|
+
return `token-goat: reindexing ${countNoun(total, "file")} that changed outside this session${breakdown}${truncated}. Symbol lookups may be briefly stale.`;
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
896
932
|
}
|
|
897
|
-
if (symbolCount <= 0) return GENERIC_REMINDER;
|
|
898
|
-
return INDEXED_REMINDER;
|
|
899
933
|
}
|
|
900
934
|
function sessionStartHandler(event) {
|
|
901
935
|
try {
|
|
902
936
|
if (!loadConfig().hints.session_start_reminder) return passOutput();
|
|
903
937
|
const cwd = getCwd(event);
|
|
904
|
-
|
|
938
|
+
const indexed = isIndexedProject(cwd);
|
|
939
|
+
let context = buildReminder(indexed);
|
|
905
940
|
if (cwd !== void 0) {
|
|
941
|
+
const drift = reconcileNote(cwd, indexed);
|
|
942
|
+
if (drift !== null) context += `
|
|
943
|
+
|
|
944
|
+
${drift}`;
|
|
906
945
|
const capsule = buildDeltaCapsule(cwd);
|
|
907
946
|
if (capsule !== null) context += `
|
|
908
947
|
|
|
@@ -7035,29 +7074,13 @@ function postFetchHandler(event) {
|
|
|
7035
7074
|
const { toolInput, url } = urlCtx;
|
|
7036
7075
|
recordStat("web_fetch");
|
|
7037
7076
|
const body = extractToolResponse(event.raw);
|
|
7038
|
-
const
|
|
7039
|
-
const injectionMatches = injCfg.enabled && body ? scanForInjectionPatterns(body) : [];
|
|
7040
|
-
if (injectionMatches.length > 0) {
|
|
7041
|
-
recordStat("injection_detected", 0, 0, void 0, injectionMatches.join(","));
|
|
7042
|
-
}
|
|
7077
|
+
const injectionMatches = body ? scanAndRecord(body) : [];
|
|
7043
7078
|
const bodyRedacted = body ? redactSecrets(body) : { text: body, count: 0 };
|
|
7044
7079
|
if (!event.sessionId) {
|
|
7045
|
-
|
|
7046
|
-
return emitRewrite(fenceUntrustedContent(bodyRedacted.text, injectionMatches), "fetch");
|
|
7047
|
-
}
|
|
7048
|
-
if (bodyRedacted.count > 0) {
|
|
7049
|
-
return emitRewrite(bodyRedacted.text, "fetch");
|
|
7050
|
-
}
|
|
7051
|
-
return passOutput();
|
|
7080
|
+
return emitRewriteIfChanged(body, fenceWithMatches(bodyRedacted.text, injectionMatches), "fetch");
|
|
7052
7081
|
}
|
|
7053
7082
|
if (!body || body.length < 1024) {
|
|
7054
|
-
|
|
7055
|
-
return emitRewrite(fenceUntrustedContent(bodyRedacted.text, injectionMatches), "fetch");
|
|
7056
|
-
}
|
|
7057
|
-
if (bodyRedacted.count > 0) {
|
|
7058
|
-
return emitRewrite(bodyRedacted.text, "fetch");
|
|
7059
|
-
}
|
|
7060
|
-
return passOutput();
|
|
7083
|
+
return emitRewriteIfChanged(body, fenceWithMatches(bodyRedacted.text, injectionMatches), "fetch");
|
|
7061
7084
|
}
|
|
7062
7085
|
const prompt = typeof toolInput["prompt"] === "string" ? toolInput["prompt"] : "";
|
|
7063
7086
|
let storedBody = body;
|
|
@@ -7072,29 +7095,24 @@ function postFetchHandler(event) {
|
|
|
7072
7095
|
const cacheId = storeWebOutput(url, storedBody, `${url}\0${prompt}`, storedBody !== body ? body : void 0);
|
|
7073
7096
|
recordWebFetch(url, prompt, cacheId);
|
|
7074
7097
|
const storedRedacted = storedBody === body ? bodyRedacted : redactSecrets(storedBody);
|
|
7075
|
-
|
|
7076
|
-
return emitRewrite(fenceUntrustedContent(storedRedacted.text, injectionMatches), "fetch");
|
|
7077
|
-
}
|
|
7098
|
+
const fencedStored = fenceWithMatches(storedRedacted.text, injectionMatches);
|
|
7078
7099
|
if (storedBody !== body) {
|
|
7079
7100
|
const noticeFor = (id) => `
|
|
7080
7101
|
[token-goat: WebFetch body compressed via extractCleanText; use \`token-goat web-output ${id} --raw\` to recall it]`;
|
|
7081
7102
|
const notice = noticeFor(cacheId);
|
|
7082
7103
|
const noticeBytes = Buffer.byteLength(notice, "utf-8");
|
|
7083
7104
|
const originalBytes = Buffer.byteLength(body, "utf-8");
|
|
7084
|
-
const rewrittenBytes = Buffer.byteLength(
|
|
7105
|
+
const rewrittenBytes = Buffer.byteLength(fencedStored, "utf-8");
|
|
7085
7106
|
if (isRewriteWorthwhile({
|
|
7086
7107
|
originalBytes,
|
|
7087
7108
|
rewrittenBytes,
|
|
7088
7109
|
noticeBytes,
|
|
7089
7110
|
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
7090
7111
|
})) {
|
|
7091
|
-
return emitRewrite(
|
|
7112
|
+
return emitRewrite(fencedStored + notice, "fetch", { kind: "webfetch:compress", originalBytes });
|
|
7092
7113
|
}
|
|
7093
7114
|
}
|
|
7094
|
-
|
|
7095
|
-
return emitRewrite(storedRedacted.text, "fetch");
|
|
7096
|
-
}
|
|
7097
|
-
return passOutput();
|
|
7115
|
+
return emitRewriteIfChanged(body, fencedStored, "fetch");
|
|
7098
7116
|
} catch {
|
|
7099
7117
|
return passOutput();
|
|
7100
7118
|
}
|
|
@@ -7150,7 +7168,7 @@ async function preSkillHandler(event) {
|
|
|
7150
7168
|
const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
|
|
7151
7169
|
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit));
|
|
7152
7170
|
return denyOutput(
|
|
7153
|
-
"Skill `" + skillName + "` was already loaded this session and is cached. Use `token-goat skill-body " + skillName + " --compact` to recall the compact slice
|
|
7171
|
+
"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."
|
|
7154
7172
|
);
|
|
7155
7173
|
}
|
|
7156
7174
|
const sourcePath = await installedSkillPath(skillName);
|
|
@@ -7165,12 +7183,12 @@ async function preSkillHandler(event) {
|
|
|
7165
7183
|
const savedBytes = bodyBytes - compactBytes;
|
|
7166
7184
|
recordStat("skill_compact_inlined", savedBytes, savedTokensFromBytes(savedBytes));
|
|
7167
7185
|
return denyOutput(
|
|
7168
|
-
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes); its compact slice (" + compactBytes + " bytes) is inlined below instead of the full body.
|
|
7186
|
+
"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
|
|
7169
7187
|
);
|
|
7170
7188
|
}
|
|
7171
7189
|
recordStat("skill_oversized_first_load");
|
|
7172
7190
|
return denyOutput(
|
|
7173
|
-
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes) and has a compact slice available. Use `token-goat skill-body " + skillName + " --compact` to load the compact slice
|
|
7191
|
+
"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."
|
|
7174
7192
|
);
|
|
7175
7193
|
}
|
|
7176
7194
|
} catch {
|
|
@@ -8403,6 +8421,40 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
8403
8421
|
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8404
8422
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8405
8423
|
}
|
|
8424
|
+
function pureFileReadPath(cmd) {
|
|
8425
|
+
return extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath ?? null;
|
|
8426
|
+
}
|
|
8427
|
+
async function maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8428
|
+
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8429
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
8430
|
+
const filePath = pureFileReadPath(cmd);
|
|
8431
|
+
if (filePath === null) return null;
|
|
8432
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8433
|
+
if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
|
|
8434
|
+
const fileKey = resolveIndexPath(filePath, cwd ?? process.cwd());
|
|
8435
|
+
const priorIds = getFileServedOutputs(fileKey);
|
|
8436
|
+
let containerId = null;
|
|
8437
|
+
let identical = false;
|
|
8438
|
+
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
8439
|
+
const id = priorIds[i];
|
|
8440
|
+
if (id === void 0) continue;
|
|
8441
|
+
const prior = getBashOutput(id);
|
|
8442
|
+
if (prior === null || !containsLineRun(prior.output, output)) continue;
|
|
8443
|
+
containerId = id;
|
|
8444
|
+
identical = prior.output === output;
|
|
8445
|
+
break;
|
|
8446
|
+
}
|
|
8447
|
+
const sessionKey = shortFingerprint(stripOutputPipeline(cmd));
|
|
8448
|
+
if (containerId === null) {
|
|
8449
|
+
const storedId = await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8450
|
+
recordBashOutput(sessionKey, storedId, originalBytes);
|
|
8451
|
+
recordFileServedOutput(fileKey, storedId);
|
|
8452
|
+
return null;
|
|
8453
|
+
}
|
|
8454
|
+
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 + "`.";
|
|
8455
|
+
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8456
|
+
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) });
|
|
8457
|
+
}
|
|
8406
8458
|
async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8407
8459
|
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8408
8460
|
if (isCompressibleSingleCommand(cmd)) return null;
|
|
@@ -8435,7 +8487,28 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8435
8487
|
return null;
|
|
8436
8488
|
}
|
|
8437
8489
|
await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8438
|
-
return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: compressed.originalBytes });
|
|
8490
|
+
return emitRewrite(body, "bash", { kind: "bash_compress:generic", originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
8491
|
+
}
|
|
8492
|
+
function maybeStripAnsiOnly(output) {
|
|
8493
|
+
if (!output.includes("\x1B")) return null;
|
|
8494
|
+
let cfg;
|
|
8495
|
+
try {
|
|
8496
|
+
cfg = loadConfig().bash_compress;
|
|
8497
|
+
} catch {
|
|
8498
|
+
return null;
|
|
8499
|
+
}
|
|
8500
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("ansi")) return null;
|
|
8501
|
+
const stripped = stripAnsiEscapes(output);
|
|
8502
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8503
|
+
if (!isRewriteWorthwhile({
|
|
8504
|
+
originalBytes,
|
|
8505
|
+
rewrittenBytes: Buffer.byteLength(stripped, "utf-8"),
|
|
8506
|
+
noticeBytes: 0,
|
|
8507
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
8508
|
+
})) {
|
|
8509
|
+
return null;
|
|
8510
|
+
}
|
|
8511
|
+
return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
|
|
8439
8512
|
}
|
|
8440
8513
|
function unwrapCompressCommand(executed) {
|
|
8441
8514
|
const t = executed.trim();
|
|
@@ -9122,9 +9195,11 @@ async function postBashHandler(event) {
|
|
|
9122
9195
|
}
|
|
9123
9196
|
const isMonitoring = getMonitoringRecallHint(cmd) !== null;
|
|
9124
9197
|
if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
|
|
9198
|
+
const identical = await maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9199
|
+
if (identical !== null) return identical;
|
|
9125
9200
|
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9126
9201
|
if (compound !== null) return compound;
|
|
9127
|
-
return passOutput();
|
|
9202
|
+
return maybeStripAnsiOnly(output) ?? passOutput();
|
|
9128
9203
|
}
|
|
9129
9204
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
9130
9205
|
const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : stripOutputPipeline(cmd);
|
|
@@ -9141,6 +9216,8 @@ async function postBashHandler(event) {
|
|
|
9141
9216
|
return contextOutput(delta + " \u2014 full output: bash-output " + id);
|
|
9142
9217
|
}
|
|
9143
9218
|
}
|
|
9219
|
+
const ansiOnly = maybeStripAnsiOnly(output);
|
|
9220
|
+
if (ansiOnly !== null) return ansiOnly;
|
|
9144
9221
|
} catch {
|
|
9145
9222
|
}
|
|
9146
9223
|
return passOutput();
|
|
@@ -9839,23 +9916,14 @@ function postMcpHandler(event) {
|
|
|
9839
9916
|
const toolInput = getToolInput(event);
|
|
9840
9917
|
const resultText = extractToolResultText(event.raw);
|
|
9841
9918
|
if (!resultText) return passOutput();
|
|
9842
|
-
|
|
9843
|
-
try {
|
|
9844
|
-
if (loadConfig().injection.enabled) injectionMatches = scanForInjectionPatterns(resultText);
|
|
9845
|
-
} catch {
|
|
9846
|
-
injectionMatches = [];
|
|
9847
|
-
}
|
|
9848
|
-
if (injectionMatches.length > 0) recordStat("injection_detected", 0, 0, void 0, injectionMatches.join(","));
|
|
9919
|
+
const injectionMatches = scanAndRecord(resultText);
|
|
9849
9920
|
const redactedResult = redactSecrets(resultText);
|
|
9850
|
-
const fenced = () =>
|
|
9851
|
-
|
|
9921
|
+
const fenced = () => emitRewriteIfChanged(
|
|
9922
|
+
resultText,
|
|
9923
|
+
fenceWithMatches(redactedResult.text, injectionMatches, UNTRUSTED_TOOL_TAG),
|
|
9852
9924
|
"mcp"
|
|
9853
9925
|
);
|
|
9854
|
-
const passOrFence = () =>
|
|
9855
|
-
if (injectionMatches.length > 0) return fenced();
|
|
9856
|
-
if (redactedResult.count > 0) return emitRewrite(redactedResult.text, "mcp");
|
|
9857
|
-
return passOutput();
|
|
9858
|
-
};
|
|
9926
|
+
const passOrFence = () => fenced();
|
|
9859
9927
|
if (!event.sessionId) return passOrFence();
|
|
9860
9928
|
if (isMcpErrorResponse(event.raw)) return passOrFence();
|
|
9861
9929
|
const readOnly = isMcpReadOnly(toolName, toolInput);
|
|
@@ -9882,7 +9950,7 @@ function postMcpHandler(event) {
|
|
|
9882
9950
|
});
|
|
9883
9951
|
if (worthwhile) {
|
|
9884
9952
|
return emitRewrite(
|
|
9885
|
-
|
|
9953
|
+
`${notice}${fenceWithMatches(redactedBody, injectionMatches, UNTRUSTED_TOOL_TAG)}`,
|
|
9886
9954
|
"mcp",
|
|
9887
9955
|
{ kind: "mcp:compress", originalBytes }
|
|
9888
9956
|
);
|
|
@@ -9934,23 +10002,9 @@ function postWebSearchHandler(event) {
|
|
|
9934
10002
|
if (getToolName(event) !== "WebSearch") return passOutput();
|
|
9935
10003
|
const resultText = extractToolResultText(event.raw);
|
|
9936
10004
|
if (!resultText) return passOutput();
|
|
9937
|
-
|
|
9938
|
-
try {
|
|
9939
|
-
if (loadConfig().injection.enabled) injectionMatches = scanForInjectionPatterns(resultText);
|
|
9940
|
-
} catch {
|
|
9941
|
-
injectionMatches = [];
|
|
9942
|
-
}
|
|
9943
|
-
if (injectionMatches.length > 0) recordStat("injection_detected", 0, 0, void 0, injectionMatches.join(","));
|
|
10005
|
+
const injectionMatches = scanAndRecord(resultText);
|
|
9944
10006
|
const redacted = redactSecrets(resultText);
|
|
9945
|
-
const passOrFence = () =>
|
|
9946
|
-
if (injectionMatches.length > 0) {
|
|
9947
|
-
return emitRewrite(fenceUntrustedContent(redacted.text, injectionMatches), "websearch");
|
|
9948
|
-
}
|
|
9949
|
-
if (redacted.count > 0) {
|
|
9950
|
-
return emitRewrite(redacted.text, "websearch");
|
|
9951
|
-
}
|
|
9952
|
-
return passOutput();
|
|
9953
|
-
};
|
|
10007
|
+
const passOrFence = () => emitRewriteIfChanged(resultText, fenceWithMatches(redacted.text, injectionMatches), "websearch");
|
|
9954
10008
|
if (!event.sessionId) return passOrFence();
|
|
9955
10009
|
const signature = webSearchSignatureInput(getToolInput(event));
|
|
9956
10010
|
if (signature === null) return passOrFence();
|
|
@@ -10470,9 +10524,6 @@ function buildEvent(eventName, payload) {
|
|
|
10470
10524
|
const tracestate = typeof rawTracestate === "string" && rawTracestate.trim() !== "" ? rawTracestate.trim() : void 0;
|
|
10471
10525
|
return { eventName, toolName, toolInput, sessionId, agentId, traceparent, tracestate, raw: obj };
|
|
10472
10526
|
}
|
|
10473
|
-
function sessionStateKey(event) {
|
|
10474
|
-
return event.agentId !== void 0 ? `${event.sessionId}:agent:${event.agentId}` : event.sessionId;
|
|
10475
|
-
}
|
|
10476
10527
|
function harnessForNormalization() {
|
|
10477
10528
|
const detected = detectHarness();
|
|
10478
10529
|
if (detected === "codex") return "codex";
|
|
@@ -10510,7 +10561,7 @@ async function relayInProcess(eventName, rawPayload) {
|
|
|
10510
10561
|
saveSessionState(stateKey);
|
|
10511
10562
|
} catch {
|
|
10512
10563
|
}
|
|
10513
|
-
return serializeOutput(output, event.eventName, harness);
|
|
10564
|
+
return serializeOutput(output, event.eventName, harness, event);
|
|
10514
10565
|
} catch {
|
|
10515
10566
|
return "{}";
|
|
10516
10567
|
}
|