token-goat 2.6.23 → 2.6.24

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 CHANGED
@@ -403,11 +403,11 @@ This writes `.pi/extensions/token-goat.ts` in the current project only. Remove i
403
403
  token-goat install --copilot
404
404
  ```
405
405
 
406
- The `--copilot` flag patches Claude Code and registers a Copilot CLI hook config: `~/.copilot/hooks/token-goat.json` (a `{ version, hooks }` file registering `preToolUse`, `postToolUse`, `preCompact`, `agentStop`, `subagentStop`, and `userPromptSubmitted`, per Copilot's own [hooks reference](https://docs.github.com/en/copilot/reference/hooks-reference)) plus the shim script it points at, `~/.copilot/hooks/token-goat-shim.js`. Unlike Codex, Copilot's event names and response schema (`permissionDecision`/`modifiedArgs` for `preToolUse`, `modifiedResult`/`additionalContext` for `postToolUse`, `decision`/`reason` for `agentStop`/`subagentStop`) genuinely differ from Claude Code's, so the shim translates rather than passes through.
406
+ The `--copilot` flag patches Claude Code and registers a Copilot CLI hook config: `~/.copilot/hooks/token-goat.json` (a `{ version, hooks }` file registering `sessionStart`, `preToolUse`, `postToolUse`, `preCompact`, `agentStop`, `subagentStop`, and `userPromptSubmitted`, per Copilot's own [hooks reference](https://docs.github.com/en/copilot/reference/hooks-reference)) plus the shim script it points at, `~/.copilot/hooks/token-goat-shim.js`. Unlike Codex, Copilot's event names and response schema (`permissionDecision`/`modifiedArgs` for `preToolUse`, `modifiedResult`/`additionalContext` for `postToolUse`, `decision`/`reason` for `agentStop`/`subagentStop`) genuinely differ from Claude Code's, so the shim translates rather than passes through.
407
407
 
408
- What works: **bash output compression and re-read denial** (`preToolUse` returns `modifiedArgs` or `permissionDecision: "deny"`), **image shrinking and post-edit indexing** (`postToolUse` returns `additionalContext`), and **stop-hallucination logging** (`agentStop`/`subagentStop` map a token-goat `deny` onto `decision: "block"`, everything else onto `decision: "allow"`). `preCompact` and `userPromptSubmitted` are notification-only on real Copilot CLI, per its docs: Copilot never reads a response body for either, so token-goat's compaction manifest and prompt-context hints have no surfacing channel there. The shim still calls through for both so token-goat's internal side effects keep running, but nothing gets injected back into the agent. Copilot's built-in tool names (`view`, `edit`, `create`, `bash`/`powershell`, `web_fetch`, `grep`, `glob`, `memory`, and MCP-server calls) are remapped onto token-goat's internal names where a clear match exists (`view`→Read, `edit`→Edit, `create`→Write, `bash`/`powershell`→Bash, `web_fetch`→WebFetch, `grep`→Grep, `glob`→Glob); `memory`, `task`, `ask_user`, and MCP tool calls pass through unmapped and simply no-op.
408
+ What works: **the command-routing reminder** (`sessionStart` returns `additionalContext`, so Copilot is told token-goat exists before it picks its first read tool — this is the one channel that lands ahead of that decision), **bash output compression and re-read denial** (`preToolUse` returns `modifiedArgs` or `permissionDecision: "deny"`), **image shrinking and post-edit indexing** (`postToolUse` returns `additionalContext`), and **stop-hallucination logging** (`agentStop`/`subagentStop` map a token-goat `deny` onto `decision: "block"`, everything else onto `decision: "allow"`). `preCompact` and `userPromptSubmitted` are notification-only on real Copilot CLI, per its docs: Copilot never reads a response body for either, so token-goat's compaction manifest and prompt-context hints have no surfacing channel there. The shim still calls through for both so token-goat's internal side effects keep running, but nothing gets injected back into the agent. Copilot's built-in tool names (`view`, `edit`, `create`, `bash`/`powershell`, `web_fetch`, `grep`, `glob`, `memory`, and MCP-server calls) are remapped onto token-goat's internal names where a clear match exists (`view`→Read, `edit`→Edit, `create`→Write, `bash`/`powershell`→Bash, `web_fetch`→WebFetch, `grep`→Grep, `glob`→Glob); `memory`, `task`, `ask_user`, and MCP tool calls pass through unmapped and simply no-op.
409
409
 
410
- No ambient environment variable documents "this process is running under Copilot CLI" the way Codex/opencode set one, so the shim sets `TOKEN_GOAT_HARNESS_OVERRIDE=copilot_cli` itself before calling `token-goat hook` (same workaround `--pi` uses). Install also writes a token-goat routing block into `~/.copilot/copilot-instructions.md` (the same delimited-block gate written to `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`), merged idempotently so any hand-written content outside the markers is preserved byte-for-byte. To install for one project instead of user scope: `token-goat install --copilot --local` (writes `.github/hooks/token-goat.json` and `.github/copilot-instructions.md` in the current project). To remove: `token-goat uninstall --copilot`.
410
+ No ambient environment variable documents "this process is running under Copilot CLI" the way Codex/opencode set one, so the shim sets `TOKEN_GOAT_HARNESS_OVERRIDE=copilot_cli` itself before calling `token-goat hook` (same workaround `--pi` uses). Install also writes a token-goat routing block into `~/.copilot/copilot-instructions.md` (the same delimited-block gate written to `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`), merged idempotently so any hand-written content outside the markers is preserved byte-for-byte. If you set `COPILOT_HOME`, install follows it — hooks go to `$COPILOT_HOME/hooks/` and the routing block to `$COPILOT_HOME/copilot-instructions.md`, matching where Copilot CLI actually reads them. To install for one project instead of user scope: `token-goat install --copilot --local` (writes `.github/hooks/token-goat.json` and `.github/copilot-instructions.md` in the current project). To remove: `token-goat uninstall --copilot`.
411
411
 
412
412
  **If Copilot CLI starts denying every tool call with `Denied by preToolUse hook ... (hook errored)`:** this is Copilot's own fail-closed behavior for a `preToolUse` hook that crashes, exits non-zero, or returns unparseable output -- it isn't limited to token-goat's own tool calls, since a fail-closed `preToolUse` hook blocks the whole session. Copilot caches hook configs at session start, so **renaming or reinstalling the hook mid-session has no effect** -- the only recovery is: run `token-goat install --copilot` (or `token-goat doctor`, which now checks the installed hook end-to-end and calls out a stale node-binary path from an nvm/fnm/volta upgrade specifically), then **fully restart Copilot CLI**.
413
413
 
@@ -49,7 +49,7 @@ var init_define_import_meta_env = __esm({
49
49
  import { createRequire } from "node:module";
50
50
  function resolveVersion() {
51
51
  if (true) {
52
- return "2.6.23";
52
+ return "2.6.24";
53
53
  }
54
54
  const require2 = createRequire(import.meta.url);
55
55
  const pkg = require2("../package.json");
@@ -1464,8 +1464,8 @@ function normalizeDarwinSystemAlias(p) {
1464
1464
  }
1465
1465
  function resolveIndexPath(file2, base = process.cwd()) {
1466
1466
  const isWindowsAbsolute = (s) => /^[a-zA-Z]:[/\\]/.test(s);
1467
- const resolve24 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
1468
- return normalizePath(resolve24(base, file2));
1467
+ const resolve25 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
1468
+ return normalizePath(resolve25(base, file2));
1469
1469
  }
1470
1470
  function toDisplayPath(root, target) {
1471
1471
  if (root === void 0) return target;
@@ -2653,14 +2653,23 @@ const path = require('node:path')
2653
2653
  const { pathToFileURL } = require('node:url')
2654
2654
 
2655
2655
  // Copilot event name -> token-goat internal HookEventName (src/types.ts's
2656
- // HOOK_EVENTS). Only these six have a token-goat handler; every other real
2656
+ // HOOK_EVENTS). Only these seven have a token-goat handler; every other real
2657
2657
  // Copilot event (sessionEnd, postToolUseFailure, subagentStart,
2658
2658
  // errorOccurred, notification, permissionRequest) is left unimplemented
2659
2659
  // rather than guessed at, and falls through to the default no-op below.
2660
- // 'sessionStart' is handled as a permanent no-op even though it's a real
2661
- // Copilot event, because token-goat has no internal session_start handler
2662
- // (mirrors PI_EXTENSION_SCRIPT's documented precedent).
2660
+ // 'sessionStart' was previously a permanent no-op on the stated grounds that
2661
+ // token-goat has no internal session_start handler. That was simply wrong --
2662
+ // hooks_session_start.ts has long emitted the command-routing reminder that
2663
+ // every other harness receives -- and the no-op was the reason Copilot CLI
2664
+ // sessions alone never got told token-goat exists. It is wired now: verified
2665
+ // against Copilot CLI 1.0.77 that a hooks.json sessionStart entry returning
2666
+ // {additionalContext} does reach the model. The github/copilot-cli#2142
2667
+ // fire-and-forget bug that would have made this dead wiring was fixed in a
2668
+ // pre-release months before that version, and its companion multi-extension
2669
+ // hook-overwrite bug never applied here: it hit runtime *extension* hooks,
2670
+ // while this config-file hooks.json path goes through Copilot's own merge.
2663
2671
  const COPILOT_TO_TG_EVENT = {
2672
+ sessionStart: 'session_start',
2664
2673
  preToolUse: 'pre_tool_use',
2665
2674
  postToolUse: 'post_tool_use',
2666
2675
  preCompact: 'pre_compact',
@@ -2772,11 +2781,6 @@ async function tryInProcess(entryPath, tgEvent, canonical) {
2772
2781
  async function main() {
2773
2782
  const copilotEvent = process.argv[2] || ''
2774
2783
 
2775
- if (copilotEvent === 'sessionStart') {
2776
- process.stdout.write('{}')
2777
- return
2778
- }
2779
-
2780
2784
  const tgEvent = COPILOT_TO_TG_EVENT[copilotEvent]
2781
2785
  if (!tgEvent) {
2782
2786
  process.stdout.write('{}')
@@ -2902,7 +2906,10 @@ function translate(copilotEvent, resp) {
2902
2906
  return {}
2903
2907
  }
2904
2908
 
2905
- if (copilotEvent === 'postToolUse') {
2909
+ if (copilotEvent === 'postToolUse' || copilotEvent === 'sessionStart') {
2910
+ // Both surface token-goat's context through the same field. sessionStart is
2911
+ // the one channel that reaches the model before it picks its first read
2912
+ // tool, so this is where the routing reminder has to land.
2906
2913
  const context = extractContext(resp)
2907
2914
  if (context) return { additionalContext: context }
2908
2915
  return {}
@@ -2926,9 +2933,8 @@ function translate(copilotEvent, resp) {
2926
2933
  // doc that both are notification-only -- Copilot never reads a response
2927
2934
  // body for either, so any additionalContext/systemMessage token-goat
2928
2935
  // produces has no surfacing channel here. This still routes through the
2929
- // token-goat hook call above (unlike sessionStart's early no-op) so the
2930
- // internal handler's own side effects keep running; only the response is
2931
- // discarded.
2936
+ // token-goat hook call above so the internal handler's own side effects keep
2937
+ // running; only the response is discarded.
2932
2938
  return {}
2933
2939
  }
2934
2940
 
@@ -2968,8 +2974,13 @@ main()
2968
2974
  import * as fs4 from "node:fs";
2969
2975
  import * as os4 from "node:os";
2970
2976
  import * as path6 from "node:path";
2977
+ function copilotCliUserRoot() {
2978
+ const override = process.env["COPILOT_HOME"];
2979
+ if (override !== void 0 && override.trim() !== "") return path6.resolve(override);
2980
+ return path6.join(os4.homedir(), ".copilot");
2981
+ }
2971
2982
  function copilotCliUserHooksDir() {
2972
- return path6.join(os4.homedir(), ".copilot", "hooks");
2983
+ return path6.join(copilotCliUserRoot(), "hooks");
2973
2984
  }
2974
2985
  function copilotCliProjectHooksDir() {
2975
2986
  return path6.join(process.cwd(), ".github", "hooks");
@@ -3074,6 +3085,7 @@ var init_copilot_cli_install = __esm({
3074
3085
  init_copilot_cli();
3075
3086
  init_guidance_block();
3076
3087
  COPILOT_CLI_HOOK_EVENTS = [
3088
+ "sessionStart",
3077
3089
  "preToolUse",
3078
3090
  "postToolUse",
3079
3091
  "preCompact",
@@ -11510,16 +11522,16 @@ var init_file_type_handler = __esm({
11510
11522
 
11511
11523
  // src/skill_cache.ts
11512
11524
  import * as fs16 from "fs/promises";
11513
- import { resolve as resolve7 } from "path";
11525
+ import { resolve as resolve8 } from "path";
11514
11526
  import { homedir as homedir7 } from "os";
11515
11527
  import { readdirSync as readdirSync8, readFileSync as readFileSync12, existsSync as existsSync14, statSync as statSync7, unlinkSync as unlinkSync9 } from "node:fs";
11516
11528
  function skillOutputsDir() {
11517
11529
  if (_skillOutputsDirOverride) return _skillOutputsDirOverride;
11518
- return resolve7(dataDir(), SKILLS_OUTPUT_SUBDIR);
11530
+ return resolve8(dataDir(), SKILLS_OUTPUT_SUBDIR);
11519
11531
  }
11520
11532
  function skillsSourceDir() {
11521
11533
  if (_skillsSourceDirOverride) return _skillsSourceDirOverride;
11522
- return resolve7(homedir7(), ".claude", "skills");
11534
+ return resolve8(homedir7(), ".claude", "skills");
11523
11535
  }
11524
11536
  async function ensureSkillsDir() {
11525
11537
  try {
@@ -11662,7 +11674,7 @@ async function listOutputs() {
11662
11674
  continue;
11663
11675
  }
11664
11676
  try {
11665
- const content = await fs16.readFile(resolve7(dir, entry.name), "utf-8");
11677
+ const content = await fs16.readFile(resolve8(dir, entry.name), "utf-8");
11666
11678
  const meta3 = JSON.parse(content);
11667
11679
  metas.push(meta3);
11668
11680
  } catch {
@@ -11696,7 +11708,7 @@ async function findCrossSessionEntry(skillName, contentSha) {
11696
11708
  continue;
11697
11709
  }
11698
11710
  const dir = skillOutputsDir();
11699
- const bodyPath = resolve7(dir, `${meta3.outputId}.txt`);
11711
+ const bodyPath = resolve8(dir, `${meta3.outputId}.txt`);
11700
11712
  try {
11701
11713
  const bodyExists = await fs16.access(bodyPath).then(() => true).catch(() => false);
11702
11714
  if (bodyExists) {
@@ -11751,7 +11763,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
11751
11763
  const truncBuf = buf.slice(truncStart);
11752
11764
  storedBody = truncBuf.toString("utf-8");
11753
11765
  }
11754
- await atomicWriteText(resolve7(dir, `${outId}.txt`), storedBody);
11766
+ await atomicWriteText(resolve8(dir, `${outId}.txt`), storedBody);
11755
11767
  const meta3 = {
11756
11768
  outputId: outId,
11757
11769
  skillName: name2,
@@ -11761,7 +11773,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
11761
11773
  truncated,
11762
11774
  sourcePath: opts?.sourcePath || ""
11763
11775
  };
11764
- await atomicWriteText(resolve7(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
11776
+ await atomicWriteText(resolve8(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
11765
11777
  pruneSkillOutputs();
11766
11778
  return meta3;
11767
11779
  } catch {
@@ -11781,7 +11793,7 @@ async function storeCompact(sessionId, skillName, compactText, sourceSha) {
11781
11793
  text = `<!-- source_sha: ${sourceSha.slice(0, 12)} -->
11782
11794
  ${text}`;
11783
11795
  }
11784
- await atomicWriteText(resolve7(dir, fileId), text);
11796
+ await atomicWriteText(resolve8(dir, fileId), text);
11785
11797
  } catch {
11786
11798
  }
11787
11799
  }
@@ -11801,7 +11813,7 @@ function getCompactAnySessionSync(skillName) {
11801
11813
  for (const entry of entries) {
11802
11814
  if (!matchesCompactSuffix(entry.name, entry.isFile(), suffix)) continue;
11803
11815
  try {
11804
- const text = readFileSync12(resolve7(dir, entry.name), "utf-8");
11816
+ const text = readFileSync12(resolve8(dir, entry.name), "utf-8");
11805
11817
  if (text.trim()) return text;
11806
11818
  } catch {
11807
11819
  continue;
@@ -11829,7 +11841,7 @@ async function readSkillHits(skillName) {
11829
11841
  const name2 = safeSkillName(skillName);
11830
11842
  if (!name2) return { count: 0, lastTs: 0 };
11831
11843
  const dir = skillOutputsDir();
11832
- const hitsFile = resolve7(dir, `${sanitizeSkillId(name2)}.hits`);
11844
+ const hitsFile = resolve8(dir, `${sanitizeSkillId(name2)}.hits`);
11833
11845
  const content = await fs16.readFile(hitsFile, "utf-8").catch(() => null);
11834
11846
  if (content) {
11835
11847
  const parsed = JSON.parse(content);
@@ -11879,7 +11891,7 @@ async function incrementSkillHit(skillName) {
11879
11891
  if (!name2) return;
11880
11892
  await ensureSkillsDir();
11881
11893
  const dir = skillOutputsDir();
11882
- const hitsFile = resolve7(dir, `${sanitizeSkillId(name2)}.hits`);
11894
+ const hitsFile = resolve8(dir, `${sanitizeSkillId(name2)}.hits`);
11883
11895
  const lockPath = `${hitsFile}.lock`;
11884
11896
  await runExclusiveInProcess(lockPath, async () => {
11885
11897
  const locked = await acquireSkillHitLock(lockPath);
@@ -11926,14 +11938,14 @@ async function listSkills(sessionId) {
11926
11938
  let compactLen = 0;
11927
11939
  let compactText = "";
11928
11940
  try {
11929
- const stat2 = await fs16.stat(resolve7(dir, compactFileId));
11941
+ const stat2 = await fs16.stat(resolve8(dir, compactFileId));
11930
11942
  compactLen = stat2.size;
11931
- compactText = await fs16.readFile(resolve7(dir, compactFileId), "utf-8").catch(() => "");
11943
+ compactText = await fs16.readFile(resolve8(dir, compactFileId), "utf-8").catch(() => "");
11932
11944
  } catch {
11933
11945
  compactLen = 0;
11934
11946
  }
11935
11947
  const hasMarker = extractCompactFromMarker(
11936
- await fs16.readFile(resolve7(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
11948
+ await fs16.readFile(resolve8(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
11937
11949
  ) !== null;
11938
11950
  const compactStale = isCompactStale(compactText, meta3.skillName, meta3.contentSha);
11939
11951
  const { count: hitCount } = await readSkillHits(meta3.skillName);
@@ -11969,7 +11981,7 @@ async function getSkillFilePath(skillName) {
11969
11981
  }
11970
11982
  }
11971
11983
  async function resolvePluginSkillPath(pluginName, skillSlug) {
11972
- const manifestPath = _pluginsManifestPathOverride ?? resolve7(homedir7(), ".claude", "plugins", "installed_plugins.json");
11984
+ const manifestPath = _pluginsManifestPathOverride ?? resolve8(homedir7(), ".claude", "plugins", "installed_plugins.json");
11973
11985
  let raw;
11974
11986
  try {
11975
11987
  raw = await fs16.readFile(manifestPath, "utf8");
@@ -11992,7 +12004,7 @@ async function resolvePluginSkillPath(pluginName, skillSlug) {
11992
12004
  if (typeof entry !== "object" || entry === null) continue;
11993
12005
  const installPath = entry["installPath"];
11994
12006
  if (typeof installPath !== "string" || installPath === "") continue;
11995
- const diskPath = resolve7(installPath, "skills", skillSlug, "SKILL.md");
12007
+ const diskPath = resolve8(installPath, "skills", skillSlug, "SKILL.md");
11996
12008
  try {
11997
12009
  await fs16.access(diskPath);
11998
12010
  return diskPath;
@@ -12011,7 +12023,7 @@ async function installedSkillPath(skillName) {
12011
12023
  const pluginPath = await resolvePluginSkillPath(name2.slice(0, colonIdx), name2.slice(colonIdx + 1));
12012
12024
  if (pluginPath !== null) return pluginPath;
12013
12025
  }
12014
- const diskPath = resolve7(skillsSourceDir(), name2, "SKILL.md");
12026
+ const diskPath = resolve8(skillsSourceDir(), name2, "SKILL.md");
12015
12027
  try {
12016
12028
  await fs16.access(diskPath);
12017
12029
  return diskPath;
@@ -12031,11 +12043,11 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
12031
12043
  const outputId = file2.slice(0, -".meta".length);
12032
12044
  let ts;
12033
12045
  try {
12034
- const parsed = JSON.parse(readFileSync12(resolve7(dir, file2), "utf-8"));
12046
+ const parsed = JSON.parse(readFileSync12(resolve8(dir, file2), "utf-8"));
12035
12047
  ts = parsed.ts;
12036
12048
  } catch {
12037
12049
  try {
12038
- ts = statSync7(resolve7(dir, file2)).mtimeMs;
12050
+ ts = statSync7(resolve8(dir, file2)).mtimeMs;
12039
12051
  } catch {
12040
12052
  continue;
12041
12053
  }
@@ -12045,7 +12057,7 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
12045
12057
  const removeEntry = (outputId) => {
12046
12058
  for (const ext2 of [".meta", ".txt"]) {
12047
12059
  try {
12048
- unlinkSync9(resolve7(dir, `${outputId}${ext2}`));
12060
+ unlinkSync9(resolve8(dir, `${outputId}${ext2}`));
12049
12061
  } catch {
12050
12062
  }
12051
12063
  }
@@ -12159,7 +12171,7 @@ async function ocrImage(input) {
12159
12171
  if (_ocrUnavailableThisProcess) return null;
12160
12172
  const entryPath = resolveTesseractEntry();
12161
12173
  if (entryPath === null) return null;
12162
- return new Promise((resolve24) => {
12174
+ return new Promise((resolve25) => {
12163
12175
  let settled = false;
12164
12176
  let child;
12165
12177
  try {
@@ -12168,7 +12180,7 @@ async function ocrImage(input) {
12168
12180
  });
12169
12181
  } catch {
12170
12182
  _ocrUnavailableThisProcess = true;
12171
- resolve24(null);
12183
+ resolve25(null);
12172
12184
  return;
12173
12185
  }
12174
12186
  const chunks = [];
@@ -12181,7 +12193,7 @@ async function ocrImage(input) {
12181
12193
  child.kill();
12182
12194
  } catch {
12183
12195
  }
12184
- resolve24(result);
12196
+ resolve25(result);
12185
12197
  };
12186
12198
  const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
12187
12199
  child.stdout?.on("data", (c) => chunks.push(c));
@@ -13619,7 +13631,7 @@ function ensureTransformerLoaded() {
13619
13631
  }
13620
13632
  }
13621
13633
  function sleep(ms) {
13622
- return new Promise((resolve24) => setTimeout(resolve24, ms));
13634
+ return new Promise((resolve25) => setTimeout(resolve25, ms));
13623
13635
  }
13624
13636
  async function buildExtractorWithRetry(pipelineFn, modelName) {
13625
13637
  let lastError;
@@ -23932,7 +23944,7 @@ import * as readline from "node:readline";
23932
23944
  async function defaultConfirm(question) {
23933
23945
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23934
23946
  try {
23935
- const answer = await new Promise((resolve24) => rl.question(question, resolve24));
23947
+ const answer = await new Promise((resolve25) => rl.question(question, resolve25));
23936
23948
  return /^y(es)?$/i.test(answer.trim());
23937
23949
  } finally {
23938
23950
  rl.close();
@@ -48520,7 +48532,7 @@ init_util2();
48520
48532
  init_disk_cache();
48521
48533
  init_lang_patterns();
48522
48534
  import { readdirSync as readdirSync11, readFileSync as readFileSync21, statSync as statSync15 } from "fs";
48523
- import { resolve as resolve9 } from "path";
48535
+ import { resolve as resolve10 } from "path";
48524
48536
 
48525
48537
  // src/recall_index.ts
48526
48538
  init_define_import_meta_env();
@@ -48753,7 +48765,7 @@ function depLockfileFingerprintSync(cmd, cwd) {
48753
48765
  if (!candidates) return null;
48754
48766
  for (const lockfile of candidates) {
48755
48767
  try {
48756
- const content = readFileSync21(resolve9(cwd, lockfile));
48768
+ const content = readFileSync21(resolve10(cwd, lockfile));
48757
48769
  return shortFingerprint(content);
48758
48770
  } catch {
48759
48771
  continue;
@@ -48906,7 +48918,7 @@ function extractFirstPathArg(cmd, cwd, fallback) {
48906
48918
  const token2 = tokens[i];
48907
48919
  if (!token2.startsWith("-")) {
48908
48920
  if (!token2.startsWith("/")) {
48909
- return resolve9(cwd, token2);
48921
+ return resolve10(cwd, token2);
48910
48922
  }
48911
48923
  return token2;
48912
48924
  }
@@ -64250,15 +64262,19 @@ var BRIDGE_CAPABILITY_MATRIX = [
64250
64262
  harness: "copilot_cli",
64251
64263
  label: "Copilot CLI",
64252
64264
  sourceFile: "src/bridges/copilot_cli_install.ts (COPILOT_CLI_HOOK_EVENTS), src/bridges/copilot_cli.ts (COPILOT_TO_TG_EVENT)",
64253
- implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "stop", "subagent_stop", "user_prompt_submit"]),
64265
+ implemented: /* @__PURE__ */ new Set([
64266
+ "session_start",
64267
+ "pre_tool_use",
64268
+ "post_tool_use",
64269
+ "pre_compact",
64270
+ "stop",
64271
+ "subagent_stop",
64272
+ "user_prompt_submit"
64273
+ ]),
64254
64274
  reasons: [
64255
64275
  {
64256
64276
  events: ["notification"],
64257
64277
  reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/postToolUseFailure/subagentStart/errorOccurred/permissionRequest) unimplemented rather than guessed at"
64258
- },
64259
- {
64260
- events: ["session_start"],
64261
- reason: "COPILOT_TO_TG_EVENT has no session-start mapping wired yet -- left unimplemented rather than guessed at"
64262
64278
  }
64263
64279
  ]
64264
64280
  },
@@ -69571,7 +69587,7 @@ import { existsSync as existsSync33, readdirSync as readdirSync20, statSync as s
69571
69587
  import * as http from "http";
69572
69588
  import * as https from "https";
69573
69589
  import { isIPv4, isIPv6 } from "net";
69574
- import { resolve as resolve17, join as join41 } from "path";
69590
+ import { resolve as resolve18, join as join41 } from "path";
69575
69591
  import { URL as URL2 } from "url";
69576
69592
  import { promisify } from "util";
69577
69593
  import { lookup as dnsLookup } from "dns";
@@ -69743,7 +69759,7 @@ function cleanupStaleDownloads() {
69743
69759
  const files = readdirSync20(cacheDir);
69744
69760
  for (const file2 of files) {
69745
69761
  if (file2.endsWith(".tmp")) {
69746
- const filePath = resolve17(cacheDir, file2);
69762
+ const filePath = resolve18(cacheDir, file2);
69747
69763
  try {
69748
69764
  const stat2 = statSync26(filePath);
69749
69765
  if (Date.now() - stat2.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -72320,8 +72336,8 @@ async function cmdMcpServe() {
72320
72336
  const server = createMcpServer2();
72321
72337
  const transport = new StdioServerTransport();
72322
72338
  await server.connect(transport);
72323
- await new Promise((resolve24) => {
72324
- server.server.onclose = resolve24;
72339
+ await new Promise((resolve25) => {
72340
+ server.server.onclose = resolve25;
72325
72341
  });
72326
72342
  }
72327
72343
  async function cmdHook(event, opts) {
@@ -73533,7 +73549,7 @@ function cmdWriteFile(dest, opts) {
73533
73549
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
73534
73550
  }
73535
73551
  const maxBytes = maxMB * 1024 * 1024;
73536
- return new Promise((resolve24, reject) => {
73552
+ return new Promise((resolve25, reject) => {
73537
73553
  const chunks = [];
73538
73554
  let totalBytes = 0;
73539
73555
  let settled = false;
@@ -73557,7 +73573,7 @@ function cmdWriteFile(dest, opts) {
73557
73573
  try {
73558
73574
  atomicWriteBuffer(dest, Buffer.concat(chunks));
73559
73575
  enqueueDirtyPathSafe(dest);
73560
- resolve24();
73576
+ resolve25();
73561
73577
  } catch (e) {
73562
73578
  try {
73563
73579
  mapFsError(e, void 0, dest);
@@ -82989,7 +83005,7 @@ init_image_shrink();
82989
83005
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
82990
83006
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
82991
83007
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
82992
- return new Promise((resolve24, reject) => {
83008
+ return new Promise((resolve25, reject) => {
82993
83009
  const chunks = [];
82994
83010
  let totalBytes = 0;
82995
83011
  let settled = false;
@@ -83024,7 +83040,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
83024
83040
  return;
83025
83041
  }
83026
83042
  try {
83027
- resolve24(JSON.parse(text));
83043
+ resolve25(JSON.parse(text));
83028
83044
  } catch (err2) {
83029
83045
  reject(err2 instanceof Error ? err2 : new Error(String(err2)));
83030
83046
  }
@@ -3050,7 +3050,7 @@ var require_commander = __commonJS({
3050
3050
  import { createRequire } from "node:module";
3051
3051
  function resolveVersion() {
3052
3052
  if (true) {
3053
- return "2.6.23";
3053
+ return "2.6.24";
3054
3054
  }
3055
3055
  const require2 = createRequire(import.meta.url);
3056
3056
  const pkg = require2("../package.json");
@@ -4283,8 +4283,8 @@ function normalizeDarwinSystemAlias(p) {
4283
4283
  }
4284
4284
  function resolveIndexPath(file2, base = process.cwd()) {
4285
4285
  const isWindowsAbsolute = (s) => /^[a-zA-Z]:[/\\]/.test(s);
4286
- const resolve24 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
4287
- return normalizePath(resolve24(base, file2));
4286
+ const resolve25 = isWindowsAbsolute(file2) || isWindowsAbsolute(base) ? path2.win32.resolve : path2.resolve;
4287
+ return normalizePath(resolve25(base, file2));
4288
4288
  }
4289
4289
  function toDisplayPath(root, target) {
4290
4290
  if (root === void 0) return target;
@@ -12084,14 +12084,23 @@ const path = require('node:path')
12084
12084
  const { pathToFileURL } = require('node:url')
12085
12085
 
12086
12086
  // Copilot event name -> token-goat internal HookEventName (src/types.ts's
12087
- // HOOK_EVENTS). Only these six have a token-goat handler; every other real
12087
+ // HOOK_EVENTS). Only these seven have a token-goat handler; every other real
12088
12088
  // Copilot event (sessionEnd, postToolUseFailure, subagentStart,
12089
12089
  // errorOccurred, notification, permissionRequest) is left unimplemented
12090
12090
  // rather than guessed at, and falls through to the default no-op below.
12091
- // 'sessionStart' is handled as a permanent no-op even though it's a real
12092
- // Copilot event, because token-goat has no internal session_start handler
12093
- // (mirrors PI_EXTENSION_SCRIPT's documented precedent).
12091
+ // 'sessionStart' was previously a permanent no-op on the stated grounds that
12092
+ // token-goat has no internal session_start handler. That was simply wrong --
12093
+ // hooks_session_start.ts has long emitted the command-routing reminder that
12094
+ // every other harness receives -- and the no-op was the reason Copilot CLI
12095
+ // sessions alone never got told token-goat exists. It is wired now: verified
12096
+ // against Copilot CLI 1.0.77 that a hooks.json sessionStart entry returning
12097
+ // {additionalContext} does reach the model. The github/copilot-cli#2142
12098
+ // fire-and-forget bug that would have made this dead wiring was fixed in a
12099
+ // pre-release months before that version, and its companion multi-extension
12100
+ // hook-overwrite bug never applied here: it hit runtime *extension* hooks,
12101
+ // while this config-file hooks.json path goes through Copilot's own merge.
12094
12102
  const COPILOT_TO_TG_EVENT = {
12103
+ sessionStart: 'session_start',
12095
12104
  preToolUse: 'pre_tool_use',
12096
12105
  postToolUse: 'post_tool_use',
12097
12106
  preCompact: 'pre_compact',
@@ -12203,11 +12212,6 @@ async function tryInProcess(entryPath, tgEvent, canonical) {
12203
12212
  async function main() {
12204
12213
  const copilotEvent = process.argv[2] || ''
12205
12214
 
12206
- if (copilotEvent === 'sessionStart') {
12207
- process.stdout.write('{}')
12208
- return
12209
- }
12210
-
12211
12215
  const tgEvent = COPILOT_TO_TG_EVENT[copilotEvent]
12212
12216
  if (!tgEvent) {
12213
12217
  process.stdout.write('{}')
@@ -12333,7 +12337,10 @@ function translate(copilotEvent, resp) {
12333
12337
  return {}
12334
12338
  }
12335
12339
 
12336
- if (copilotEvent === 'postToolUse') {
12340
+ if (copilotEvent === 'postToolUse' || copilotEvent === 'sessionStart') {
12341
+ // Both surface token-goat's context through the same field. sessionStart is
12342
+ // the one channel that reaches the model before it picks its first read
12343
+ // tool, so this is where the routing reminder has to land.
12337
12344
  const context = extractContext(resp)
12338
12345
  if (context) return { additionalContext: context }
12339
12346
  return {}
@@ -12357,9 +12364,8 @@ function translate(copilotEvent, resp) {
12357
12364
  // doc that both are notification-only -- Copilot never reads a response
12358
12365
  // body for either, so any additionalContext/systemMessage token-goat
12359
12366
  // produces has no surfacing channel here. This still routes through the
12360
- // token-goat hook call above (unlike sessionStart's early no-op) so the
12361
- // internal handler's own side effects keep running; only the response is
12362
- // discarded.
12367
+ // token-goat hook call above so the internal handler's own side effects keep
12368
+ // running; only the response is discarded.
12363
12369
  return {}
12364
12370
  }
12365
12371
 
@@ -12399,8 +12405,13 @@ main()
12399
12405
  import * as fs11 from "node:fs";
12400
12406
  import * as os5 from "node:os";
12401
12407
  import * as path11 from "node:path";
12408
+ function copilotCliUserRoot() {
12409
+ const override = process.env["COPILOT_HOME"];
12410
+ if (override !== void 0 && override.trim() !== "") return path11.resolve(override);
12411
+ return path11.join(os5.homedir(), ".copilot");
12412
+ }
12402
12413
  function copilotCliUserHooksDir() {
12403
- return path11.join(os5.homedir(), ".copilot", "hooks");
12414
+ return path11.join(copilotCliUserRoot(), "hooks");
12404
12415
  }
12405
12416
  function copilotCliProjectHooksDir() {
12406
12417
  return path11.join(process.cwd(), ".github", "hooks");
@@ -12505,6 +12516,7 @@ var init_copilot_cli_install = __esm({
12505
12516
  init_copilot_cli();
12506
12517
  init_guidance_block();
12507
12518
  COPILOT_CLI_HOOK_EVENTS = [
12519
+ "sessionStart",
12508
12520
  "preToolUse",
12509
12521
  "postToolUse",
12510
12522
  "preCompact",
@@ -15646,16 +15658,16 @@ var init_file_type_handler = __esm({
15646
15658
 
15647
15659
  // src/skill_cache.ts
15648
15660
  import * as fs18 from "fs/promises";
15649
- import { resolve as resolve7 } from "path";
15661
+ import { resolve as resolve8 } from "path";
15650
15662
  import { homedir as homedir7 } from "os";
15651
15663
  import { readdirSync as readdirSync8, readFileSync as readFileSync13, existsSync as existsSync14, statSync as statSync8, unlinkSync as unlinkSync9 } from "node:fs";
15652
15664
  function skillOutputsDir() {
15653
15665
  if (_skillOutputsDirOverride) return _skillOutputsDirOverride;
15654
- return resolve7(dataDir(), SKILLS_OUTPUT_SUBDIR);
15666
+ return resolve8(dataDir(), SKILLS_OUTPUT_SUBDIR);
15655
15667
  }
15656
15668
  function skillsSourceDir() {
15657
15669
  if (_skillsSourceDirOverride) return _skillsSourceDirOverride;
15658
- return resolve7(homedir7(), ".claude", "skills");
15670
+ return resolve8(homedir7(), ".claude", "skills");
15659
15671
  }
15660
15672
  async function ensureSkillsDir() {
15661
15673
  try {
@@ -15798,7 +15810,7 @@ async function listOutputs() {
15798
15810
  continue;
15799
15811
  }
15800
15812
  try {
15801
- const content = await fs18.readFile(resolve7(dir, entry.name), "utf-8");
15813
+ const content = await fs18.readFile(resolve8(dir, entry.name), "utf-8");
15802
15814
  const meta3 = JSON.parse(content);
15803
15815
  metas.push(meta3);
15804
15816
  } catch {
@@ -15832,7 +15844,7 @@ async function findCrossSessionEntry(skillName, contentSha) {
15832
15844
  continue;
15833
15845
  }
15834
15846
  const dir = skillOutputsDir();
15835
- const bodyPath = resolve7(dir, `${meta3.outputId}.txt`);
15847
+ const bodyPath = resolve8(dir, `${meta3.outputId}.txt`);
15836
15848
  try {
15837
15849
  const bodyExists = await fs18.access(bodyPath).then(() => true).catch(() => false);
15838
15850
  if (bodyExists) {
@@ -15887,7 +15899,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
15887
15899
  const truncBuf = buf.slice(truncStart);
15888
15900
  storedBody = truncBuf.toString("utf-8");
15889
15901
  }
15890
- await atomicWriteText(resolve7(dir, `${outId}.txt`), storedBody);
15902
+ await atomicWriteText(resolve8(dir, `${outId}.txt`), storedBody);
15891
15903
  const meta3 = {
15892
15904
  outputId: outId,
15893
15905
  skillName: name2,
@@ -15897,7 +15909,7 @@ async function storeOutput(sessionId, skillName, body, opts) {
15897
15909
  truncated,
15898
15910
  sourcePath: opts?.sourcePath || ""
15899
15911
  };
15900
- await atomicWriteText(resolve7(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
15912
+ await atomicWriteText(resolve8(dir, `${outId}.meta`), JSON.stringify(meta3, null, 2));
15901
15913
  pruneSkillOutputs();
15902
15914
  return meta3;
15903
15915
  } catch {
@@ -15917,7 +15929,7 @@ async function storeCompact(sessionId, skillName, compactText, sourceSha) {
15917
15929
  text = `<!-- source_sha: ${sourceSha.slice(0, 12)} -->
15918
15930
  ${text}`;
15919
15931
  }
15920
- await atomicWriteText(resolve7(dir, fileId), text);
15932
+ await atomicWriteText(resolve8(dir, fileId), text);
15921
15933
  } catch {
15922
15934
  }
15923
15935
  }
@@ -15937,7 +15949,7 @@ function getCompactAnySessionSync(skillName) {
15937
15949
  for (const entry of entries) {
15938
15950
  if (!matchesCompactSuffix(entry.name, entry.isFile(), suffix)) continue;
15939
15951
  try {
15940
- const text = readFileSync13(resolve7(dir, entry.name), "utf-8");
15952
+ const text = readFileSync13(resolve8(dir, entry.name), "utf-8");
15941
15953
  if (text.trim()) return text;
15942
15954
  } catch {
15943
15955
  continue;
@@ -15965,7 +15977,7 @@ async function readSkillHits(skillName) {
15965
15977
  const name2 = safeSkillName(skillName);
15966
15978
  if (!name2) return { count: 0, lastTs: 0 };
15967
15979
  const dir = skillOutputsDir();
15968
- const hitsFile = resolve7(dir, `${sanitizeSkillId(name2)}.hits`);
15980
+ const hitsFile = resolve8(dir, `${sanitizeSkillId(name2)}.hits`);
15969
15981
  const content = await fs18.readFile(hitsFile, "utf-8").catch(() => null);
15970
15982
  if (content) {
15971
15983
  const parsed = JSON.parse(content);
@@ -16015,7 +16027,7 @@ async function incrementSkillHit(skillName) {
16015
16027
  if (!name2) return;
16016
16028
  await ensureSkillsDir();
16017
16029
  const dir = skillOutputsDir();
16018
- const hitsFile = resolve7(dir, `${sanitizeSkillId(name2)}.hits`);
16030
+ const hitsFile = resolve8(dir, `${sanitizeSkillId(name2)}.hits`);
16019
16031
  const lockPath = `${hitsFile}.lock`;
16020
16032
  await runExclusiveInProcess(lockPath, async () => {
16021
16033
  const locked = await acquireSkillHitLock(lockPath);
@@ -16062,14 +16074,14 @@ async function listSkills(sessionId) {
16062
16074
  let compactLen = 0;
16063
16075
  let compactText = "";
16064
16076
  try {
16065
- const stat2 = await fs18.stat(resolve7(dir, compactFileId));
16077
+ const stat2 = await fs18.stat(resolve8(dir, compactFileId));
16066
16078
  compactLen = stat2.size;
16067
- compactText = await fs18.readFile(resolve7(dir, compactFileId), "utf-8").catch(() => "");
16079
+ compactText = await fs18.readFile(resolve8(dir, compactFileId), "utf-8").catch(() => "");
16068
16080
  } catch {
16069
16081
  compactLen = 0;
16070
16082
  }
16071
16083
  const hasMarker = extractCompactFromMarker(
16072
- await fs18.readFile(resolve7(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
16084
+ await fs18.readFile(resolve8(dir, `${meta3.outputId}.txt`), "utf-8").catch(() => "")
16073
16085
  ) !== null;
16074
16086
  const compactStale = isCompactStale(compactText, meta3.skillName, meta3.contentSha);
16075
16087
  const { count: hitCount } = await readSkillHits(meta3.skillName);
@@ -16105,7 +16117,7 @@ async function getSkillFilePath(skillName) {
16105
16117
  }
16106
16118
  }
16107
16119
  async function resolvePluginSkillPath(pluginName, skillSlug) {
16108
- const manifestPath = _pluginsManifestPathOverride ?? resolve7(homedir7(), ".claude", "plugins", "installed_plugins.json");
16120
+ const manifestPath = _pluginsManifestPathOverride ?? resolve8(homedir7(), ".claude", "plugins", "installed_plugins.json");
16109
16121
  let raw;
16110
16122
  try {
16111
16123
  raw = await fs18.readFile(manifestPath, "utf8");
@@ -16128,7 +16140,7 @@ async function resolvePluginSkillPath(pluginName, skillSlug) {
16128
16140
  if (typeof entry !== "object" || entry === null) continue;
16129
16141
  const installPath = entry["installPath"];
16130
16142
  if (typeof installPath !== "string" || installPath === "") continue;
16131
- const diskPath = resolve7(installPath, "skills", skillSlug, "SKILL.md");
16143
+ const diskPath = resolve8(installPath, "skills", skillSlug, "SKILL.md");
16132
16144
  try {
16133
16145
  await fs18.access(diskPath);
16134
16146
  return diskPath;
@@ -16147,7 +16159,7 @@ async function installedSkillPath(skillName) {
16147
16159
  const pluginPath = await resolvePluginSkillPath(name2.slice(0, colonIdx), name2.slice(colonIdx + 1));
16148
16160
  if (pluginPath !== null) return pluginPath;
16149
16161
  }
16150
- const diskPath = resolve7(skillsSourceDir(), name2, "SKILL.md");
16162
+ const diskPath = resolve8(skillsSourceDir(), name2, "SKILL.md");
16151
16163
  try {
16152
16164
  await fs18.access(diskPath);
16153
16165
  return diskPath;
@@ -16167,11 +16179,11 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
16167
16179
  const outputId = file2.slice(0, -".meta".length);
16168
16180
  let ts;
16169
16181
  try {
16170
- const parsed = JSON.parse(readFileSync13(resolve7(dir, file2), "utf-8"));
16182
+ const parsed = JSON.parse(readFileSync13(resolve8(dir, file2), "utf-8"));
16171
16183
  ts = parsed.ts;
16172
16184
  } catch {
16173
16185
  try {
16174
- ts = statSync8(resolve7(dir, file2)).mtimeMs;
16186
+ ts = statSync8(resolve8(dir, file2)).mtimeMs;
16175
16187
  } catch {
16176
16188
  continue;
16177
16189
  }
@@ -16181,7 +16193,7 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
16181
16193
  const removeEntry = (outputId) => {
16182
16194
  for (const ext2 of [".meta", ".txt"]) {
16183
16195
  try {
16184
- unlinkSync9(resolve7(dir, `${outputId}${ext2}`));
16196
+ unlinkSync9(resolve8(dir, `${outputId}${ext2}`));
16185
16197
  } catch {
16186
16198
  }
16187
16199
  }
@@ -16273,7 +16285,7 @@ async function ocrImage(input) {
16273
16285
  if (_ocrUnavailableThisProcess) return null;
16274
16286
  const entryPath = resolveTesseractEntry();
16275
16287
  if (entryPath === null) return null;
16276
- return new Promise((resolve24) => {
16288
+ return new Promise((resolve25) => {
16277
16289
  let settled = false;
16278
16290
  let child;
16279
16291
  try {
@@ -16282,7 +16294,7 @@ async function ocrImage(input) {
16282
16294
  });
16283
16295
  } catch {
16284
16296
  _ocrUnavailableThisProcess = true;
16285
- resolve24(null);
16297
+ resolve25(null);
16286
16298
  return;
16287
16299
  }
16288
16300
  const chunks = [];
@@ -16295,7 +16307,7 @@ async function ocrImage(input) {
16295
16307
  child.kill();
16296
16308
  } catch {
16297
16309
  }
16298
- resolve24(result);
16310
+ resolve25(result);
16299
16311
  };
16300
16312
  const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
16301
16313
  child.stdout?.on("data", (c) => chunks.push(c));
@@ -17537,7 +17549,7 @@ import * as readline from "node:readline";
17537
17549
  async function defaultConfirm(question) {
17538
17550
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
17539
17551
  try {
17540
- const answer = await new Promise((resolve24) => rl.question(question, resolve24));
17552
+ const answer = await new Promise((resolve25) => rl.question(question, resolve25));
17541
17553
  return /^y(es)?$/i.test(answer.trim());
17542
17554
  } finally {
17543
17555
  rl.close();
@@ -17603,7 +17615,7 @@ function ensureTransformerLoaded() {
17603
17615
  }
17604
17616
  }
17605
17617
  function sleep(ms) {
17606
- return new Promise((resolve24) => setTimeout(resolve24, ms));
17618
+ return new Promise((resolve25) => setTimeout(resolve25, ms));
17607
17619
  }
17608
17620
  async function buildExtractorWithRetry(pipelineFn, modelName) {
17609
17621
  let lastError;
@@ -24655,10 +24667,10 @@ function embedFileSerialized(absPath, dbPath, sha) {
24655
24667
  activeEmbedSlots += 1;
24656
24668
  return dispatchEmbed();
24657
24669
  }
24658
- return new Promise((resolve24) => {
24670
+ return new Promise((resolve25) => {
24659
24671
  embedSlotWaiters.push(() => {
24660
24672
  activeEmbedSlots += 1;
24661
- resolve24(dispatchEmbed());
24673
+ resolve25(dispatchEmbed());
24662
24674
  });
24663
24675
  });
24664
24676
  };
@@ -25064,7 +25076,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
25064
25076
  lastKnownRootsSweepMs = Date.now();
25065
25077
  }
25066
25078
  if (shouldStop()) break;
25067
- await new Promise((resolve24) => setTimeout(resolve24, pollIntervalMs));
25079
+ await new Promise((resolve25) => setTimeout(resolve25, pollIntervalMs));
25068
25080
  }
25069
25081
  }
25070
25082
  function runDetachedWorkerDaemon() {
@@ -49089,7 +49101,7 @@ init_util2();
49089
49101
  init_disk_cache();
49090
49102
  init_lang_patterns();
49091
49103
  import { readdirSync as readdirSync13, readFileSync as readFileSync24, statSync as statSync17 } from "fs";
49092
- import { resolve as resolve12 } from "path";
49104
+ import { resolve as resolve13 } from "path";
49093
49105
 
49094
49106
  // src/recall_index.ts
49095
49107
  init_define_import_meta_env();
@@ -49322,7 +49334,7 @@ function depLockfileFingerprintSync(cmd, cwd) {
49322
49334
  if (!candidates) return null;
49323
49335
  for (const lockfile of candidates) {
49324
49336
  try {
49325
- const content = readFileSync24(resolve12(cwd, lockfile));
49337
+ const content = readFileSync24(resolve13(cwd, lockfile));
49326
49338
  return shortFingerprint(content);
49327
49339
  } catch {
49328
49340
  continue;
@@ -49475,7 +49487,7 @@ function extractFirstPathArg(cmd, cwd, fallback) {
49475
49487
  const token2 = tokens[i];
49476
49488
  if (!token2.startsWith("-")) {
49477
49489
  if (!token2.startsWith("/")) {
49478
- return resolve12(cwd, token2);
49490
+ return resolve13(cwd, token2);
49479
49491
  }
49480
49492
  return token2;
49481
49493
  }
@@ -72393,7 +72405,7 @@ init_image_shrink();
72393
72405
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
72394
72406
  var MAX_STDIN_BYTES = 64 * 1024 * 1024;
72395
72407
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDIN_BYTES) {
72396
- return new Promise((resolve24, reject) => {
72408
+ return new Promise((resolve25, reject) => {
72397
72409
  const chunks = [];
72398
72410
  let totalBytes = 0;
72399
72411
  let settled = false;
@@ -72428,7 +72440,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS, maxBytes = MAX_STDI
72428
72440
  return;
72429
72441
  }
72430
72442
  try {
72431
- resolve24(JSON.parse(text));
72443
+ resolve25(JSON.parse(text));
72432
72444
  } catch (err2) {
72433
72445
  reject(err2 instanceof Error ? err2 : new Error(String(err2)));
72434
72446
  }
@@ -73878,15 +73890,19 @@ var BRIDGE_CAPABILITY_MATRIX = [
73878
73890
  harness: "copilot_cli",
73879
73891
  label: "Copilot CLI",
73880
73892
  sourceFile: "src/bridges/copilot_cli_install.ts (COPILOT_CLI_HOOK_EVENTS), src/bridges/copilot_cli.ts (COPILOT_TO_TG_EVENT)",
73881
- implemented: /* @__PURE__ */ new Set(["pre_tool_use", "post_tool_use", "pre_compact", "stop", "subagent_stop", "user_prompt_submit"]),
73893
+ implemented: /* @__PURE__ */ new Set([
73894
+ "session_start",
73895
+ "pre_tool_use",
73896
+ "post_tool_use",
73897
+ "pre_compact",
73898
+ "stop",
73899
+ "subagent_stop",
73900
+ "user_prompt_submit"
73901
+ ]),
73882
73902
  reasons: [
73883
73903
  {
73884
73904
  events: ["notification"],
73885
73905
  reason: "Copilot CLI has a real 'notification' hook event, but copilot_cli.ts's COPILOT_TO_TG_EVENT deliberately leaves it (and sessionEnd/postToolUseFailure/subagentStart/errorOccurred/permissionRequest) unimplemented rather than guessed at"
73886
- },
73887
- {
73888
- events: ["session_start"],
73889
- reason: "COPILOT_TO_TG_EVENT has no session-start mapping wired yet -- left unimplemented rather than guessed at"
73890
73906
  }
73891
73907
  ]
73892
73908
  },
@@ -78722,7 +78738,7 @@ import { existsSync as existsSync34, readdirSync as readdirSync20, statSync as s
78722
78738
  import * as http from "http";
78723
78739
  import * as https from "https";
78724
78740
  import { isIPv4, isIPv6 } from "net";
78725
- import { resolve as resolve17, join as join41 } from "path";
78741
+ import { resolve as resolve18, join as join41 } from "path";
78726
78742
  import { URL as URL2 } from "url";
78727
78743
  import { promisify } from "util";
78728
78744
  import { lookup as dnsLookup } from "dns";
@@ -78894,7 +78910,7 @@ function cleanupStaleDownloads() {
78894
78910
  const files = readdirSync20(cacheDir);
78895
78911
  for (const file2 of files) {
78896
78912
  if (file2.endsWith(".tmp")) {
78897
- const filePath = resolve17(cacheDir, file2);
78913
+ const filePath = resolve18(cacheDir, file2);
78898
78914
  try {
78899
78915
  const stat2 = statSync27(filePath);
78900
78916
  if (Date.now() - stat2.mtimeMs < STALE_DOWNLOAD_AGE_MS) continue;
@@ -81471,8 +81487,8 @@ async function cmdMcpServe() {
81471
81487
  const server = createMcpServer2();
81472
81488
  const transport = new StdioServerTransport();
81473
81489
  await server.connect(transport);
81474
- await new Promise((resolve24) => {
81475
- server.server.onclose = resolve24;
81490
+ await new Promise((resolve25) => {
81491
+ server.server.onclose = resolve25;
81476
81492
  });
81477
81493
  }
81478
81494
  async function cmdHook(event, opts) {
@@ -82684,7 +82700,7 @@ function cmdWriteFile(dest, opts) {
82684
82700
  throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
82685
82701
  }
82686
82702
  const maxBytes = maxMB * 1024 * 1024;
82687
- return new Promise((resolve24, reject) => {
82703
+ return new Promise((resolve25, reject) => {
82688
82704
  const chunks = [];
82689
82705
  let totalBytes = 0;
82690
82706
  let settled = false;
@@ -82708,7 +82724,7 @@ function cmdWriteFile(dest, opts) {
82708
82724
  try {
82709
82725
  atomicWriteBuffer(dest, Buffer.concat(chunks));
82710
82726
  enqueueDirtyPathSafe(dest);
82711
- resolve24();
82727
+ resolve25();
82712
82728
  } catch (e) {
82713
82729
  try {
82714
82730
  mapFsError(e, void 0, dest);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.6.23",
3
+ "version": "2.6.24",
4
4
  "description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
5
5
  "type": "module",
6
6
  "main": "./dist/token-goat.mjs",