token-goat 2.9.10 → 2.9.12
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 +54 -11
- package/SECURITY.md +17 -19
- package/THIRD_PARTY_NOTICES.md +667 -0
- package/dist/{token-goat-chunk-YFXFUG3L.mjs → token-goat-chunk-4CK445AW.mjs} +1275 -655
- package/dist/token-goat-chunk-6F5TLJC7.mjs +3728 -0
- package/dist/{token-goat-chunk-ATVQ6IIT.mjs → token-goat-chunk-6NIGPPN6.mjs} +10 -2
- package/dist/{token-goat-chunk-SLGOIXKF.mjs → token-goat-chunk-A6QTLAWO.mjs} +85 -328
- package/dist/{token-goat-chunk-7ALFFPJW.mjs → token-goat-chunk-GCXX67HM.mjs} +2699 -2175
- package/dist/{token-goat-chunk-PA5UXNOT.mjs → token-goat-chunk-GM6QZWCF.mjs} +5088 -7746
- package/dist/{token-goat-chunk-DSHYDU53.mjs → token-goat-chunk-HKFOH6JH.mjs} +11 -7
- package/dist/{token-goat-chunk-LYWIRYCF.mjs → token-goat-chunk-LHLQFGWQ.mjs} +1 -1
- package/dist/token-goat-chunk-LOCOX2ML.mjs +3564 -0
- package/dist/token-goat-chunk-OTW7LC4Y.mjs +5942 -0
- package/dist/{token-goat-chunk-FQD3OB5W.mjs → token-goat-chunk-QCQUIPIP.mjs} +6068 -1687
- package/dist/{token-goat-chunk-DCYHAUW3.mjs → token-goat-chunk-SBYRP3X4.mjs} +69 -37
- package/dist/{token-goat-chunk-JS44SRAX.mjs → token-goat-chunk-SF2CKCFQ.mjs} +3 -2
- package/dist/{token-goat-chunk-PXWBHSFB.mjs → token-goat-chunk-T6M7DAW3.mjs} +31 -4
- package/dist/{token-goat-chunk-ZQODJQTP.mjs → token-goat-chunk-THLHC6QJ.mjs} +354 -180
- package/dist/{token-goat-chunk-VCEK77ED.mjs → token-goat-chunk-TOGYS5A7.mjs} +5307 -6350
- package/dist/{token-goat-chunk-2TJBY5RD.mjs → token-goat-chunk-VRWX6QYW.mjs} +6 -5
- package/dist/token-goat-hook.mjs +6 -5
- package/dist/token-goat.core.mjs +8 -6
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +4 -1
- package/docs/install.md +131 -11
- package/docs/security.md +2 -0
- package/package.json +8 -3
|
@@ -2,7 +2,7 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
detectHarness
|
|
5
|
-
} from "./token-goat-chunk-
|
|
5
|
+
} from "./token-goat-chunk-TOGYS5A7.mjs";
|
|
6
6
|
import {
|
|
7
7
|
init_define_import_meta_env
|
|
8
8
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -13,6 +13,14 @@ var CLAUDE_CODE_BASH_OUTPUT_CAP_BYTES = 2e4;
|
|
|
13
13
|
function bashOutputCapBytes(harness = detectHarness()) {
|
|
14
14
|
return harness === "claudecode" ? CLAUDE_CODE_BASH_OUTPUT_CAP_BYTES : null;
|
|
15
15
|
}
|
|
16
|
+
function clipToDeliveryCap(text, reserveBytes) {
|
|
17
|
+
const cap = bashOutputCapBytes();
|
|
18
|
+
const room = cap === null ? null : cap - reserveBytes;
|
|
19
|
+
if (room === null || room <= 0 || Buffer.byteLength(text, "utf-8") <= room) return { text, clipped: false };
|
|
20
|
+
const sliced = Buffer.from(text, "utf-8").subarray(0, room).toString("utf-8");
|
|
21
|
+
const lastNewline = sliced.lastIndexOf("\n");
|
|
22
|
+
return { text: lastNewline > 0 ? sliced.slice(0, lastNewline) : sliced, clipped: true };
|
|
23
|
+
}
|
|
16
24
|
function deliveredOutputBytes(originalBytes, harness) {
|
|
17
25
|
const cap = bashOutputCapBytes(harness);
|
|
18
26
|
if (cap === null) return Math.max(0, originalBytes);
|
|
@@ -78,7 +86,7 @@ function canRunWrappedShell() {
|
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
export {
|
|
81
|
-
|
|
89
|
+
clipToDeliveryCap,
|
|
82
90
|
deliveredOutputBytes,
|
|
83
91
|
wrappedShell,
|
|
84
92
|
canRunWrappedShell
|
|
@@ -1,329 +1,43 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
+
PARSER_FINGERPRINT,
|
|
4
5
|
enqueueDirtyPathSafe,
|
|
5
|
-
estimateTokensFromLength,
|
|
6
6
|
getProjectFileEntries,
|
|
7
7
|
getTrackedFiles,
|
|
8
8
|
indexRecallEntry,
|
|
9
9
|
isBlobStale,
|
|
10
10
|
loadBlob,
|
|
11
11
|
storeBlob
|
|
12
|
-
} from "./token-goat-chunk-
|
|
12
|
+
} from "./token-goat-chunk-GM6QZWCF.mjs";
|
|
13
|
+
import {
|
|
14
|
+
estimateTokensFromLength,
|
|
15
|
+
fingerprintFile,
|
|
16
|
+
getDb,
|
|
17
|
+
getDisplayRoot,
|
|
18
|
+
redactSecrets,
|
|
19
|
+
shortFingerprint
|
|
20
|
+
} from "./token-goat-chunk-TOGYS5A7.mjs";
|
|
21
|
+
import {
|
|
22
|
+
registerReset
|
|
23
|
+
} from "./token-goat-chunk-EEIDFMEM.mjs";
|
|
13
24
|
import {
|
|
14
25
|
SYMBOL_BODY_CHAR_CAP,
|
|
15
|
-
|
|
26
|
+
compileGuardedRegex,
|
|
16
27
|
countNoun,
|
|
28
|
+
displaySafeJson,
|
|
17
29
|
displaySafeText,
|
|
18
30
|
extractErrorMessage,
|
|
19
|
-
fingerprintFile,
|
|
20
31
|
foldPath,
|
|
21
|
-
|
|
22
|
-
getDisplayRoot,
|
|
32
|
+
globalDbPath,
|
|
23
33
|
normalizePath,
|
|
24
|
-
redactSecrets,
|
|
25
34
|
resolveIndexPath,
|
|
26
|
-
shortFingerprint,
|
|
27
35
|
toDisplayPath
|
|
28
|
-
} from "./token-goat-chunk-
|
|
29
|
-
import {
|
|
30
|
-
registerReset
|
|
31
|
-
} from "./token-goat-chunk-EEIDFMEM.mjs";
|
|
36
|
+
} from "./token-goat-chunk-LOCOX2ML.mjs";
|
|
32
37
|
import {
|
|
33
38
|
init_define_import_meta_env
|
|
34
39
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
35
40
|
|
|
36
|
-
// src/hooks_cli.ts
|
|
37
|
-
init_define_import_meta_env();
|
|
38
|
-
|
|
39
|
-
// src/copilot_mcp_names.ts
|
|
40
|
-
init_define_import_meta_env();
|
|
41
|
-
|
|
42
|
-
// src/copilot_mcp_tools.ts
|
|
43
|
-
init_define_import_meta_env();
|
|
44
|
-
import fs from "node:fs";
|
|
45
|
-
import path from "node:path";
|
|
46
|
-
function wireShapeLength(tools) {
|
|
47
|
-
const wire = tools.map((tool) => {
|
|
48
|
-
const out = {};
|
|
49
|
-
if (typeof tool.name === "string") out["name"] = tool.name;
|
|
50
|
-
if (typeof tool.description === "string") out["description"] = tool.description;
|
|
51
|
-
if (tool.inputSchema !== void 0) out["inputSchema"] = tool.inputSchema;
|
|
52
|
-
return out;
|
|
53
|
-
});
|
|
54
|
-
return JSON.stringify(wire).length;
|
|
55
|
-
}
|
|
56
|
-
function readCopilotMcpTools(dir = copilotCliMcpToolsDir()) {
|
|
57
|
-
let entries;
|
|
58
|
-
try {
|
|
59
|
-
entries = fs.readdirSync(dir);
|
|
60
|
-
} catch {
|
|
61
|
-
return { cacheFound: false, servers: [], unreadable: 0 };
|
|
62
|
-
}
|
|
63
|
-
const newest = /* @__PURE__ */ new Map();
|
|
64
|
-
let unreadable = 0;
|
|
65
|
-
for (const entry of entries) {
|
|
66
|
-
if (!entry.toLowerCase().endsWith(".json")) continue;
|
|
67
|
-
let parsed;
|
|
68
|
-
try {
|
|
69
|
-
parsed = JSON.parse(fs.readFileSync(path.join(dir, entry), "utf8"));
|
|
70
|
-
} catch {
|
|
71
|
-
unreadable += 1;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
75
|
-
unreadable += 1;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
const record = parsed;
|
|
79
|
-
const serverName = typeof record.serverName === "string" ? record.serverName.trim() : "";
|
|
80
|
-
if (serverName === "") {
|
|
81
|
-
unreadable += 1;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const tools = Array.isArray(record.tools) ? record.tools : [];
|
|
85
|
-
const updatedAt = typeof record.updatedAt === "string" ? record.updatedAt : "";
|
|
86
|
-
const definitionBytes = wireShapeLength(tools);
|
|
87
|
-
const candidate = {
|
|
88
|
-
serverName,
|
|
89
|
-
toolCount: tools.length,
|
|
90
|
-
definitionBytes,
|
|
91
|
-
estimatedTokens: estimateTokensFromLength(definitionBytes),
|
|
92
|
-
updatedAt,
|
|
93
|
-
// `tools` is an unchecked cast off a JSON array, so every element is
|
|
94
|
-
// treated as untrusted here rather than trusted to match CachedTool.
|
|
95
|
-
toolNames: tools.map((t) => t?.name).filter((n) => typeof n === "string" && n !== "")
|
|
96
|
-
};
|
|
97
|
-
const existing = newest.get(serverName);
|
|
98
|
-
if (existing === void 0 || candidate.updatedAt > existing.updatedAt) newest.set(serverName, candidate);
|
|
99
|
-
}
|
|
100
|
-
const servers = [...newest.values()].sort(
|
|
101
|
-
(a, b) => b.definitionBytes - a.definitionBytes || a.serverName.localeCompare(b.serverName)
|
|
102
|
-
);
|
|
103
|
-
return { cacheFound: true, servers, unreadable };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/copilot_mcp_names.ts
|
|
107
|
-
var _cached = null;
|
|
108
|
-
function copilotMcpToolNameMap() {
|
|
109
|
-
if (_cached) return _cached;
|
|
110
|
-
const map = /* @__PURE__ */ new Map();
|
|
111
|
-
try {
|
|
112
|
-
for (const server of readCopilotMcpTools().servers) {
|
|
113
|
-
for (const toolName of server.toolNames) {
|
|
114
|
-
map.set(`${server.serverName}-${toolName}`, `mcp__${server.serverName}__${toolName}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
_cached = map;
|
|
120
|
-
return map;
|
|
121
|
-
}
|
|
122
|
-
function canonicalizeCopilotMcpToolName(toolName) {
|
|
123
|
-
return copilotMcpToolNameMap().get(toolName) ?? toolName;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/hooks_cli.ts
|
|
127
|
-
var _LOG = {
|
|
128
|
-
warn: (msg, ...args) => console.warn(`[hooks_cli] ${msg}`, ...args),
|
|
129
|
-
debug: (msg, ...args) => console.error(`[hooks_cli] ${msg}`, ...args),
|
|
130
|
-
error: (msg, err, ...args) => console.error(`[hooks_cli] ${msg}`, ...err ? [err] : [], ...args)
|
|
131
|
-
};
|
|
132
|
-
var CODEX_TOOL_NAME_MAP = {
|
|
133
|
-
bash: "Bash",
|
|
134
|
-
edit_file: "Edit",
|
|
135
|
-
edit: "Edit",
|
|
136
|
-
apply_patch: "Edit",
|
|
137
|
-
write_file: "Write",
|
|
138
|
-
search_files: "Grep",
|
|
139
|
-
grep: "Grep",
|
|
140
|
-
list_files: "Glob",
|
|
141
|
-
glob: "Glob",
|
|
142
|
-
web_search: "WebFetch"
|
|
143
|
-
};
|
|
144
|
-
var GEMINI_TOOL_NAME_MAP = {
|
|
145
|
-
run_shell_command: "Bash",
|
|
146
|
-
read_file: "Read",
|
|
147
|
-
read_many_files: "Read",
|
|
148
|
-
list_directory: "Read",
|
|
149
|
-
write_file: "Write",
|
|
150
|
-
replace: "Edit",
|
|
151
|
-
glob: "Glob",
|
|
152
|
-
grep_search: "Grep",
|
|
153
|
-
// 'search_file_content' was GREP_TOOL_NAME's value before gemini-cli renamed it to 'grep_search' (confirmed against gemini-cli's current base-declarations.ts, which now defines GREP_TOOL_NAME = 'grep_search'; some of gemini-cli's own docs pages still lag the rename). Kept as a backward-compat entry for older installed gemini-cli versions that still emit the pre-rename name -- not dead/hallucinated, just legacy.
|
|
154
|
-
search_file_content: "Grep",
|
|
155
|
-
// Gemini's real web-search tool is registered as 'google_web_search' (WEB_SEARCH_TOOL_NAME in gemini-cli's tool-names.ts) -- 'web_search' is not a tool name Gemini CLI ever emits, so the old entry here silently never matched a real invocation.
|
|
156
|
-
google_web_search: "WebFetch",
|
|
157
|
-
web_fetch: "WebFetch"
|
|
158
|
-
};
|
|
159
|
-
var GEMINI_INPUT_KEY_MAP = {
|
|
160
|
-
// Gemini's real write_file/replace tool schemas already use 'file_path' (and replace's 'old_string'/'new_string') verbatim -- identical to token-goat's own canonical keys (see getFilePath() in hooks_common.ts, which reads event.toolInput['file_path']) -- confirmed against gemini-cli's own EditToolParams/WriteFileToolParams interfaces. No remap is needed for Write/Edit; the previous entries here actively renamed a key that was already correct (file_path -> path), which would have silently corrupted the path argument on every real Gemini Write/Edit call.
|
|
161
|
-
// 'Read' covers THREE distinct raw Gemini tools (see GEMINI_TOOL_NAME_MAP above), and they do NOT share one schema: read_file already uses 'file_path' verbatim, no remap needed; list_directory uses 'dir_path' (confirmed against gemini-cli's LSToolParams) -- remapped to 'file_path' below, exactly mirroring Grep's existing dir_path->path fix; read_many_files uses 'include' (confirmed against gemini-cli's ReadManyFilesParams), an ARRAY of glob patterns, not a single file path -- there is no single string to remap it to, so it is deliberately left unmapped. getFilePath() (hooks_common.ts) will return undefined for this call, and preReadHandler/postReadHandler already fall back to passOutput() on an undefined path (see preReadHandler's `if (filePath === undefined) return passOutput()`), so a real read_many_files call still succeeds normally; it just doesn't get session-dedup tracking or read-count hints. Accepted limitation, not a bug to force-fit.
|
|
162
|
-
Read: { dir_path: "file_path" },
|
|
163
|
-
// Grep's real tool (grep_search) calls its target-directory argument 'dir_path', which preReadHandler's Grep fallback (event.toolInput['path']) doesn't recognize, so that one remains remapped.
|
|
164
|
-
Grep: { dir_path: "path" }
|
|
165
|
-
};
|
|
166
|
-
var GROK_TOOL_NAME_MAP = {
|
|
167
|
-
read_file: "Read",
|
|
168
|
-
write: "Write",
|
|
169
|
-
search_replace: "Edit",
|
|
170
|
-
run_terminal_command: "Bash",
|
|
171
|
-
// The grok 0.2.93 binary registers its shell tool as `tool.run_terminal_cmd` (tracing-id table in ~/.grok/bin/agent.exe) and its newer embedded hooks doc's PreToolUse example sends `"toolName": "run_terminal_cmd"`, while the older embedded doc revision and a 2026-07-09 live capture on the same version both show `run_terminal_command`. Which spelling arrives is profile/version dependent, so BOTH are mapped: an unmatched extra entry is a harmless no-op, a missing one silently kills every Bash hook (compression, wrap, hints) on that profile.
|
|
172
|
-
run_terminal_cmd: "Bash",
|
|
173
|
-
grep: "Grep",
|
|
174
|
-
list_dir: "Glob",
|
|
175
|
-
// `glob` is a distinct registered grok tool (tool.glob in the 0.2.93 binary's tracing-id table), separate from list_dir; unmapped it arrived as lowercase 'glob' and matched no handler.
|
|
176
|
-
glob: "Glob",
|
|
177
|
-
// web_fetch/web_search were entirely absent from this map, so grok's URL fetches bypassed token-goat's WebFetch pipeline (URL-policy deny included) and its searches bypassed the WebSearch dedup/compression. Verified against the 0.2.93 binary: tool.web_fetch / tool.web_search registered ids, grok_build WebFetchInput's field described as "The URL to fetch content from" (i.e. `url`), grok_build WebSearchInput's fields query/citations/allowed_domains -- all matching the keys hooks_fetch.ts / hooks_websearch.ts already read, so a name map alone revives both.
|
|
178
|
-
web_fetch: "WebFetch",
|
|
179
|
-
web_search: "WebSearch",
|
|
180
|
-
// The Hashline prompt profile registers hashline_read/hashline_edit/hashline_grep (tool.hashline_* in the binary; grok's own hook alias table pairs them with Read/Edit/Grep). Their input key shapes were not individually verified: if one differs, the mapped handler degrades to the same no-op as an unmapped name, never a wrong rewrite. The *_concise twins are the GrokBuildConcise profile's registrations of the same three core tools.
|
|
181
|
-
hashline_read: "Read",
|
|
182
|
-
hashline_edit: "Edit",
|
|
183
|
-
hashline_grep: "Grep",
|
|
184
|
-
read_file_concise: "Read",
|
|
185
|
-
search_replace_concise: "Edit",
|
|
186
|
-
run_terminal_cmd_concise: "Bash"
|
|
187
|
-
};
|
|
188
|
-
var GROK_INPUT_KEY_MAP = {
|
|
189
|
-
Read: { target_file: "file_path" }
|
|
190
|
-
};
|
|
191
|
-
var KIMI_TOOL_NAME_MAP = {
|
|
192
|
-
FetchURL: "WebFetch",
|
|
193
|
-
ReadMediaFile: "Read"
|
|
194
|
-
};
|
|
195
|
-
var KIMI_INPUT_KEY_MAP = {
|
|
196
|
-
// line_offset/n_lines are Kimi's Read paging arguments (ReadInputSchema in MoonshotAI/kimi-code packages/agent-core-v2/src/agent/tools/os/read/read.ts: `path`, `line_offset` "the line number to start reading from", `n_lines` "the number of lines to read"). Unmapped, every ranged Kimi read looked unbounded to hooks_read.ts's estimateRequestedSlice and was gated on the whole file's size -- a small slice of a big file could draw the large-file deny meant for full reads. line_offset's 1-indexed positive form matches Read's own offset semantics exactly; its negative tail-read form has no token-goat equivalent and is clamped to 1 by estimateRequestedSlice's `offset >= 1` guard, degrading to a window of the right SIZE (which is all the gate consumes). ReadMediaFile shares this map via its Read rename and carries only `path`, so the extra entries never touch it.
|
|
197
|
-
Read: { path: "file_path", line_offset: "offset", n_lines: "limit" },
|
|
198
|
-
Write: { path: "file_path" },
|
|
199
|
-
Edit: { path: "file_path" }
|
|
200
|
-
};
|
|
201
|
-
var QWEN_TOOL_NAME_MAP = {
|
|
202
|
-
read_file: "Read",
|
|
203
|
-
write_file: "Write",
|
|
204
|
-
edit: "Edit",
|
|
205
|
-
replace: "Edit",
|
|
206
|
-
notebook_edit: "NotebookEdit",
|
|
207
|
-
run_shell_command: "Bash",
|
|
208
|
-
grep_search: "Grep",
|
|
209
|
-
search_file_content: "Grep",
|
|
210
|
-
glob: "Glob",
|
|
211
|
-
web_fetch: "WebFetch",
|
|
212
|
-
web_search: "WebSearch",
|
|
213
|
-
list_directory: "Read"
|
|
214
|
-
};
|
|
215
|
-
var QWEN_INPUT_KEY_MAP = {
|
|
216
|
-
Read: { path: "file_path" }
|
|
217
|
-
};
|
|
218
|
-
function remapInputKeys(input, keyMap) {
|
|
219
|
-
const newInput = {};
|
|
220
|
-
for (const [k, v] of Object.entries(input)) {
|
|
221
|
-
newInput[keyMap[k] || k] = v;
|
|
222
|
-
}
|
|
223
|
-
return newInput;
|
|
224
|
-
}
|
|
225
|
-
function remapToolName(obj, toolName, nameMap, inputKeyMap) {
|
|
226
|
-
const mapped = nameMap[toolName] ?? toolName;
|
|
227
|
-
const result = { ...obj };
|
|
228
|
-
result["tool_name"] = mapped;
|
|
229
|
-
const rawInput = obj["tool_input"];
|
|
230
|
-
const keyMap = inputKeyMap[mapped];
|
|
231
|
-
if (keyMap && typeof rawInput === "object" && rawInput !== null && !Array.isArray(rawInput)) {
|
|
232
|
-
result["tool_input"] = remapInputKeys(rawInput, keyMap);
|
|
233
|
-
}
|
|
234
|
-
return result;
|
|
235
|
-
}
|
|
236
|
-
function grokToCanonicalWire(obj) {
|
|
237
|
-
const wire = { ...obj };
|
|
238
|
-
if (typeof wire["toolName"] === "string" && wire["tool_name"] === void 0) {
|
|
239
|
-
wire["tool_name"] = wire["toolName"];
|
|
240
|
-
}
|
|
241
|
-
if (typeof wire["toolInput"] === "object" && wire["toolInput"] !== null && !Array.isArray(wire["toolInput"]) && wire["tool_input"] === void 0) {
|
|
242
|
-
wire["tool_input"] = wire["toolInput"];
|
|
243
|
-
}
|
|
244
|
-
if (typeof wire["sessionId"] === "string" && wire["session_id"] === void 0) {
|
|
245
|
-
wire["session_id"] = wire["sessionId"];
|
|
246
|
-
}
|
|
247
|
-
const toolResult = wire["toolResult"];
|
|
248
|
-
if (toolResult !== null && typeof toolResult === "object" && !Array.isArray(toolResult)) {
|
|
249
|
-
const tr = toolResult;
|
|
250
|
-
if (typeof tr["output_for_prompt"] === "string") {
|
|
251
|
-
wire["tool_response"] = { content: tr["output_for_prompt"], exit_code: tr["exit_code"] };
|
|
252
|
-
} else {
|
|
253
|
-
const firstString = Object.entries(tr).find(([k, v]) => k !== "type" && typeof v === "string");
|
|
254
|
-
if (firstString) {
|
|
255
|
-
wire["tool_response"] = { content: firstString[1] };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return wire;
|
|
260
|
-
}
|
|
261
|
-
function aliasLlmContentToOutput(result) {
|
|
262
|
-
const toolResponse = result["tool_response"];
|
|
263
|
-
if (toolResponse === null || typeof toolResponse !== "object" || Array.isArray(toolResponse)) return;
|
|
264
|
-
const responseRecord = toolResponse;
|
|
265
|
-
if (typeof responseRecord["llmContent"] === "string" && responseRecord["output"] === void 0) {
|
|
266
|
-
result["tool_response"] = { ...responseRecord, output: responseRecord["llmContent"] };
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function normalizePayload(payload, harness = "claude") {
|
|
270
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
271
|
-
_LOG.warn("normalizePayload: payload is not a dict; received %s", typeof payload);
|
|
272
|
-
return {};
|
|
273
|
-
}
|
|
274
|
-
const rawObj = payload;
|
|
275
|
-
const obj = harness === "grok" ? grokToCanonicalWire(rawObj) : rawObj;
|
|
276
|
-
const toolName = obj["tool_name"];
|
|
277
|
-
if (typeof toolName !== "string" || !toolName.trim()) {
|
|
278
|
-
_LOG.debug("normalizePayload: tool_name missing or invalid; received %s", toolName);
|
|
279
|
-
return {};
|
|
280
|
-
}
|
|
281
|
-
if (harness === "grok") {
|
|
282
|
-
const result2 = remapToolName(obj, toolName, GROK_TOOL_NAME_MAP, GROK_INPUT_KEY_MAP);
|
|
283
|
-
result2["_tg_harness"] = harness;
|
|
284
|
-
return result2;
|
|
285
|
-
}
|
|
286
|
-
if (harness === "codex") {
|
|
287
|
-
const mapped = CODEX_TOOL_NAME_MAP[toolName];
|
|
288
|
-
const result2 = { ...obj };
|
|
289
|
-
if (mapped) {
|
|
290
|
-
result2["tool_name"] = mapped;
|
|
291
|
-
}
|
|
292
|
-
result2["_tg_harness"] = harness;
|
|
293
|
-
return result2;
|
|
294
|
-
}
|
|
295
|
-
if (harness === "copilot_cli") {
|
|
296
|
-
const result2 = { ...obj };
|
|
297
|
-
result2["tool_name"] = canonicalizeCopilotMcpToolName(toolName);
|
|
298
|
-
result2["_tg_harness"] = harness;
|
|
299
|
-
return result2;
|
|
300
|
-
}
|
|
301
|
-
if (harness === "kimi") {
|
|
302
|
-
const result2 = remapToolName(obj, toolName, KIMI_TOOL_NAME_MAP, KIMI_INPUT_KEY_MAP);
|
|
303
|
-
result2["_tg_harness"] = harness;
|
|
304
|
-
return result2;
|
|
305
|
-
}
|
|
306
|
-
if (harness === "qwen") {
|
|
307
|
-
const result2 = remapToolName(obj, toolName, QWEN_TOOL_NAME_MAP, QWEN_INPUT_KEY_MAP);
|
|
308
|
-
aliasLlmContentToOutput(result2);
|
|
309
|
-
result2["_tg_harness"] = harness;
|
|
310
|
-
return result2;
|
|
311
|
-
}
|
|
312
|
-
if (harness === "gemini") {
|
|
313
|
-
const result2 = remapToolName(obj, toolName, GEMINI_TOOL_NAME_MAP, GEMINI_INPUT_KEY_MAP);
|
|
314
|
-
if ("functionCallId" in result2 && !("toolUseId" in result2)) {
|
|
315
|
-
result2["toolUseId"] = result2["functionCallId"];
|
|
316
|
-
delete result2["functionCallId"];
|
|
317
|
-
}
|
|
318
|
-
aliasLlmContentToOutput(result2);
|
|
319
|
-
result2["_tg_harness"] = harness;
|
|
320
|
-
return result2;
|
|
321
|
-
}
|
|
322
|
-
const result = { ...obj };
|
|
323
|
-
result["_tg_harness"] = harness;
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
41
|
// src/web_cache.ts
|
|
328
42
|
init_define_import_meta_env();
|
|
329
43
|
var WEB_OUTPUT_SUBDIR = "web_outputs";
|
|
@@ -426,12 +140,8 @@ function entryMatches(entry, re, pattern) {
|
|
|
426
140
|
return haystacks.some((h) => re !== null ? re.test(h) : h.includes(pattern));
|
|
427
141
|
}
|
|
428
142
|
function filterCommandManifest(manifest, pattern) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
re = new RegExp(pattern);
|
|
432
|
-
} catch {
|
|
433
|
-
re = null;
|
|
434
|
-
}
|
|
143
|
+
const guarded = compileGuardedRegex(pattern);
|
|
144
|
+
const re = guarded.ok ? guarded.re : null;
|
|
435
145
|
const result = [];
|
|
436
146
|
for (const entry of manifest) {
|
|
437
147
|
if (entryMatches(entry, re, pattern)) {
|
|
@@ -467,8 +177,30 @@ function formatCommandManifest(manifest) {
|
|
|
467
177
|
|
|
468
178
|
// src/reconcile.ts
|
|
469
179
|
init_define_import_meta_env();
|
|
470
|
-
import * as
|
|
180
|
+
import * as fs from "node:fs";
|
|
471
181
|
var DEFAULT_RECONCILE_BUDGET_MS = 1500;
|
|
182
|
+
function readReconcileCursor(dbPath, root) {
|
|
183
|
+
try {
|
|
184
|
+
const row = getDb(dbPath).prepare("SELECT last_scanned_path FROM reconcile_cursor WHERE root = ?").get(root);
|
|
185
|
+
return row?.last_scanned_path ?? null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function writeReconcileCursor(dbPath, root, lastScannedPath) {
|
|
191
|
+
try {
|
|
192
|
+
getDb(dbPath).prepare(
|
|
193
|
+
"INSERT INTO reconcile_cursor (root, last_scanned_path, updated_at) VALUES (?, ?, ?) ON CONFLICT(root) DO UPDATE SET last_scanned_path = excluded.last_scanned_path, updated_at = excluded.updated_at"
|
|
194
|
+
).run(root, lastScannedPath, Date.now());
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function clearReconcileCursor(dbPath, root) {
|
|
199
|
+
try {
|
|
200
|
+
getDb(dbPath).prepare("DELETE FROM reconcile_cursor WHERE root = ?").run(root);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
472
204
|
function runReconcile(opts = {}) {
|
|
473
205
|
const raw = reconcileProject(opts);
|
|
474
206
|
const root = getDisplayRoot(opts.cwd ?? process.cwd());
|
|
@@ -480,7 +212,7 @@ function runReconcile(opts = {}) {
|
|
|
480
212
|
removed: display(raw.removed)
|
|
481
213
|
};
|
|
482
214
|
if (opts.json === true) {
|
|
483
|
-
process.stdout.write(`${
|
|
215
|
+
process.stdout.write(`${displaySafeJson(result)}
|
|
484
216
|
`);
|
|
485
217
|
return 0;
|
|
486
218
|
}
|
|
@@ -503,6 +235,10 @@ function runReconcile(opts = {}) {
|
|
|
503
235
|
if (result.mtimeOnly > 0) {
|
|
504
236
|
lines.push(`${countNoun(result.mtimeOnly, "file")} had a newer timestamp but identical content, so ${result.mtimeOnly === 1 ? "it was" : "they were"} left alone.`);
|
|
505
237
|
}
|
|
238
|
+
if (result.parserStale > 0) {
|
|
239
|
+
const reparseVerb = opts.dryRun === true ? "would reparse" : "queued for reparse";
|
|
240
|
+
lines.push(`${countNoun(result.parserStale, "file")} of the above ${result.parserStale === 1 ? "was" : "were"} unchanged on disk but indexed by an older parser (${reparseVerb}).`);
|
|
241
|
+
}
|
|
506
242
|
if (result.trackedUnavailable) {
|
|
507
243
|
lines.push("This project has an index but git listed no files in it, so nothing could be compared and no deletions were computed. Run token-goat inside the repository, or reindex with --walk if this directory is deliberately not under git.");
|
|
508
244
|
}
|
|
@@ -520,21 +256,34 @@ function reconcileProject(opts = {}) {
|
|
|
520
256
|
const cwd = opts.cwd ?? process.cwd();
|
|
521
257
|
const budgetMs = opts.budgetMs ?? DEFAULT_RECONCILE_BUDGET_MS;
|
|
522
258
|
const startedAt = Date.now();
|
|
259
|
+
const dbPath = opts.dbPath ?? globalDbPath();
|
|
523
260
|
const tracked = getTrackedFiles(cwd);
|
|
524
261
|
const projectRoot = resolveIndexPath(".", cwd);
|
|
525
|
-
const indexed = getProjectFileEntries(projectRoot);
|
|
262
|
+
const indexed = getProjectFileEntries(projectRoot, dbPath);
|
|
263
|
+
let scanOrder = tracked;
|
|
264
|
+
const cursorPath = tracked.length > 0 ? readReconcileCursor(dbPath, projectRoot) : null;
|
|
265
|
+
if (cursorPath !== null) {
|
|
266
|
+
const cursorFolded = foldPath(normalizePath(cursorPath));
|
|
267
|
+
const cursorIdx = tracked.findIndex((f) => foldPath(normalizePath(f)) === cursorFolded);
|
|
268
|
+
if (cursorIdx !== -1) {
|
|
269
|
+
scanOrder = [...tracked.slice(cursorIdx + 1), ...tracked.slice(0, cursorIdx + 1)];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
526
272
|
const changed = [];
|
|
527
273
|
const added = [];
|
|
528
274
|
const seenOnDisk = /* @__PURE__ */ new Set();
|
|
529
275
|
let mtimeOnly = 0;
|
|
276
|
+
let parserStale = 0;
|
|
530
277
|
let scanned = 0;
|
|
531
278
|
let budgetExhausted = false;
|
|
532
|
-
|
|
279
|
+
let lastScanned = null;
|
|
280
|
+
for (const file of scanOrder) {
|
|
533
281
|
if (Date.now() - startedAt > budgetMs) {
|
|
534
282
|
budgetExhausted = true;
|
|
535
283
|
break;
|
|
536
284
|
}
|
|
537
285
|
scanned++;
|
|
286
|
+
lastScanned = file;
|
|
538
287
|
const folded = foldPath(normalizePath(file));
|
|
539
288
|
seenOnDisk.add(folded);
|
|
540
289
|
const entry = indexed.get(folded);
|
|
@@ -542,9 +291,14 @@ function reconcileProject(opts = {}) {
|
|
|
542
291
|
added.push(file);
|
|
543
292
|
continue;
|
|
544
293
|
}
|
|
294
|
+
if (entry.parserSha !== PARSER_FINGERPRINT) {
|
|
295
|
+
changed.push(file);
|
|
296
|
+
parserStale++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
545
299
|
let mtimeMs;
|
|
546
300
|
try {
|
|
547
|
-
mtimeMs =
|
|
301
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
548
302
|
} catch {
|
|
549
303
|
changed.push(file);
|
|
550
304
|
continue;
|
|
@@ -574,6 +328,11 @@ function reconcileProject(opts = {}) {
|
|
|
574
328
|
enqueueDirtyPathSafe(resolveIndexPath(p, cwd), { alreadyResolved: true });
|
|
575
329
|
enqueued++;
|
|
576
330
|
}
|
|
331
|
+
if (budgetExhausted) {
|
|
332
|
+
if (lastScanned !== null) writeReconcileCursor(dbPath, projectRoot, lastScanned);
|
|
333
|
+
} else if (tracked.length > 0) {
|
|
334
|
+
clearReconcileCursor(dbPath, projectRoot);
|
|
335
|
+
}
|
|
577
336
|
}
|
|
578
337
|
return {
|
|
579
338
|
scanned,
|
|
@@ -581,6 +340,7 @@ function reconcileProject(opts = {}) {
|
|
|
581
340
|
added,
|
|
582
341
|
removed,
|
|
583
342
|
mtimeOnly,
|
|
343
|
+
parserStale,
|
|
584
344
|
budgetExhausted,
|
|
585
345
|
trackedUnavailable,
|
|
586
346
|
unscanned: Math.max(0, tracked.length - scanned),
|
|
@@ -658,10 +418,10 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
|
|
|
658
418
|
|
|
659
419
|
// src/symbol_body_probe.ts
|
|
660
420
|
init_define_import_meta_env();
|
|
661
|
-
import * as
|
|
421
|
+
import * as fs2 from "fs";
|
|
662
422
|
var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
|
|
663
423
|
function checkSymbolBodySize(dbPath) {
|
|
664
|
-
if (!
|
|
424
|
+
if (!fs2.existsSync(dbPath)) {
|
|
665
425
|
return { name: "Symbol body size", status: "ok", message: "no database yet" };
|
|
666
426
|
}
|
|
667
427
|
try {
|
|
@@ -686,7 +446,7 @@ function checkSymbolBodySize(dbPath) {
|
|
|
686
446
|
|
|
687
447
|
// src/resident_context.ts
|
|
688
448
|
init_define_import_meta_env();
|
|
689
|
-
import * as
|
|
449
|
+
import * as fs3 from "node:fs";
|
|
690
450
|
var LARGE_TASK_LIST_BYTES = 2e4;
|
|
691
451
|
var LARGE_SKILL_BODY_BYTES = 2e4;
|
|
692
452
|
var SKILL_BODY_REPEAT_THRESHOLD = 2;
|
|
@@ -775,9 +535,9 @@ function collectInvokedSkills(acc, record) {
|
|
|
775
535
|
function skillName(skill) {
|
|
776
536
|
const name = skill["name"];
|
|
777
537
|
if (typeof name === "string" && name.trim() !== "") return name.trim();
|
|
778
|
-
const
|
|
779
|
-
if (typeof
|
|
780
|
-
const segments =
|
|
538
|
+
const path = skill["path"];
|
|
539
|
+
if (typeof path !== "string") return null;
|
|
540
|
+
const segments = path.split(/[\\/]/).filter((part) => part !== "");
|
|
781
541
|
const last = segments[segments.length - 1];
|
|
782
542
|
return last === void 0 || last === "" ? null : last;
|
|
783
543
|
}
|
|
@@ -848,13 +608,13 @@ function repeatedSkillBodyHint(injections) {
|
|
|
848
608
|
function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES) {
|
|
849
609
|
let fd = null;
|
|
850
610
|
try {
|
|
851
|
-
const stat =
|
|
611
|
+
const stat = fs3.statSync(transcriptPath);
|
|
852
612
|
if (!stat.isFile() || stat.size === 0) return [];
|
|
853
613
|
const start = Math.max(0, stat.size - maxBytes);
|
|
854
614
|
const length = stat.size - start;
|
|
855
|
-
fd =
|
|
615
|
+
fd = fs3.openSync(transcriptPath, "r");
|
|
856
616
|
const buf = Buffer.allocUnsafe(length);
|
|
857
|
-
const read =
|
|
617
|
+
const read = fs3.readSync(fd, buf, 0, length, start);
|
|
858
618
|
const lines = buf.subarray(0, read).toString("utf8").split("\n");
|
|
859
619
|
if (start > 0) lines.shift();
|
|
860
620
|
return lines;
|
|
@@ -863,7 +623,7 @@ function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES)
|
|
|
863
623
|
} finally {
|
|
864
624
|
if (fd !== null) {
|
|
865
625
|
try {
|
|
866
|
-
|
|
626
|
+
fs3.closeSync(fd);
|
|
867
627
|
} catch {
|
|
868
628
|
}
|
|
869
629
|
}
|
|
@@ -889,9 +649,7 @@ function accumulateResidentLines(lines) {
|
|
|
889
649
|
}
|
|
890
650
|
|
|
891
651
|
export {
|
|
892
|
-
|
|
893
|
-
GEMINI_TOOL_NAME_MAP,
|
|
894
|
-
normalizePayload,
|
|
652
|
+
checkSymbolBodySize,
|
|
895
653
|
WEB_OUTPUT_SUBDIR,
|
|
896
654
|
storeWebOutput,
|
|
897
655
|
getWebOutput,
|
|
@@ -906,7 +664,6 @@ export {
|
|
|
906
664
|
runReconcile,
|
|
907
665
|
isReconcileClean,
|
|
908
666
|
reconcileProject,
|
|
909
|
-
checkSymbolBodySize,
|
|
910
667
|
createResidentContextStats,
|
|
911
668
|
accumulateResidentLine,
|
|
912
669
|
summarizeResidentContext,
|