token-goat 2.9.4 → 2.9.6
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 +7 -4
- package/dist/{token-goat-chunk-CBO45DYZ.mjs → token-goat-chunk-3HJQR4OO.mjs} +9778 -9438
- package/dist/{token-goat-chunk-GEXHIIKM.mjs → token-goat-chunk-3ZAMYHQG.mjs} +5 -5
- package/dist/{token-goat-chunk-US57I7GN.mjs → token-goat-chunk-46VKOCUH.mjs} +241 -33
- package/dist/{token-goat-chunk-FSC44QOH.mjs → token-goat-chunk-7GJBID7S.mjs} +2 -2
- package/dist/{token-goat-chunk-OZAEJQ7L.mjs → token-goat-chunk-C7LLUVUT.mjs} +3 -3
- package/dist/{token-goat-chunk-63XEQMNU.mjs → token-goat-chunk-EIZYCVBM.mjs} +220 -31
- package/dist/{token-goat-chunk-6MU6FHE3.mjs → token-goat-chunk-J7LGMKF3.mjs} +82 -64
- package/dist/{token-goat-chunk-EPX5DWGM.mjs → token-goat-chunk-LXIC7MTW.mjs} +2 -1
- package/dist/{token-goat-chunk-3ZR4NLB3.mjs → token-goat-chunk-T2OE7MYM.mjs} +370 -105
- package/dist/{token-goat-chunk-EQKNR7CN.mjs → token-goat-chunk-TX64YIIV.mjs} +5 -5
- package/dist/{token-goat-chunk-5BHXZEOI.mjs → token-goat-chunk-UJORWTI4.mjs} +2 -2
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/docs/security.md +8 -4
- package/package.json +1 -1
|
@@ -18,10 +18,11 @@ import {
|
|
|
18
18
|
storeWebOutput,
|
|
19
19
|
summarizeResidentContext,
|
|
20
20
|
taskListPruneHint
|
|
21
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-7GJBID7S.mjs";
|
|
22
22
|
import {
|
|
23
23
|
BASH_OUTPUT_SUBDIR,
|
|
24
|
-
|
|
24
|
+
OUTLINE_MAX_REPLACEMENT_RATIO,
|
|
25
|
+
OUTLINE_MIN_HEADINGS,
|
|
25
26
|
WEB_FETCH_KEY_SEP,
|
|
26
27
|
appendDirtyPath,
|
|
27
28
|
applyHintTracking,
|
|
@@ -38,8 +39,13 @@ import {
|
|
|
38
39
|
ensureWorkerAlive,
|
|
39
40
|
estimateTokens,
|
|
40
41
|
extractCompactFromMarker,
|
|
42
|
+
extractMarkdownHeadings,
|
|
41
43
|
fenceUntrusted,
|
|
42
44
|
fenceWithMatches,
|
|
45
|
+
foldDelivery,
|
|
46
|
+
foldDetail,
|
|
47
|
+
foldingEnabled,
|
|
48
|
+
formatHeadingTreeParts,
|
|
43
49
|
formatProjectMap,
|
|
44
50
|
formatShrinkSummary,
|
|
45
51
|
getBashOutput,
|
|
@@ -61,10 +67,12 @@ import {
|
|
|
61
67
|
imageQualifiesForShrink,
|
|
62
68
|
incrementSkillHit,
|
|
63
69
|
indexRecallEntry,
|
|
70
|
+
indexServedBody,
|
|
64
71
|
installedSkillPath,
|
|
65
72
|
isBashEntryStale,
|
|
66
73
|
isBuildCommand,
|
|
67
74
|
isScopedGitStatusOrDiffStatCommand,
|
|
75
|
+
isStructuralRewriteAccepted,
|
|
68
76
|
isTestRunnerCommand,
|
|
69
77
|
lastTabContextMatches,
|
|
70
78
|
listSiblingSessionStates,
|
|
@@ -78,6 +86,9 @@ import {
|
|
|
78
86
|
matchesDenyPattern,
|
|
79
87
|
meetsSavingsFloor,
|
|
80
88
|
metadataEndpointRefusal,
|
|
89
|
+
planMarkdownOutline,
|
|
90
|
+
planServedElisions,
|
|
91
|
+
planSourceSkeleton,
|
|
81
92
|
probeImageMeta,
|
|
82
93
|
recordBashOutput,
|
|
83
94
|
recordBashRerun,
|
|
@@ -98,6 +109,7 @@ import {
|
|
|
98
109
|
removeOutstandingAgentSpawn,
|
|
99
110
|
saveSessionState,
|
|
100
111
|
scanAndRecord,
|
|
112
|
+
servedRunNotice,
|
|
101
113
|
sessionOutputBodyBytes,
|
|
102
114
|
sessionSidecarPath,
|
|
103
115
|
setLastTabContext,
|
|
@@ -112,17 +124,19 @@ import {
|
|
|
112
124
|
wasCliReadThisSession,
|
|
113
125
|
wasFileReadThisSession,
|
|
114
126
|
wasHintShown
|
|
115
|
-
} from "./token-goat-chunk-
|
|
127
|
+
} from "./token-goat-chunk-3HJQR4OO.mjs";
|
|
116
128
|
import {
|
|
129
|
+
bashOutputCapBytes,
|
|
117
130
|
canRunWrappedShell,
|
|
118
131
|
deliveredOutputBytes
|
|
119
|
-
} from "./token-goat-chunk-
|
|
132
|
+
} from "./token-goat-chunk-LXIC7MTW.mjs";
|
|
120
133
|
import {
|
|
121
134
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
122
135
|
ENV_KEYS,
|
|
123
136
|
IDENTICAL_READ_MIN_BODY_BYTES,
|
|
124
137
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
125
138
|
PER_FILE_COUNTERFACTUAL_CEILING,
|
|
139
|
+
UNTRUSTED_TOOL_TAG,
|
|
126
140
|
VERSION,
|
|
127
141
|
compressOutput,
|
|
128
142
|
containsLineRun,
|
|
@@ -134,6 +148,7 @@ import {
|
|
|
134
148
|
detectHarness,
|
|
135
149
|
detectLanguage,
|
|
136
150
|
displaySafePath,
|
|
151
|
+
displaySafeText,
|
|
137
152
|
emitRewrite,
|
|
138
153
|
emitRewriteIfChanged,
|
|
139
154
|
envBool,
|
|
@@ -141,7 +156,9 @@ import {
|
|
|
141
156
|
extractErrorMessage,
|
|
142
157
|
extractToolResponseField,
|
|
143
158
|
extractToolResultText,
|
|
159
|
+
fenceUntrustedFileContent,
|
|
144
160
|
filterByName,
|
|
161
|
+
findProject,
|
|
145
162
|
foldPath,
|
|
146
163
|
getCwd,
|
|
147
164
|
getDb,
|
|
@@ -172,8 +189,9 @@ import {
|
|
|
172
189
|
shortFingerprint,
|
|
173
190
|
splitOwnTrailingNotices,
|
|
174
191
|
stripAnsiEscapes,
|
|
192
|
+
toDisplayPath,
|
|
175
193
|
toKB
|
|
176
|
-
} from "./token-goat-chunk-
|
|
194
|
+
} from "./token-goat-chunk-T2OE7MYM.mjs";
|
|
177
195
|
import {
|
|
178
196
|
init_define_import_meta_env
|
|
179
197
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -498,10 +516,10 @@ function renderReadRow(entry) {
|
|
|
498
516
|
const kb = Math.max(1, toKB(entry.sizeBytes));
|
|
499
517
|
const plural = entry.readCount === 1 ? "read" : "reads";
|
|
500
518
|
const edited = entry.wasEdited ? ", edited" : "";
|
|
501
|
-
return `- ${entry.path} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
|
|
519
|
+
return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
|
|
502
520
|
}
|
|
503
521
|
function renderSymbolReadRow(entry) {
|
|
504
|
-
return `- ${entry.path} (symbols: ${(entry.symbols_read ?? []).join(", ")})`;
|
|
522
|
+
return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
|
|
505
523
|
}
|
|
506
524
|
function mergeManifestFiles(parent, siblingFiles) {
|
|
507
525
|
const byPath = /* @__PURE__ */ new Map();
|
|
@@ -540,7 +558,7 @@ function buildManifest(sessionId, cwd) {
|
|
|
540
558
|
appendCappedSection(
|
|
541
559
|
lines,
|
|
542
560
|
"### Edited files",
|
|
543
|
-
editedFiles.map((entry) => `- ${entry.path}`),
|
|
561
|
+
editedFiles.map((entry) => `- ${displaySafePath(entry.path)}`),
|
|
544
562
|
MAX_ROWS
|
|
545
563
|
);
|
|
546
564
|
appendCappedSection(lines, "### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles.map(renderSymbolReadRow), MAX_ROWS);
|
|
@@ -607,7 +625,7 @@ function buildSafeToDiscardSection(files) {
|
|
|
607
625
|
const entry = getBashOutput(id);
|
|
608
626
|
if (entry === null) continue;
|
|
609
627
|
const flatCommand = entry.command.replace(/[\t\r\n]+/g, " ");
|
|
610
|
-
rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + "`");
|
|
628
|
+
rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + " --full`");
|
|
611
629
|
}
|
|
612
630
|
const supersededReadRows = [];
|
|
613
631
|
for (const f of files) {
|
|
@@ -622,7 +640,7 @@ function buildSafeToDiscardSection(files) {
|
|
|
622
640
|
const entry = getBashOutput(id);
|
|
623
641
|
if (entry === null) continue;
|
|
624
642
|
const flatCommand = entry.command.replace(/[\t\r\n]+/g, " ");
|
|
625
|
-
cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + "`");
|
|
643
|
+
cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + " --full`");
|
|
626
644
|
}
|
|
627
645
|
const total = rerunRows.length + supersededReadRows.length + cachedOutputRows.length;
|
|
628
646
|
if (total === 0) return [];
|
|
@@ -703,7 +721,7 @@ init_define_import_meta_env();
|
|
|
703
721
|
var TRACKED_SKILL = "token-goat";
|
|
704
722
|
var MAX_COMMANDS_SHOWN = 8;
|
|
705
723
|
async function currentCommandNames() {
|
|
706
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
724
|
+
const { buildProgram } = await import("./token-goat-chunk-3ZAMYHQG.mjs");
|
|
707
725
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
708
726
|
}
|
|
709
727
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -7205,7 +7223,7 @@ function preFetchHandler(event) {
|
|
|
7205
7223
|
if (cachedBytes >= loadConfig().hints.web_dedup_min_bytes) {
|
|
7206
7224
|
recordStat("webfetch:recall", cachedBytes, savedTokensFromBytes(cachedBytes));
|
|
7207
7225
|
return denyOutput(
|
|
7208
|
-
"Already fetched this URL with this prompt; the response is cached. Use `token-goat web-output " + cacheId + "` to recall it (append `--grep PATTERN` to filter or `--section Heading` for a markdown section) instead of re-fetching."
|
|
7226
|
+
"Already fetched this URL with this prompt; the response is cached. Use `token-goat web-output " + cacheId + " --full` to recall it (append `--grep PATTERN` to filter or `--section Heading` for a markdown section) instead of re-fetching."
|
|
7209
7227
|
);
|
|
7210
7228
|
}
|
|
7211
7229
|
}
|
|
@@ -7340,6 +7358,21 @@ async function preSkillHandler(event) {
|
|
|
7340
7358
|
return denyOutput(
|
|
7341
7359
|
"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."
|
|
7342
7360
|
);
|
|
7361
|
+
} else if (bodyBytes > OVERSIZED_FIRST_LOAD_THRESHOLD_BYTES) {
|
|
7362
|
+
const headings = extractMarkdownHeadings(body);
|
|
7363
|
+
if (headings.length >= OUTLINE_MIN_HEADINGS) {
|
|
7364
|
+
const { sectionsList } = formatHeadingTreeParts(headings, skillName);
|
|
7365
|
+
const treeBytes = Buffer.byteLength(sectionsList, "utf-8");
|
|
7366
|
+
if (treeBytes <= bodyBytes * OUTLINE_MAX_REPLACEMENT_RATIO) {
|
|
7367
|
+
const savedBytes = bodyBytes - treeBytes;
|
|
7368
|
+
recordStat("skill_heading_tree_inlined", savedBytes, savedTokensFromBytes(savedBytes));
|
|
7369
|
+
const totalHeadings = extractMarkdownHeadings(body, Number.MAX_SAFE_INTEGER).length;
|
|
7370
|
+
const headingCountPhrase = totalHeadings > headings.length ? "its heading tree shows " + headings.length + " of " + totalHeadings + " headings below instead of the full body; the remaining sections are reachable only through `token-goat skill-body " + skillName + "`." : "its heading tree (" + headings.length + " headings) is inlined below instead of the full body.";
|
|
7371
|
+
return denyOutput(
|
|
7372
|
+
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes) with no compact slice; " + headingCountPhrase + " Use `token-goat skill-section " + skillName + " '<heading>'` to load a specific section, or `token-goat skill-body " + skillName + "` for the full body.\n\n" + fenceUntrustedFileContent(sectionsList)
|
|
7373
|
+
);
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7343
7376
|
}
|
|
7344
7377
|
} catch {
|
|
7345
7378
|
}
|
|
@@ -7376,8 +7409,9 @@ registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
|
|
|
7376
7409
|
// src/hooks_bash.ts
|
|
7377
7410
|
init_define_import_meta_env();
|
|
7378
7411
|
import { statSync as statSync3, existsSync, openSync, readSync, closeSync } from "node:fs";
|
|
7412
|
+
var CD_PREFIX_RE = /^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)[ \t]*(?:&&|;|\r?\n)\s*)+/;
|
|
7379
7413
|
function stripCdPrefix(cmd) {
|
|
7380
|
-
const stripped = cmd.replace(
|
|
7414
|
+
const stripped = cmd.replace(CD_PREFIX_RE, "");
|
|
7381
7415
|
return stripped.trim() || cmd;
|
|
7382
7416
|
}
|
|
7383
7417
|
function stripTrailingStderrRedirect(cmd) {
|
|
@@ -7393,10 +7427,10 @@ function isDirectTestRunnerCommand(cmd) {
|
|
|
7393
7427
|
return /^(?:pytest|(?:npx\s+)?(?:jest|vitest)|go\s+test|cargo\s+test)\b/i.test(cmd);
|
|
7394
7428
|
}
|
|
7395
7429
|
function extractCdPrefixDirs(rawCmd) {
|
|
7396
|
-
const prefixMatch = rawCmd.match(
|
|
7430
|
+
const prefixMatch = rawCmd.match(CD_PREFIX_RE);
|
|
7397
7431
|
if (prefixMatch === null) return [];
|
|
7398
7432
|
const dirs = [];
|
|
7399
|
-
const segmentPattern = /cd\s+(?:"([^"]*)"|'([^']*)'|(\S+))\
|
|
7433
|
+
const segmentPattern = /cd\s+(?:"([^"]*)"|'([^']*)'|(\S+))[ \t]*(?:&&|;|\r?\n)/g;
|
|
7400
7434
|
let match;
|
|
7401
7435
|
while ((match = segmentPattern.exec(prefixMatch[0])) !== null) {
|
|
7402
7436
|
const dir = match[1] ?? match[2] ?? match[3];
|
|
@@ -7405,13 +7439,13 @@ function extractCdPrefixDirs(rawCmd) {
|
|
|
7405
7439
|
return dirs;
|
|
7406
7440
|
}
|
|
7407
7441
|
function resolveCdHintPath(rawCmd, filePath, cwd) {
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
return
|
|
7442
|
+
if (extractCdPrefixDirs(rawCmd).length === 0) return filePath;
|
|
7443
|
+
return resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd));
|
|
7444
|
+
}
|
|
7445
|
+
function cdPrefixCwd(rawCmd, cwd) {
|
|
7446
|
+
let dir = cwd;
|
|
7447
|
+
for (const target of extractCdPrefixDirs(rawCmd)) dir = resolveIndexPath(target, dir);
|
|
7448
|
+
return dir;
|
|
7415
7449
|
}
|
|
7416
7450
|
function surgicalHintFor(hintPath, isEnv, isConfig, isDoc) {
|
|
7417
7451
|
return isEnv ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to read one function or class.';
|
|
@@ -8573,16 +8607,110 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
8573
8607
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8574
8608
|
}
|
|
8575
8609
|
function pureFileReadPath(cmd) {
|
|
8576
|
-
|
|
8610
|
+
const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
|
|
8611
|
+
if (single !== void 0) return single;
|
|
8612
|
+
return singleFileCompoundReadPath(cmd);
|
|
8613
|
+
}
|
|
8614
|
+
function singleFileCompoundReadPath(cmd) {
|
|
8615
|
+
const reads = extractLineRangeReadsCompound(cmd);
|
|
8616
|
+
if (reads === null || reads.length !== 1) return null;
|
|
8617
|
+
return reads[0]?.filePath ?? null;
|
|
8618
|
+
}
|
|
8619
|
+
function deliveredLineNumbers(cmd, lineCount) {
|
|
8620
|
+
const ranged = extractLineRangeRead(cmd);
|
|
8621
|
+
if (ranged !== null) {
|
|
8622
|
+
const nums = [];
|
|
8623
|
+
for (const [lo, hi] of ranged.ranges) for (let n = lo; n <= hi; n++) nums.push(n);
|
|
8624
|
+
if (nums.length === 0) return null;
|
|
8625
|
+
while (nums.length < lineCount) nums.push((nums[nums.length - 1] ?? 0) + 1);
|
|
8626
|
+
return nums;
|
|
8627
|
+
}
|
|
8628
|
+
if (extractCatFile(cmd) !== null || extractHeadFile(cmd) !== null) return Array.from({ length: lineCount }, (_, i) => i + 1);
|
|
8629
|
+
if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
|
|
8630
|
+
return null;
|
|
8577
8631
|
}
|
|
8578
|
-
|
|
8632
|
+
function elideServedShellLines(cmd, output, priorIds) {
|
|
8633
|
+
if (priorIds.length === 0) return null;
|
|
8634
|
+
const lines = output.split("\n");
|
|
8635
|
+
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
8636
|
+
if (numbers === null) return null;
|
|
8637
|
+
const rows = lines.map((text, i) => ({ no: numbers[i] ?? null, text, raw: text }));
|
|
8638
|
+
const bodies = [];
|
|
8639
|
+
for (let i = priorIds.length - 1; i >= 0; i--) {
|
|
8640
|
+
const id = priorIds[i];
|
|
8641
|
+
if (id === void 0) continue;
|
|
8642
|
+
const prior = getBashOutput(id);
|
|
8643
|
+
if (prior !== null) bodies.push(indexServedBody(id, prior.output));
|
|
8644
|
+
}
|
|
8645
|
+
const cuts = planServedElisions(rows, bodies);
|
|
8646
|
+
if (cuts.length === 0) return null;
|
|
8647
|
+
const out = [];
|
|
8648
|
+
let at = 0;
|
|
8649
|
+
for (const cut of cuts) {
|
|
8650
|
+
for (let i = at; i < cut.start; i++) out.push(rows[i]?.raw ?? "");
|
|
8651
|
+
const first2 = rows[cut.start];
|
|
8652
|
+
const last = rows[cut.start + cut.len - 1];
|
|
8653
|
+
if (first2 === void 0 || last === void 0) return null;
|
|
8654
|
+
out.push(servedRunNotice(first2.no, last.no, cut.id, cut.len));
|
|
8655
|
+
at = cut.start + cut.len;
|
|
8656
|
+
}
|
|
8657
|
+
for (let i = at; i < rows.length; i++) out.push(rows[i]?.raw ?? "");
|
|
8658
|
+
return out.join("\n");
|
|
8659
|
+
}
|
|
8660
|
+
function foldShellReadBodies(cmd, output, fileKey, cwd) {
|
|
8661
|
+
if (!foldingEnabled()) return null;
|
|
8662
|
+
if (redactSecrets(output).count > 0) return null;
|
|
8663
|
+
const lines = output.split("\n");
|
|
8664
|
+
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
8665
|
+
if (numbers === null) return null;
|
|
8666
|
+
const rows = [];
|
|
8667
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8668
|
+
const no = numbers[i];
|
|
8669
|
+
if (no === void 0 || no === null) return null;
|
|
8670
|
+
rows.push({ no, text: lines[i] ?? "", raw: lines[i] ?? "" });
|
|
8671
|
+
}
|
|
8672
|
+
const headRead = extractHeadFile(cmd);
|
|
8673
|
+
const windowed = extractLineRangeRead(cmd) !== null || headRead !== null && lines.length >= headRead.n;
|
|
8674
|
+
const folded = foldDelivery(rows, fileKey, displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey)), windowed);
|
|
8675
|
+
if (folded === null) return null;
|
|
8676
|
+
return { text: folded.numbered.join("\n"), folds: folded.folds };
|
|
8677
|
+
}
|
|
8678
|
+
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;
|
|
8679
|
+
function isWholeFileDump(cmd, extractedPath) {
|
|
8680
|
+
const m = WHOLE_FILE_DUMP_RE.exec(cmd);
|
|
8681
|
+
if (m === null) return false;
|
|
8682
|
+
const shaped = m[1] ?? m[2] ?? m[3];
|
|
8683
|
+
return shaped !== void 0 && shaped === extractedPath;
|
|
8684
|
+
}
|
|
8685
|
+
function foldShellReadStructure(cmd, filePath, output, fileKey, cwd) {
|
|
8686
|
+
if (!isWholeFileDump(cmd, filePath)) return null;
|
|
8687
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8688
|
+
let onDisk;
|
|
8689
|
+
try {
|
|
8690
|
+
onDisk = statSync3(fileKey).size;
|
|
8691
|
+
} catch {
|
|
8692
|
+
return null;
|
|
8693
|
+
}
|
|
8694
|
+
if (onDisk !== originalBytes) return null;
|
|
8695
|
+
if (redactSecrets(output).count > 0) return null;
|
|
8696
|
+
const lines = output.split("\n");
|
|
8697
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
8698
|
+
const rows = lines.map((text2, i) => ({ no: i + 1, text: text2, raw: text2 }));
|
|
8699
|
+
const shown = displaySafePath(toDisplayPath(findProject(cwd ?? process.cwd())?.root, fileKey));
|
|
8700
|
+
const fold = planMarkdownOutline(rows, fileKey, shown, originalBytes) ?? planSourceSkeleton(rows, fileKey, shown, originalBytes);
|
|
8701
|
+
if (fold === null) return null;
|
|
8702
|
+
const text = fold.numbered.join("\n");
|
|
8703
|
+
if (!isStructuralRewriteAccepted(originalBytes, Buffer.byteLength(text, "utf-8"), fold.ratioCap)) return null;
|
|
8704
|
+
return { text, kind: fold.kind === "read:markdown_outline" ? "bash_compress:markdown-outline" : "bash_compress:source-skeleton", detail: fold.detail };
|
|
8705
|
+
}
|
|
8706
|
+
async function maybeCollapseIdenticalRead(cmd, rawCmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8579
8707
|
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8580
8708
|
if (exitCode !== null && exitCode !== 0) return null;
|
|
8581
8709
|
const filePath = pureFileReadPath(cmd);
|
|
8582
8710
|
if (filePath === null) return null;
|
|
8583
8711
|
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8584
8712
|
if (originalBytes < Math.max(cacheMinBytes, IDENTICAL_READ_MIN_BODY_BYTES)) return null;
|
|
8585
|
-
const fileKey = resolveIndexPath(filePath, cwd ?? process.cwd());
|
|
8713
|
+
const fileKey = resolveIndexPath(filePath, cdPrefixCwd(rawCmd, cwd ?? process.cwd()));
|
|
8586
8714
|
const priorIds = getFileServedOutputs(fileKey);
|
|
8587
8715
|
let containerId = null;
|
|
8588
8716
|
let identical = false;
|
|
@@ -8597,12 +8725,28 @@ async function maybeCollapseIdenticalRead(cmd, output, exitCode, cwd, cacheMinBy
|
|
|
8597
8725
|
}
|
|
8598
8726
|
const sessionKey = shortFingerprint(stripOutputPipeline(cmd));
|
|
8599
8727
|
if (containerId === null) {
|
|
8600
|
-
const
|
|
8728
|
+
const elided = elideServedShellLines(cmd, output, priorIds);
|
|
8729
|
+
const priced = (text) => isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(text, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() });
|
|
8730
|
+
let rewrite = null;
|
|
8731
|
+
if (elided !== null) {
|
|
8732
|
+
if (priced(elided)) rewrite = { text: elided, reason: "already-served file lines withheld", kind: "bash_compress:served-elide" };
|
|
8733
|
+
} else {
|
|
8734
|
+
const structural = foldShellReadStructure(cmd, filePath, output, fileKey, cwd);
|
|
8735
|
+
if (structural !== null && priced(structural.text)) {
|
|
8736
|
+
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 };
|
|
8737
|
+
}
|
|
8738
|
+
if (rewrite === null) {
|
|
8739
|
+
const folded = foldShellReadBodies(cmd, output, fileKey, cwd);
|
|
8740
|
+
if (folded !== null && priced(folded.text)) rewrite = { text: folded.text, reason: "code bodies folded", kind: "bash_compress:body-fold", detail: foldDetail(fileKey, folded.folds) };
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
const storedId = await storeBashOutput(cmd, rewrite?.text ?? output, exitCode ?? 0, cwd);
|
|
8601
8744
|
recordBashOutput(sessionKey, storedId, originalBytes);
|
|
8602
8745
|
recordFileServedOutput(fileKey, storedId);
|
|
8603
|
-
return null;
|
|
8746
|
+
if (rewrite === null) return null;
|
|
8747
|
+
return emitRewrite(rewrite.text, rewrite.reason, { kind: rewrite.kind, originalBytes: deliveredOutputBytes(originalBytes), ...rewrite.detail === void 0 ? {} : { detail: rewrite.detail } });
|
|
8604
8748
|
}
|
|
8605
|
-
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 + "`.";
|
|
8749
|
+
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`.";
|
|
8606
8750
|
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8607
8751
|
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) });
|
|
8608
8752
|
}
|
|
@@ -8663,6 +8807,48 @@ function maybeStripAnsiOnly(output) {
|
|
|
8663
8807
|
}
|
|
8664
8808
|
return emitRewrite(stripped, "ansi escapes stripped", { kind: "bash_compress:ansi", originalBytes: deliveredOutputBytes(originalBytes) }, "counted-elsewhere");
|
|
8665
8809
|
}
|
|
8810
|
+
function maybeFoldCurlHtml(cmd, output, id) {
|
|
8811
|
+
if (!isCurlGetCommand(cmd) || !looksLikeHtml(output)) return null;
|
|
8812
|
+
let cfg;
|
|
8813
|
+
try {
|
|
8814
|
+
cfg = loadConfig().bash_compress;
|
|
8815
|
+
} catch {
|
|
8816
|
+
return null;
|
|
8817
|
+
}
|
|
8818
|
+
if (!cfg.enabled || cfg.disabled_filters.includes("curl-html")) return null;
|
|
8819
|
+
let cleaned;
|
|
8820
|
+
try {
|
|
8821
|
+
cleaned = extractCleanText(output);
|
|
8822
|
+
} catch {
|
|
8823
|
+
return null;
|
|
8824
|
+
}
|
|
8825
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8826
|
+
const notice = `[token-goat: curl HTML body cleaned via extractCleanText; use \`token-goat bash-output ${id} --full\` to recall the raw markup]
|
|
8827
|
+
`;
|
|
8828
|
+
const noticeBytes = Buffer.byteLength(notice, "utf-8");
|
|
8829
|
+
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]";
|
|
8830
|
+
const cap = bashOutputCapBytes();
|
|
8831
|
+
const fenceOverhead = Buffer.byteLength(fenceUntrusted(cleaned, UNTRUSTED_TOOL_TAG), "utf-8") - Buffer.byteLength(cleaned, "utf-8");
|
|
8832
|
+
const room = cap === null ? null : cap - noticeBytes - fenceOverhead - Buffer.byteLength(clipNote, "utf-8");
|
|
8833
|
+
let body = cleaned;
|
|
8834
|
+
let clipped = "";
|
|
8835
|
+
if (room !== null && room > 0 && Buffer.byteLength(cleaned, "utf-8") > room) {
|
|
8836
|
+
const sliced = Buffer.from(cleaned, "utf-8").subarray(0, room).toString("utf-8");
|
|
8837
|
+
const lastNewline = sliced.lastIndexOf("\n");
|
|
8838
|
+
body = lastNewline > 0 ? sliced.slice(0, lastNewline) : sliced;
|
|
8839
|
+
clipped = clipNote;
|
|
8840
|
+
}
|
|
8841
|
+
const fenced = fenceUntrusted(body, UNTRUSTED_TOOL_TAG) + clipped;
|
|
8842
|
+
if (!isRewriteWorthwhile({
|
|
8843
|
+
originalBytes,
|
|
8844
|
+
rewrittenBytes: Buffer.byteLength(fenced, "utf-8"),
|
|
8845
|
+
noticeBytes,
|
|
8846
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
8847
|
+
})) {
|
|
8848
|
+
return null;
|
|
8849
|
+
}
|
|
8850
|
+
return emitRewrite(notice + fenced, "curl HTML body cleaned", { kind: "bash_compress:curl-html", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
8851
|
+
}
|
|
8666
8852
|
function unwrapCompressCommand(executed) {
|
|
8667
8853
|
const t = executed.trim();
|
|
8668
8854
|
if (!/^(?:token-goat|tg)\s+compress\b/.test(t)) return null;
|
|
@@ -9347,8 +9533,9 @@ async function postBashHandler(event) {
|
|
|
9347
9533
|
recordBashOutput(gitScopedCacheHash, gitScopedCacheId, Buffer.byteLength(output, "utf-8"));
|
|
9348
9534
|
}
|
|
9349
9535
|
const isMonitoring = getMonitoringRecallHint(cmd) !== null;
|
|
9350
|
-
|
|
9351
|
-
|
|
9536
|
+
const isFileRead = pureFileReadPath(cmd) !== null;
|
|
9537
|
+
if (isFileRead || !isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) {
|
|
9538
|
+
const identical = await maybeCollapseIdenticalRead(cmd, rawCmd, output, exitCode, cwd, cacheMinBytes);
|
|
9352
9539
|
if (identical !== null) return identical;
|
|
9353
9540
|
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9354
9541
|
if (compound !== null) return compound;
|
|
@@ -9366,9 +9553,11 @@ async function postBashHandler(event) {
|
|
|
9366
9553
|
const delta = summarizeOutputDelta(priorEntry.output, output);
|
|
9367
9554
|
if (delta !== null) {
|
|
9368
9555
|
recordStat("session_hint", 0, 0);
|
|
9369
|
-
return contextOutput(delta + " \u2014 full output: bash-output " + id);
|
|
9556
|
+
return contextOutput(delta + " \u2014 full output: bash-output " + id + " --full");
|
|
9370
9557
|
}
|
|
9371
9558
|
}
|
|
9559
|
+
const curlHtmlFold = maybeFoldCurlHtml(cmd, output, id);
|
|
9560
|
+
if (curlHtmlFold !== null) return curlHtmlFold;
|
|
9372
9561
|
const ansiOnly = maybeStripAnsiOnly(output);
|
|
9373
9562
|
if (ansiOnly !== null) return ansiOnly;
|
|
9374
9563
|
} catch {
|