token-goat 2.9.5 → 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-5T2K7DEE.mjs → token-goat-chunk-2SBHUPBF.mjs} +64 -29
- package/dist/{token-goat-chunk-2FVSKFZU.mjs → token-goat-chunk-3YANZKER.mjs} +5 -5
- package/dist/{token-goat-chunk-4P6GTCMM.mjs → token-goat-chunk-6RAUVAKW.mjs} +24 -13
- package/dist/{token-goat-chunk-DRL5USXI.mjs → token-goat-chunk-EZZ7IJYM.mjs} +3 -3
- package/dist/{token-goat-chunk-ZOKNDG6V.mjs → token-goat-chunk-JZYJH76S.mjs} +2861 -2702
- package/dist/{token-goat-chunk-GFBXHGFY.mjs → token-goat-chunk-Q4VCAIBR.mjs} +5 -5
- package/dist/{token-goat-chunk-HTJP6FHK.mjs → token-goat-chunk-REHUP2OE.mjs} +1 -1
- package/dist/{token-goat-chunk-U4FTM2SB.mjs → token-goat-chunk-TXZY7C24.mjs} +116 -87
- package/dist/{token-goat-chunk-JO5JX72D.mjs → token-goat-chunk-VEEHPNBV.mjs} +5 -3
- package/dist/{token-goat-chunk-KFFAZ5DJ.mjs → token-goat-chunk-WT5OLTT6.mjs} +2 -2
- package/dist/{token-goat-chunk-ZLP6TCGN.mjs → token-goat-chunk-XYLPMQ6I.mjs} +338 -87
- 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 +2 -2
- package/package.json +1 -1
|
@@ -18,12 +18,12 @@ 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
|
-
UNTRUSTED_TOOL_TAG,
|
|
27
27
|
WEB_FETCH_KEY_SEP,
|
|
28
28
|
appendDirtyPath,
|
|
29
29
|
applyHintTracking,
|
|
@@ -42,7 +42,6 @@ import {
|
|
|
42
42
|
extractCompactFromMarker,
|
|
43
43
|
extractMarkdownHeadings,
|
|
44
44
|
fenceUntrusted,
|
|
45
|
-
fenceUntrustedFileContent,
|
|
46
45
|
fenceWithMatches,
|
|
47
46
|
foldDelivery,
|
|
48
47
|
foldDetail,
|
|
@@ -126,18 +125,19 @@ import {
|
|
|
126
125
|
wasCliReadThisSession,
|
|
127
126
|
wasFileReadThisSession,
|
|
128
127
|
wasHintShown
|
|
129
|
-
} from "./token-goat-chunk-
|
|
128
|
+
} from "./token-goat-chunk-TXZY7C24.mjs";
|
|
130
129
|
import {
|
|
131
130
|
bashOutputCapBytes,
|
|
132
131
|
canRunWrappedShell,
|
|
133
132
|
deliveredOutputBytes
|
|
134
|
-
} from "./token-goat-chunk-
|
|
133
|
+
} from "./token-goat-chunk-REHUP2OE.mjs";
|
|
135
134
|
import {
|
|
136
135
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
137
136
|
ENV_KEYS,
|
|
138
137
|
IDENTICAL_READ_MIN_BODY_BYTES,
|
|
139
138
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
140
139
|
PER_FILE_COUNTERFACTUAL_CEILING,
|
|
140
|
+
UNTRUSTED_TOOL_TAG,
|
|
141
141
|
VERSION,
|
|
142
142
|
compressOutput,
|
|
143
143
|
containsLineRun,
|
|
@@ -149,6 +149,7 @@ import {
|
|
|
149
149
|
detectHarness,
|
|
150
150
|
detectLanguage,
|
|
151
151
|
displaySafePath,
|
|
152
|
+
displaySafeText,
|
|
152
153
|
emitRewrite,
|
|
153
154
|
emitRewriteIfChanged,
|
|
154
155
|
envBool,
|
|
@@ -156,6 +157,7 @@ import {
|
|
|
156
157
|
extractErrorMessage,
|
|
157
158
|
extractToolResponseField,
|
|
158
159
|
extractToolResultText,
|
|
160
|
+
fenceUntrustedFileContent,
|
|
159
161
|
filterByName,
|
|
160
162
|
findProject,
|
|
161
163
|
foldPath,
|
|
@@ -167,11 +169,13 @@ import {
|
|
|
167
169
|
getToolName,
|
|
168
170
|
globalDbPath,
|
|
169
171
|
hasBareBackgroundOrNewline,
|
|
172
|
+
hasUnquotedOperator,
|
|
170
173
|
isMcpErrorResponse,
|
|
171
174
|
isRewriteWorthwhile,
|
|
172
175
|
isUnderSystemTemp,
|
|
173
176
|
loadConfig,
|
|
174
177
|
makeDedupHintHandlers,
|
|
178
|
+
neutralizeSpokenMarkers,
|
|
175
179
|
normalizePath,
|
|
176
180
|
passOutput,
|
|
177
181
|
recordStat,
|
|
@@ -190,7 +194,7 @@ import {
|
|
|
190
194
|
stripAnsiEscapes,
|
|
191
195
|
toDisplayPath,
|
|
192
196
|
toKB
|
|
193
|
-
} from "./token-goat-chunk-
|
|
197
|
+
} from "./token-goat-chunk-JZYJH76S.mjs";
|
|
194
198
|
import {
|
|
195
199
|
init_define_import_meta_env
|
|
196
200
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -351,17 +355,66 @@ function foldGrepContentHandler(event) {
|
|
|
351
355
|
return passOutput();
|
|
352
356
|
}
|
|
353
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
|
+
}
|
|
354
400
|
function postGrepHandler(event) {
|
|
355
401
|
const dedupResult = dedupPostHandler(event);
|
|
356
402
|
const foldResult = foldGrepContentHandler(event);
|
|
357
403
|
if (foldResult.hookType === "rewriteOutput") return foldResult;
|
|
358
404
|
return dedupResult;
|
|
359
405
|
}
|
|
360
|
-
registerHook("pre_tool_use",
|
|
406
|
+
registerHook("pre_tool_use", preGrepHandler, { toolName: "Grep" });
|
|
361
407
|
registerHook("post_tool_use", postGrepHandler, { toolName: "Grep" });
|
|
362
408
|
|
|
363
409
|
// src/hooks_glob.ts
|
|
364
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
|
+
}
|
|
365
418
|
function globSignature(toolInput) {
|
|
366
419
|
const pattern = toolInput["pattern"];
|
|
367
420
|
if (typeof pattern !== "string" || pattern === "") return null;
|
|
@@ -376,7 +429,24 @@ var { post: postGlobHandler, pre: preGlobDedupHandler } = makeDedupHintHandlers(
|
|
|
376
429
|
minMatchesConfigKey: "glob_dedup_min_matches",
|
|
377
430
|
statName: "glob_dedup_hint"
|
|
378
431
|
});
|
|
379
|
-
|
|
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" });
|
|
380
450
|
registerHook("post_tool_use", postGlobHandler, { toolName: "Glob" });
|
|
381
451
|
|
|
382
452
|
// src/hooks_edit.ts
|
|
@@ -515,10 +585,10 @@ function renderReadRow(entry) {
|
|
|
515
585
|
const kb = Math.max(1, toKB(entry.sizeBytes));
|
|
516
586
|
const plural = entry.readCount === 1 ? "read" : "reads";
|
|
517
587
|
const edited = entry.wasEdited ? ", edited" : "";
|
|
518
|
-
return `- ${entry.path} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
|
|
588
|
+
return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
|
|
519
589
|
}
|
|
520
590
|
function renderSymbolReadRow(entry) {
|
|
521
|
-
return `- ${entry.path} (symbols: ${(entry.symbols_read ?? []).join(", ")})`;
|
|
591
|
+
return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
|
|
522
592
|
}
|
|
523
593
|
function mergeManifestFiles(parent, siblingFiles) {
|
|
524
594
|
const byPath = /* @__PURE__ */ new Map();
|
|
@@ -557,7 +627,7 @@ function buildManifest(sessionId, cwd) {
|
|
|
557
627
|
appendCappedSection(
|
|
558
628
|
lines,
|
|
559
629
|
"### Edited files",
|
|
560
|
-
editedFiles.map((entry) => `- ${entry.path}`),
|
|
630
|
+
editedFiles.map((entry) => `- ${displaySafePath(entry.path)}`),
|
|
561
631
|
MAX_ROWS
|
|
562
632
|
);
|
|
563
633
|
appendCappedSection(lines, "### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles.map(renderSymbolReadRow), MAX_ROWS);
|
|
@@ -566,8 +636,8 @@ function buildManifest(sessionId, cwd) {
|
|
|
566
636
|
"### Web URLs fetched",
|
|
567
637
|
webFetches.map(([key, cacheId]) => {
|
|
568
638
|
const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
|
|
569
|
-
const promptSuffix = prompt ? `, prompt: ${JSON.stringify(prompt)}` : "";
|
|
570
|
-
return `- ${url} (cacheId: ${cacheId}${promptSuffix})`;
|
|
639
|
+
const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
|
|
640
|
+
return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
|
|
571
641
|
}),
|
|
572
642
|
MAX_ROWS
|
|
573
643
|
);
|
|
@@ -672,15 +742,20 @@ function buildMemEpochSection() {
|
|
|
672
742
|
];
|
|
673
743
|
}
|
|
674
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
|
+
}
|
|
675
750
|
function preCompactHandler(event) {
|
|
676
|
-
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)}
|
|
677
752
|
|
|
678
753
|
${buildManifest(event.sessionId, getCwd(event))}`) : passOutput();
|
|
679
754
|
markCompacted();
|
|
680
755
|
return out;
|
|
681
756
|
}
|
|
682
757
|
registerHook("pre_compact", preCompactHandler);
|
|
683
|
-
var MANIFEST_SURVIVAL_SAMPLE =
|
|
758
|
+
var MANIFEST_SURVIVAL_SAMPLE = 64;
|
|
684
759
|
function manifestPathSample(sessionId) {
|
|
685
760
|
const ownFiles = [...getSessionFiles().values()];
|
|
686
761
|
const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
|
|
@@ -700,12 +775,15 @@ function postCompactHandler(event) {
|
|
|
700
775
|
const haystack = foldPath(summary);
|
|
701
776
|
const survived = sample.filter((p) => haystack.includes(foldPath(p))).length;
|
|
702
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;
|
|
703
781
|
recordStat(
|
|
704
782
|
"compact_summary",
|
|
705
783
|
0,
|
|
706
784
|
0,
|
|
707
785
|
void 0,
|
|
708
|
-
`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}`
|
|
709
787
|
);
|
|
710
788
|
return passOutput();
|
|
711
789
|
}
|
|
@@ -720,7 +798,7 @@ init_define_import_meta_env();
|
|
|
720
798
|
var TRACKED_SKILL = "token-goat";
|
|
721
799
|
var MAX_COMMANDS_SHOWN = 8;
|
|
722
800
|
async function currentCommandNames() {
|
|
723
|
-
const { buildProgram } = await import("./token-goat-chunk-
|
|
801
|
+
const { buildProgram } = await import("./token-goat-chunk-3YANZKER.mjs");
|
|
724
802
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
725
803
|
}
|
|
726
804
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -7320,6 +7398,16 @@ function resolveSkillContext(event) {
|
|
|
7320
7398
|
}
|
|
7321
7399
|
return { skillName };
|
|
7322
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
|
+
}
|
|
7323
7411
|
async function preSkillHandler(event) {
|
|
7324
7412
|
try {
|
|
7325
7413
|
const ctx = resolveSkillContext(event);
|
|
@@ -7333,7 +7421,7 @@ async function preSkillHandler(event) {
|
|
|
7333
7421
|
if (await hasSessionOutput(event.sessionId, skillName)) {
|
|
7334
7422
|
const cachedBytes = await sessionOutputBodyBytes(event.sessionId, skillName);
|
|
7335
7423
|
const denyCredit = cachedBytes !== null ? Math.min(cachedBytes, PER_FILE_COUNTERFACTUAL_CEILING) : 0;
|
|
7336
|
-
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit));
|
|
7424
|
+
recordStat("session_hint", denyCredit, savedTokensFromBytes(denyCredit), void 0, "skill-reload-deny");
|
|
7337
7425
|
return denyOutput(
|
|
7338
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."
|
|
7339
7427
|
);
|
|
@@ -7343,35 +7431,33 @@ async function preSkillHandler(event) {
|
|
|
7343
7431
|
try {
|
|
7344
7432
|
const body = await readFile(sourcePath, "utf-8");
|
|
7345
7433
|
const bodyBytes = Buffer.byteLength(body, "utf-8");
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
"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
|
|
7354
|
-
);
|
|
7355
|
-
}
|
|
7356
|
-
recordStat("skill_oversized_first_load");
|
|
7357
|
-
return denyOutput(
|
|
7358
|
-
"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."
|
|
7359
|
-
);
|
|
7360
|
-
} else if (bodyBytes > OVERSIZED_FIRST_LOAD_THRESHOLD_BYTES) {
|
|
7361
|
-
const headings = extractMarkdownHeadings(body);
|
|
7362
|
-
if (headings.length >= OUTLINE_MIN_HEADINGS) {
|
|
7363
|
-
const { sectionsList } = formatHeadingTreeParts(headings, skillName);
|
|
7364
|
-
const treeBytes = Buffer.byteLength(sectionsList, "utf-8");
|
|
7365
|
-
if (treeBytes <= bodyBytes * OUTLINE_MAX_REPLACEMENT_RATIO) {
|
|
7366
|
-
const savedBytes = bodyBytes - treeBytes;
|
|
7367
|
-
recordStat("skill_heading_tree_inlined", savedBytes, savedTokensFromBytes(savedBytes));
|
|
7368
|
-
const totalHeadings = extractMarkdownHeadings(body, Number.MAX_SAFE_INTEGER).length;
|
|
7369
|
-
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));
|
|
7370
7441
|
return denyOutput(
|
|
7371
|
-
"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
|
|
7372
7443
|
);
|
|
7373
7444
|
}
|
|
7374
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
|
+
}
|
|
7375
7461
|
}
|
|
7376
7462
|
} catch {
|
|
7377
7463
|
}
|
|
@@ -7742,6 +7828,7 @@ function extractCatFilesMulti(cmd) {
|
|
|
7742
7828
|
}
|
|
7743
7829
|
var POWERSHELL_WRAP_RE = /^(?:powershell|pwsh)(?:\.exe)?(?:\s+-[a-zA-Z]+(?:\s+\S+)?)*\s+(?:-Command|-c|-EncodedCommand)\s+(?:"([^"]*)"|'([^']*)')\s*$/i;
|
|
7744
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;
|
|
7745
7832
|
var PS_TEMP_READ_FLOOD_BYTES = 16 * 1024;
|
|
7746
7833
|
function isLargeFileOnDisk(filePath, floor) {
|
|
7747
7834
|
try {
|
|
@@ -7764,6 +7851,24 @@ function extractPowerShellWrappedGetContent(cmd) {
|
|
|
7764
7851
|
if (isTempPath(filePath) && !isLargeFileOnDisk(filePath, PS_TEMP_READ_FLOOD_BYTES)) return null;
|
|
7765
7852
|
return { filePath, ...flags };
|
|
7766
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
|
+
}
|
|
7767
7872
|
function extractRgSymbolSearch(cmd) {
|
|
7768
7873
|
if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
|
|
7769
7874
|
if (!/-n\b/.test(cmd)) return null;
|
|
@@ -7917,57 +8022,75 @@ function pythonOpenPathsAreAllLiteral(text) {
|
|
|
7917
8022
|
const calls = pythonOpenCalls(text);
|
|
7918
8023
|
return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
|
|
7919
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");
|
|
7920
8027
|
function extractPythonFileRead(cmd) {
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
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);
|
|
7924
8043
|
if (outputOpen?.[1]) {
|
|
7925
8044
|
const filePath = outputOpen[1];
|
|
7926
8045
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7927
|
-
return { filePath, isDoc: false, isOutputFile: true };
|
|
8046
|
+
return { filePath, isDoc: false, isConfig: false, isEnv: false, isSql: false, isOutputFile: true };
|
|
7928
8047
|
}
|
|
7929
|
-
const
|
|
7930
|
-
|
|
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);
|
|
7931
8058
|
if (heredocMatch) {
|
|
7932
8059
|
const body = heredocMatch[2] ?? "";
|
|
7933
8060
|
if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
|
|
7934
|
-
const heredocOpen =
|
|
8061
|
+
const heredocOpen = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
7935
8062
|
if (heredocOpen?.[1]) {
|
|
7936
8063
|
const filePath = heredocOpen[1];
|
|
7937
8064
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7938
|
-
|
|
7939
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8065
|
+
return classifyResult(filePath);
|
|
7940
8066
|
}
|
|
7941
8067
|
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
7942
|
-
const literal2 =
|
|
8068
|
+
const literal2 = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(body);
|
|
7943
8069
|
if (literal2?.[1]) {
|
|
7944
8070
|
const filePath = literal2[1];
|
|
7945
8071
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7946
8072
|
if (OPEN_EXT.test(filePath)) {
|
|
7947
|
-
|
|
7948
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8073
|
+
return classifyResult(filePath);
|
|
7949
8074
|
}
|
|
7950
8075
|
}
|
|
7951
8076
|
}
|
|
7952
8077
|
return null;
|
|
7953
8078
|
}
|
|
7954
|
-
const direct =
|
|
8079
|
+
const direct = new RegExp(`open\\s*\\(\\s*r?['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
7955
8080
|
if (direct) {
|
|
7956
8081
|
const filePath = direct[1] ?? "";
|
|
7957
8082
|
if (!filePath) return null;
|
|
7958
8083
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7959
|
-
|
|
7960
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8084
|
+
return classifyResult(filePath);
|
|
7961
8085
|
}
|
|
7962
|
-
if (/open\s*\(/.test(
|
|
7963
|
-
const literal2 =
|
|
8086
|
+
if (/open\s*\(/.test(pythonBody) && !pythonOpenPathsAreAllLiteral(pythonBody)) {
|
|
8087
|
+
const literal2 = new RegExp(`['"]([^'"]+\\.(?:${KNOWN_PYTHON_EXT_STR}))['"]`, "i").exec(pythonBody);
|
|
7964
8088
|
if (literal2) {
|
|
7965
8089
|
const filePath = literal2[1] ?? "";
|
|
7966
8090
|
if (filePath) {
|
|
7967
8091
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7968
8092
|
if (OPEN_EXT.test(filePath)) {
|
|
7969
|
-
|
|
7970
|
-
return { filePath, isDoc, isOutputFile: false };
|
|
8093
|
+
return classifyResult(filePath);
|
|
7971
8094
|
}
|
|
7972
8095
|
}
|
|
7973
8096
|
}
|
|
@@ -8290,7 +8413,7 @@ function extractToolResultsFile(cmd) {
|
|
|
8290
8413
|
return null;
|
|
8291
8414
|
}
|
|
8292
8415
|
function extractDirectoryListing(cmd) {
|
|
8293
|
-
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);
|
|
8294
8417
|
}
|
|
8295
8418
|
function extractForLoopWcL(cmd) {
|
|
8296
8419
|
return /^for\s+\w+\s+in\s+.*;\s*do\s+wc\s+-l/.test(cmd);
|
|
@@ -8605,6 +8728,73 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
8605
8728
|
const wrapped = `token-goat compress -f ${filterName} --timeout ${cfg.timeout_seconds} -c ${shellQuoteSingle(rawCmd)}`;
|
|
8606
8729
|
return { hookType: "rewriteInput", updatedInput: { ...event.toolInput, command: wrapped } };
|
|
8607
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
|
+
}
|
|
8608
8798
|
function pureFileReadPath(cmd) {
|
|
8609
8799
|
const single = extractCatFile(cmd)?.filePath ?? extractHeadFile(cmd)?.filePath ?? extractTailFile(cmd)?.filePath ?? extractLineRangeRead(cmd)?.filePath;
|
|
8610
8800
|
if (single !== void 0) return single;
|
|
@@ -8628,10 +8818,10 @@ function deliveredLineNumbers(cmd, lineCount) {
|
|
|
8628
8818
|
if (singleFileCompoundReadPath(cmd) !== null) return Array.from({ length: lineCount }, () => null);
|
|
8629
8819
|
return null;
|
|
8630
8820
|
}
|
|
8631
|
-
function elideServedShellLines(cmd, output, priorIds) {
|
|
8821
|
+
function elideServedShellLines(cmd, output, priorIds, unknownLineNumbers = false) {
|
|
8632
8822
|
if (priorIds.length === 0) return null;
|
|
8633
8823
|
const lines = output.split("\n");
|
|
8634
|
-
const numbers = deliveredLineNumbers(cmd, lines.length);
|
|
8824
|
+
const numbers = unknownLineNumbers ? Array.from({ length: lines.length }, () => null) : deliveredLineNumbers(cmd, lines.length);
|
|
8635
8825
|
if (numbers === null) return null;
|
|
8636
8826
|
const rows = lines.map((text, i) => ({ no: numbers[i] ?? null, text, raw: text }));
|
|
8637
8827
|
const bodies = [];
|
|
@@ -8749,6 +8939,26 @@ async function maybeCollapseIdenticalRead(cmd, rawCmd, output, exitCode, cwd, ca
|
|
|
8749
8939
|
if (!isRewriteWorthwhile({ originalBytes, rewrittenBytes: Buffer.byteLength(pointer, "utf-8"), noticeBytes: 0, minNetSavingsBytes: resolveMinNetSavingsBytes() })) return null;
|
|
8750
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) });
|
|
8751
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
|
+
}
|
|
8752
8962
|
async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes) {
|
|
8753
8963
|
if (process.env["TOKEN_GOAT_BASH_COMPRESS"] === "0") return null;
|
|
8754
8964
|
if (isCompressibleSingleCommand(cmd)) return null;
|
|
@@ -8761,7 +8971,8 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8761
8971
|
}
|
|
8762
8972
|
if (!cfg.enabled || cfg.disabled_filters.includes("generic")) return null;
|
|
8763
8973
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return null;
|
|
8764
|
-
const
|
|
8974
|
+
const shaped = pipelineShapeFilter(cmd, cwd);
|
|
8975
|
+
const filter3 = shaped !== null && !cfg.disabled_filters.includes(shaped.name) ? shaped : filterByName("generic");
|
|
8765
8976
|
if (filter3 === null) return null;
|
|
8766
8977
|
const compressed = compressOutput(filter3, output, "", exitCode ?? 0, [], {
|
|
8767
8978
|
maxLines: cfg.max_lines,
|
|
@@ -8783,7 +8994,7 @@ async function maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinB
|
|
|
8783
8994
|
return null;
|
|
8784
8995
|
}
|
|
8785
8996
|
await storeBashOutput(cmd, output, exitCode ?? 0, cwd);
|
|
8786
|
-
return emitRewrite(body, "bash", { kind:
|
|
8997
|
+
return emitRewrite(body, "bash", { kind: `bash_compress:${filter3.name}`, originalBytes: deliveredOutputBytes(compressed.originalBytes) });
|
|
8787
8998
|
}
|
|
8788
8999
|
function maybeStripAnsiOnly(output) {
|
|
8789
9000
|
if (!output.includes("\x1B")) return null;
|
|
@@ -9078,7 +9289,7 @@ function preBashHandlerInner(event) {
|
|
|
9078
9289
|
recordStat("session_hint", 0, 0);
|
|
9079
9290
|
const hints = [];
|
|
9080
9291
|
for (const { filePath, ranges, tool } of sedReads) {
|
|
9081
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9292
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9082
9293
|
const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
9083
9294
|
const overlapHints = [];
|
|
9084
9295
|
const freshRanges = [];
|
|
@@ -9099,7 +9310,7 @@ function preBashHandlerInner(event) {
|
|
|
9099
9310
|
const catJsonPipe = extractCatJsonPipe(cmd);
|
|
9100
9311
|
if (catJsonPipe !== null) {
|
|
9101
9312
|
const { filePath } = catJsonPipe;
|
|
9102
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9313
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9103
9314
|
recordStat("session_hint", 0, 0);
|
|
9104
9315
|
return contextOutput(
|
|
9105
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.'
|
|
@@ -9108,7 +9319,7 @@ function preBashHandlerInner(event) {
|
|
|
9108
9319
|
const catResult = extractCatFile(cmd);
|
|
9109
9320
|
if (catResult !== null) {
|
|
9110
9321
|
const { filePath, isDoc, isEnv, isConfig, isSql, cmd0, advisoryOnly } = catResult;
|
|
9111
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9322
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9112
9323
|
recordStat("session_hint", 0, 0);
|
|
9113
9324
|
if (isSql) {
|
|
9114
9325
|
return contextOutput(
|
|
@@ -9123,7 +9334,7 @@ function preBashHandlerInner(event) {
|
|
|
9123
9334
|
recordStat("session_hint", 0, 0);
|
|
9124
9335
|
const cmd0 = catMulti[0].cmd0;
|
|
9125
9336
|
const perPath = catMulti.map(({ filePath, isDoc, isEnv, isConfig, isSql }) => {
|
|
9126
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9337
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9127
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"';
|
|
9128
9339
|
return " " + hintPath + " -> `" + how + "`";
|
|
9129
9340
|
});
|
|
@@ -9133,7 +9344,7 @@ function preBashHandlerInner(event) {
|
|
|
9133
9344
|
const psGetContentResult = extractPowerShellWrappedGetContent(cmd);
|
|
9134
9345
|
if (psGetContentResult !== null) {
|
|
9135
9346
|
const { filePath, isDoc, isEnv, isConfig, isSql } = psGetContentResult;
|
|
9136
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9347
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9137
9348
|
recordStat("session_hint", 0, 0);
|
|
9138
9349
|
const lead = "`Get-Content` via a `powershell -Command` wrapper bypasses read hooks and loads the entire file into context. ";
|
|
9139
9350
|
if (isSql) {
|
|
@@ -9145,7 +9356,7 @@ function preBashHandlerInner(event) {
|
|
|
9145
9356
|
const wslCatResult = extractWslCatFile(cmd);
|
|
9146
9357
|
if (wslCatResult !== null) {
|
|
9147
9358
|
const { filePath, isDoc, isEnv, isConfig, isSql } = wslCatResult;
|
|
9148
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9359
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9149
9360
|
recordStat("session_hint", 0, 0);
|
|
9150
9361
|
if (isSql) {
|
|
9151
9362
|
return contextOutput(
|
|
@@ -9157,8 +9368,8 @@ function preBashHandlerInner(event) {
|
|
|
9157
9368
|
}
|
|
9158
9369
|
const pyRead = extractPythonFileRead(cmd);
|
|
9159
9370
|
if (pyRead !== null) {
|
|
9160
|
-
const { filePath, isDoc, isOutputFile } = pyRead;
|
|
9161
|
-
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);
|
|
9162
9373
|
recordStat("session_hint", 0, 0);
|
|
9163
9374
|
if (isOutputFile) {
|
|
9164
9375
|
if (taskOutputIsJsonlTranscript(filePath)) {
|
|
@@ -9169,41 +9380,46 @@ function preBashHandlerInner(event) {
|
|
|
9169
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.'
|
|
9170
9381
|
);
|
|
9171
9382
|
}
|
|
9172
|
-
|
|
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);
|
|
9173
9389
|
return cdStripped ? contextOutput("Python `open()` file reads bypass read hooks. " + hint) : denyOutput("Python `open()` file reads bypass read hooks. " + hint);
|
|
9174
9390
|
}
|
|
9175
9391
|
const tailResult = extractTailFile(cmd);
|
|
9176
9392
|
if (tailResult !== null) {
|
|
9177
9393
|
const { filePath, isDoc, isConfig, isSql } = tailResult;
|
|
9178
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9394
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9179
9395
|
recordStat("session_hint", 0, 0);
|
|
9180
9396
|
return contextOutput("`tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
|
|
9181
9397
|
}
|
|
9182
9398
|
const headResult = extractHeadFile(cmd);
|
|
9183
9399
|
if (headResult !== null) {
|
|
9184
9400
|
const { filePath, isDoc, isConfig, isSql, n } = headResult;
|
|
9185
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9401
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9186
9402
|
recordStat("session_hint", 0, 0);
|
|
9187
9403
|
return contextOutput(leadingLinesHint("`head` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
9188
9404
|
}
|
|
9189
9405
|
const gcTailResult = extractGetContentTail(cmd);
|
|
9190
9406
|
if (gcTailResult !== null) {
|
|
9191
9407
|
const { filePath, isDoc, isConfig, isSql } = gcTailResult;
|
|
9192
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9408
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9193
9409
|
recordStat("session_hint", 0, 0);
|
|
9194
9410
|
return contextOutput("`Get-Content -Tail` bypasses read hooks. " + surgicalHintForConfigDoc(hintPath, isConfig, isDoc, isSql));
|
|
9195
9411
|
}
|
|
9196
9412
|
const gcSelectResult = extractGetContentSelectFirst(cmd);
|
|
9197
9413
|
if (gcSelectResult !== null) {
|
|
9198
9414
|
const { filePath, isDoc, isConfig, isSql, n } = gcSelectResult;
|
|
9199
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9415
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9200
9416
|
recordStat("session_hint", 0, 0);
|
|
9201
9417
|
return contextOutput(leadingLinesHint("`Select-Object -First` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
9202
9418
|
}
|
|
9203
9419
|
const nodeRead = extractNodeFileRead(cmd);
|
|
9204
9420
|
if (nodeRead !== null) {
|
|
9205
9421
|
const { filePath, isDoc, isConfig, isSql } = nodeRead;
|
|
9206
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9422
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9207
9423
|
const lead = "Node.js `fs.readFileSync()` bypasses read hooks. ";
|
|
9208
9424
|
if (isSql) {
|
|
9209
9425
|
recordStat("session_hint", 0, 0);
|
|
@@ -9213,6 +9429,19 @@ function preBashHandlerInner(event) {
|
|
|
9213
9429
|
recordStat("session_hint", 0, 0);
|
|
9214
9430
|
return cdStripped ? contextOutput(lead + hint) : denyOutput(lead + hint);
|
|
9215
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
|
+
}
|
|
9216
9445
|
if (extractGrepPipeChain(cmd)) {
|
|
9217
9446
|
recordStat("session_hint", 0, 0);
|
|
9218
9447
|
return contextOutput(
|
|
@@ -9222,7 +9451,7 @@ function preBashHandlerInner(event) {
|
|
|
9222
9451
|
const mdHeadingGrep = extractMarkdownHeadingGrep(cmd);
|
|
9223
9452
|
if (mdHeadingGrep !== null) {
|
|
9224
9453
|
const { filePath } = mdHeadingGrep;
|
|
9225
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9454
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9226
9455
|
recordStat("session_hint", 0, 0);
|
|
9227
9456
|
return contextOutput(
|
|
9228
9457
|
'Use `token-goat outline "' + hintPath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + hintPath + '::Heading"` to read one section.'
|
|
@@ -9239,7 +9468,7 @@ function preBashHandlerInner(event) {
|
|
|
9239
9468
|
const rgStructural = extractRgStructuralSearch(cmd);
|
|
9240
9469
|
if (rgStructural !== null) {
|
|
9241
9470
|
const { filePath } = rgStructural;
|
|
9242
|
-
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
9471
|
+
const hintPath = displaySafePath(cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath);
|
|
9243
9472
|
recordStat("session_hint", 0, 0);
|
|
9244
9473
|
return contextOutput(
|
|
9245
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.'
|
|
@@ -9443,6 +9672,21 @@ function recordBashFileReadsForSessionCache(cmd, cwd) {
|
|
|
9443
9672
|
recordFileRead(resolve(wslCat.filePath));
|
|
9444
9673
|
return;
|
|
9445
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
|
+
}
|
|
9446
9690
|
}
|
|
9447
9691
|
async function postBashHandler(event) {
|
|
9448
9692
|
try {
|
|
@@ -9538,6 +9782,10 @@ async function postBashHandler(event) {
|
|
|
9538
9782
|
if (identical !== null) return identical;
|
|
9539
9783
|
const compound = await maybeCompressCompoundOutput(cmd, output, exitCode, cwd, cacheMinBytes);
|
|
9540
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
|
+
}
|
|
9541
9789
|
return maybeStripAnsiOnly(output) ?? passOutput();
|
|
9542
9790
|
}
|
|
9543
9791
|
if (Buffer.byteLength(output, "utf-8") < cacheMinBytes) return passOutput();
|
|
@@ -9794,7 +10042,7 @@ function writeLedger(target, ledger) {
|
|
|
9794
10042
|
}
|
|
9795
10043
|
}
|
|
9796
10044
|
function repeatFailureNotice(toolName) {
|
|
9797
|
-
const tool = toolName === void 0 || toolName === "" ? "This tool" : toolName;
|
|
10045
|
+
const tool = toolName === void 0 || toolName === "" ? "This tool" : displaySafeText(toolName);
|
|
9798
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.`;
|
|
9799
10047
|
}
|
|
9800
10048
|
function postToolUseFailureHandler(event) {
|
|
@@ -10722,13 +10970,16 @@ var SPAWN_RESTRICT_HINT_KEY = "agent-spawn-restrict-hint";
|
|
|
10722
10970
|
var SPAWN_RESTRICT_MAX_NAMES = 3;
|
|
10723
10971
|
var ROSTER_WALK_MAX_DEPTH = 4;
|
|
10724
10972
|
var ROSTER_WALK_MAX_FILES = 400;
|
|
10973
|
+
var AGENT_NAME_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
10725
10974
|
function parseAgentDefinition(text, fallbackName) {
|
|
10726
10975
|
const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
10727
10976
|
if (!fmMatch) return null;
|
|
10728
10977
|
const fm = fmMatch[1];
|
|
10729
10978
|
const nameMatch = /^name:(.*)$/m.exec(fm);
|
|
10730
10979
|
const rawName = nameMatch ? nameMatch[1].trim().replace(/^["']|["']$/g, "") : "";
|
|
10731
|
-
const
|
|
10980
|
+
const resolved = AGENT_NAME_RE.test(rawName) ? rawName : fallbackName;
|
|
10981
|
+
if (!AGENT_NAME_RE.test(resolved)) return null;
|
|
10982
|
+
const name2 = resolved;
|
|
10732
10983
|
const toolsMatch = /^tools:(.*)$/m.exec(fm);
|
|
10733
10984
|
if (!toolsMatch) return { name: name2, restricted: false };
|
|
10734
10985
|
const inline = toolsMatch[1].trim();
|
|
@@ -10742,7 +10993,7 @@ function parseAgentDefinition(text, fallbackName) {
|
|
|
10742
10993
|
return { name: name2, restricted: false };
|
|
10743
10994
|
}
|
|
10744
10995
|
function findRestrictedAgentNames(roots) {
|
|
10745
|
-
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")];
|
|
10746
10997
|
const names = /* @__PURE__ */ new Set();
|
|
10747
10998
|
const visited = /* @__PURE__ */ new Set();
|
|
10748
10999
|
let filesSeen = 0;
|
|
@@ -10802,7 +11053,7 @@ function buildUnrestrictedSpawnAdvisory(toolInput) {
|
|
|
10802
11053
|
if (names.length === 0) return "";
|
|
10803
11054
|
markHintShown(SPAWN_RESTRICT_HINT_KEY);
|
|
10804
11055
|
recordStat("session_hint", 0, 0, void 0, "agent-spawn-restrict");
|
|
10805
|
-
const shown = names.slice(0, SPAWN_RESTRICT_MAX_NAMES).join(", ");
|
|
11056
|
+
const shown = neutralizeSpokenMarkers(names.slice(0, SPAWN_RESTRICT_MAX_NAMES).join(", "));
|
|
10806
11057
|
const more = names.length > SPAWN_RESTRICT_MAX_NAMES ? ` and ${names.length - SPAWN_RESTRICT_MAX_NAMES} more` : "";
|
|
10807
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.`;
|
|
10808
11059
|
} catch {
|