token-goat 2.9.6 → 2.9.8
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 +5 -2
- package/dist/{token-goat-chunk-J7LGMKF3.mjs → token-goat-chunk-2SBHUPBF.mjs} +44 -9
- package/dist/{token-goat-chunk-3ZAMYHQG.mjs → token-goat-chunk-3YANZKER.mjs} +5 -5
- package/dist/{token-goat-chunk-46VKOCUH.mjs → token-goat-chunk-6RAUVAKW.mjs} +2 -2
- package/dist/{token-goat-chunk-C7LLUVUT.mjs → token-goat-chunk-EZZ7IJYM.mjs} +3 -3
- package/dist/{token-goat-chunk-T2OE7MYM.mjs → token-goat-chunk-JZYJH76S.mjs} +2712 -2697
- package/dist/{token-goat-chunk-TX64YIIV.mjs → token-goat-chunk-Q4VCAIBR.mjs} +5 -5
- package/dist/{token-goat-chunk-LXIC7MTW.mjs → token-goat-chunk-REHUP2OE.mjs} +1 -1
- package/dist/{token-goat-chunk-3HJQR4OO.mjs → token-goat-chunk-TXZY7C24.mjs} +105 -19
- package/dist/{token-goat-chunk-7GJBID7S.mjs → token-goat-chunk-VEEHPNBV.mjs} +5 -3
- package/dist/{token-goat-chunk-UJORWTI4.mjs → token-goat-chunk-WT5OLTT6.mjs} +2 -2
- package/dist/{token-goat-chunk-EIZYCVBM.mjs → token-goat-chunk-XYLPMQ6I.mjs} +332 -82
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/docs/install.md +62 -0
- package/docs/security.md +1 -1
- package/package.json +1 -1
|
@@ -18,9 +18,10 @@ import {
|
|
|
18
18
|
storeWebOutput,
|
|
19
19
|
summarizeResidentContext,
|
|
20
20
|
taskListPruneHint
|
|
21
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-VEEHPNBV.mjs";
|
|
22
22
|
import {
|
|
23
23
|
BASH_OUTPUT_SUBDIR,
|
|
24
|
+
GENERIC_SERVED_OUTPUT_KEY,
|
|
24
25
|
OUTLINE_MAX_REPLACEMENT_RATIO,
|
|
25
26
|
OUTLINE_MIN_HEADINGS,
|
|
26
27
|
WEB_FETCH_KEY_SEP,
|
|
@@ -124,12 +125,12 @@ import {
|
|
|
124
125
|
wasCliReadThisSession,
|
|
125
126
|
wasFileReadThisSession,
|
|
126
127
|
wasHintShown
|
|
127
|
-
} from "./token-goat-chunk-
|
|
128
|
+
} from "./token-goat-chunk-TXZY7C24.mjs";
|
|
128
129
|
import {
|
|
129
130
|
bashOutputCapBytes,
|
|
130
131
|
canRunWrappedShell,
|
|
131
132
|
deliveredOutputBytes
|
|
132
|
-
} from "./token-goat-chunk-
|
|
133
|
+
} from "./token-goat-chunk-REHUP2OE.mjs";
|
|
133
134
|
import {
|
|
134
135
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
135
136
|
ENV_KEYS,
|
|
@@ -168,11 +169,13 @@ import {
|
|
|
168
169
|
getToolName,
|
|
169
170
|
globalDbPath,
|
|
170
171
|
hasBareBackgroundOrNewline,
|
|
172
|
+
hasUnquotedOperator,
|
|
171
173
|
isMcpErrorResponse,
|
|
172
174
|
isRewriteWorthwhile,
|
|
173
175
|
isUnderSystemTemp,
|
|
174
176
|
loadConfig,
|
|
175
177
|
makeDedupHintHandlers,
|
|
178
|
+
neutralizeSpokenMarkers,
|
|
176
179
|
normalizePath,
|
|
177
180
|
passOutput,
|
|
178
181
|
recordStat,
|
|
@@ -191,7 +194,7 @@ import {
|
|
|
191
194
|
stripAnsiEscapes,
|
|
192
195
|
toDisplayPath,
|
|
193
196
|
toKB
|
|
194
|
-
} from "./token-goat-chunk-
|
|
197
|
+
} from "./token-goat-chunk-JZYJH76S.mjs";
|
|
195
198
|
import {
|
|
196
199
|
init_define_import_meta_env
|
|
197
200
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -352,17 +355,66 @@ function foldGrepContentHandler(event) {
|
|
|
352
355
|
return passOutput();
|
|
353
356
|
}
|
|
354
357
|
}
|
|
358
|
+
var DOC_EXT_RE = /\.(?:md|mdx|rst|txt)$/i;
|
|
359
|
+
var SOURCE_EXT_RE = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less)$/i;
|
|
360
|
+
var STRUCTURAL_DOC_PATTERN_RE = /^(?:\^)?#+\s*/;
|
|
361
|
+
var STRUCTURAL_SOURCE_PATTERN_RE = /^(?:\^|\s)*(?:def|class|function|async\s+def|async\s+function|export\s+(?:default\s+)?(?:class|function|interface|type|const|enum)|func|fn|struct|interface|impl|type)\b/i;
|
|
362
|
+
function extractGrepStructuralSearch(toolInput) {
|
|
363
|
+
const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"].trim() : "";
|
|
364
|
+
if (!pattern) return null;
|
|
365
|
+
let rawPath = null;
|
|
366
|
+
if (typeof toolInput["path"] === "string" && toolInput["path"].trim() !== "") {
|
|
367
|
+
rawPath = toolInput["path"].trim();
|
|
368
|
+
} else if (typeof toolInput["paths"] === "string" && toolInput["paths"].trim() !== "") {
|
|
369
|
+
rawPath = toolInput["paths"].trim();
|
|
370
|
+
} else if (Array.isArray(toolInput["paths"]) && toolInput["paths"].length === 1 && typeof toolInput["paths"][0] === "string") {
|
|
371
|
+
rawPath = toolInput["paths"][0].trim();
|
|
372
|
+
}
|
|
373
|
+
if (!rawPath) return null;
|
|
374
|
+
if (/[*?[{]/.test(rawPath)) return null;
|
|
375
|
+
if (DOC_EXT_RE.test(rawPath) && STRUCTURAL_DOC_PATTERN_RE.test(pattern)) {
|
|
376
|
+
return { filePath: rawPath, isDoc: true, isSource: false };
|
|
377
|
+
}
|
|
378
|
+
if (SOURCE_EXT_RE.test(rawPath) && STRUCTURAL_SOURCE_PATTERN_RE.test(pattern)) {
|
|
379
|
+
return { filePath: rawPath, isDoc: false, isSource: true };
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
function preGrepHandler(event) {
|
|
384
|
+
try {
|
|
385
|
+
if (getToolName(event) !== "Grep") return passOutput();
|
|
386
|
+
const toolInput = getToolInput(event);
|
|
387
|
+
const structSearch = extractGrepStructuralSearch(toolInput);
|
|
388
|
+
if (structSearch !== null) {
|
|
389
|
+
recordStat("session_hint", 0, 0);
|
|
390
|
+
const { isDoc } = structSearch;
|
|
391
|
+
const filePath = displaySafePath(structSearch.filePath);
|
|
392
|
+
const hint = isDoc ? 'Scanning a document for headings loads large match output. Use `token-goat section "' + filePath + '::SectionHeading"` to read one section or `token-goat outline "' + filePath + '"` to see the document outline.' : 'Scanning a source file for symbols loads large match output. Use `token-goat skeleton "' + filePath + '"` to see the file structure or `token-goat read "' + filePath + '::SymbolName"` to inspect a specific symbol.';
|
|
393
|
+
return contextOutput(hint);
|
|
394
|
+
}
|
|
395
|
+
return preGrepDedupHandler(event);
|
|
396
|
+
} catch {
|
|
397
|
+
return passOutput();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
355
400
|
function postGrepHandler(event) {
|
|
356
401
|
const dedupResult = dedupPostHandler(event);
|
|
357
402
|
const foldResult = foldGrepContentHandler(event);
|
|
358
403
|
if (foldResult.hookType === "rewriteOutput") return foldResult;
|
|
359
404
|
return dedupResult;
|
|
360
405
|
}
|
|
361
|
-
registerHook("pre_tool_use",
|
|
406
|
+
registerHook("pre_tool_use", preGrepHandler, { toolName: "Grep" });
|
|
362
407
|
registerHook("post_tool_use", postGrepHandler, { toolName: "Grep" });
|
|
363
408
|
|
|
364
409
|
// src/hooks_glob.ts
|
|
365
410
|
init_define_import_meta_env();
|
|
411
|
+
function isBroadCatchAllGlob(pattern, pathArg) {
|
|
412
|
+
const p = pattern.trim();
|
|
413
|
+
const isBroad = p === "*" || p === "**/*" || p === "**" || p === "*.*" || p === "**/*.*" || p === "**/*.**";
|
|
414
|
+
if (!isBroad) return false;
|
|
415
|
+
const target = (pathArg ?? "").trim().replace(/[\\/]+$/, "");
|
|
416
|
+
return target === "" || target === "." || target === "./" || target === ".\\" || !target.includes("/") && !target.includes("\\");
|
|
417
|
+
}
|
|
366
418
|
function globSignature(toolInput) {
|
|
367
419
|
const pattern = toolInput["pattern"];
|
|
368
420
|
if (typeof pattern !== "string" || pattern === "") return null;
|
|
@@ -377,7 +429,24 @@ var { post: postGlobHandler, pre: preGlobDedupHandler } = makeDedupHintHandlers(
|
|
|
377
429
|
minMatchesConfigKey: "glob_dedup_min_matches",
|
|
378
430
|
statName: "glob_dedup_hint"
|
|
379
431
|
});
|
|
380
|
-
|
|
432
|
+
function preGlobHandler(event) {
|
|
433
|
+
try {
|
|
434
|
+
if (getToolName(event) !== "Glob") return passOutput();
|
|
435
|
+
const toolInput = getToolInput(event);
|
|
436
|
+
const pattern = typeof toolInput["pattern"] === "string" ? toolInput["pattern"] : "";
|
|
437
|
+
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;
|
|
438
|
+
if (pattern && isBroadCatchAllGlob(pattern, pathArg)) {
|
|
439
|
+
recordStat("session_hint", 0, 0);
|
|
440
|
+
return contextOutput(
|
|
441
|
+
'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.'
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return preGlobDedupHandler(event);
|
|
445
|
+
} catch {
|
|
446
|
+
return passOutput();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
registerHook("pre_tool_use", preGlobHandler, { toolName: "Glob" });
|
|
381
450
|
registerHook("post_tool_use", postGlobHandler, { toolName: "Glob" });
|
|
382
451
|
|
|
383
452
|
// src/hooks_edit.ts
|
|
@@ -567,8 +636,8 @@ function buildManifest(sessionId, cwd) {
|
|
|
567
636
|
"### Web URLs fetched",
|
|
568
637
|
webFetches.map(([key, cacheId]) => {
|
|
569
638
|
const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
|
|
570
|
-
const promptSuffix = prompt ? `, prompt: ${JSON.stringify(prompt)}` : "";
|
|
571
|
-
return `- ${url} (cacheId: ${cacheId}${promptSuffix})`;
|
|
639
|
+
const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
|
|
640
|
+
return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
|
|
572
641
|
}),
|
|
573
642
|
MAX_ROWS
|
|
574
643
|
);
|
|
@@ -673,15 +742,20 @@ function buildMemEpochSection() {
|
|
|
673
742
|
];
|
|
674
743
|
}
|
|
675
744
|
var MANIFEST_PREAMBLE = "When summarizing this session, keep the file paths and symbol names below exactly as written -- they are the handles the next turn needs to resume work. Do not paraphrase them into prose.";
|
|
745
|
+
var BUDGET_ESCALATION_MARKER = "TG-BUDGET-ESCALATION:";
|
|
746
|
+
function summaryBudgetDirective(budgetChars) {
|
|
747
|
+
if (budgetChars <= 0) return "";
|
|
748
|
+
return ` Aim for at most ${budgetChars} characters. Prefer a shorter summary that keeps every path, symbol, command and decision over a longer one that reproduces tool output verbatim: cite the recall id (\`token-goat bash-output <id> --full\`) rather than quoting the output again. If this session genuinely cannot be summarized within that target without losing state the next turn needs, exceed it and open the summary with a single line reading \`${BUDGET_ESCALATION_MARKER} <one-sentence reason>\`.`;
|
|
749
|
+
}
|
|
676
750
|
function preCompactHandler(event) {
|
|
677
|
-
const out = loadConfig().compact_assist.enabled ? contextOutput(`${MANIFEST_PREAMBLE}
|
|
751
|
+
const out = loadConfig().compact_assist.enabled ? contextOutput(`${MANIFEST_PREAMBLE}${summaryBudgetDirective(loadConfig().compact_assist.summary_budget_chars)}
|
|
678
752
|
|
|
679
753
|
${buildManifest(event.sessionId, getCwd(event))}`) : passOutput();
|
|
680
754
|
markCompacted();
|
|
681
755
|
return out;
|
|
682
756
|
}
|
|
683
757
|
registerHook("pre_compact", preCompactHandler);
|
|
684
|
-
var MANIFEST_SURVIVAL_SAMPLE =
|
|
758
|
+
var MANIFEST_SURVIVAL_SAMPLE = 64;
|
|
685
759
|
function manifestPathSample(sessionId) {
|
|
686
760
|
const ownFiles = [...getSessionFiles().values()];
|
|
687
761
|
const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
|
|
@@ -701,12 +775,15 @@ function postCompactHandler(event) {
|
|
|
701
775
|
const haystack = foldPath(summary);
|
|
702
776
|
const survived = sample.filter((p) => haystack.includes(foldPath(p))).length;
|
|
703
777
|
const trigger = typeof event.raw["trigger"] === "string" ? event.raw["trigger"] : "unknown";
|
|
778
|
+
const budget = loadConfig().compact_assist.summary_budget_chars;
|
|
779
|
+
const over = budget > 0 && summary.length > budget ? 1 : 0;
|
|
780
|
+
const escalated = summary.includes(BUDGET_ESCALATION_MARKER) ? 1 : 0;
|
|
704
781
|
recordStat(
|
|
705
782
|
"compact_summary",
|
|
706
783
|
0,
|
|
707
784
|
0,
|
|
708
785
|
void 0,
|
|
709
|
-
`trigger=${trigger} bytes=${bytes} est_tokens=${savedTokensFromBytes(bytes)} manifest_paths=${survived}/${sample.length}`
|
|
786
|
+
`trigger=${trigger} bytes=${bytes} est_tokens=${savedTokensFromBytes(bytes)} manifest_paths=${survived}/${sample.length} budget=${budget} over=${over} escalated=${escalated}`
|
|
710
787
|
);
|
|
711
788
|
return passOutput();
|
|
712
789
|
}
|
|
@@ -721,7 +798,7 @@ init_define_import_meta_env();
|
|
|
721
798
|
var TRACKED_SKILL = "token-goat";
|
|
722
799
|
var MAX_COMMANDS_SHOWN = 8;
|
|
723
800
|
async function currentCommandNames() {
|
|
724
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
801
|
+
const { buildProgram } = await import("./token-goat-chunk-3YANZKER.mjs");
|
|
725
802
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
726
803
|
}
|
|
727
804
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -7321,6 +7398,16 @@ function resolveSkillContext(event) {
|
|
|
7321
7398
|
}
|
|
7322
7399
|
return { skillName };
|
|
7323
7400
|
}
|
|
7401
|
+
function planHeadingTree(body, bodyBytes, skillName) {
|
|
7402
|
+
const headings = extractMarkdownHeadings(body);
|
|
7403
|
+
if (headings.length < OUTLINE_MIN_HEADINGS) return null;
|
|
7404
|
+
const { sectionsList } = formatHeadingTreeParts(headings, skillName);
|
|
7405
|
+
const treeBytes = Buffer.byteLength(sectionsList, "utf-8");
|
|
7406
|
+
if (treeBytes > bodyBytes * OUTLINE_MAX_REPLACEMENT_RATIO) return null;
|
|
7407
|
+
const totalHeadings = extractMarkdownHeadings(body, Number.MAX_SAFE_INTEGER).length;
|
|
7408
|
+
const phrase = 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.";
|
|
7409
|
+
return { sectionsList, treeBytes, phrase };
|
|
7410
|
+
}
|
|
7324
7411
|
async function preSkillHandler(event) {
|
|
7325
7412
|
try {
|
|
7326
7413
|
const ctx = resolveSkillContext(event);
|
|
@@ -7334,7 +7421,7 @@ async function preSkillHandler(event) {
|
|
|
7334
7421
|
if (await hasSessionOutput(event.sessionId, skillName)) {
|
|
7335
7422
|
const cachedBytes = await sessionOutputBodyBytes(event.sessionId, skillName);
|
|
7336
7423
|
const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
|
|
7337
|
-
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit));
|
|
7424
|
+
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit), void 0, "skill-reload-deny");
|
|
7338
7425
|
return denyOutput(
|
|
7339
7426
|
"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."
|
|
7340
7427
|
);
|
|
@@ -7344,35 +7431,33 @@ async function preSkillHandler(event) {
|
|
|
7344
7431
|
try {
|
|
7345
7432
|
const body = await readFile(sourcePath, "utf-8");
|
|
7346
7433
|
const bodyBytes = Buffer.byteLength(body, "utf-8");
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
"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
|
|
7355
|
-
);
|
|
7356
|
-
}
|
|
7357
|
-
recordStat("skill_oversized_first_load");
|
|
7358
|
-
return denyOutput(
|
|
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."
|
|
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.";
|
|
7434
|
+
if (bodyBytes > OVERSIZED_FIRST_LOAD_THRESHOLD_BYTES) {
|
|
7435
|
+
const compact = extractCompactFromMarker(body);
|
|
7436
|
+
if (compact !== null) {
|
|
7437
|
+
const compactBytes = Buffer.byteLength(compact, "utf-8");
|
|
7438
|
+
if (compactBytes * 2 <= bodyBytes && compactBytes <= COMPACT_INLINE_MAX_BYTES) {
|
|
7439
|
+
const savedBytes = bodyBytes - compactBytes;
|
|
7440
|
+
recordStat("skill_compact_inlined", savedBytes, savedTokensFromBytes(savedBytes));
|
|
7371
7441
|
return denyOutput(
|
|
7372
|
-
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes)
|
|
7442
|
+
"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
|
|
7373
7443
|
);
|
|
7374
7444
|
}
|
|
7375
7445
|
}
|
|
7446
|
+
const tree = planHeadingTree(body, bodyBytes, skillName);
|
|
7447
|
+
if (tree !== null) {
|
|
7448
|
+
const replaced = compact !== null ? Buffer.byteLength(compact, "utf-8") : bodyBytes;
|
|
7449
|
+
const savedBytes = Math.max(0, replaced - tree.treeBytes);
|
|
7450
|
+
recordStat("skill_heading_tree_inlined", savedBytes, savedTokensFromBytes(savedBytes), void 0, "skill=" + skillName + " marker=" + (compact !== null ? "unusable" : "none"));
|
|
7451
|
+
return denyOutput(
|
|
7452
|
+
"Skill `" + skillName + "` is large (" + bodyBytes + " bytes)" + (compact !== null ? ", and its compact slice is too large to inline; " : " with no compact slice; ") + tree.phrase + " Use `token-goat skill-section " + skillName + " '<heading>'` to load a specific section" + (compact !== null ? ", `token-goat skill-body " + skillName + " --compact` to load the compact slice" : "") + ", or `token-goat skill-body " + skillName + "` for the full body.\n\n" + fenceUntrustedFileContent(tree.sectionsList)
|
|
7453
|
+
);
|
|
7454
|
+
}
|
|
7455
|
+
if (compact !== null) {
|
|
7456
|
+
recordStat("skill_oversized_first_load");
|
|
7457
|
+
return denyOutput(
|
|
7458
|
+
"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."
|
|
7459
|
+
);
|
|
7460
|
+
}
|
|
7376
7461
|
}
|
|
7377
7462
|
} catch {
|
|
7378
7463
|
}
|
|
@@ -7743,6 +7828,7 @@ function extractCatFilesMulti(cmd) {
|
|
|
7743
7828
|
}
|
|
7744
7829
|
var POWERSHELL_WRAP_RE = /^(?:powershell|pwsh)(?:\.exe)?(?:\s+-[a-zA-Z]+(?:\s+\S+)?)*\s+(?:-Command|-c|-EncodedCommand)\s+(?:"([^"]*)"|'([^']*)')\s*$/i;
|
|
7745
7830
|
var PS_GETCONTENT_INNER_RE = /^(?:Get-Content|gc|cat|type)(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+?))(?:\s+-[a-zA-Z].*)?\s*$/i;
|
|
7831
|
+
var PS_FILE_METHOD_RE = /\[(?:System\.)?IO\.File\]::(?:ReadAllText|ReadAllLines|ReadAllBytes|ReadLines|OpenText)\(\s*['"]([^'"]+)['"]/i;
|
|
7746
7832
|
var PS_TEMP_READ_FLOOD_BYTES = 16 * 1024;
|
|
7747
7833
|
function isLargeFileOnDisk(filePath, floor) {
|
|
7748
7834
|
try {
|
|
@@ -7765,6 +7851,24 @@ function extractPowerShellWrappedGetContent(cmd) {
|
|
|
7765
7851
|
if (isTempPath(filePath) && !isLargeFileOnDisk(filePath, PS_TEMP_READ_FLOOD_BYTES)) return null;
|
|
7766
7852
|
return { filePath, ...flags };
|
|
7767
7853
|
}
|
|
7854
|
+
function extractPowerShellFileMethodRead(cmd) {
|
|
7855
|
+
let inner = cmd.trim();
|
|
7856
|
+
const w = POWERSHELL_WRAP_RE.exec(inner);
|
|
7857
|
+
if (w) {
|
|
7858
|
+
inner = (w[1] ?? w[2] ?? "").trim();
|
|
7859
|
+
}
|
|
7860
|
+
const m = PS_FILE_METHOD_RE.exec(inner);
|
|
7861
|
+
if (!m?.[1]) return null;
|
|
7862
|
+
const filePath = m[1];
|
|
7863
|
+
if (isOrchestratorStateFile(filePath)) return null;
|
|
7864
|
+
if (isTempPath(filePath) && !isLargeFileOnDisk(filePath, PS_TEMP_READ_FLOOD_BYTES)) return null;
|
|
7865
|
+
const flags = classifyFileExtensions(filePath);
|
|
7866
|
+
if (flags === null) {
|
|
7867
|
+
const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
|
|
7868
|
+
return { filePath, isDoc, isConfig, isSql };
|
|
7869
|
+
}
|
|
7870
|
+
return { filePath, isDoc: flags.isDoc, isConfig: flags.isConfig, isSql: flags.isSql };
|
|
7871
|
+
}
|
|
7768
7872
|
function extractRgSymbolSearch(cmd) {
|
|
7769
7873
|
if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
|
|
7770
7874
|
if (!/-n\b/.test(cmd)) return null;
|
|
@@ -7918,57 +8022,75 @@ function pythonOpenPathsAreAllLiteral(text) {
|
|
|
7918
8022
|
const calls = pythonOpenCalls(text);
|
|
7919
8023
|
return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
|
|
7920
8024
|
}
|
|
8025
|
+
var KNOWN_PYTHON_EXT_STR = "java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|html|htm|conf|cfg|ini|properties|sql|ps1|psm1|env";
|
|
8026
|
+
var OPEN_EXT = new RegExp(`\\.(?:${KNOWN_PYTHON_EXT_STR})$`, "i");
|
|
7921
8027
|
function extractPythonFileRead(cmd) {
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
8028
|
+
let inner = cmd.trim();
|
|
8029
|
+
const w = POWERSHELL_WRAP_RE.exec(inner);
|
|
8030
|
+
if (w) {
|
|
8031
|
+
inner = (w[1] ?? w[2] ?? "").trim();
|
|
8032
|
+
}
|
|
8033
|
+
const psHereMatch = /^@'([\s\S]*?)'@\s*\|\s*(?:python3?|py)(?:\.exe)?(?:\s+-\S*|\s+-)?\s*$/i.exec(inner) ?? /^@"([\s\S]*?)"@\s*\|\s*(?:python3?|py)(?:\.exe)?(?:\s+-\S*|\s+-)?\s*$/i.exec(inner);
|
|
8034
|
+
let pythonBody = null;
|
|
8035
|
+
if (psHereMatch) {
|
|
8036
|
+
pythonBody = (psHereMatch[1] ?? "").trim();
|
|
8037
|
+
} else if (/^(?:python3?|py)(?:\.exe)?\b/i.test(inner)) {
|
|
8038
|
+
pythonBody = inner;
|
|
8039
|
+
}
|
|
8040
|
+
if (pythonBody === null) return null;
|
|
8041
|
+
if (pythonOpenWritesAFile(pythonBody) || pythonWritesThroughFileObject(pythonBody)) return null;
|
|
8042
|
+
const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(pythonBody);
|
|
7925
8043
|
if (outputOpen?.[1]) {
|
|
7926
8044
|
const filePath = outputOpen[1];
|
|
7927
8045
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7928
|
-
return { filePath, isDoc: false, isOutputFile: true };
|
|
8046
|
+
return { filePath, isDoc: false, isConfig: false, isEnv: false, isSql: false, isOutputFile: true };
|
|
7929
8047
|
}
|
|
7930
|
-
const
|
|
7931
|
-
|
|
8048
|
+
const classifyResult = (filePath) => {
|
|
8049
|
+
const flags = classifyFileExtensions(filePath);
|
|
8050
|
+
if (flags !== null) {
|
|
8051
|
+
return { filePath, isDoc: flags.isDoc, isConfig: flags.isConfig, isEnv: flags.isEnv, isSql: flags.isSql, isOutputFile: false };
|
|
8052
|
+
}
|
|
8053
|
+
const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
|
|
8054
|
+
const isEnv = /\.env(\.\w+)?$/i.test(filePath);
|
|
8055
|
+
return { filePath, isDoc, isConfig, isEnv, isSql, isOutputFile: false };
|
|
8056
|
+
};
|
|
8057
|
+
const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(pythonBody);
|
|
7932
8058
|
if (heredocMatch) {
|
|
7933
8059
|
const body = heredocMatch[2] ?? "";
|
|
7934
8060
|
if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
|
|
7935
|
-
const heredocOpen =
|
|
8061
|
+
const heredocOpen = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
7936
8062
|
if (heredocOpen?.[1]) {
|
|
7937
8063
|
const filePath = heredocOpen[1];
|
|
7938
8064
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7939
|
-
|
|
7940
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8065
|
+
return classifyResult(filePath);
|
|
7941
8066
|
}
|
|
7942
8067
|
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
7943
|
-
const literal2 =
|
|
8068
|
+
const literal2 = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
7944
8069
|
if (literal2?.[1]) {
|
|
7945
8070
|
const filePath = literal2[1];
|
|
7946
8071
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7947
8072
|
if (OPEN_EXT.test(filePath)) {
|
|
7948
|
-
|
|
7949
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8073
|
+
return classifyResult(filePath);
|
|
7950
8074
|
}
|
|
7951
8075
|
}
|
|
7952
8076
|
}
|
|
7953
8077
|
return null;
|
|
7954
8078
|
}
|
|
7955
|
-
const direct =
|
|
8079
|
+
const direct = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
7956
8080
|
if (direct) {
|
|
7957
8081
|
const filePath = direct[1] ?? "";
|
|
7958
8082
|
if (!filePath) return null;
|
|
7959
8083
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7960
|
-
|
|
7961
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8084
|
+
return classifyResult(filePath);
|
|
7962
8085
|
}
|
|
7963
|
-
if (/open\s*\(/.test(
|
|
7964
|
-
const literal2 =
|
|
8086
|
+
if (/open\s*\(/.test(pythonBody) && !pythonOpenPathsAreAllLiteral(pythonBody)) {
|
|
8087
|
+
const literal2 = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
7965
8088
|
if (literal2) {
|
|
7966
8089
|
const filePath = literal2[1] ?? "";
|
|
7967
8090
|
if (filePath) {
|
|
7968
8091
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7969
8092
|
if (OPEN_EXT.test(filePath)) {
|
|
7970
|
-
|
|
7971
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8093
|
+
return classifyResult(filePath);
|
|
7972
8094
|
}
|
|
7973
8095
|
}
|
|
7974
8096
|
}
|
|
@@ -8291,7 +8413,7 @@ function extractToolResultsFile(cmd) {
|
|
|
8291
8413
|
return null;
|
|
8292
8414
|
}
|
|
8293
8415
|
function extractDirectoryListing(cmd) {
|
|
8294
|
-
return /^eza\s+.*--long\s+\S+/.test(cmd) || /^eza\s+.*--tree/.test(cmd) || /^tree(\s|$)/.test(cmd) || /^ls\s+(?:\S+\s+)*-[a-zA-Z]*R[a-zA-Z]*(?:\s|$)/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*head/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*grep/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*wc/.test(cmd);
|
|
8416
|
+
return /^eza\s+.*--long\s+\S+/.test(cmd) || /^eza\s+.*--tree/.test(cmd) || /^tree(\s|$)/.test(cmd) || /^ls\s+(?:\S+\s+)*-[a-zA-Z]*R[a-zA-Z]*(?:\s|$)/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*head/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*grep/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*wc/.test(cmd) || /^(?:Get-ChildItem|gci|dir)\b.*(?:-Recurse|-r\b|\/s\b)/i.test(cmd);
|
|
8295
8417
|
}
|
|
8296
8418
|
function extractForLoopWcL(cmd) {
|
|
8297
8419
|
return /^for\s+\w+\s+in\s+.*;\s*do\s+wc\s+-l/.test(cmd);
|
|
@@ -8606,6 +8728,73 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
8606
8728
|
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8607
8729
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8608
8730
|
}
|
|
8731
|
+
var PIPELINE_PASSTHROUGH_HEADS = /* @__PURE__ */ new Set(["head", "tail", "cat", "tee", "less", "more"]);
|
|
8732
|
+
var CI_BUILD_TEST_FILTER_NAMES = /* @__PURE__ */ new Set([
|
|
8733
|
+
"generic-ci",
|
|
8734
|
+
"jest",
|
|
8735
|
+
"vitest",
|
|
8736
|
+
"pytest",
|
|
8737
|
+
"go_test",
|
|
8738
|
+
"cargo_test",
|
|
8739
|
+
"cargo",
|
|
8740
|
+
"go",
|
|
8741
|
+
"make",
|
|
8742
|
+
"cmake",
|
|
8743
|
+
"gradle",
|
|
8744
|
+
"maven",
|
|
8745
|
+
"dotnet",
|
|
8746
|
+
"turbo",
|
|
8747
|
+
"nx",
|
|
8748
|
+
"lerna",
|
|
8749
|
+
"webpack",
|
|
8750
|
+
"eslint",
|
|
8751
|
+
"ruff",
|
|
8752
|
+
"clippy",
|
|
8753
|
+
"flake8",
|
|
8754
|
+
"mypy",
|
|
8755
|
+
"prettier",
|
|
8756
|
+
"tsc"
|
|
8757
|
+
]);
|
|
8758
|
+
function isCiBuildTestSegment(cleaned, cwd) {
|
|
8759
|
+
if (isTestRunnerCommand(cleaned) || isBuildCommand(cleaned) || isTscCommand(cleaned)) return true;
|
|
8760
|
+
if (/^\s*(npm|pnpm|yarn|bun)\s+(run\s+)?(test|build|lint|typecheck|check|ci|guards)\b/i.test(cleaned)) return true;
|
|
8761
|
+
if (/^\s*(cargo|go|dotnet|make|gradle|mvn|pytest|vitest|jest|eslint|ruff)\b/i.test(cleaned)) return true;
|
|
8762
|
+
const detected = detectFromCommand(cleaned, cwd ?? void 0);
|
|
8763
|
+
if (detected !== null && CI_BUILD_TEST_FILTER_NAMES.has(detected.filter.name)) return true;
|
|
8764
|
+
return false;
|
|
8765
|
+
}
|
|
8766
|
+
function pipelineShapeFilter(cmd, cwd) {
|
|
8767
|
+
if (hasUnquotedOperator(cmd, ["&&", "||", ";"])) {
|
|
8768
|
+
const forSplit2 = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
8769
|
+
if (hasBareBackgroundOrNewline(forSplit2)) return null;
|
|
8770
|
+
const segments2 = splitShellSegments(forSplit2);
|
|
8771
|
+
if (segments2.length >= 2) {
|
|
8772
|
+
let recognizedCiCount = 0;
|
|
8773
|
+
for (const segment of segments2) {
|
|
8774
|
+
const cleaned = stripOutputPipeline(segment.trim());
|
|
8775
|
+
if (cleaned.length === 0) continue;
|
|
8776
|
+
if (isCiBuildTestSegment(cleaned, cwd)) {
|
|
8777
|
+
recognizedCiCount++;
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
if (recognizedCiCount > 0) {
|
|
8781
|
+
return filterByName("generic-ci");
|
|
8782
|
+
}
|
|
8783
|
+
}
|
|
8784
|
+
return null;
|
|
8785
|
+
}
|
|
8786
|
+
const forSplit = cmd.replace(/\s2>(?:&1|\/dev\/null)/g, "");
|
|
8787
|
+
if (hasBareBackgroundOrNewline(forSplit)) return null;
|
|
8788
|
+
const segments = splitShellSegments(forSplit);
|
|
8789
|
+
if (segments.length < 2) return null;
|
|
8790
|
+
for (const segment of segments.slice(1)) {
|
|
8791
|
+
const head = safeShlexSplit(segment)?.[0];
|
|
8792
|
+
if (head === void 0) return null;
|
|
8793
|
+
if (!PIPELINE_PASSTHROUGH_HEADS.has(head.replace(/^.*[/\\]/, ""))) return null;
|
|
8794
|
+
}
|
|
8795
|
+
const detected = detectFromCommand(stripOutputPipeline(cmd), cwd ?? void 0);
|
|
8796
|
+
return detected === null ? null : detected.filter;
|
|
8797
|
+
}
|
|
8609
8798
|
function pureFileReadPath(cmd) {
|
|
8610
8799
|
const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
|
|
8611
8800
|
if (single !== void 0) return single;
|
|
@@ -8629,10 +8818,10 @@ function deliveredLineNumbers(cmd, lineCount) {
|
|
|
8629
8818
|
if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
|
|
8630
8819
|
return null;
|
|
8631
8820
|
}
|
|
8632
|
-
function elideServedShellLines(cmd, output, priorIds) {
|
|
8821
|
+
function elideServedShellLines(cmd, output, priorIds, unknownLineNumbers = false) {
|
|
8633
8822
|
if (priorIds.length === 0) return null;
|
|
8634
8823
|
const lines = output.split("\n");
|
|
8635
|
-
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
8824
|
+
const numbers = unknownLineNumbers ? Array.from({ length: lines.length }, () => null) : deliveredLineNumbers(cmd, lines.length);
|
|
8636
8825
|
if (numbers === null) return null;
|
|
8637
8826
|
const rows = lines.map((text, i) => ({ no: numbers[i] ?? null, text, raw: text }));
|
|
8638
8827
|
const bodies = [];
|
|
@@ -8750,6 +8939,26 @@ async function maybeCollapseIdenticalRead(cmd, rawCmd, output, exitCode, cwd, ca
|
|
|
8750
8939
|
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8751
8940
|
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) });
|
|
8752
8941
|
}
|
|
8942
|
+
async function maybeElideServedGenericOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8943
|
+
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8944
|
+
if (!loadConfig().bash_compress.elide_served_shell_output) return null;
|
|
8945
|
+
if (exitCode !== null && exitCode !== 0) return null;
|
|
8946
|
+
if (redactSecrets(output).count > 0) return null;
|
|
8947
|
+
const originalBytes = Buffer.byteLength(output, "utf-8");
|
|
8948
|
+
if (originalBytes < cacheMinBytes) return null;
|
|
8949
|
+
const priorIds = getFileServedOutputs(GENERIC_SERVED_OUTPUT_KEY).filter((id) => getBashOutput(id)?.command !== cmd);
|
|
8950
|
+
let rewrittenText = null;
|
|
8951
|
+
if (priorIds.length > 0) {
|
|
8952
|
+
const elided = elideServedShellLines(cmd, output, priorIds, true);
|
|
8953
|
+
if (elided !== null && isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(elided, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) {
|
|
8954
|
+
rewrittenText = elided;
|
|
8955
|
+
}
|
|
8956
|
+
}
|
|
8957
|
+
const storedId = await storeBashOutput(cmd, rewrittenText ?? output, exitCode ?? 0, cwd);
|
|
8958
|
+
recordFileServedOutput(GENERIC_SERVED_OUTPUT_KEY, storedId);
|
|
8959
|
+
if (rewrittenText === null) return null;
|
|
8960
|
+
return emitRewrite(rewrittenText, "already-served shell output collapsed", { kind: "bash_compress:generic-served-elision", originalBytes: deliveredOutputBytes(originalBytes) });
|
|
8961
|
+
}
|
|
8753
8962
|
async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8754
8963
|
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8755
8964
|
if (isCompressibleSingleCommand(cmd)) return null;
|
|
@@ -8762,7 +8971,8 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8762
8971
|
}
|
|
8763
8972
|
if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
|
|
8764
8973
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
|
|
8765
|
-
const
|
|
8974
|
+
const shaped = pipelineShapeFilter(cmd, cwd);
|
|
8975
|
+
const filter3 = shaped !== null && !cfg.disabled_filters.includes(shaped.name) ? shaped : filterByName("generic");
|
|
8766
8976
|
if (filter3 === null) return null;
|
|
8767
8977
|
const compressed = compressOutput(filter3, output, "", exitCode ?? 0, [], {
|
|
8768
8978
|
maxLines: cfg.max_lines,
|
|
@@ -8784,7 +8994,7 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8784
8994
|
return null;
|
|
8785
8995
|
}
|
|
8786
8996
|
await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8787
|
-
return emitRewrite(body, "bash", { kind:
|
|
8997
|
+
return emitRewrite(body, "bash", { kind: `bash_compress:${filter3.name}`, originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
8788
8998
|
}
|
|
8789
8999
|
function maybeStripAnsiOnly(output) {
|
|
8790
9000
|
if (!output.includes("\x1B")) return null;
|
|
@@ -9079,7 +9289,7 @@ function preBashHandlerInner(event) {
|
|
|
9079
9289
|
recordStat("session_hint", 0, 0);
|
|
9080
9290
|
const hints = [];
|
|
9081
9291
|
for (const { filePath, ranges, tool } of sedReads) {
|
|
9082
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9292
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9083
9293
|
const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
9084
9294
|
const overlapHints = [];
|
|
9085
9295
|
const freshRanges = [];
|
|
@@ -9100,7 +9310,7 @@ function preBashHandlerInner(event) {
|
|
|
9100
9310
|
const catJsonPipe = extractCatJsonPipe(cmd);
|
|
9101
9311
|
if (catJsonPipe !== null) {
|
|
9102
9312
|
const { filePath } = catJsonPipe;
|
|
9103
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9313
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9104
9314
|
recordStat("session_hint", 0, 0);
|
|
9105
9315
|
return contextOutput(
|
|
9106
9316
|
'`cat | jq` loads the whole file. Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to slice one value.'
|
|
@@ -9109,7 +9319,7 @@ function preBashHandlerInner(event) {
|
|
|
9109
9319
|
const catResult = extractCatFile(cmd);
|
|
9110
9320
|
if (catResult !== null) {
|
|
9111
9321
|
const { filePath, isDoc, isEnv, isConfig, isSql, cmd0, advisoryOnly } = catResult;
|
|
9112
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9322
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9113
9323
|
recordStat("session_hint", 0, 0);
|
|
9114
9324
|
if (isSql) {
|
|
9115
9325
|
return contextOutput(
|
|
@@ -9124,7 +9334,7 @@ function preBashHandlerInner(event) {
|
|
|
9124
9334
|
recordStat("session_hint", 0, 0);
|
|
9125
9335
|
const cmd0 = catMulti[0].cmd0;
|
|
9126
9336
|
const perPath = catMulti.map(({ filePath, isDoc, isEnv, isConfig, isSql }) => {
|
|
9127
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9337
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9128
9338
|
const how = isSql ? 'token-goat section "' + hintPath + '::table_name"' : isEnv || isConfig ? 'token-goat config-get "' + hintPath + '" KEY_NAME' : isDoc ? 'token-goat section "' + hintPath + '::SectionHeading"' : 'token-goat read "' + hintPath + '::SymbolName"';
|
|
9129
9339
|
return " " + hintPath + " -> `" + how + "`";
|
|
9130
9340
|
});
|
|
@@ -9134,7 +9344,7 @@ function preBashHandlerInner(event) {
|
|
|
9134
9344
|
const psGetContentResult = extractPowerShellWrappedGetContent(cmd);
|
|
9135
9345
|
if (psGetContentResult !== null) {
|
|
9136
9346
|
const { filePath, isDoc, isEnv, isConfig, isSql } = psGetContentResult;
|
|
9137
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9347
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9138
9348
|
recordStat("session_hint", 0, 0);
|
|
9139
9349
|
const lead = "`Get-Content` via a `powershell -Command` wrapper bypasses read hooks and loads the entire file into context. ";
|
|
9140
9350
|
if (isSql) {
|
|
@@ -9146,7 +9356,7 @@ function preBashHandlerInner(event) {
|
|
|
9146
9356
|
const wslCatResult = extractWslCatFile(cmd);
|
|
9147
9357
|
if (wslCatResult !== null) {
|
|
9148
9358
|
const { filePath, isDoc, isEnv, isConfig, isSql } = wslCatResult;
|
|
9149
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9359
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9150
9360
|
recordStat("session_hint", 0, 0);
|
|
9151
9361
|
if (isSql) {
|
|
9152
9362
|
return contextOutput(
|
|
@@ -9158,8 +9368,8 @@ function preBashHandlerInner(event) {
|
|
|
9158
9368
|
}
|
|
9159
9369
|
const pyRead = extractPythonFileRead(cmd);
|
|
9160
9370
|
if (pyRead !== null) {
|
|
9161
|
-
const { filePath, isDoc, isOutputFile } = pyRead;
|
|
9162
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9371
|
+
const { filePath, isDoc, isConfig, isEnv, isSql, isOutputFile } = pyRead;
|
|
9372
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9163
9373
|
recordStat("session_hint", 0, 0);
|
|
9164
9374
|
if (isOutputFile) {
|
|
9165
9375
|
if (taskOutputIsJsonlTranscript(filePath)) {
|
|
@@ -9170,41 +9380,46 @@ function preBashHandlerInner(event) {
|
|
|
9170
9380
|
"This `.output` file is a background command's stdout. Use `token-goat bash-output --file \"" + hintPath + '"` to narrow it with `--grep PATTERN`, `--tail N` or `--head N`, instead of reading the whole file.'
|
|
9171
9381
|
);
|
|
9172
9382
|
}
|
|
9173
|
-
|
|
9383
|
+
if (isSql) {
|
|
9384
|
+
return contextOutput(
|
|
9385
|
+
'Python `open()` file reads bypass read hooks. Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.'
|
|
9386
|
+
);
|
|
9387
|
+
}
|
|
9388
|
+
const hint = surgicalHintFor(hintPath, isEnv, isConfig, isDoc);
|
|
9174
9389
|
return cdStripped ? contextOutput("Python `open()` file reads bypass read hooks. " + hint) : denyOutput("Python `open()` file reads bypass read hooks. " + hint);
|
|
9175
9390
|
}
|
|
9176
9391
|
const tailResult = extractTailFile(cmd);
|
|
9177
9392
|
if (tailResult !== null) {
|
|
9178
9393
|
const { filePath, isDoc, isConfig, isSql } = tailResult;
|
|
9179
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9394
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9180
9395
|
recordStat("session_hint", 0, 0);
|
|
9181
9396
|
return contextOutput("`tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
|
|
9182
9397
|
}
|
|
9183
9398
|
const headResult = extractHeadFile(cmd);
|
|
9184
9399
|
if (headResult !== null) {
|
|
9185
9400
|
const { filePath, isDoc, isConfig, isSql, n } = headResult;
|
|
9186
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9401
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9187
9402
|
recordStat("session_hint", 0, 0);
|
|
9188
9403
|
return contextOutput(leadingLinesHint("`head` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
9189
9404
|
}
|
|
9190
9405
|
const gcTailResult = extractGetContentTail(cmd);
|
|
9191
9406
|
if (gcTailResult !== null) {
|
|
9192
9407
|
const { filePath, isDoc, isConfig, isSql } = gcTailResult;
|
|
9193
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9408
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9194
9409
|
recordStat("session_hint", 0, 0);
|
|
9195
9410
|
return contextOutput("`Get-Content -Tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
|
|
9196
9411
|
}
|
|
9197
9412
|
const gcSelectResult = extractGetContentSelectFirst(cmd);
|
|
9198
9413
|
if (gcSelectResult !== null) {
|
|
9199
9414
|
const { filePath, isDoc, isConfig, isSql, n } = gcSelectResult;
|
|
9200
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9415
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9201
9416
|
recordStat("session_hint", 0, 0);
|
|
9202
9417
|
return contextOutput(leadingLinesHint("`Select-Object -First` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
9203
9418
|
}
|
|
9204
9419
|
const nodeRead = extractNodeFileRead(cmd);
|
|
9205
9420
|
if (nodeRead !== null) {
|
|
9206
9421
|
const { filePath, isDoc, isConfig, isSql } = nodeRead;
|
|
9207
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9422
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9208
9423
|
const lead = "Node.js `fs.readFileSync()` bypasses read hooks. ";
|
|
9209
9424
|
if (isSql) {
|
|
9210
9425
|
recordStat("session_hint", 0, 0);
|
|
@@ -9214,6 +9429,19 @@ function preBashHandlerInner(event) {
|
|
|
9214
9429
|
recordStat("session_hint", 0, 0);
|
|
9215
9430
|
return cdStripped ? contextOutput(lead + hint) : denyOutput(lead + hint);
|
|
9216
9431
|
}
|
|
9432
|
+
const psMethodRead = extractPowerShellFileMethodRead(cmd);
|
|
9433
|
+
if (psMethodRead !== null) {
|
|
9434
|
+
const { filePath, isDoc, isConfig, isSql } = psMethodRead;
|
|
9435
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9436
|
+
const lead = "PowerShell `[IO.File]::ReadAllText()` bypasses read hooks. ";
|
|
9437
|
+
if (isSql) {
|
|
9438
|
+
recordStat("session_hint", 0, 0);
|
|
9439
|
+
return contextOutput(lead + 'Use `token-goat section "' + hintPath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.');
|
|
9440
|
+
}
|
|
9441
|
+
const hint = isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : isConfig ? 'Use `token-goat config-get "' + hintPath + '" KEY_NAME` or `token-goat section "' + hintPath + '::sectionName"` to read a specific value.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to extract a specific symbol.';
|
|
9442
|
+
recordStat("session_hint", 0, 0);
|
|
9443
|
+
return cdStripped ? contextOutput(lead + hint) : denyOutput(lead + hint);
|
|
9444
|
+
}
|
|
9217
9445
|
if (extractGrepPipeChain(cmd)) {
|
|
9218
9446
|
recordStat("session_hint", 0, 0);
|
|
9219
9447
|
return contextOutput(
|
|
@@ -9223,7 +9451,7 @@ function preBashHandlerInner(event) {
|
|
|
9223
9451
|
const mdHeadingGrep = extractMarkdownHeadingGrep(cmd);
|
|
9224
9452
|
if (mdHeadingGrep !== null) {
|
|
9225
9453
|
const { filePath } = mdHeadingGrep;
|
|
9226
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9454
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9227
9455
|
recordStat("session_hint", 0, 0);
|
|
9228
9456
|
return contextOutput(
|
|
9229
9457
|
'Use `token-goat outline "' + hintPath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + hintPath + '::Heading"` to read one section.'
|
|
@@ -9240,7 +9468,7 @@ function preBashHandlerInner(event) {
|
|
|
9240
9468
|
const rgStructural = extractRgStructuralSearch(cmd);
|
|
9241
9469
|
if (rgStructural !== null) {
|
|
9242
9470
|
const { filePath } = rgStructural;
|
|
9243
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9471
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9244
9472
|
recordStat("session_hint", 0, 0);
|
|
9245
9473
|
return contextOutput(
|
|
9246
9474
|
'Searching for code definitions with `rg`/`grep` is slower than surgical reads. Use `token-goat skeleton "' + hintPath + '"` to see all symbols with line numbers, or `token-goat outline "' + hintPath + '"` for symbols with docstrings and line ranges.'
|
|
@@ -9444,6 +9672,21 @@ function recordBashFileReadsForSessionCache(cmd, cwd) {
|
|
|
9444
9672
|
recordFileRead(resolve(wslCat.filePath));
|
|
9445
9673
|
return;
|
|
9446
9674
|
}
|
|
9675
|
+
const nodeRead = extractNodeFileRead(cmd);
|
|
9676
|
+
if (nodeRead !== null) {
|
|
9677
|
+
recordFileRead(resolve(nodeRead.filePath));
|
|
9678
|
+
return;
|
|
9679
|
+
}
|
|
9680
|
+
const psMethod = extractPowerShellFileMethodRead(cmd);
|
|
9681
|
+
if (psMethod !== null) {
|
|
9682
|
+
recordFileRead(resolve(psMethod.filePath));
|
|
9683
|
+
return;
|
|
9684
|
+
}
|
|
9685
|
+
const pyRead = extractPythonFileRead(cmd);
|
|
9686
|
+
if (pyRead !== null && !pyRead.isOutputFile) {
|
|
9687
|
+
recordFileRead(resolve(pyRead.filePath));
|
|
9688
|
+
return;
|
|
9689
|
+
}
|
|
9447
9690
|
}
|
|
9448
9691
|
async function postBashHandler(event) {
|
|
9449
9692
|
try {
|
|
@@ -9539,6 +9782,10 @@ async function postBashHandler(event) {
|
|
|
9539
9782
|
if (identical !== null) return identical;
|
|
9540
9783
|
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9541
9784
|
if (compound !== null) return compound;
|
|
9785
|
+
if (!isFileRead && extractLineRangeReadsCompound(cmd) === null) {
|
|
9786
|
+
const genericElision = await maybeElideServedGenericOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9787
|
+
if (genericElision !== null) return genericElision;
|
|
9788
|
+
}
|
|
9542
9789
|
return maybeStripAnsiOnly(output) ?? passOutput();
|
|
9543
9790
|
}
|
|
9544
9791
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
@@ -9795,7 +10042,7 @@ function writeLedger(target, ledger) {
|
|
|
9795
10042
|
}
|
|
9796
10043
|
}
|
|
9797
10044
|
function repeatFailureNotice(toolName) {
|
|
9798
|
-
const tool = toolName === void 0 || toolName === "" ? "This tool" : toolName;
|
|
10045
|
+
const tool = toolName === void 0 || toolName === "" ? "This tool" : displaySafeText(toolName);
|
|
9799
10046
|
return `[token-goat] ${tool} just failed with the same error as an earlier call this session. Retrying it unchanged will fail the same way -- change the arguments, the tool, or the approach.`;
|
|
9800
10047
|
}
|
|
9801
10048
|
function postToolUseFailureHandler(event) {
|
|
@@ -10723,13 +10970,16 @@ var SPAWN_RESTRICT_HINT_KEY = "agent-spawn-restrict-hint";
|
|
|
10723
10970
|
var SPAWN_RESTRICT_MAX_NAMES = 3;
|
|
10724
10971
|
var ROSTER_WALK_MAX_DEPTH = 4;
|
|
10725
10972
|
var ROSTER_WALK_MAX_FILES = 400;
|
|
10973
|
+
var AGENT_NAME_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
10726
10974
|
function parseAgentDefinition(text, fallbackName) {
|
|
10727
10975
|
const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
10728
10976
|
if (!fmMatch) return null;
|
|
10729
10977
|
const fm = fmMatch[1];
|
|
10730
10978
|
const nameMatch = /^name:(.*)$/m.exec(fm);
|
|
10731
10979
|
const rawName = nameMatch ? nameMatch[1].trim().replace(/^["']|["']$/g, "") : "";
|
|
10732
|
-
const
|
|
10980
|
+
const resolved = AGENT_NAME_RE.test(rawName) ? rawName : fallbackName;
|
|
10981
|
+
if (!AGENT_NAME_RE.test(resolved)) return null;
|
|
10982
|
+
const name2 = resolved;
|
|
10733
10983
|
const toolsMatch = /^tools:(.*)$/m.exec(fm);
|
|
10734
10984
|
if (!toolsMatch) return { name: name2, restricted: false };
|
|
10735
10985
|
const inline = toolsMatch[1].trim();
|
|
@@ -10743,7 +10993,7 @@ function parseAgentDefinition(text, fallbackName) {
|
|
|
10743
10993
|
return { name: name2, restricted: false };
|
|
10744
10994
|
}
|
|
10745
10995
|
function findRestrictedAgentNames(roots) {
|
|
10746
|
-
const scanRoots = roots ?? [path2.join(os.homedir(), ".claude", "agents")];
|
|
10996
|
+
const scanRoots = roots ?? [path2.join(os.homedir(), ".claude", "agents"), path2.join(process.cwd(), ".claude", "agents")];
|
|
10747
10997
|
const names = /* @__PURE__ */ new Set();
|
|
10748
10998
|
const visited = /* @__PURE__ */ new Set();
|
|
10749
10999
|
let filesSeen = 0;
|
|
@@ -10803,7 +11053,7 @@ function buildUnrestrictedSpawnAdvisory(toolInput) {
|
|
|
10803
11053
|
if (names.length === 0) return "";
|
|
10804
11054
|
markHintShown(SPAWN_RESTRICT_HINT_KEY);
|
|
10805
11055
|
recordStat("session_hint", 0, 0, void 0, "agent-spawn-restrict");
|
|
10806
|
-
const shown = names.slice(0, SPAWN_RESTRICT_MAX_NAMES).join(", ");
|
|
11056
|
+
const shown = neutralizeSpokenMarkers(names.slice(0, SPAWN_RESTRICT_MAX_NAMES).join(", "));
|
|
10807
11057
|
const more = names.length > SPAWN_RESTRICT_MAX_NAMES ? ` and ${names.length - SPAWN_RESTRICT_MAX_NAMES} more` : "";
|
|
10808
11058
|
return `[token-goat] This spawn ran as general-purpose (the default when subagent_type is omitted), 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}. A future spawn that fits one of them can pass that name as subagent_type to start with a much smaller prefix. Advisory only: this spawn has already run, and this notice saved nothing.`;
|
|
10809
11059
|
} catch {
|