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.
Files changed (35) hide show
  1. package/README.md +35 -7
  2. package/SECURITY.md +47 -16
  3. package/dist/{token-goat-chunk-I2KCEGIP.mjs → token-goat-chunk-2F6TFBZE.mjs} +1355 -408
  4. package/dist/token-goat-chunk-324QOJYZ.mjs +91 -0
  5. package/dist/{token-goat-chunk-K7W3P2TJ.mjs → token-goat-chunk-44Y77VHR.mjs} +425 -39
  6. package/dist/{token-goat-chunk-UIZVX3HN.mjs → token-goat-chunk-4KZILRZN.mjs} +2 -2
  7. package/dist/token-goat-chunk-5CVKO3DA.mjs +185 -0
  8. package/dist/token-goat-chunk-A62K4XW2.mjs +23 -0
  9. package/dist/{token-goat-chunk-SIHYQCTM.mjs → token-goat-chunk-AO6MFFTW.mjs} +331 -13
  10. package/dist/{token-goat-chunk-DRAIYVUI.mjs → token-goat-chunk-FBGBTICM.mjs} +4 -4
  11. package/dist/{token-goat-chunk-YF67EWRH.mjs → token-goat-chunk-J35AKWEQ.mjs} +5 -5
  12. package/dist/{token-goat-hook-chunk-4POU6OHK.mjs → token-goat-chunk-LVCBDJVE.mjs} +318 -51
  13. package/dist/token-goat-chunk-R4SR7MQY.mjs +486 -0
  14. package/dist/{token-goat-chunk-D5IZEGN2.mjs → token-goat-chunk-SRAR6DOK.mjs} +144 -147
  15. package/dist/{token-goat-hook-chunk-NIPXIAPA.mjs → token-goat-chunk-TUPJRK7R.mjs} +1 -1
  16. package/dist/{token-goat-chunk-HW4VUKJ5.mjs → token-goat-chunk-VXSYZGBA.mjs} +589 -141
  17. package/dist/{token-goat-chunk-DG53MVNJ.mjs → token-goat-chunk-WN5T5EW5.mjs} +212 -212
  18. package/dist/token-goat-hook.mjs +7 -7
  19. package/dist/token-goat.core.mjs +5 -5
  20. package/package.json +18 -15
  21. package/scripts/install-git-hooks.mjs +55 -0
  22. package/dist/token-goat-chunk-DQIT5JIF.mjs +0 -13659
  23. package/dist/token-goat-chunk-Y2NH6DGZ.mjs +0 -10048
  24. package/dist/token-goat-hook-chunk-2O7P4Z6Q.mjs +0 -6419
  25. package/dist/token-goat-hook-chunk-3AAH72SO.mjs +0 -15970
  26. package/dist/token-goat-hook-chunk-3QYSN4QV.mjs +0 -14764
  27. package/dist/token-goat-hook-chunk-BUOCULAM.mjs +0 -29
  28. package/dist/token-goat-hook-chunk-CRBMPT74.mjs +0 -900
  29. package/dist/token-goat-hook-chunk-D6UJEFHX.mjs +0 -153
  30. package/dist/token-goat-hook-chunk-J4HKWUWZ.mjs +0 -23
  31. package/dist/token-goat-hook-chunk-NXVT4F3S.mjs +0 -10406
  32. package/dist/token-goat-hook-chunk-RFRLWOQH.mjs +0 -11
  33. package/dist/token-goat-hook-chunk-VU6VGZBM.mjs +0 -706
  34. package/dist/token-goat-hook-chunk-XM4W3WCO.mjs +0 -18144
  35. 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-DQIT5JIF.mjs";
13
+ } from "./token-goat-chunk-TUPJRK7R.mjs";
14
14
  import {
15
15
  loadConfig,
16
16
  recordStat
17
- } from "./token-goat-chunk-K7W3P2TJ.mjs";
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-I2KCEGIP.mjs";
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-K7W3P2TJ.mjs";
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(path) {
467
+ function dirStateFingerprintSync(path2) {
377
468
  try {
378
- const stat = statSync(path);
469
+ const stat = statSync(path2);
379
470
  if (!stat.isDirectory()) return null;
380
- const entries = readdirSync(path);
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(path) {
481
+ function fileStateFingerprintSync(path2) {
391
482
  try {
392
- const stat = statSync(path);
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(path));
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(timer);
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
- const timer = setTimeout(() => {
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 fs from "fs";
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 (!fs.existsSync(dbPath)) {
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
  };