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
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
selectFilter,
|
|
11
11
|
shlexSplit,
|
|
12
12
|
wrappedShell
|
|
13
|
-
} from "./token-goat-chunk-
|
|
13
|
+
} from "./token-goat-chunk-TUPJRK7R.mjs";
|
|
14
14
|
import {
|
|
15
15
|
loadConfig,
|
|
16
16
|
recordStat
|
|
17
|
-
} from "./token-goat-chunk-
|
|
17
|
+
} from "./token-goat-chunk-44Y77VHR.mjs";
|
|
18
18
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
19
19
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
20
20
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
external_exports
|
|
5
|
+
} from "./token-goat-chunk-WN5T5EW5.mjs";
|
|
6
|
+
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
7
|
+
|
|
8
|
+
// src/mcp_jsonrpc.ts
|
|
9
|
+
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
10
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
11
|
+
LATEST_PROTOCOL_VERSION,
|
|
12
|
+
"2025-06-18",
|
|
13
|
+
"2025-03-26",
|
|
14
|
+
"2024-11-05",
|
|
15
|
+
"2024-10-07"
|
|
16
|
+
];
|
|
17
|
+
var JSONRPC_INVALID_REQUEST = -32600;
|
|
18
|
+
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
19
|
+
var JSONRPC_INVALID_PARAMS = -32602;
|
|
20
|
+
var DRAFT_07_SCHEMA_ID = "http://json-schema.org/draft-07/schema#";
|
|
21
|
+
var MCP_INVALID_PARAMS_PREFIX = `MCP error ${JSONRPC_INVALID_PARAMS}: `;
|
|
22
|
+
var EMPTY_OBJECT_JSON_SCHEMA = { type: "object", properties: {} };
|
|
23
|
+
function buildJsonSchema(shape) {
|
|
24
|
+
if (shape === void 0 || Object.keys(shape).length === 0) return { ...EMPTY_OBJECT_JSON_SCHEMA };
|
|
25
|
+
const schema = external_exports.toJSONSchema(external_exports.object(shape), { io: "input" });
|
|
26
|
+
return { ...schema, $schema: DRAFT_07_SCHEMA_ID };
|
|
27
|
+
}
|
|
28
|
+
function describeParseError(error) {
|
|
29
|
+
return error.issues.map((issue) => {
|
|
30
|
+
if (issue.path.length === 0) return issue.message;
|
|
31
|
+
const dotted = issue.path.reduce(
|
|
32
|
+
(acc, seg, i) => i === 0 ? String(seg) : typeof seg === "number" ? `${acc}[${seg}]` : `${acc}.${String(seg)}`,
|
|
33
|
+
""
|
|
34
|
+
);
|
|
35
|
+
return `${issue.message} at ${dotted}`;
|
|
36
|
+
}).join("\n");
|
|
37
|
+
}
|
|
38
|
+
function errorText(message) {
|
|
39
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
40
|
+
}
|
|
41
|
+
function isRequest(message) {
|
|
42
|
+
return "method" in message && "id" in message && message.id !== void 0 && message.id !== null;
|
|
43
|
+
}
|
|
44
|
+
var McpServer = class {
|
|
45
|
+
info;
|
|
46
|
+
tools = /* @__PURE__ */ new Map();
|
|
47
|
+
handlers = /* @__PURE__ */ new Map();
|
|
48
|
+
transport;
|
|
49
|
+
/** Set by the caller to learn when the peer hung up; `mcp-serve` waits on exactly this. */
|
|
50
|
+
onclose;
|
|
51
|
+
constructor(info) {
|
|
52
|
+
this.info = info;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Registers a tool. Schema generation happens here rather than at `tools/list` time, which also
|
|
56
|
+
* means a malformed shape fails loudly at startup instead of on a client's first listing.
|
|
57
|
+
*/
|
|
58
|
+
registerTool(name, definition, handler) {
|
|
59
|
+
if (this.tools.has(name)) throw new Error(`MCP tool registered twice: ${name}`);
|
|
60
|
+
const shape = definition.inputSchema;
|
|
61
|
+
this.tools.set(name, {
|
|
62
|
+
name,
|
|
63
|
+
description: definition.description,
|
|
64
|
+
jsonSchema: buildJsonSchema(shape),
|
|
65
|
+
// An absent shape still needs a validator, so an argumentless tool called with arguments is
|
|
66
|
+
// not silently handed them.
|
|
67
|
+
validator: external_exports.object(shape ?? {})
|
|
68
|
+
});
|
|
69
|
+
this.handlers.set(name, handler);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wires this server to a transport and starts it.
|
|
73
|
+
*
|
|
74
|
+
* `onmessage` is assigned before `start()` on purpose: a transport may have buffered messages
|
|
75
|
+
* that arrive the moment it starts (the SDK's in-memory transport queues anything sent before a
|
|
76
|
+
* handler exists and drains the queue inside `start()`), so a server that started first would
|
|
77
|
+
* drop the client's `initialize`.
|
|
78
|
+
*/
|
|
79
|
+
async connect(transport) {
|
|
80
|
+
this.transport = transport;
|
|
81
|
+
transport.onmessage = (message) => {
|
|
82
|
+
void this.handleMessage(message);
|
|
83
|
+
};
|
|
84
|
+
transport.onclose = () => {
|
|
85
|
+
this.transport = void 0;
|
|
86
|
+
this.onclose?.();
|
|
87
|
+
};
|
|
88
|
+
await transport.start();
|
|
89
|
+
}
|
|
90
|
+
async close() {
|
|
91
|
+
const transport = this.transport;
|
|
92
|
+
this.transport = void 0;
|
|
93
|
+
await transport?.close();
|
|
94
|
+
}
|
|
95
|
+
async send(message) {
|
|
96
|
+
try {
|
|
97
|
+
await this.transport?.send(message);
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async handleMessage(message) {
|
|
102
|
+
if (!isRequest(message)) return;
|
|
103
|
+
const { id, method } = message;
|
|
104
|
+
const params = message.params ?? {};
|
|
105
|
+
try {
|
|
106
|
+
switch (method) {
|
|
107
|
+
case "initialize":
|
|
108
|
+
await this.send({ jsonrpc: "2.0", id, result: this.initializeResult(params) });
|
|
109
|
+
return;
|
|
110
|
+
case "ping":
|
|
111
|
+
await this.send({ jsonrpc: "2.0", id, result: {} });
|
|
112
|
+
return;
|
|
113
|
+
case "tools/list":
|
|
114
|
+
await this.send({ jsonrpc: "2.0", id, result: { tools: this.listTools() } });
|
|
115
|
+
return;
|
|
116
|
+
case "tools/call":
|
|
117
|
+
await this.send({ jsonrpc: "2.0", id, result: await this.callTool(params) });
|
|
118
|
+
return;
|
|
119
|
+
default:
|
|
120
|
+
await this.sendError(id, JSONRPC_METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
await this.sendError(id, JSONRPC_INVALID_REQUEST, err instanceof Error ? err.message : String(err));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async sendError(id, code, message) {
|
|
128
|
+
await this.send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
129
|
+
}
|
|
130
|
+
initializeResult(params) {
|
|
131
|
+
const requested = params["protocolVersion"];
|
|
132
|
+
const protocolVersion = typeof requested === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : LATEST_PROTOCOL_VERSION;
|
|
133
|
+
return {
|
|
134
|
+
protocolVersion,
|
|
135
|
+
// Tools only, and they never change after startup, so no `listChanged`. This is the one
|
|
136
|
+
// place we deliberately do not copy the SDK, which declares `listChanged: true` because its
|
|
137
|
+
// McpServer is *able* to send that notification. Token-goat registers every tool before it
|
|
138
|
+
// connects and never adds or removes one, so we would be promising a notification we can
|
|
139
|
+
// never have cause to send. No client behaviour turns on it either way -- a subscription
|
|
140
|
+
// that never fires and an absent subscription are the same thing when the list is fixed --
|
|
141
|
+
// so the honest declaration wins. Contrast the tool-error text below, which a model reads as
|
|
142
|
+
// content and is therefore matched to the SDK exactly.
|
|
143
|
+
capabilities: { tools: {} },
|
|
144
|
+
serverInfo: { name: this.info.name, version: this.info.version }
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
listTools() {
|
|
148
|
+
return [...this.tools.values()].map((tool) => ({
|
|
149
|
+
name: tool.name,
|
|
150
|
+
...tool.description !== void 0 ? { description: tool.description } : {},
|
|
151
|
+
inputSchema: tool.jsonSchema,
|
|
152
|
+
// The SDK emits this for every tool it registers, and it is literally true of ours: we do
|
|
153
|
+
// not implement task augmentation, so a client that asks for it gets refused. Emitting the
|
|
154
|
+
// same thing keeps `tools/list` byte-identical to what token-goat sent before, which is what
|
|
155
|
+
// the differential test in tests/mcp_jsonrpc.test.ts pins.
|
|
156
|
+
execution: { taskSupport: "forbidden" }
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
async callTool(params) {
|
|
160
|
+
const name = params["name"];
|
|
161
|
+
if (typeof name !== "string") return errorText('Invalid params: tools/call requires a string "name"');
|
|
162
|
+
const tool = this.tools.get(name);
|
|
163
|
+
const handler = this.handlers.get(name);
|
|
164
|
+
if (tool === void 0 || handler === void 0) return errorText(`${MCP_INVALID_PARAMS_PREFIX}Tool ${name} not found`);
|
|
165
|
+
const parsed = tool.validator.safeParse(params["arguments"] ?? {});
|
|
166
|
+
if (!parsed.success) {
|
|
167
|
+
return errorText(
|
|
168
|
+
`${MCP_INVALID_PARAMS_PREFIX}Input validation error: Invalid arguments for tool ${name}: ${describeParseError(parsed.error)}`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
return await handler(parsed.data);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return errorText(err instanceof Error ? err.message : String(err));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
export {
|
|
179
|
+
JSONRPC_INVALID_PARAMS,
|
|
180
|
+
JSONRPC_INVALID_REQUEST,
|
|
181
|
+
JSONRPC_METHOD_NOT_FOUND,
|
|
182
|
+
LATEST_PROTOCOL_VERSION,
|
|
183
|
+
McpServer,
|
|
184
|
+
SUPPORTED_PROTOCOL_VERSIONS
|
|
185
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
buildEvent,
|
|
5
|
+
relay,
|
|
6
|
+
relayInProcess
|
|
7
|
+
} from "./token-goat-chunk-LVCBDJVE.mjs";
|
|
8
|
+
import {
|
|
9
|
+
MAX_STDIN_BYTES,
|
|
10
|
+
readStdinJson
|
|
11
|
+
} from "./token-goat-chunk-AO6MFFTW.mjs";
|
|
12
|
+
import "./token-goat-chunk-2F6TFBZE.mjs";
|
|
13
|
+
import "./token-goat-chunk-TUPJRK7R.mjs";
|
|
14
|
+
import "./token-goat-chunk-44Y77VHR.mjs";
|
|
15
|
+
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
16
|
+
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
17
|
+
export {
|
|
18
|
+
MAX_STDIN_BYTES,
|
|
19
|
+
buildEvent,
|
|
20
|
+
readStdinJson,
|
|
21
|
+
relay,
|
|
22
|
+
relayInProcess
|
|
23
|
+
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
+
copilotCliMcpToolsDir,
|
|
5
|
+
estimateTokensFromLength,
|
|
4
6
|
isBlobStale,
|
|
5
7
|
isBuildCommand,
|
|
6
8
|
loadBlob,
|
|
7
9
|
sanitizeFtsQuery,
|
|
8
10
|
storeBlob
|
|
9
|
-
} from "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-2F6TFBZE.mjs";
|
|
10
12
|
import {
|
|
11
13
|
SYMBOL_BODY_CHAR_CAP,
|
|
12
14
|
extractErrorMessage,
|
|
@@ -16,11 +18,94 @@ import {
|
|
|
16
18
|
redactSecrets,
|
|
17
19
|
runGit,
|
|
18
20
|
shortFingerprint
|
|
19
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-44Y77VHR.mjs";
|
|
20
22
|
import {
|
|
21
23
|
registerReset
|
|
22
24
|
} from "./token-goat-chunk-AO2QD2AG.mjs";
|
|
23
25
|
|
|
26
|
+
// src/copilot_mcp_tools.ts
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
function wireShapeLength(tools) {
|
|
30
|
+
const wire = tools.map((tool) => {
|
|
31
|
+
const out = {};
|
|
32
|
+
if (typeof tool.name === "string") out["name"] = tool.name;
|
|
33
|
+
if (typeof tool.description === "string") out["description"] = tool.description;
|
|
34
|
+
if (tool.inputSchema !== void 0) out["inputSchema"] = tool.inputSchema;
|
|
35
|
+
return out;
|
|
36
|
+
});
|
|
37
|
+
return JSON.stringify(wire).length;
|
|
38
|
+
}
|
|
39
|
+
function readCopilotMcpTools(dir = copilotCliMcpToolsDir()) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = fs.readdirSync(dir);
|
|
43
|
+
} catch {
|
|
44
|
+
return { cacheFound: false, servers: [], unreadable: 0 };
|
|
45
|
+
}
|
|
46
|
+
const newest = /* @__PURE__ */ new Map();
|
|
47
|
+
let unreadable = 0;
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.toLowerCase().endsWith(".json")) continue;
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(fs.readFileSync(path.join(dir, entry), "utf8"));
|
|
53
|
+
} catch {
|
|
54
|
+
unreadable += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
58
|
+
unreadable += 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const record = parsed;
|
|
62
|
+
const serverName = typeof record.serverName === "string" ? record.serverName.trim() : "";
|
|
63
|
+
if (serverName === "") {
|
|
64
|
+
unreadable += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const tools = Array.isArray(record.tools) ? record.tools : [];
|
|
68
|
+
const updatedAt = typeof record.updatedAt === "string" ? record.updatedAt : "";
|
|
69
|
+
const definitionBytes = wireShapeLength(tools);
|
|
70
|
+
const candidate = {
|
|
71
|
+
serverName,
|
|
72
|
+
toolCount: tools.length,
|
|
73
|
+
definitionBytes,
|
|
74
|
+
estimatedTokens: estimateTokensFromLength(definitionBytes),
|
|
75
|
+
updatedAt,
|
|
76
|
+
// `tools` is an unchecked cast off a JSON array, so every element is
|
|
77
|
+
// treated as untrusted here rather than trusted to match CachedTool.
|
|
78
|
+
toolNames: tools.map((t) => t?.name).filter((n) => typeof n === "string" && n !== "")
|
|
79
|
+
};
|
|
80
|
+
const existing = newest.get(serverName);
|
|
81
|
+
if (existing === void 0 || candidate.updatedAt > existing.updatedAt) newest.set(serverName, candidate);
|
|
82
|
+
}
|
|
83
|
+
const servers = [...newest.values()].sort(
|
|
84
|
+
(a, b) => b.definitionBytes - a.definitionBytes || a.serverName.localeCompare(b.serverName)
|
|
85
|
+
);
|
|
86
|
+
return { cacheFound: true, servers, unreadable };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/copilot_mcp_names.ts
|
|
90
|
+
var _cached = null;
|
|
91
|
+
function copilotMcpToolNameMap() {
|
|
92
|
+
if (_cached) return _cached;
|
|
93
|
+
const map = /* @__PURE__ */ new Map();
|
|
94
|
+
try {
|
|
95
|
+
for (const server of readCopilotMcpTools().servers) {
|
|
96
|
+
for (const toolName of server.toolNames) {
|
|
97
|
+
map.set(`${server.serverName}-${toolName}`, `mcp__${server.serverName}__${toolName}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
_cached = map;
|
|
103
|
+
return map;
|
|
104
|
+
}
|
|
105
|
+
function canonicalizeCopilotMcpToolName(toolName) {
|
|
106
|
+
return copilotMcpToolNameMap().get(toolName) ?? toolName;
|
|
107
|
+
}
|
|
108
|
+
|
|
24
109
|
// src/hooks_cli.ts
|
|
25
110
|
var _LOG = {
|
|
26
111
|
warn: (msg, ...args) => console.warn(`[hooks_cli] ${msg}`, ...args),
|
|
@@ -150,6 +235,12 @@ function normalizePayload(payload, harness = "claude") {
|
|
|
150
235
|
result2["_tg_harness"] = harness;
|
|
151
236
|
return result2;
|
|
152
237
|
}
|
|
238
|
+
if (harness === "copilot_cli") {
|
|
239
|
+
const result2 = { ...obj };
|
|
240
|
+
result2["tool_name"] = canonicalizeCopilotMcpToolName(toolName);
|
|
241
|
+
result2["_tg_harness"] = harness;
|
|
242
|
+
return result2;
|
|
243
|
+
}
|
|
153
244
|
if (harness === "kimi") {
|
|
154
245
|
const result2 = remapToolName(obj, toolName, KIMI_TOOL_NAME_MAP, KIMI_INPUT_KEY_MAP);
|
|
155
246
|
result2["_tg_harness"] = harness;
|
|
@@ -373,11 +464,11 @@ function gitStateFingerprintSync(cwd) {
|
|
|
373
464
|
}
|
|
374
465
|
}
|
|
375
466
|
var DIR_FINGERPRINT_LISTING_CAP_ENTRIES = 1e4;
|
|
376
|
-
function dirStateFingerprintSync(
|
|
467
|
+
function dirStateFingerprintSync(path2) {
|
|
377
468
|
try {
|
|
378
|
-
const stat = statSync(
|
|
469
|
+
const stat = statSync(path2);
|
|
379
470
|
if (!stat.isDirectory()) return null;
|
|
380
|
-
const entries = readdirSync(
|
|
471
|
+
const entries = readdirSync(path2);
|
|
381
472
|
if (entries.length <= DIR_FINGERPRINT_LISTING_CAP_ENTRIES) {
|
|
382
473
|
return shortFingerprint(entries.slice().sort().join("\0"));
|
|
383
474
|
}
|
|
@@ -387,12 +478,12 @@ function dirStateFingerprintSync(path) {
|
|
|
387
478
|
}
|
|
388
479
|
}
|
|
389
480
|
var FILE_FINGERPRINT_CONTENT_CAP_BYTES = 2 * 1024 * 1024;
|
|
390
|
-
function fileStateFingerprintSync(
|
|
481
|
+
function fileStateFingerprintSync(path2) {
|
|
391
482
|
try {
|
|
392
|
-
const stat = statSync(
|
|
483
|
+
const stat = statSync(path2);
|
|
393
484
|
if (!stat.isFile()) return null;
|
|
394
485
|
if (stat.size <= FILE_FINGERPRINT_CONTENT_CAP_BYTES) {
|
|
395
|
-
return shortFingerprint(readFileSync(
|
|
486
|
+
return shortFingerprint(readFileSync(path2));
|
|
396
487
|
}
|
|
397
488
|
return shortFingerprint(`${stat.mtimeMs}\0${stat.size}`);
|
|
398
489
|
} catch {
|
|
@@ -715,9 +806,11 @@ var HOOK_EVENTS = [
|
|
|
715
806
|
"notification",
|
|
716
807
|
"stop",
|
|
717
808
|
"pre_compact",
|
|
809
|
+
"post_compact",
|
|
718
810
|
"user_prompt_submit",
|
|
719
811
|
"subagent_stop",
|
|
720
|
-
"session_start"
|
|
812
|
+
"session_start",
|
|
813
|
+
"post_tool_use_failure"
|
|
721
814
|
];
|
|
722
815
|
|
|
723
816
|
// src/cli_commands.ts
|
|
@@ -790,6 +883,7 @@ function formatCommandManifest(manifest) {
|
|
|
790
883
|
|
|
791
884
|
// src/stdin_json.ts
|
|
792
885
|
var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
|
|
886
|
+
var MAX_STDIN_WALL_MS = 6e4;
|
|
793
887
|
var MAX_STDIN_BYTES = 64 * 1024 * 1024;
|
|
794
888
|
function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
|
|
795
889
|
return new Promise((resolve2, reject) => {
|
|
@@ -799,16 +893,27 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
|
|
|
799
893
|
const finish = (fn) => {
|
|
800
894
|
if (settled) return;
|
|
801
895
|
settled = true;
|
|
802
|
-
clearTimeout(
|
|
896
|
+
clearTimeout(idleTimer);
|
|
897
|
+
clearTimeout(wallTimer);
|
|
803
898
|
process.stdin.removeListener("data", onData);
|
|
804
899
|
process.stdin.removeListener("end", onEnd);
|
|
805
900
|
process.stdin.removeListener("error", onError);
|
|
806
901
|
fn();
|
|
807
902
|
};
|
|
808
|
-
|
|
903
|
+
let idleTimer = setTimeout(() => {
|
|
809
904
|
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
810
905
|
}, timeoutMs);
|
|
906
|
+
const wallTimer = setTimeout(() => {
|
|
907
|
+
finish(() => {
|
|
908
|
+
process.stdin.destroy();
|
|
909
|
+
reject(new Error(`readStdinJson: stdin took longer than ${MAX_STDIN_WALL_MS} ms`));
|
|
910
|
+
});
|
|
911
|
+
}, MAX_STDIN_WALL_MS);
|
|
811
912
|
const onData = (chunk) => {
|
|
913
|
+
clearTimeout(idleTimer);
|
|
914
|
+
idleTimer = setTimeout(() => {
|
|
915
|
+
finish(() => reject(new Error("readStdinJson: timed out waiting for stdin")));
|
|
916
|
+
}, timeoutMs);
|
|
812
917
|
totalBytes += chunk.length;
|
|
813
918
|
if (totalBytes > maxBytes) {
|
|
814
919
|
finish(() => {
|
|
@@ -843,10 +948,10 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
|
|
|
843
948
|
}
|
|
844
949
|
|
|
845
950
|
// src/symbol_body_probe.ts
|
|
846
|
-
import * as
|
|
951
|
+
import * as fs2 from "fs";
|
|
847
952
|
var OVERSIZED_BODY_PROBE_SQL = `SELECT 1 FROM symbols WHERE LENGTH(body) > ${SYMBOL_BODY_CHAR_CAP} LIMIT 1`;
|
|
848
953
|
function checkSymbolBodySize(dbPath) {
|
|
849
|
-
if (!
|
|
954
|
+
if (!fs2.existsSync(dbPath)) {
|
|
850
955
|
return { name: "Symbol body size", status: "ok", message: "no database yet" };
|
|
851
956
|
}
|
|
852
957
|
try {
|
|
@@ -869,7 +974,210 @@ function checkSymbolBodySize(dbPath) {
|
|
|
869
974
|
}
|
|
870
975
|
}
|
|
871
976
|
|
|
977
|
+
// src/resident_context.ts
|
|
978
|
+
import * as fs3 from "node:fs";
|
|
979
|
+
var LARGE_TASK_LIST_BYTES = 2e4;
|
|
980
|
+
var LARGE_SKILL_BODY_BYTES = 2e4;
|
|
981
|
+
var SKILL_BODY_REPEAT_THRESHOLD = 2;
|
|
982
|
+
var RESIDENT_TAIL_MAX_BYTES = 1048576;
|
|
983
|
+
function createResidentContextStats() {
|
|
984
|
+
return {
|
|
985
|
+
attachmentsByType: /* @__PURE__ */ new Map(),
|
|
986
|
+
latestTaskList: null,
|
|
987
|
+
taskReminderCount: 0,
|
|
988
|
+
taskReminderBytes: 0,
|
|
989
|
+
skillBodies: /* @__PURE__ */ new Map(),
|
|
990
|
+
compactionCount: 0
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
function bump(map, key, bytes) {
|
|
994
|
+
const entry = map.get(key);
|
|
995
|
+
if (entry === void 0) map.set(key, { count: 1, bytes });
|
|
996
|
+
else {
|
|
997
|
+
entry.count += 1;
|
|
998
|
+
entry.bytes += bytes;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function messageText(message) {
|
|
1002
|
+
if (message === null || typeof message !== "object") return "";
|
|
1003
|
+
const content = message["content"];
|
|
1004
|
+
if (typeof content === "string") return content;
|
|
1005
|
+
if (!Array.isArray(content)) return "";
|
|
1006
|
+
let out = "";
|
|
1007
|
+
for (const block of content) {
|
|
1008
|
+
if (block === null || typeof block !== "object") continue;
|
|
1009
|
+
const text = block["text"];
|
|
1010
|
+
if (typeof text === "string") out += text;
|
|
1011
|
+
}
|
|
1012
|
+
return out;
|
|
1013
|
+
}
|
|
1014
|
+
function skillNameFromBody(text) {
|
|
1015
|
+
const dir = /^Base directory for this skill:\s*(.+?)\s*$/m.exec(text);
|
|
1016
|
+
if (dir?.[1] !== void 0) {
|
|
1017
|
+
const segments = dir[1].split(/[\\/]/).filter((s) => s.length > 0);
|
|
1018
|
+
const last = segments[segments.length - 1];
|
|
1019
|
+
if (last !== void 0 && last.length > 0) return last;
|
|
1020
|
+
}
|
|
1021
|
+
const heading = /^#\s+(.+?)\s*$/m.exec(text);
|
|
1022
|
+
if (heading?.[1] !== void 0 && heading[1].length > 0) return heading[1];
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
function readTaskList(attachment, bytes) {
|
|
1026
|
+
const snapshot = {
|
|
1027
|
+
bytes,
|
|
1028
|
+
itemCount: typeof attachment["itemCount"] === "number" ? attachment["itemCount"] : 0,
|
|
1029
|
+
completed: 0,
|
|
1030
|
+
inProgress: 0,
|
|
1031
|
+
pending: 0,
|
|
1032
|
+
descriptionBytes: 0,
|
|
1033
|
+
completedDescriptionBytes: 0
|
|
1034
|
+
};
|
|
1035
|
+
const content = attachment["content"];
|
|
1036
|
+
if (!Array.isArray(content)) return snapshot;
|
|
1037
|
+
if (content.length > snapshot.itemCount) snapshot.itemCount = content.length;
|
|
1038
|
+
for (const item of content) {
|
|
1039
|
+
if (item === null || typeof item !== "object") continue;
|
|
1040
|
+
const record = item;
|
|
1041
|
+
const status = typeof record["status"] === "string" ? record["status"] : "";
|
|
1042
|
+
const description = typeof record["description"] === "string" ? record["description"] : "";
|
|
1043
|
+
snapshot.descriptionBytes += description.length;
|
|
1044
|
+
if (status === "completed") {
|
|
1045
|
+
snapshot.completed += 1;
|
|
1046
|
+
snapshot.completedDescriptionBytes += description.length;
|
|
1047
|
+
} else if (status === "in_progress") snapshot.inProgress += 1;
|
|
1048
|
+
else if (status === "pending") snapshot.pending += 1;
|
|
1049
|
+
}
|
|
1050
|
+
return snapshot;
|
|
1051
|
+
}
|
|
1052
|
+
function collectInvokedSkills(acc, record) {
|
|
1053
|
+
const skills = record["skills"];
|
|
1054
|
+
if (!Array.isArray(skills)) return;
|
|
1055
|
+
for (const entry of skills) {
|
|
1056
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
1057
|
+
const skill = entry;
|
|
1058
|
+
const content = skill["content"];
|
|
1059
|
+
if (typeof content !== "string" || content.length < LARGE_SKILL_BODY_BYTES) continue;
|
|
1060
|
+
const name = skillName(skill);
|
|
1061
|
+
if (name !== null) bump(acc.skillBodies, name, content.length);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function skillName(skill) {
|
|
1065
|
+
const name = skill["name"];
|
|
1066
|
+
if (typeof name === "string" && name.trim() !== "") return name.trim();
|
|
1067
|
+
const path2 = skill["path"];
|
|
1068
|
+
if (typeof path2 !== "string") return null;
|
|
1069
|
+
const segments = path2.split(/[\\/]/).filter((part) => part !== "");
|
|
1070
|
+
const last = segments[segments.length - 1];
|
|
1071
|
+
return last === void 0 || last === "" ? null : last;
|
|
1072
|
+
}
|
|
1073
|
+
function accumulateResidentLine(acc, parsed, bytes) {
|
|
1074
|
+
try {
|
|
1075
|
+
if (parsed === null || typeof parsed !== "object") return;
|
|
1076
|
+
const line = parsed;
|
|
1077
|
+
if (line["type"] === "system" && line["subtype"] === "compact_boundary") acc.compactionCount += 1;
|
|
1078
|
+
const attachment = line["attachment"];
|
|
1079
|
+
if (attachment !== null && typeof attachment === "object") {
|
|
1080
|
+
const record = attachment;
|
|
1081
|
+
const type = typeof record["type"] === "string" ? record["type"] : "unknown";
|
|
1082
|
+
bump(acc.attachmentsByType, type, bytes);
|
|
1083
|
+
if (type === "task_reminder") {
|
|
1084
|
+
acc.taskReminderCount += 1;
|
|
1085
|
+
acc.taskReminderBytes += bytes;
|
|
1086
|
+
acc.latestTaskList = readTaskList(record, bytes);
|
|
1087
|
+
}
|
|
1088
|
+
if (type === "invoked_skills") collectInvokedSkills(acc, record);
|
|
1089
|
+
}
|
|
1090
|
+
if (line["isMeta"] === true && line["type"] === "user") {
|
|
1091
|
+
const text = messageText(line["message"]);
|
|
1092
|
+
if (text.length >= LARGE_SKILL_BODY_BYTES) {
|
|
1093
|
+
const skill = skillNameFromBody(text);
|
|
1094
|
+
if (skill !== null) bump(acc.skillBodies, skill, text.length);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function summarizeResidentContext(acc) {
|
|
1101
|
+
const attachmentClasses = [...acc.attachmentsByType.entries()].map(([type, v]) => ({ type, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.type.localeCompare(b.type));
|
|
1102
|
+
const repeatedSkillBodies = [...acc.skillBodies.entries()].filter(([, v]) => v.count >= SKILL_BODY_REPEAT_THRESHOLD).map(([skill, v]) => ({ skill, count: v.count, bytes: v.bytes })).sort((a, b) => b.bytes - a.bytes || a.skill.localeCompare(b.skill));
|
|
1103
|
+
return {
|
|
1104
|
+
attachmentClasses,
|
|
1105
|
+
totalAttachmentBytes: attachmentClasses.reduce((n, c) => n + c.bytes, 0),
|
|
1106
|
+
latestTaskList: acc.latestTaskList,
|
|
1107
|
+
taskReminderCount: acc.taskReminderCount,
|
|
1108
|
+
taskReminderBytes: acc.taskReminderBytes,
|
|
1109
|
+
repeatedSkillBodies,
|
|
1110
|
+
compactionCount: acc.compactionCount
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function formatBytes(bytes) {
|
|
1114
|
+
if (bytes >= 1e6) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
1115
|
+
if (bytes >= 1e3) return `${Math.round(bytes / 1024)} KB`;
|
|
1116
|
+
return `${bytes} B`;
|
|
1117
|
+
}
|
|
1118
|
+
function formatTokenEstimate(tokens) {
|
|
1119
|
+
return tokens >= 1e3 ? `${Math.round(tokens / 1e3)}K` : String(tokens);
|
|
1120
|
+
}
|
|
1121
|
+
function taskListPruneHint(snapshot) {
|
|
1122
|
+
if (snapshot === null) return null;
|
|
1123
|
+
if (snapshot.bytes < LARGE_TASK_LIST_BYTES) return null;
|
|
1124
|
+
if (snapshot.completed === 0) return null;
|
|
1125
|
+
const tokens = formatTokenEstimate(estimateTokensFromLength(snapshot.bytes));
|
|
1126
|
+
const prunable = formatBytes(snapshot.completedDescriptionBytes);
|
|
1127
|
+
return `Your task list is ${formatBytes(snapshot.bytes)} (~${tokens} tok est) and is re-injected in full whenever it changes; ${snapshot.completed} of ${snapshot.itemCount} items are already completed and their descriptions alone are ${prunable}. Prune completed items or shorten their descriptions with TaskUpdate. Check ownership first if other agents share this list.`;
|
|
1128
|
+
}
|
|
1129
|
+
function repeatedSkillBodyHint(injections) {
|
|
1130
|
+
const worst = injections[0];
|
|
1131
|
+
if (worst === void 0) return null;
|
|
1132
|
+
if (worst.count < SKILL_BODY_REPEAT_THRESHOLD || worst.bytes < LARGE_SKILL_BODY_BYTES) return null;
|
|
1133
|
+
const tokens = formatTokenEstimate(estimateTokensFromLength(worst.bytes));
|
|
1134
|
+
return `The \`${worst.skill}\` skill body has been injected ${worst.count} times this session (${formatBytes(worst.bytes)} total, ~${tokens} tok est). Slash expansion and the Skill tool both send the whole body every time, and no hook can intercept either. If it is already loaded, work from it instead of re-invoking; to reread one part, use \`token-goat skill-section ${worst.skill} '<heading>'\`.`;
|
|
1135
|
+
}
|
|
1136
|
+
function readTranscriptTail(transcriptPath, maxBytes = RESIDENT_TAIL_MAX_BYTES) {
|
|
1137
|
+
let fd = null;
|
|
1138
|
+
try {
|
|
1139
|
+
const stat = fs3.statSync(transcriptPath);
|
|
1140
|
+
if (!stat.isFile() || stat.size === 0) return [];
|
|
1141
|
+
const start = Math.max(0, stat.size - maxBytes);
|
|
1142
|
+
const length = stat.size - start;
|
|
1143
|
+
fd = fs3.openSync(transcriptPath, "r");
|
|
1144
|
+
const buf = Buffer.allocUnsafe(length);
|
|
1145
|
+
const read = fs3.readSync(fd, buf, 0, length, start);
|
|
1146
|
+
const lines = buf.subarray(0, read).toString("utf8").split("\n");
|
|
1147
|
+
if (start > 0) lines.shift();
|
|
1148
|
+
return lines;
|
|
1149
|
+
} catch {
|
|
1150
|
+
return [];
|
|
1151
|
+
} finally {
|
|
1152
|
+
if (fd !== null) {
|
|
1153
|
+
try {
|
|
1154
|
+
fs3.closeSync(fd);
|
|
1155
|
+
} catch {
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function lineMayCarryResidentSignal(line) {
|
|
1161
|
+
return line.includes('"task_reminder"') || line.includes('"compact_boundary"') || line.includes('"invoked_skills"') || line.includes('"isMeta"');
|
|
1162
|
+
}
|
|
1163
|
+
function accumulateResidentLines(lines) {
|
|
1164
|
+
const acc = createResidentContextStats();
|
|
1165
|
+
for (const line of lines) {
|
|
1166
|
+
const trimmed = line.trim();
|
|
1167
|
+
if (trimmed === "") continue;
|
|
1168
|
+
let parsed;
|
|
1169
|
+
try {
|
|
1170
|
+
parsed = JSON.parse(trimmed);
|
|
1171
|
+
} catch {
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
accumulateResidentLine(acc, parsed, trimmed.length);
|
|
1175
|
+
}
|
|
1176
|
+
return acc;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
872
1179
|
export {
|
|
1180
|
+
readCopilotMcpTools,
|
|
873
1181
|
GEMINI_TOOL_NAME_MAP,
|
|
874
1182
|
normalizePayload,
|
|
875
1183
|
isRecallCacheType,
|
|
@@ -895,6 +1203,16 @@ export {
|
|
|
895
1203
|
filterCommandManifest,
|
|
896
1204
|
formatCommandManifest,
|
|
897
1205
|
checkSymbolBodySize,
|
|
1206
|
+
createResidentContextStats,
|
|
1207
|
+
accumulateResidentLine,
|
|
1208
|
+
summarizeResidentContext,
|
|
1209
|
+
formatBytes,
|
|
1210
|
+
formatTokenEstimate,
|
|
1211
|
+
taskListPruneHint,
|
|
1212
|
+
repeatedSkillBodyHint,
|
|
1213
|
+
readTranscriptTail,
|
|
1214
|
+
lineMayCarryResidentSignal,
|
|
1215
|
+
accumulateResidentLines,
|
|
898
1216
|
MAX_STDIN_BYTES,
|
|
899
1217
|
readStdinJson
|
|
900
1218
|
};
|