token-goat 2.6.35 → 2.8.0
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 +35 -7
- package/SECURITY.md +47 -16
- package/dist/{token-goat-chunk-I2KCEGIP.mjs → token-goat-chunk-2F6TFBZE.mjs} +1355 -408
- package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
- package/dist/{token-goat-chunk-K7W3P2TJ.mjs → token-goat-chunk-44Y77VHR.mjs} +425 -39
- package/dist/{token-goat-chunk-UIZVX3HN.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
- package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
- package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
- package/dist/{token-goat-chunk-SIHYQCTM.mjs → token-goat-chunk-AO6MFFTW.mjs} +331 -13
- package/dist/{token-goat-chunk-DRAIYVUI.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
- package/dist/{token-goat-chunk-YF67EWRH.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-4POU6OHK.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
- package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
- package/dist/{token-goat-chunk-D5IZEGN2.mjs → token-goat-chunk-SRAR6DOK.mjs} +144 -147
- package/dist/{token-goat-hook-chunk-NIPXIAPA.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
- package/dist/{token-goat-chunk-HW4VUKJ5.mjs → token-goat-chunk-VXSYZGBA.mjs} +589 -141
- package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
- package/dist/token-goat-hook.mjs +7 -7
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +18 -15
- package/scripts/install-git-hooks.mjs +55 -0
- package/dist/token-goat-chunk-DQIT5JIF.mjs +0 -13659
- package/dist/token-goat-chunk-Y2NH6DGZ.mjs +0 -10048
- package/dist/token-goat-hook-chunk-2O7P4Z6Q.mjs +0 -6419
- package/dist/token-goat-hook-chunk-3AAH72SO.mjs +0 -15970
- package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
- package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
- package/dist/token-goat-hook-chunk-CRBMPT74.mjs +0 -900
- package/dist/token-goat-hook-chunk-D6UJEFHX.mjs +0 -153
- package/dist/token-goat-hook-chunk-J4HKWUWZ.mjs +0 -23
- package/dist/token-goat-hook-chunk-NXVT4F3S.mjs +0 -10406
- package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
- package/dist/token-goat-hook-chunk-VU6VGZBM.mjs +0 -706
- package/dist/token-goat-hook-chunk-XM4W3WCO.mjs +0 -18144
- package/dist/token-goat-hook-chunk-XUMIVYEN.mjs +0 -109
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
canRunWrappedShell,
|
|
5
|
-
compressOutput,
|
|
6
|
-
dedupeConsecutive,
|
|
7
|
-
detectFromCommand,
|
|
8
|
-
filterByName,
|
|
9
|
-
hasBareBackgroundOrNewline,
|
|
10
|
-
isRewriteWorthwhile,
|
|
11
|
-
resolveMinNetSavingsBytes,
|
|
12
|
-
shlexSplit
|
|
13
|
-
} from "./token-goat-hook-chunk-NIPXIAPA.mjs";
|
|
14
3
|
import {
|
|
15
4
|
BASH_OUTPUT_SUBDIR,
|
|
16
5
|
HOOK_EVENTS,
|
|
6
|
+
accumulateResidentLines,
|
|
17
7
|
buildCommandManifest,
|
|
18
8
|
checkSymbolBodySize,
|
|
19
9
|
commandHash,
|
|
@@ -23,12 +13,17 @@ import {
|
|
|
23
13
|
indexRecallEntry,
|
|
24
14
|
isBashEntryStale,
|
|
25
15
|
isScopedGitStatusOrDiffStatCommand,
|
|
16
|
+
lineMayCarryResidentSignal,
|
|
26
17
|
normalizePayload,
|
|
27
18
|
readStdinJson,
|
|
19
|
+
readTranscriptTail,
|
|
20
|
+
repeatedSkillBodyHint,
|
|
28
21
|
storeBashOutput,
|
|
29
22
|
storeWebOutput,
|
|
30
|
-
summarizeOutputDelta
|
|
31
|
-
|
|
23
|
+
summarizeOutputDelta,
|
|
24
|
+
summarizeResidentContext,
|
|
25
|
+
taskListPruneHint
|
|
26
|
+
} from "./token-goat-chunk-AO6MFFTW.mjs";
|
|
32
27
|
import {
|
|
33
28
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
34
29
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
@@ -49,6 +44,7 @@ import {
|
|
|
49
44
|
enqueueDirtyPathSafe,
|
|
50
45
|
ensureWorkerAlive,
|
|
51
46
|
estimateTokens,
|
|
47
|
+
estimateTokensFromLength,
|
|
52
48
|
extractCompactFromMarker,
|
|
53
49
|
extractToolResponseField,
|
|
54
50
|
extractToolResultText,
|
|
@@ -72,7 +68,9 @@ import {
|
|
|
72
68
|
getSessionWebFetches,
|
|
73
69
|
getToolInput,
|
|
74
70
|
getToolName,
|
|
71
|
+
hasSeenImage,
|
|
75
72
|
hasSessionOutput,
|
|
73
|
+
imageQualifiesForShrink,
|
|
76
74
|
incrementSkillHit,
|
|
77
75
|
installedSkillPath,
|
|
78
76
|
isBuildCommand,
|
|
@@ -102,6 +100,7 @@ import {
|
|
|
102
100
|
recordKnownRootThrottled,
|
|
103
101
|
recordOutstandingAgentSpawn,
|
|
104
102
|
recordScheduledPrompt,
|
|
103
|
+
recordSeenImage,
|
|
105
104
|
recordSymbolRead,
|
|
106
105
|
recordWebFetch,
|
|
107
106
|
registerHook,
|
|
@@ -110,6 +109,7 @@ import {
|
|
|
110
109
|
saveSessionState,
|
|
111
110
|
scanForInjectionPatterns,
|
|
112
111
|
serializeOutput,
|
|
112
|
+
sessionSidecarPath,
|
|
113
113
|
setLastTabContext,
|
|
114
114
|
shrinkImage,
|
|
115
115
|
storeBlob,
|
|
@@ -118,7 +118,18 @@ import {
|
|
|
118
118
|
wasCliReadThisSession,
|
|
119
119
|
wasFileReadThisSession,
|
|
120
120
|
wasHintShown
|
|
121
|
-
} from "./token-goat-
|
|
121
|
+
} from "./token-goat-chunk-2F6TFBZE.mjs";
|
|
122
|
+
import {
|
|
123
|
+
canRunWrappedShell,
|
|
124
|
+
compressOutput,
|
|
125
|
+
dedupeConsecutive,
|
|
126
|
+
detectFromCommand,
|
|
127
|
+
filterByName,
|
|
128
|
+
hasBareBackgroundOrNewline,
|
|
129
|
+
isRewriteWorthwhile,
|
|
130
|
+
resolveMinNetSavingsBytes,
|
|
131
|
+
shlexSplit
|
|
132
|
+
} from "./token-goat-chunk-TUPJRK7R.mjs";
|
|
122
133
|
import {
|
|
123
134
|
VERSION,
|
|
124
135
|
detectHarness,
|
|
@@ -127,6 +138,7 @@ import {
|
|
|
127
138
|
extractErrorMessage,
|
|
128
139
|
foldPath,
|
|
129
140
|
getDb,
|
|
141
|
+
getHarnessName,
|
|
130
142
|
globalDbPath,
|
|
131
143
|
isUnderSystemTemp,
|
|
132
144
|
loadConfig,
|
|
@@ -137,7 +149,7 @@ import {
|
|
|
137
149
|
runGit,
|
|
138
150
|
shortFingerprint,
|
|
139
151
|
toKB
|
|
140
|
-
} from "./token-goat-
|
|
152
|
+
} from "./token-goat-chunk-44Y77VHR.mjs";
|
|
141
153
|
|
|
142
154
|
// src/hooks_grep.ts
|
|
143
155
|
function grepIntInput(toolInput, key) {
|
|
@@ -561,12 +573,45 @@ function buildMemEpochSection() {
|
|
|
561
573
|
`mem epoch: ${stdout} (no live TGMEM block is tracked in this session -- only the epoch counter is folded in; see buildMemEpochSection's doc comment)`
|
|
562
574
|
];
|
|
563
575
|
}
|
|
576
|
+
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.";
|
|
564
577
|
function preCompactHandler(event) {
|
|
565
|
-
const out = loadConfig().compact_assist.enabled ? contextOutput(
|
|
578
|
+
const out = loadConfig().compact_assist.enabled ? contextOutput(`${MANIFEST_PREAMBLE}
|
|
579
|
+
|
|
580
|
+
${buildManifest(event.sessionId, getCwd(event))}`) : passOutput();
|
|
566
581
|
markCompacted();
|
|
567
582
|
return out;
|
|
568
583
|
}
|
|
569
584
|
registerHook("pre_compact", preCompactHandler);
|
|
585
|
+
var MANIFEST_SURVIVAL_SAMPLE = 12;
|
|
586
|
+
function manifestPathSample(sessionId) {
|
|
587
|
+
const ownFiles = [...getSessionFiles().values()];
|
|
588
|
+
const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
|
|
589
|
+
const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
|
|
590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
591
|
+
for (const entry of files) {
|
|
592
|
+
if (entry.path) seen.add(entry.path);
|
|
593
|
+
if (seen.size >= MANIFEST_SURVIVAL_SAMPLE) break;
|
|
594
|
+
}
|
|
595
|
+
return [...seen];
|
|
596
|
+
}
|
|
597
|
+
function postCompactHandler(event) {
|
|
598
|
+
const raw = event.raw["compact_summary"];
|
|
599
|
+
const summary = typeof raw === "string" ? raw : "";
|
|
600
|
+
const bytes = Buffer.byteLength(summary, "utf-8");
|
|
601
|
+
const sample = manifestPathSample(event.sessionId);
|
|
602
|
+
const haystack = foldPath(summary);
|
|
603
|
+
const survived = sample.filter((p) => haystack.includes(foldPath(p))).length;
|
|
604
|
+
const trigger = typeof event.raw["trigger"] === "string" ? event.raw["trigger"] : "unknown";
|
|
605
|
+
recordStat(
|
|
606
|
+
"compact_summary",
|
|
607
|
+
0,
|
|
608
|
+
0,
|
|
609
|
+
void 0,
|
|
610
|
+
`trigger=${trigger} bytes=${bytes} est_tokens=${estimateTokensFromLength(summary.length)} manifest_paths=${survived}/${sample.length}`
|
|
611
|
+
);
|
|
612
|
+
return passOutput();
|
|
613
|
+
}
|
|
614
|
+
registerHook("post_compact", postCompactHandler);
|
|
570
615
|
|
|
571
616
|
// src/hooks_session.ts
|
|
572
617
|
import crypto from "node:crypto";
|
|
@@ -575,7 +620,7 @@ import crypto from "node:crypto";
|
|
|
575
620
|
var TRACKED_SKILL = "token-goat";
|
|
576
621
|
var MAX_COMMANDS_SHOWN = 8;
|
|
577
622
|
async function currentCommandNames() {
|
|
578
|
-
const { buildProgram } = await import("./token-goat-
|
|
623
|
+
const { buildProgram } = await import("./token-goat-chunk-J35AKWEQ.mjs");
|
|
579
624
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
580
625
|
}
|
|
581
626
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -627,7 +672,59 @@ async function checkSkillVersionDrift(sessionId) {
|
|
|
627
672
|
}
|
|
628
673
|
}
|
|
629
674
|
|
|
675
|
+
// src/pending_context.ts
|
|
676
|
+
import { readFileSync as readFileSync2, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
677
|
+
import { dirname } from "node:path";
|
|
678
|
+
var PENDING_SUFFIX = ".pending-context.txt";
|
|
679
|
+
var MAX_PENDING_CONTEXT_BYTES = 4096;
|
|
680
|
+
function queuePendingContext(sessionId, text) {
|
|
681
|
+
const trimmed = text.trim();
|
|
682
|
+
if (trimmed === "") return;
|
|
683
|
+
const target = sessionSidecarPath(sessionId, PENDING_SUFFIX);
|
|
684
|
+
if (target === null) return;
|
|
685
|
+
try {
|
|
686
|
+
const existing = readPending(target);
|
|
687
|
+
const merged = existing === null ? trimmed : `${existing}
|
|
688
|
+
${trimmed}`;
|
|
689
|
+
const capped = merged.length <= MAX_PENDING_CONTEXT_BYTES ? merged : merged.slice(merged.length - MAX_PENDING_CONTEXT_BYTES);
|
|
690
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
691
|
+
writeFileSync(target, capped, "utf8");
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function drainPendingContext(sessionId) {
|
|
696
|
+
const target = sessionSidecarPath(sessionId, PENDING_SUFFIX);
|
|
697
|
+
if (target === null) return null;
|
|
698
|
+
const text = readPending(target);
|
|
699
|
+
try {
|
|
700
|
+
rmSync(target, { force: true });
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
return text;
|
|
704
|
+
}
|
|
705
|
+
function readPending(target) {
|
|
706
|
+
try {
|
|
707
|
+
const raw = readFileSync2(target, "utf8").trim();
|
|
708
|
+
return raw === "" ? null : raw;
|
|
709
|
+
} catch {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
630
714
|
// src/hooks_session.ts
|
|
715
|
+
var PROMPT_SUBMIT_CONTEXT_DROPPED = /* @__PURE__ */ new Set([]);
|
|
716
|
+
function dropsPromptSubmitContext() {
|
|
717
|
+
return PROMPT_SUBMIT_CONTEXT_DROPPED.has(getHarnessName());
|
|
718
|
+
}
|
|
719
|
+
function pendingContextHandler(event) {
|
|
720
|
+
try {
|
|
721
|
+
if (!event.sessionId || !dropsPromptSubmitContext()) return passOutput();
|
|
722
|
+
const pending = drainPendingContext(event.sessionId);
|
|
723
|
+
return pending === null ? passOutput() : contextOutput(pending);
|
|
724
|
+
} catch {
|
|
725
|
+
return passOutput();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
631
728
|
var EMBEDDED_SKILL_CONTEXT_RE = /^\s*<skill-context\b/i;
|
|
632
729
|
var CONTINUATION_PROMPT_RE = /^(?:continue|resume|next|go on)$/i;
|
|
633
730
|
var SCHEDULED_PROMPT_RE = /^\[Scheduled prompt #(\d+)\]/i;
|
|
@@ -635,6 +732,35 @@ var SCHEDULED_PROMPT_PRESSURE_THRESHOLDS = /* @__PURE__ */ new Set([25, 100, 250
|
|
|
635
732
|
function promptFingerprint(prompt) {
|
|
636
733
|
return crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 16);
|
|
637
734
|
}
|
|
735
|
+
function residentContextHints(event) {
|
|
736
|
+
const transcriptPath = event.raw["transcript_path"];
|
|
737
|
+
if (typeof transcriptPath !== "string" || transcriptPath === "") return [];
|
|
738
|
+
const taskKey = `resident-task-list:${event.sessionId}`;
|
|
739
|
+
const skillKey = `resident-skill-body:${event.sessionId}`;
|
|
740
|
+
const wantTask = !wasHintShown(taskKey);
|
|
741
|
+
const wantSkill = !wasHintShown(skillKey);
|
|
742
|
+
if (!wantTask && !wantSkill) return [];
|
|
743
|
+
const lines = readTranscriptTail(transcriptPath).filter(lineMayCarryResidentSignal);
|
|
744
|
+
if (lines.length === 0) return [];
|
|
745
|
+
const summary = summarizeResidentContext(accumulateResidentLines(lines));
|
|
746
|
+
const hints = [];
|
|
747
|
+
if (wantTask) {
|
|
748
|
+
const hint = taskListPruneHint(summary.latestTaskList);
|
|
749
|
+
if (hint !== null) {
|
|
750
|
+
markHintShown(taskKey);
|
|
751
|
+
hints.push(hint);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (wantSkill) {
|
|
755
|
+
const hint = repeatedSkillBodyHint(summary.repeatedSkillBodies);
|
|
756
|
+
if (hint !== null) {
|
|
757
|
+
markHintShown(skillKey);
|
|
758
|
+
hints.push(hint);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (hints.length > 0) recordStat("session_hint", 0, 0);
|
|
762
|
+
return hints;
|
|
763
|
+
}
|
|
638
764
|
async function userPromptSubmitHandler(event) {
|
|
639
765
|
try {
|
|
640
766
|
const rawPrompt = event.raw["prompt"] || "";
|
|
@@ -685,7 +811,8 @@ async function userPromptSubmitHandler(event) {
|
|
|
685
811
|
}
|
|
686
812
|
}
|
|
687
813
|
const driftNudge = await checkSkillVersionDrift(event.sessionId);
|
|
688
|
-
|
|
814
|
+
const residentHints = residentContextHints(event);
|
|
815
|
+
if (parts.length === 0 && !driftNudge && residentHints.length === 0) {
|
|
689
816
|
return passOutput();
|
|
690
817
|
}
|
|
691
818
|
const lines = [];
|
|
@@ -695,7 +822,15 @@ async function userPromptSubmitHandler(event) {
|
|
|
695
822
|
if (driftNudge) {
|
|
696
823
|
lines.push(driftNudge);
|
|
697
824
|
}
|
|
698
|
-
|
|
825
|
+
for (const hint of residentHints) {
|
|
826
|
+
lines.push(hint);
|
|
827
|
+
}
|
|
828
|
+
const text = lines.join("\n");
|
|
829
|
+
if (dropsPromptSubmitContext()) {
|
|
830
|
+
queuePendingContext(event.sessionId, text);
|
|
831
|
+
return passOutput();
|
|
832
|
+
}
|
|
833
|
+
return contextOutput(text);
|
|
699
834
|
} catch {
|
|
700
835
|
return passOutput();
|
|
701
836
|
}
|
|
@@ -736,6 +871,7 @@ function subagentStopHandler(event) {
|
|
|
736
871
|
}
|
|
737
872
|
registerHook("user_prompt_submit", userPromptSubmitHandler);
|
|
738
873
|
registerHook("subagent_stop", subagentStopHandler);
|
|
874
|
+
registerHook("post_tool_use", pendingContextHandler, { advisory: true, followsMatcher: true });
|
|
739
875
|
|
|
740
876
|
// src/hooks_session_start.ts
|
|
741
877
|
var GENERIC_REMINDER = 'token-goat: prefer surgical reads over the Read/Grep tools on this codebase; shell commands like `rg`, `grep`, `fd`, `sed`, `cat`, `find`, and `ls` are just commands, not tool names -- `token-goat symbol <name>`, `token-goat read "file::symbol"`, `token-goat section "file::Heading"`, `token-goat semantic "description"`, `token-goat outline <file>`. Run `token-goat index .` if this project is not indexed yet.';
|
|
@@ -7049,7 +7185,7 @@ registerHook("pre_tool_use", preSkillHandler, { toolName: "Skill" });
|
|
|
7049
7185
|
registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
|
|
7050
7186
|
|
|
7051
7187
|
// src/hooks_bash.ts
|
|
7052
|
-
import { statSync as statSync3, existsSync,
|
|
7188
|
+
import { statSync as statSync3, existsSync, openSync, readSync, closeSync } from "node:fs";
|
|
7053
7189
|
function stripCdPrefix(cmd) {
|
|
7054
7190
|
const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
|
|
7055
7191
|
return stripped.trim() || cmd;
|
|
@@ -7563,7 +7699,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7563
7699
|
if (outputOpen?.[1]) {
|
|
7564
7700
|
const filePath = outputOpen[1];
|
|
7565
7701
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7566
|
-
return { filePath, isDoc: false,
|
|
7702
|
+
return { filePath, isDoc: false, isOutputFile: true };
|
|
7567
7703
|
}
|
|
7568
7704
|
const OPEN_EXT = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|ps1|psm1)/i;
|
|
7569
7705
|
const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
|
|
@@ -7575,7 +7711,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7575
7711
|
const filePath = heredocOpen[1];
|
|
7576
7712
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7577
7713
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7578
|
-
return { filePath, isDoc,
|
|
7714
|
+
return { filePath, isDoc, isOutputFile: false };
|
|
7579
7715
|
}
|
|
7580
7716
|
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
7581
7717
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
|
|
@@ -7584,7 +7720,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7584
7720
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7585
7721
|
if (OPEN_EXT.test(filePath)) {
|
|
7586
7722
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7587
|
-
return { filePath, isDoc,
|
|
7723
|
+
return { filePath, isDoc, isOutputFile: false };
|
|
7588
7724
|
}
|
|
7589
7725
|
}
|
|
7590
7726
|
}
|
|
@@ -7596,7 +7732,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7596
7732
|
if (!filePath) return null;
|
|
7597
7733
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7598
7734
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7599
|
-
return { filePath, isDoc,
|
|
7735
|
+
return { filePath, isDoc, isOutputFile: false };
|
|
7600
7736
|
}
|
|
7601
7737
|
if (/open\s*\(/.test(cmd) && !pythonOpenPathsAreAllLiteral(cmd)) {
|
|
7602
7738
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(cmd);
|
|
@@ -7606,7 +7742,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7606
7742
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7607
7743
|
if (OPEN_EXT.test(filePath)) {
|
|
7608
7744
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7609
|
-
return { filePath, isDoc,
|
|
7745
|
+
return { filePath, isDoc, isOutputFile: false };
|
|
7610
7746
|
}
|
|
7611
7747
|
}
|
|
7612
7748
|
}
|
|
@@ -7715,6 +7851,12 @@ function sedRangeHint(filePath, ranges, tool) {
|
|
|
7715
7851
|
}
|
|
7716
7852
|
return prefix + "Use " + allReads + " to read exactly those lines.";
|
|
7717
7853
|
}
|
|
7854
|
+
function leadingLinesHint(lead, hintPath, start, end, flags, preHookCwd) {
|
|
7855
|
+
const key = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
7856
|
+
const prior = findRangeOverlap(getFileLineRanges(key), start, end);
|
|
7857
|
+
if (prior !== null) return sedOverlapHint(hintPath, prior, start, end);
|
|
7858
|
+
return lead + surgicalHintForConfigDoc(hintPath, flags.isConfig, flags.isDoc, flags.isSql);
|
|
7859
|
+
}
|
|
7718
7860
|
function findRangeOverlap(prior, start, end) {
|
|
7719
7861
|
let best = null;
|
|
7720
7862
|
let bestOverlap = 0;
|
|
@@ -7740,9 +7882,9 @@ function sedOverlapHint(filePath, prior, start, end) {
|
|
|
7740
7882
|
}
|
|
7741
7883
|
function extractNodeFileRead(cmd) {
|
|
7742
7884
|
if (!/^node\s+-e/.test(cmd)) return null;
|
|
7743
|
-
const
|
|
7744
|
-
if (
|
|
7745
|
-
const filePath =
|
|
7885
|
+
const readSync2 = /readFileSync\(['"]([^'"]+\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql))['"]/i.exec(cmd);
|
|
7886
|
+
if (readSync2?.[1]) {
|
|
7887
|
+
const filePath = readSync2[1];
|
|
7746
7888
|
if (isOrchestratorStateFile(filePath)) return null;
|
|
7747
7889
|
if (isTempPath(filePath)) return null;
|
|
7748
7890
|
const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
|
|
@@ -7802,6 +7944,24 @@ function extractGetContentSelectFirst(cmd) {
|
|
|
7802
7944
|
const { isDoc, isConfig, isSql } = classifyDocConfig(filePath);
|
|
7803
7945
|
return { filePath, isDoc, isConfig, isSql, n };
|
|
7804
7946
|
}
|
|
7947
|
+
function taskOutputIsJsonlTranscript(outPath) {
|
|
7948
|
+
let fd = null;
|
|
7949
|
+
try {
|
|
7950
|
+
fd = openSync(normalizePath(outPath), "r");
|
|
7951
|
+
const buf = Buffer.alloc(64);
|
|
7952
|
+
const read = readSync(fd, buf, 0, buf.length, 0);
|
|
7953
|
+
return buf.subarray(0, read).toString("utf-8").trim().startsWith("{");
|
|
7954
|
+
} catch {
|
|
7955
|
+
return false;
|
|
7956
|
+
} finally {
|
|
7957
|
+
if (fd !== null) {
|
|
7958
|
+
try {
|
|
7959
|
+
closeSync(fd);
|
|
7960
|
+
} catch {
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7805
7965
|
function extractTasksOutput(cmd) {
|
|
7806
7966
|
const taskOutputRe = /[/\\]tasks[/\\]([a-z0-9]+)\.output$/;
|
|
7807
7967
|
const catM = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
|
|
@@ -8390,17 +8550,11 @@ function preBashHandlerInner(event) {
|
|
|
8390
8550
|
if (taskOutput !== null) {
|
|
8391
8551
|
const { id, path: outPath, n } = taskOutput;
|
|
8392
8552
|
recordStat("session_hint", 0, 0);
|
|
8393
|
-
|
|
8394
|
-
const
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
const tail = n ?? 50;
|
|
8399
|
-
return denyOutput(
|
|
8400
|
-
"Task output " + id + ' is a JSONL agent transcript on disk. Use `token-goat bash-output --file "' + outPath + '" --transcript` to read the assistant text, then narrow with `--grep PATTERN` or `--tail ' + tail + '`, or read a specific line range (the only way to reach the MIDDLE of a large artifact) with `token-goat read "' + outPath + '@START-END"`, instead of reading the whole file.'
|
|
8401
|
-
);
|
|
8402
|
-
}
|
|
8403
|
-
} catch {
|
|
8553
|
+
if (taskOutputIsJsonlTranscript(outPath)) {
|
|
8554
|
+
const tail = n ?? 50;
|
|
8555
|
+
return denyOutput(
|
|
8556
|
+
"Task output " + id + ' is a JSONL agent transcript on disk. Use `token-goat bash-output --file "' + outPath + '" --transcript` to read the assistant text, then narrow with `--grep PATTERN` or `--tail ' + tail + '`, or read a specific line range (the only way to reach the MIDDLE of a large artifact) with `token-goat read "' + outPath + '@START-END"`, instead of reading the whole file.'
|
|
8557
|
+
);
|
|
8404
8558
|
}
|
|
8405
8559
|
}
|
|
8406
8560
|
const toolResults = extractToolResultsFile(cmd);
|
|
@@ -8519,12 +8673,17 @@ function preBashHandlerInner(event) {
|
|
|
8519
8673
|
}
|
|
8520
8674
|
const pyRead = extractPythonFileRead(cmd);
|
|
8521
8675
|
if (pyRead !== null) {
|
|
8522
|
-
const { filePath, isDoc,
|
|
8676
|
+
const { filePath, isDoc, isOutputFile } = pyRead;
|
|
8523
8677
|
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
8524
8678
|
recordStat("session_hint", 0, 0);
|
|
8525
|
-
if (
|
|
8526
|
-
|
|
8527
|
-
|
|
8679
|
+
if (isOutputFile) {
|
|
8680
|
+
if (taskOutputIsJsonlTranscript(filePath)) {
|
|
8681
|
+
const tHint = 'This `.output` file is a JSONL agent transcript. Use `token-goat bash-output --file "' + hintPath + '" --transcript` to read the assistant text, then narrow with `--grep PATTERN` or `--tail N`, instead of hand-parsing the JSONL.';
|
|
8682
|
+
return cdStripped ? contextOutput(tHint) : denyOutput(tHint);
|
|
8683
|
+
}
|
|
8684
|
+
return contextOutput(
|
|
8685
|
+
"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.'
|
|
8686
|
+
);
|
|
8528
8687
|
}
|
|
8529
8688
|
const hint = isDoc ? 'Use `token-goat section "' + hintPath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + hintPath + '::SymbolName"` to extract a specific symbol.';
|
|
8530
8689
|
return cdStripped ? contextOutput("Python `open()` file reads bypass read hooks. " + hint) : denyOutput("Python `open()` file reads bypass read hooks. " + hint);
|
|
@@ -8538,10 +8697,10 @@ function preBashHandlerInner(event) {
|
|
|
8538
8697
|
}
|
|
8539
8698
|
const headResult = extractHeadFile(cmd);
|
|
8540
8699
|
if (headResult !== null) {
|
|
8541
|
-
const { filePath, isDoc, isConfig, isSql } = headResult;
|
|
8700
|
+
const { filePath, isDoc, isConfig, isSql, n } = headResult;
|
|
8542
8701
|
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
8543
8702
|
recordStat("session_hint", 0, 0);
|
|
8544
|
-
return contextOutput("`head` bypasses read hooks. "
|
|
8703
|
+
return contextOutput(leadingLinesHint("`head` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
8545
8704
|
}
|
|
8546
8705
|
const gcTailResult = extractGetContentTail(cmd);
|
|
8547
8706
|
if (gcTailResult !== null) {
|
|
@@ -8552,10 +8711,10 @@ function preBashHandlerInner(event) {
|
|
|
8552
8711
|
}
|
|
8553
8712
|
const gcSelectResult = extractGetContentSelectFirst(cmd);
|
|
8554
8713
|
if (gcSelectResult !== null) {
|
|
8555
|
-
const { filePath, isDoc, isConfig, isSql } = gcSelectResult;
|
|
8714
|
+
const { filePath, isDoc, isConfig, isSql, n } = gcSelectResult;
|
|
8556
8715
|
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
8557
8716
|
recordStat("session_hint", 0, 0);
|
|
8558
|
-
return contextOutput("`Select-Object -First` bypasses read hooks. "
|
|
8717
|
+
return contextOutput(leadingLinesHint("`Select-Object -First` bypasses read hooks. ", hintPath, 1, n, { isConfig, isDoc, isSql }, preHookCwd));
|
|
8559
8718
|
}
|
|
8560
8719
|
const nodeRead = extractNodeFileRead(cmd);
|
|
8561
8720
|
if (nodeRead !== null) {
|
|
@@ -9092,6 +9251,89 @@ function postTaskOutputHandler(event) {
|
|
|
9092
9251
|
}
|
|
9093
9252
|
registerHook("post_tool_use", postTaskOutputHandler, { toolName: "TaskOutput" });
|
|
9094
9253
|
|
|
9254
|
+
// src/hooks_tool_failure.ts
|
|
9255
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9256
|
+
import { dirname as dirname2 } from "node:path";
|
|
9257
|
+
var FAILURE_SUFFIX = ".tool-failures.json";
|
|
9258
|
+
var MAX_TRACKED_FAILURES = 64;
|
|
9259
|
+
var SIGNATURE_ERROR_CHARS = 200;
|
|
9260
|
+
function failureSignature(toolName, errorText) {
|
|
9261
|
+
const tool = toolName === void 0 || toolName === "" ? "unknown" : toolName;
|
|
9262
|
+
const normalized = errorText.replace(/\s+/g, " ").trim().slice(0, SIGNATURE_ERROR_CHARS);
|
|
9263
|
+
return `${tool} :: ${normalized}`;
|
|
9264
|
+
}
|
|
9265
|
+
function extractFailureText(raw) {
|
|
9266
|
+
for (const key of ["error", "errorMessage", "tool_error", "toolError", "message", "reason"]) {
|
|
9267
|
+
const value = raw[key];
|
|
9268
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
9269
|
+
}
|
|
9270
|
+
const response = raw["tool_response"] ?? raw["toolResponse"];
|
|
9271
|
+
if (typeof response === "string" && response.trim() !== "") return response;
|
|
9272
|
+
if (response !== null && typeof response === "object") {
|
|
9273
|
+
const nested = response["error"];
|
|
9274
|
+
if (typeof nested === "string" && nested.trim() !== "") return nested;
|
|
9275
|
+
}
|
|
9276
|
+
return "";
|
|
9277
|
+
}
|
|
9278
|
+
function ledgerPath(sessionId) {
|
|
9279
|
+
return sessionSidecarPath(sessionId, FAILURE_SUFFIX);
|
|
9280
|
+
}
|
|
9281
|
+
function readLedger(target) {
|
|
9282
|
+
try {
|
|
9283
|
+
const parsed = JSON.parse(readFileSync3(target, "utf8"));
|
|
9284
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
9285
|
+
const seen = parsed.seen;
|
|
9286
|
+
const order = parsed.order;
|
|
9287
|
+
if (seen !== null && typeof seen === "object" && Array.isArray(order)) {
|
|
9288
|
+
return { seen, order: order.filter((k) => typeof k === "string") };
|
|
9289
|
+
}
|
|
9290
|
+
}
|
|
9291
|
+
} catch {
|
|
9292
|
+
}
|
|
9293
|
+
return { seen: {}, order: [] };
|
|
9294
|
+
}
|
|
9295
|
+
function writeLedger(target, ledger) {
|
|
9296
|
+
try {
|
|
9297
|
+
mkdirSync2(dirname2(target), { recursive: true });
|
|
9298
|
+
writeFileSync2(target, JSON.stringify(ledger), "utf8");
|
|
9299
|
+
} catch {
|
|
9300
|
+
}
|
|
9301
|
+
}
|
|
9302
|
+
function repeatFailureNotice(toolName) {
|
|
9303
|
+
const tool = toolName === void 0 || toolName === "" ? "This tool" : toolName;
|
|
9304
|
+
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.`;
|
|
9305
|
+
}
|
|
9306
|
+
function postToolUseFailureHandler(event) {
|
|
9307
|
+
try {
|
|
9308
|
+
if (!event.sessionId) return passOutput();
|
|
9309
|
+
const errorText = extractFailureText(event.raw);
|
|
9310
|
+
if (errorText === "") return passOutput();
|
|
9311
|
+
const target = ledgerPath(event.sessionId);
|
|
9312
|
+
if (target === null) return passOutput();
|
|
9313
|
+
const toolName = getToolName(event);
|
|
9314
|
+
const signature = failureSignature(toolName, errorText);
|
|
9315
|
+
const ledger = readLedger(target);
|
|
9316
|
+
const priorState = ledger.seen[signature];
|
|
9317
|
+
if (priorState === void 0) {
|
|
9318
|
+
ledger.seen[signature] = false;
|
|
9319
|
+
ledger.order.push(signature);
|
|
9320
|
+
while (ledger.order.length > MAX_TRACKED_FAILURES) {
|
|
9321
|
+
const evicted = ledger.order.shift();
|
|
9322
|
+
if (evicted !== void 0) delete ledger.seen[evicted];
|
|
9323
|
+
}
|
|
9324
|
+
writeLedger(target, ledger);
|
|
9325
|
+
return passOutput();
|
|
9326
|
+
}
|
|
9327
|
+
if (priorState) return passOutput();
|
|
9328
|
+
ledger.seen[signature] = true;
|
|
9329
|
+
writeLedger(target, ledger);
|
|
9330
|
+
return contextOutput(repeatFailureNotice(toolName));
|
|
9331
|
+
} catch {
|
|
9332
|
+
return passOutput();
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
registerHook("post_tool_use_failure", postToolUseFailureHandler);
|
|
9336
|
+
|
|
9095
9337
|
// src/hooks_exitplanmode.ts
|
|
9096
9338
|
var PLAN_OMIT_POINTER = "[token-goat: plan body omitted -- identical to this call's own tool_input, already saved to the plans file]";
|
|
9097
9339
|
var APPROVED_PLAN_MARKER = "## Approved Plan:";
|
|
@@ -9646,6 +9888,7 @@ function preScreenshotHandler(event) {
|
|
|
9646
9888
|
registerHook("pre_tool_use", preScreenshotHandler, { toolPattern: "^mcp__" });
|
|
9647
9889
|
|
|
9648
9890
|
// src/hooks_browser_image.ts
|
|
9891
|
+
import { createHash } from "node:crypto";
|
|
9649
9892
|
function getResponseContentBlocks(raw) {
|
|
9650
9893
|
const tr = raw["tool_response"];
|
|
9651
9894
|
if (!tr || typeof tr !== "object") return null;
|
|
@@ -9653,6 +9896,10 @@ function getResponseContentBlocks(raw) {
|
|
|
9653
9896
|
return Array.isArray(content) ? content : null;
|
|
9654
9897
|
}
|
|
9655
9898
|
var TAB_CONTEXT_RE = /^\n*Tab Context:/;
|
|
9899
|
+
var SCREENSHOT_REPEAT_NOTICE = "[token-goat: screenshot identical to one already shown this session; not re-sent. Nothing on the page has changed since.]";
|
|
9900
|
+
function screenshotFingerprint(data) {
|
|
9901
|
+
return createHash("sha256").update(data).digest("hex").slice(0, 32);
|
|
9902
|
+
}
|
|
9656
9903
|
async function shrinkImageBlock(block) {
|
|
9657
9904
|
const source = block.source;
|
|
9658
9905
|
const mediaType = typeof source?.media_type === "string" ? source.media_type : "image/png";
|
|
@@ -9665,7 +9912,25 @@ async function shrinkImageBlock(block) {
|
|
|
9665
9912
|
} catch {
|
|
9666
9913
|
return { text: originalDataUrl, changed: false, savedBytes: 0 };
|
|
9667
9914
|
}
|
|
9668
|
-
const
|
|
9915
|
+
const fingerprint = screenshotFingerprint(data);
|
|
9916
|
+
const alreadyShown = hasSeenImage(fingerprint);
|
|
9917
|
+
recordSeenImage(fingerprint);
|
|
9918
|
+
if (alreadyShown) {
|
|
9919
|
+
const worthwhile = isRewriteWorthwhile({
|
|
9920
|
+
originalBytes: originalDataUrl.length,
|
|
9921
|
+
rewrittenBytes: 0,
|
|
9922
|
+
noticeBytes: Buffer.byteLength(SCREENSHOT_REPEAT_NOTICE, "utf-8"),
|
|
9923
|
+
minNetSavingsBytes: resolveMinNetSavingsBytes()
|
|
9924
|
+
});
|
|
9925
|
+
if (worthwhile) {
|
|
9926
|
+
return {
|
|
9927
|
+
text: SCREENSHOT_REPEAT_NOTICE,
|
|
9928
|
+
changed: true,
|
|
9929
|
+
savedBytes: originalDataUrl.length - SCREENSHOT_REPEAT_NOTICE.length
|
|
9930
|
+
};
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
const result = await imageQualifiesForShrink(buffer) ? await shrinkImage(buffer, { sizeThresholdBytes: 0 }) : null;
|
|
9669
9934
|
if (result === null) return { text: originalDataUrl, changed: false, savedBytes: 0 };
|
|
9670
9935
|
const saved = result.originalBytes - result.shrunkBytes;
|
|
9671
9936
|
const { summary, dataUrl } = formatShrinkSummary(result, "an inline browser screenshot");
|
|
@@ -9725,7 +9990,7 @@ async function postBrowserImageHandler(event) {
|
|
|
9725
9990
|
registerHook("post_tool_use", postBrowserImageHandler, { toolPattern: "^mcp__" });
|
|
9726
9991
|
|
|
9727
9992
|
// src/hooks_agent_spawn.ts
|
|
9728
|
-
import { createHash } from "node:crypto";
|
|
9993
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
9729
9994
|
var BRIEFING_TARGET_TOKENS = 550;
|
|
9730
9995
|
function buildSubagentBriefing() {
|
|
9731
9996
|
try {
|
|
@@ -9870,7 +10135,7 @@ function dedupeFencedBlocks(collapsed, original, recallHint) {
|
|
|
9870
10135
|
const collapsedBlocks = findFencedBlockLines(collapsedLines);
|
|
9871
10136
|
if (collapsedBlocks.length !== originalBlocks.length) return collapsed;
|
|
9872
10137
|
const bodyHashes = originalBlocks.map(
|
|
9873
|
-
({ fenceStart, closerIndex }) =>
|
|
10138
|
+
({ fenceStart, closerIndex }) => createHash2("sha256").update(originalLines.slice(fenceStart + 1, closerIndex).join("\n")).digest("hex")
|
|
9874
10139
|
);
|
|
9875
10140
|
const firstOccurrenceOf = /* @__PURE__ */ new Map();
|
|
9876
10141
|
const out = [];
|
|
@@ -9989,6 +10254,7 @@ function harnessForNormalization() {
|
|
|
9989
10254
|
if (detected === "gemini") return "gemini";
|
|
9990
10255
|
if (detected === "grok") return "grok";
|
|
9991
10256
|
if (detected === "kimi") return "kimi";
|
|
10257
|
+
if (detected === "copilot_cli") return "copilot_cli";
|
|
9992
10258
|
return "claude";
|
|
9993
10259
|
}
|
|
9994
10260
|
async function relayInProcess(eventName, rawPayload) {
|
|
@@ -9996,6 +10262,7 @@ async function relayInProcess(eventName, rawPayload) {
|
|
|
9996
10262
|
if (!isHookEventName(eventName)) {
|
|
9997
10263
|
return "{}";
|
|
9998
10264
|
}
|
|
10265
|
+
const harness = detectHarness();
|
|
9999
10266
|
const payload = eventName === "pre_tool_use" || eventName === "post_tool_use" ? normalizePayload(rawPayload, harnessForNormalization()) : rawPayload;
|
|
10000
10267
|
const event = buildEvent(eventName, payload);
|
|
10001
10268
|
if (!process.env["CLAUDE_CODE_SESSION_ID"] && event.sessionId) {
|
|
@@ -10011,7 +10278,7 @@ async function relayInProcess(eventName, rawPayload) {
|
|
|
10011
10278
|
saveSessionState(stateKey);
|
|
10012
10279
|
} catch {
|
|
10013
10280
|
}
|
|
10014
|
-
return serializeOutput(output, event.eventName);
|
|
10281
|
+
return serializeOutput(output, event.eventName, harness);
|
|
10015
10282
|
} catch {
|
|
10016
10283
|
return "{}";
|
|
10017
10284
|
}
|