token-goat 2.9.30 → 2.9.31

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 (172) hide show
  1. package/README.md +24 -7
  2. package/SECURITY.md +10 -2
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/native/linux-arm64/tg-hook +0 -0
  5. package/dist/native/linux-x64/tg-hook +0 -0
  6. package/dist/native/win32-arm64/tg-hook.exe +0 -0
  7. package/dist/native/win32-x64/tg-hook.exe +0 -0
  8. package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
  9. package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
  10. package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
  11. package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
  12. package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
  13. package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
  14. package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
  15. package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
  16. package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
  17. package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
  18. package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
  19. package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
  20. package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
  21. package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
  22. package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
  23. package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
  24. package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
  25. package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
  26. package/dist/token-goat-chunk-646E6U44.mjs +398 -0
  27. package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
  28. package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
  29. package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
  30. package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
  31. package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
  32. package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
  33. package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
  34. package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
  35. package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
  36. package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
  37. package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
  38. package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
  39. package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
  40. package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
  41. package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
  42. package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
  43. package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
  44. package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
  45. package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
  46. package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
  47. package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
  48. package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
  49. package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
  50. package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
  51. package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
  52. package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
  53. package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
  54. package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
  55. package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
  56. package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
  57. package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
  58. package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
  59. package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
  60. package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
  61. package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
  62. package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
  63. package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
  64. package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
  65. package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
  66. package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
  67. package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
  68. package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
  69. package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
  70. package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
  71. package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
  72. package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
  73. package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
  74. package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
  75. package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
  76. package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
  77. package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
  78. package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
  79. package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
  80. package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
  81. package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
  82. package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
  83. package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
  84. package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
  85. package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
  86. package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
  87. package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
  88. package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
  89. package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
  90. package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
  91. package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
  92. package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
  93. package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
  94. package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
  95. package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
  96. package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
  97. package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
  98. package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
  99. package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
  100. package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
  101. package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
  102. package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
  103. package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
  104. package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
  105. package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
  106. package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
  107. package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
  108. package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
  109. package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
  110. package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
  111. package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
  112. package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
  113. package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
  114. package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
  115. package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
  116. package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
  117. package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
  118. package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
  119. package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
  120. package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
  121. package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
  122. package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
  123. package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
  124. package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
  125. package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
  126. package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
  127. package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
  128. package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
  129. package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
  130. package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
  131. package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
  132. package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
  133. package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
  134. package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
  135. package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
  136. package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
  137. package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
  138. package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
  139. package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
  140. package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
  141. package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
  142. package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
  143. package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
  144. package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
  145. package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
  146. package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
  147. package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
  148. package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
  149. package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
  150. package/dist/token-goat-hook-client.cjs +1 -1
  151. package/dist/token-goat-hook-client.mjs +3 -3
  152. package/dist/token-goat-hook.mjs +67 -38
  153. package/dist/token-goat.core.mjs +4 -4
  154. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  155. package/docs/cli.md +40 -21
  156. package/docs/install.md +19 -9
  157. package/docs/security.md +5 -3
  158. package/package.json +2 -1
  159. package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
  160. package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
  161. package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
  162. package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
  163. package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
  164. package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
  165. package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
  166. package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
  167. package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
  168. package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
  169. package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
  170. package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
  171. package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
  172. package/dist/token-goat-chunk-Y776AENC.mjs +0 -1597
@@ -2,33 +2,34 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  compareSemver
5
- } from "./token-goat-chunk-B2RZZCSS.mjs";
5
+ } from "./token-goat-chunk-CXHOFN7R.mjs";
6
6
  import {
7
+ emitErr,
7
8
  jsonc,
8
9
  stripBom
9
- } from "./token-goat-chunk-XKI4SEMX.mjs";
10
+ } from "./token-goat-chunk-QQTWJY2O.mjs";
10
11
  import {
11
12
  HOOKS_CONFIG_FILE,
12
13
  copilotHooksOwnersPath,
13
14
  readCopilotHooksOwners
14
15
  } from "./token-goat-chunk-G5PJ7AGM.mjs";
16
+ import {
17
+ claudeConfigDir
18
+ } from "./token-goat-chunk-BPGCUMR7.mjs";
15
19
  import {
16
20
  toolMatcherFor
17
- } from "./token-goat-chunk-P4QMM4WK.mjs";
21
+ } from "./token-goat-chunk-C75NA7PL.mjs";
18
22
  import {
19
23
  COPILOT_CLI_TOOL_NAME_MAP,
20
24
  copilotCliUserRoot,
21
25
  foldToolName
22
- } from "./token-goat-chunk-VPBBTSJR.mjs";
23
- import {
24
- claudeConfigDir
25
- } from "./token-goat-chunk-BPGCUMR7.mjs";
26
+ } from "./token-goat-chunk-76FGDLRO.mjs";
26
27
  import {
27
28
  loadConfig,
28
29
  nativeHooksEnabled,
29
30
  parse,
30
31
  stringify
31
- } from "./token-goat-chunk-7IN2MKDS.mjs";
32
+ } from "./token-goat-chunk-7EZTZOMC.mjs";
32
33
  import {
33
34
  registerReset
34
35
  } from "./token-goat-chunk-YZAAZU2S.mjs";
@@ -65,7 +66,11 @@ import {
65
66
  writeConfigText,
66
67
  writeIfDifferent,
67
68
  writeJsonSettings
68
- } from "./token-goat-chunk-GOLEOXZP.mjs";
69
+ } from "./token-goat-chunk-HU4TORC5.mjs";
70
+ import {
71
+ echoedValue,
72
+ fencedCommand
73
+ } from "./token-goat-chunk-GGGG7RWB.mjs";
69
74
  import {
70
75
  powershellHookLine,
71
76
  quotePosixShellWord,
@@ -78,7 +83,7 @@ import {
78
83
  } from "./token-goat-chunk-FNFBKLRD.mjs";
79
84
  import {
80
85
  dataDir
81
- } from "./token-goat-chunk-VES7EL7O.mjs";
86
+ } from "./token-goat-chunk-CO3UYX6G.mjs";
82
87
  import {
83
88
  init_define_import_meta_env
84
89
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -173,10 +178,18 @@ var SHIM_SPAWN_LADDER = ` const res = entryPath
173
178
  ? spawnSync(process.execPath, [entryPath, 'hook', eventName], {
174
179
  input,
175
180
  encoding: 'utf8',
176
- timeout: 3000,
181
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
177
182
  killSignal: 'SIGKILL',
178
183
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
179
184
  })`;
185
+ var SHIM_FALLBACK_TIMEOUT_MS = { claudecode: 2e4, codex: 45e3, copilot: 45e3, grok: 3e3, kimi: 2e4, pi: 2e4, relay: 1e4 };
186
+ var SHIM_SESSION_START_TIMEOUT_MS = 1e4;
187
+ var SESSION_START_TEST_BUDGET_ENV = "TG_TEST_SESSION_START_BUDGET_MS";
188
+ var SESSION_START_TEST_BUDGET_MIN_MS = 1e3;
189
+ var SESSION_START_TEST_BUDGET_MAX_MS = 6e5;
190
+ function shimFallbackTimeoutConst(harness) {
191
+ return `const SHIM_FALLBACK_TIMEOUT_MS = ${SHIM_FALLBACK_TIMEOUT_MS[harness]}`;
192
+ }
180
193
  var SHIM_MAX_BUFFER_CONST = `const SHIM_MAX_BUFFER_BYTES = 32 * 1024 * 1024`;
181
194
  var SHIM_ASYNC_DETACH = `const ASYNC_DETACH_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit'])
182
195
  const ASYNC_DETACH_SKIP_EXT_RE = /\\.(md|mdx|markdown|rst)$/i
@@ -194,6 +207,14 @@ function bashResultByteLength(resp) {
194
207
  }
195
208
  return 0
196
209
  }
210
+ // A token-goat read|section|symbol result is never detached, however short: see isAsyncDetachEligible in src/hook_adapters.ts.
211
+ const TG_LEDGER_PREFIX_RE = /^(?:(?:cd\\s+(?:"[^"]*"|'[^']*'|\\S+)|export[ \\t]+[A-Za-z_]\\w*=(?:"[^"]*"|'[^']*'|[^\\s"'])*(?:[ \\t]+[A-Za-z_]\\w*=(?:"[^"]*"|'[^']*'|[^\\s"'])*)*)[ \\t]*(?:&&|;|\\r?\\n)\\s*)+/
212
+ const TG_LEDGER_READ_RE = /^(?:[A-Za-z_][A-Za-z0-9_]*=\\S+\\s+)*(?:env\\s+(?:(?:(?:-u|--unset)\\s+\\S+|-(?![A-Za-z]*C|-c)\\S*|[A-Za-z_][A-Za-z0-9_]*=\\S+)\\s+)*)?(?:token-goat|tg|node(?:\\.exe)?\\s+["']?(?:\\S*[\\\\/])?token-goat(?:\\.mjs)?["']?)\\s+(?:read|section|symbol)\\b/i
213
+ function isLedgerReadCommand(cmd) {
214
+ if (typeof cmd !== 'string') return false
215
+ const body = cmd.trim().replace(/^\\(\\s*/, '')
216
+ return TG_LEDGER_READ_RE.test((body.replace(TG_LEDGER_PREFIX_RE, '') || body).trim())
217
+ }
197
218
  function isAsyncDetachEligible(eventName, input) {
198
219
  if (eventName === 'subagent_stop') return true
199
220
  if (eventName !== 'post_tool_use') return false
@@ -210,7 +231,7 @@ function isAsyncDetachEligible(eventName, input) {
210
231
  : ''
211
232
  return !ASYNC_DETACH_SKIP_EXT_RE.test(filePath)
212
233
  }
213
- if (toolName === 'Bash') return bashResultByteLength(payload['tool_response']) < ASYNC_DETACH_BASH_MAX_BYTES
234
+ if (toolName === 'Bash') return !isLedgerReadCommand((payload['tool_input'] || {})['command']) && bashResultByteLength(payload['tool_response']) < ASYNC_DETACH_BASH_MAX_BYTES
214
235
  return false
215
236
  } catch {
216
237
  return false
@@ -254,6 +275,7 @@ ${SHIM_TRY_SERVER}
254
275
  ${SHIM_TRY_IN_PROCESS}
255
276
 
256
277
  ${SHIM_MAX_BUFFER_CONST}
278
+ ${shimFallbackTimeoutConst("claudecode")}
257
279
 
258
280
  ${SHIM_ASYNC_DETACH}
259
281
 
@@ -304,9 +326,9 @@ async function main() {
304
326
  // spawning a second node process altogether; fall back to the entry path directly via
305
327
  // process.execPath, then finally to the old PATH-based shell:true invocation (an
306
328
  // older cached hook config with no argv[3], or a config wired directly to
307
- // \`token-goat hook <event>\` rather than through this shim). A 3000ms
308
- // timeout/killSignal on both spawnSync fallbacks means token-goat degrades to its own
309
- // fail-open '{}' rather than being force-killed by Claude Code's own hook timeout.
329
+ // \`token-goat hook <event>\` rather than through this shim). A
330
+ // SHIM_FALLBACK_TIMEOUT_MS timeout/killSignal on both spawnSync fallbacks means token-goat degrades to its own
331
+ // fail-open '{}' rather than being force-killed by Claude Code's own hook timeout (30 s on UserPromptSubmit).
310
332
  const entryPath = process.argv[3]
311
333
  let stdout = await tryInProcess(entryPath, eventName, input, asyncDetachAtMs)
312
334
  if (stdout === undefined) {
@@ -322,7 +344,7 @@ ${SHIM_SPAWN_LADDER}
322
344
  input,
323
345
  encoding: 'utf8',
324
346
  shell: true,
325
- timeout: 3000,
347
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
326
348
  killSignal: 'SIGKILL',
327
349
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
328
350
  })
@@ -351,15 +373,16 @@ function buildGuidanceBody(fallbackToolClause, opts = {}) {
351
373
  "",
352
374
  `This gate decides *whether* to reach for a read tool at all. ${fallbackToolClause} only pick the *fallback* once token-goat has been ruled out for this read \u2014 they never authorize skipping the gate.`,
353
375
  "",
354
- "Fallback clauses may name your harness's own native read, search, and edit tools, or its shell helpers. Shell binaries and editor programs are commands invoked through the shell tool, never tool identifiers, and must never appear in an agent's tools frontmatter or an allowed-tools list. This paragraph deliberately names no specific tool or binary: instruction-file loaders harvest such names into a tool allowlist and then warn that every one of them is unknown.",
376
+ "Shell binaries and editor programs are commands run through the shell tool, never tool identifiers: keep them out of an agent's tools and allowed-tools lists, where loaders harvest the names and warn that each is unknown.",
355
377
  "",
356
378
  "Exemptions (gate passes, read directly): the file is under ~200 lines and you need all of it; it was never indexed (new, untracked, or generated this turn); it is a genuinely opaque binary (not an image); the target has no symbol handle (e.g. a literal mid-function).",
357
379
  "",
358
380
  "Failure shapes to catch yourself in, and the command that replaces each:",
359
- '- not knowing which command answers a question you can already state in words \u2192 `answer "<question>"`, which resolves the subject in the index, runs the right command, prints `via: <command>`, and refuses rather than guess',
381
+ '- not knowing which command answers a question you can already state in words \u2192 `answer "<question>"`, which runs the right command and refuses rather than guess',
360
382
  '- a shell text search with context flags to find a function body \u2192 `read "file::symbol"`',
361
383
  '- paging one function with view/view_range \u2192 `read "file::symbol"`',
362
- "- a read refused by a `[tg]` deny, retried with a shifted range or a different read tool \u2192 the command the deny names",
384
+ '- issuing sequential or overlapping line-range reads across a file (paging) \u2192 `read "file::symbol"` / `outline file` / `skeleton file` / rg <pattern> <file>',
385
+ "- a read refused by a `[tg]` deny, retried with a shifted range, different lines, or another read tool \u2192 never retry reading the file at different lines; run the command the deny names or use bash-output / token-goat read",
363
386
  '- reading a symbol plus chasing its callers and containing doc section as separate reads \u2192 `brief "file::symbol"`',
364
387
  '- reading one heading of a large doc \u2192 `section "file::Heading"`',
365
388
  "- searching for a symbol's callers \u2192 `refs file::symbol --callers`",
@@ -370,11 +393,10 @@ function buildGuidanceBody(fallbackToolClause, opts = {}) {
370
393
  "- pulling one value or subtree out of a JSON/YAML/XML file (manifest, lockfile, spec, config) \u2192 `json-query file 'a.b.c'` / `yaml-query file 'a.b.c'` / `xml-query file 'a.b.c'`",
371
394
  "- opening an image to check its dimensions, format, or size \u2192 `image-meta file`",
372
395
  "- opening a screenshot, diagram, or scan to read the text in it \u2192 `image-text file`",
373
- "- opening a PDF or Office document \u2192 inspect its format first, then read a narrow slice: PDF `pdf-meta`/`pdf-outline` then `pdf-locate` to find the pages and `pdf-extract` only those; Word `docx-outline` then `docx-tables`/`docx-text`; PowerPoint `pptx-outline` then `pptx-slide`/`pptx-notes`; Excel `xlsx-sheets` then `xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`",
374
396
  "- running tests immediately on edited code without fast static validation \u2192 run language validator first (e.g. `ruff check --select F821 <file>`, `node --check`, `tsc --noEmit`) to catch missing imports and syntax errors before test execution",
375
397
  "- passing concatenated or unquoted paths to shell tools \u2192 verify whitespace and argument separation in multi-path commands (e.g. `git add path1 path2`, never `git add path1path2`)",
376
398
  "",
377
- 'Commands: `search "<query>"`, `answer "<question>"`, `symbol NAME`, `read "file::symbol"`, `brief "file::symbol"`, `section "file::Heading"`, `semantic "description"`, `outline file`/`skeleton file`, `map --compact`, `refs file::symbol --callers`, `changed --symbol`, `config-get file KEY`, `json-query file \'a.b.c\'`/`yaml-query`/`xml-query`, `json-outline file`/`yaml-outline`/`xml-outline`, `bash-output`/`web-output`/`mcp-output`, ' + (gdrive ? "`gdrive-sections <file-id>`, " : "") + "`image-meta file`/`image-text file`, `pdf-meta`/`pdf-outline`/`pdf-locate`/`pdf-extract`, `docx-outline`/`docx-tables`/`docx-text`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `xlsx-sheets`/`xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`.",
399
+ "Commands: `symbol NAME`, `changed --symbol`, `config-get file KEY`, `json-outline`/`yaml-outline`/`xml-outline`, " + (gdrive ? "`gdrive-sections <file-id>`, " : "") + "and `token-goat --help` for the rest.",
378
400
  "",
379
401
  "Sub-agent briefs must carry this gate verbatim: a sub-agent inherits none of this context and its reads spend the same token budget.",
380
402
  "",
@@ -388,17 +410,6 @@ function buildGuidanceBlock(h) {
388
410
  return [h.beginMarker, buildGuidanceBody(h.fallbackToolClause, h.gdrive === void 0 ? {} : { gdrive: h.gdrive }), h.endMarker].join("\n");
389
411
  }
390
412
 
391
- // src/install.ts
392
- init_define_import_meta_env();
393
- import * as fs2 from "node:fs";
394
- import * as path2 from "node:path";
395
-
396
- // src/canonical_skill.ts
397
- init_define_import_meta_env();
398
- import { gunzipSync } from "node:zlib";
399
- var CANONICAL_SKILL_GZIP_BASE64 = "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";
400
- var CANONICAL_SKILL_MD = gunzipSync(Buffer.from(CANONICAL_SKILL_GZIP_BASE64, "base64")).toString("utf8");
401
-
402
413
  // src/native_hook.ts
403
414
  init_define_import_meta_env();
404
415
  import { spawnSync } from "node:child_process";
@@ -522,6 +533,9 @@ function nativeSelftest(bin) {
522
533
  return result;
523
534
  }
524
535
  var _decisions = /* @__PURE__ */ new Map();
536
+ var COPY_FAILED = "its copy could not be put in place in the data directory (a scanner or another process held the file)";
537
+ var COPY_STALE = "the data-directory copy is missing or differs from the packaged binary";
538
+ var TRANSIENT_WHY = /could not be put in place|no answer within/;
525
539
  registerReset(() => {
526
540
  _selftests.clear();
527
541
  _decisions.clear();
@@ -533,13 +547,33 @@ function nativeHookBinary(entryPath = process.argv[1], opts = {}) {
533
547
  const cached = _decisions.get(key);
534
548
  if (cached !== void 0) return cached ?? void 0;
535
549
  const packaged = packagedNativeBinary(entryPath);
536
- let bin = packaged;
537
- if (packaged !== void 0 && process.platform === "win32") {
538
- const copy = nativeCopyPath(entryPath);
539
- const current = sync ? syncNativeCopy(packaged, copy) : nativeCopyCurrent(packaged, copy);
540
- bin = current ? copy : void 0;
550
+ let bin;
551
+ let why;
552
+ if (packaged !== void 0) {
553
+ for (let attempt = 1; attempt <= 2 && bin === void 0; attempt++) {
554
+ if (attempt === 2) {
555
+ if (why === void 0 || !TRANSIENT_WHY.test(why)) break;
556
+ _selftests.clear();
557
+ }
558
+ why = void 0;
559
+ let candidate = packaged;
560
+ if (process.platform === "win32") {
561
+ const copy = nativeCopyPath(entryPath);
562
+ const current = sync ? syncNativeCopy(packaged, copy) : nativeCopyCurrent(packaged, copy);
563
+ if (current) candidate = copy;
564
+ else {
565
+ candidate = void 0;
566
+ why = sync ? COPY_FAILED : COPY_STALE;
567
+ }
568
+ }
569
+ if (candidate !== void 0) {
570
+ const test = nativeSelftest(candidate);
571
+ if (test.ok) bin = candidate;
572
+ else why = `its self-test failed: ${test.reason ?? "no reason given"}`;
573
+ }
574
+ }
541
575
  }
542
- if (bin !== void 0 && !nativeSelftest(bin).ok) bin = void 0;
576
+ if (bin === void 0 && sync && why !== void 0 && why !== COPY_STALE) emitErr(`token-goat: wrote the Node form of the hooks, not the native hook client, because ${why}. Run \`token-goat doctor --repair\` (or install again) to retry.`);
543
577
  _decisions.set(key, bin ?? null);
544
578
  if (sync) _decisions.set(`false\0${entryPath}`, bin ?? null);
545
579
  return bin;
@@ -630,6 +664,17 @@ function parseNativeInvocation(argv) {
630
664
  return { bin, harness: h >= 0 ? flags[h + 1] : void 0, wrapped: argv.slice(split + 1) };
631
665
  }
632
666
 
667
+ // src/install.ts
668
+ init_define_import_meta_env();
669
+ import * as fs2 from "node:fs";
670
+ import * as path2 from "node:path";
671
+
672
+ // src/canonical_skill.ts
673
+ init_define_import_meta_env();
674
+ import { gunzipSync } from "node:zlib";
675
+ var CANONICAL_SKILL_GZIP_BASE64 = "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";
676
+ var CANONICAL_SKILL_MD = gunzipSync(Buffer.from(CANONICAL_SKILL_GZIP_BASE64, "base64")).toString("utf8");
677
+
633
678
  // src/install.ts
634
679
  var HOOK_EVENT_MAP = [
635
680
  ["PreToolUse", "pre_tool_use"],
@@ -639,6 +684,7 @@ var HOOK_EVENT_MAP = [
639
684
  ["PreCompact", "pre_compact"],
640
685
  ["PostCompact", "post_compact"],
641
686
  ["UserPromptSubmit", "user_prompt_submit"],
687
+ ["Stop", "stop"],
642
688
  ["SubagentStop", "subagent_stop"],
643
689
  ["SessionStart", "session_start"]
644
690
  ];
@@ -748,7 +794,7 @@ var SettingsParseError = class extends Error {
748
794
  };
749
795
  function readSettings(p, opts = {}) {
750
796
  const refuse = (problem, detail) => new SettingsParseError(
751
- (opts.command === "uninstall" ? `settings file '${p}' is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name and the file's backups; fix the file and run uninstall again.` : `settings file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
797
+ (opts.command === "uninstall" ? `settings file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name and the file's backups; fix the file and run uninstall again.` : `settings file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
752
798
  );
753
799
  let raw;
754
800
  try {
@@ -919,6 +965,13 @@ function isInstalled(scope = "user") {
919
965
  const gaps = hookEventGaps(scope);
920
966
  return gaps !== null && gaps.missing.length === 0 && gaps.outdated.length === 0 && gaps.broken.length === 0;
921
967
  }
968
+ function hasFiringHook(scope) {
969
+ const gaps = hookEventGaps(scope);
970
+ return gaps !== null && gaps.missing.length + gaps.broken.length < HOOK_EVENT_MAP.length;
971
+ }
972
+ function claudeHooksInstalledAnyScope() {
973
+ return hasFiringHook("user") || hasFiringHook("project");
974
+ }
922
975
  var LIVE_HOOK_MARKER_PATTERNS = [SHIM_COMMAND_MARKER, COMMAND_MARKER].map(anchoredMarkerPattern);
923
976
  var SHIM_FILE_WORD = /(?:^|[\\/])token-goat-shim\.c?js$/;
924
977
  function isLiveTokenGoatHookCommand(command, args) {
@@ -946,7 +999,7 @@ function hookEventGaps(scope = "user") {
946
999
  const broken = [];
947
1000
  for (const [eventKey, eventArg] of HOOK_EVENT_MAP) {
948
1001
  const expected = expectedHookEntryFor(scriptPath, eventArg, { sync: false });
949
- if (groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected))) continue;
1002
+ if (groupHasTokenGoat(hooks[eventKey], (c, a) => hookEntryMatches(c, a, expected) && isLiveTokenGoatHookCommand(c, a))) continue;
950
1003
  if (groupHasTokenGoat(hooks[eventKey], isLiveTokenGoatHookCommand)) outdated.push(eventKey);
951
1004
  else if (groupHasTokenGoat(hooks[eventKey], isBrokenNativeHookCommand)) broken.push(eventKey);
952
1005
  else missing.push(eventKey);
@@ -1149,6 +1202,7 @@ ${SHIM_TRY_SERVER}
1149
1202
  ${SHIM_TRY_IN_PROCESS}
1150
1203
 
1151
1204
  ${SHIM_MAX_BUFFER_CONST}
1205
+ ${shimFallbackTimeoutConst("codex")}
1152
1206
 
1153
1207
  async function main() {
1154
1208
  const eventName = process.argv[2] || ''
@@ -1172,7 +1226,7 @@ async function main() {
1172
1226
  // CLI bridge's inner hook call (a bare 'token-goat' on PATH failing to resolve
1173
1227
  // crashes this call, and Codex -- like Copilot -- fails closed on a non-zero-exit
1174
1228
  // hook). Falls back further to the old PATH-based shell:true invocation when argv[3]
1175
- // is absent (an older cached hook config). A 3000ms timeout/killSignal on both
1229
+ // is absent (an older cached hook config). A SHIM_FALLBACK_TIMEOUT_MS timeout/killSignal on both
1176
1230
  // spawnSync fallbacks bounds them well under Codex's own hook timeout budget, so
1177
1231
  // token-goat degrades to its own fail-open '{}' rather than being force-killed by
1178
1232
  // Codex first.
@@ -1184,7 +1238,7 @@ ${SHIM_SPAWN_LADDER}
1184
1238
  input,
1185
1239
  encoding: 'utf8',
1186
1240
  shell: true,
1187
- timeout: 3000,
1241
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
1188
1242
  killSignal: 'SIGKILL',
1189
1243
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
1190
1244
  })
@@ -1251,35 +1305,41 @@ function groupHasTokenGoat2(groups, matcher, predicate) {
1251
1305
 
1252
1306
  // src/bridges/codex_install.ts
1253
1307
  var CODEX_COMMAND_MARKER = "token-goat-shim";
1254
- var CODEX_MATCHERS = ["view_image|Bash|exec|shell|bash", "apply_patch", "web_search"];
1308
+ var CODEX_MATCHERS = ["view_image|Bash|exec|shell|bash", "apply_patch", "web_search", "spawn_agent|multi_agent_v1wait_agent"];
1255
1309
  var CODEX_HOOK_EVENTS = ["PreToolUse", "PostToolUse"];
1256
1310
  var CODEX_EVENT_ARG = {
1257
1311
  PreToolUse: "pre_tool_use",
1258
1312
  PostToolUse: "post_tool_use"
1259
1313
  };
1260
- var CODEX_GLOBAL_HOOK_EVENTS = ["PreCompact", "UserPromptSubmit", "SubagentStop"];
1314
+ var CODEX_GLOBAL_HOOK_EVENTS = ["PreCompact", "UserPromptSubmit", "Stop", "SubagentStop"];
1261
1315
  var CODEX_GLOBAL_EVENT_ARG = {
1262
1316
  PreCompact: "pre_compact",
1263
1317
  UserPromptSubmit: "user_prompt_submit",
1318
+ Stop: "stop",
1264
1319
  SubagentStop: "subagent_stop"
1265
1320
  };
1266
1321
  var CodexConfigParseError = class extends Error {
1267
1322
  };
1323
+ function codexHomeDir() {
1324
+ const override = process.env["CODEX_HOME"];
1325
+ if (override !== void 0 && override !== "") return path3.resolve(override);
1326
+ return path3.join(os.homedir(), ".codex");
1327
+ }
1268
1328
  function codexConfigPath() {
1269
- return path3.join(os.homedir(), ".codex", "config.toml");
1329
+ return path3.join(codexHomeDir(), "config.toml");
1270
1330
  }
1271
1331
  function codexAgentsPath() {
1272
- return path3.join(os.homedir(), ".codex", "AGENTS.md");
1332
+ return path3.join(codexHomeDir(), "AGENTS.md");
1273
1333
  }
1274
1334
  function codexHookScriptPath() {
1275
- return path3.join(os.homedir(), ".codex", "hooks", SHIM_FILE);
1335
+ return path3.join(codexHomeDir(), "hooks", SHIM_FILE);
1276
1336
  }
1277
1337
  function codexLegacyHookScriptPath() {
1278
- return path3.join(os.homedir(), ".codex", "hooks", LEGACY_SHIM_FILE);
1338
+ return path3.join(codexHomeDir(), "hooks", LEGACY_SHIM_FILE);
1279
1339
  }
1280
1340
  function readCodexConfig(p, opts = {}) {
1281
1341
  const refuse = (problem, detail) => new CodexConfigParseError(
1282
- (opts.command === "uninstall" ? `Codex config file '${p}' is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name, the AGENTS.md block and the file's backups; fix the file and run uninstall again.` : `Codex config file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
1342
+ (opts.command === "uninstall" ? `Codex config file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name, the AGENTS.md block and the file's backups; fix the file and run uninstall again.` : `Codex config file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
1283
1343
  );
1284
1344
  let raw;
1285
1345
  try {
@@ -1710,6 +1770,449 @@ function stripAgentsBlock(p) {
1710
1770
  // src/bridges/copilot_cli.ts
1711
1771
  init_define_import_meta_env();
1712
1772
 
1773
+ // src/bridges/copilot_session_start.ts
1774
+ init_define_import_meta_env();
1775
+ var COPILOT_SESSION_START_HELPERS = `
1776
+ const SHIM_SESSION_START_TIMEOUT_MS = ${SHIM_SESSION_START_TIMEOUT_MS}
1777
+
1778
+ // Test-only seam, mirrored by the native owner: a plain decimal in range replaces the budget so a test need not race the clock; anything else is ignored.
1779
+ function sessionStartBudgetMs() {
1780
+ const text = process.env.${SESSION_START_TEST_BUDGET_ENV}
1781
+ if (typeof text !== 'string' || !/^[0-9]+$/.test(text)) return SHIM_SESSION_START_TIMEOUT_MS
1782
+ const value = Number(text)
1783
+ return value >= ${SESSION_START_TEST_BUDGET_MIN_MS} && value <= ${SESSION_START_TEST_BUDGET_MAX_MS} ? value : SHIM_SESSION_START_TIMEOUT_MS
1784
+ }
1785
+ const SESSION_START_CLEANUP_RESERVE_MS = 1000
1786
+ // How long past the deadline the shim keeps its stdin listener for a payload already in the pipe: on Windows the pipe read completes on a worker thread, so the completion can land many event-loop turns after the listener attaches. Measured resume-to-data under 52 busy loops on 26 CPUs: p50 5.7 ms, max 204 ms.
1787
+ const SESSION_START_LATE_PAYLOAD_GRACE_MS = 1000
1788
+ const SESSION_START_MAX_INPUT_BYTES = 1024 * 1024
1789
+
1790
+ function failOpenSessionStart(reason) {
1791
+ process.stderr.write('[token-goat] SessionStart failed open: ' + reason + '\\n')
1792
+ process.stdout.write('{}')
1793
+ }
1794
+
1795
+ function readSessionStartInput(timeoutMs) {
1796
+ return new Promise((resolve, reject) => {
1797
+ const chunks = []
1798
+ const closers = []
1799
+ let totalBytes = 0
1800
+ let frameStart = -1
1801
+ let frameEnd = -1
1802
+ let inString = false
1803
+ let escaped = false
1804
+ let settled = false
1805
+ let timer
1806
+
1807
+ function cleanup() {
1808
+ clearTimeout(timer)
1809
+ process.stdin.removeListener('data', onData)
1810
+ process.stdin.removeListener('end', onEnd)
1811
+ process.stdin.removeListener('error', onError)
1812
+ process.stdin.pause()
1813
+ if (typeof process.stdin.unref === 'function') process.stdin.unref()
1814
+ process.stdin.destroy()
1815
+ }
1816
+
1817
+ function finish(error, value) {
1818
+ if (settled) return
1819
+ settled = true
1820
+ cleanup()
1821
+ if (error) reject(new Error(error))
1822
+ else resolve(value)
1823
+ }
1824
+
1825
+ function onData(chunk) {
1826
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
1827
+ const chunkStart = totalBytes
1828
+ totalBytes += bytes.length
1829
+ if (totalBytes > SESSION_START_MAX_INPUT_BYTES) {
1830
+ finish('input exceeded 1 MiB')
1831
+ return
1832
+ }
1833
+ chunks.push(bytes)
1834
+
1835
+ for (let index = 0; index < bytes.length; index += 1) {
1836
+ const byte = bytes[index]
1837
+ const position = chunkStart + index
1838
+ if (frameEnd !== -1) {
1839
+ if (byte !== 0x20 && byte !== 0x09 && byte !== 0x0a && byte !== 0x0d) {
1840
+ finish('input contained trailing data')
1841
+ return
1842
+ }
1843
+ continue
1844
+ }
1845
+ if (frameStart === -1) {
1846
+ if (byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d) continue
1847
+ if (byte !== 0x7b) {
1848
+ finish('input was not a JSON object')
1849
+ return
1850
+ }
1851
+ frameStart = position
1852
+ closers.push(0x7d)
1853
+ continue
1854
+ }
1855
+ if (inString) {
1856
+ if (escaped) escaped = false
1857
+ else if (byte === 0x5c) escaped = true
1858
+ else if (byte === 0x22) inString = false
1859
+ continue
1860
+ }
1861
+ if (byte === 0x22) {
1862
+ inString = true
1863
+ } else if (byte === 0x7b) {
1864
+ closers.push(0x7d)
1865
+ } else if (byte === 0x5b) {
1866
+ closers.push(0x5d)
1867
+ } else if (byte === 0x7d || byte === 0x5d) {
1868
+ if (closers.pop() !== byte) {
1869
+ finish('input contained malformed JSON')
1870
+ return
1871
+ }
1872
+ if (closers.length === 0) frameEnd = position + 1
1873
+ }
1874
+ }
1875
+
1876
+ if (frameEnd !== -1) {
1877
+ const framed = Buffer.concat(chunks, totalBytes)
1878
+ const raw = framed.subarray(frameStart, frameEnd).toString('utf8')
1879
+ try {
1880
+ const parsed = JSON.parse(raw)
1881
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
1882
+ finish('input was not a JSON object')
1883
+ return
1884
+ }
1885
+ } catch {
1886
+ finish('input contained malformed JSON')
1887
+ return
1888
+ }
1889
+ finish(undefined, raw)
1890
+ }
1891
+ }
1892
+
1893
+ function onEnd() {
1894
+ finish(frameStart === -1 ? 'stdin closed without input' : 'stdin closed before the JSON object was complete')
1895
+ }
1896
+
1897
+ function onError() {
1898
+ finish('stdin could not be read')
1899
+ }
1900
+
1901
+ // A payload already in the pipe must be framed even when the deadline has passed: expiry opens one bounded grace window that data, end or error closes at once, so only a payload that never arrives costs the full grace.
1902
+ timer = setTimeout(() => { timer = setTimeout(() => finish('input deadline exceeded'), SESSION_START_LATE_PAYLOAD_GRACE_MS) }, Math.max(0, timeoutMs))
1903
+ process.stdin.on('data', onData)
1904
+ process.stdin.on('end', onEnd)
1905
+ process.stdin.on('error', onError)
1906
+ process.stdin.resume()
1907
+ })
1908
+ }
1909
+
1910
+ // Plain-JS mirror of session_start_owed.ts::resetSessionStartOwed for a main-thread key: tests/session_start_owed_reset.test.ts pins both to the same files. It runs before anything can throw so a helper killed before its own stamp still leaves the start begun, and an earlier start's marker cannot read settled.
1911
+ function resetSessionStartOwedFiles(payload) {
1912
+ stampSessionStartOwedFiles(payload, true)
1913
+ }
1914
+
1915
+ // A start this shim failed open on is owed whatever the helper's own marker says: the helper judges "settled in time" from its own clock, and the shim can still discard its response (a helper whose exit outlasts the budget, a cleanup that fails, an unreadable response). The shim is the party that knows it printed nothing, so it re-stamps the start begun after the helper tree is gone. A plain-JS mirror of session_start_owed.ts::recordSessionStartBegun.
1916
+ function stampSessionStartOwedFiles(payload, dropClaim) {
1917
+ try {
1918
+ const raw = payload && (payload.session_id !== undefined ? payload.session_id : payload.sessionId)
1919
+ if (typeof raw !== 'string' || raw === '' || raw.indexOf(':agent:') !== -1) return
1920
+ // A subagent's key is salted, and its start is not the parent's to reset.
1921
+ if (payload.agent_id || payload.agentId) return
1922
+ const override = (process.env.TOKEN_GOAT_HOME || '').trim()
1923
+ const rooted = override !== '' && require('node:path').isAbsolute(override) &&
1924
+ (process.platform !== 'win32' || !['', '/', String.fromCharCode(92)].includes(require('node:path').parse(override).root))
1925
+ const home = rooted ? override : require('node:path').join(require('node:os').homedir(), '.token-goat')
1926
+ const stem = raw.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64)
1927
+ if (stem === '') return
1928
+ // The claim goes, and the settle file is rewritten as a begun stamp: only a begun, unsettled start is owed.
1929
+ if (dropClaim) require('node:fs').rmSync(require('node:path').join(home, 'sessions', stem + '.session-start-owed'), { force: true })
1930
+ require('node:fs').mkdirSync(require('node:path').join(home, 'sessions'), { recursive: true })
1931
+ require('node:fs').writeFileSync(require('node:path').join(home, 'sessions', stem + '.session-start'), JSON.stringify({ begunAtMs: Date.now() }), 'utf8')
1932
+ } catch {
1933
+ // Best-effort: the helper clears the marker itself when it gets that far.
1934
+ }
1935
+ }
1936
+
1937
+ async function runBoundedSessionStart(entryPath, eventName, payload, harness, deadline) {
1938
+ resetSessionStartOwedFiles(payload)
1939
+ // Do not probe entryPath here: even existsSync can block on a network filesystem.
1940
+ if (typeof entryPath !== 'string' || entryPath === '') throw new Error('hook entry unavailable')
1941
+ const remainingMs = deadline - Date.now()
1942
+ if (remainingMs <= 0) throw new Error('overall deadline exceeded')
1943
+ // Reject before spawning when only the process-tree teardown reserve remains.
1944
+ if (remainingMs <= SESSION_START_CLEANUP_RESERVE_MS) {
1945
+ throw new Error('insufficient time for process-tree cleanup')
1946
+ }
1947
+
1948
+ const args = [entryPath, 'hook', eventName]
1949
+ const env = Object.assign({}, process.env, { TOKEN_GOAT_HOOK_SERVER: '0' })
1950
+ if (process.platform === 'win32') env.TG_SESSION_START_DEFER_WORKER = '1'
1951
+ // Lets the helper's settle marker say how close to the deadline it finished.
1952
+ env.TG_SESSION_START_DEADLINE_EPOCH = String(deadline)
1953
+ if (harness === 'vscode') {
1954
+ args.push('--harness', 'vscode')
1955
+ env.TOKEN_GOAT_HARNESS_OVERRIDE = 'vscode'
1956
+ env.TOKEN_GOAT_VSCODE_HOOKS_DIR = __dirname
1957
+ } else {
1958
+ env.TOKEN_GOAT_HARNESS_OVERRIDE = 'copilot_cli'
1959
+ env.TOKEN_GOAT_COPILOT_HOOKS_DIR = __dirname
1960
+ }
1961
+
1962
+ let stdout
1963
+ try {
1964
+ stdout = await superviseSessionStart({ args, env, payload, deadline })
1965
+ } catch (error) {
1966
+ // The caller prints the failed-open line and exactly {}: the start is owed, whatever marker the helper left.
1967
+ stampSessionStartOwedFiles(payload, false)
1968
+ throw error
1969
+ }
1970
+ try {
1971
+ const parsed = JSON.parse(stdout.trim())
1972
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
1973
+ throw new Error('hook helper returned a non-object response')
1974
+ }
1975
+ return JSON.stringify(parsed)
1976
+ } catch {
1977
+ stampSessionStartOwedFiles(payload, false)
1978
+ throw new Error('hook helper returned invalid JSON')
1979
+ }
1980
+ }
1981
+
1982
+ function superviseSessionStart({ args, env, payload, deadline }) {
1983
+ if (process.platform === 'win32') return superviseWindowsSessionStart({ args, env, payload, deadline })
1984
+ const { spawn } = require('node:child_process')
1985
+ // On POSIX, retain the canonical root until its detached group is killed.
1986
+ const supervisor = [
1987
+ "const keeper = setInterval(() => {}, 1000); keeper.unref()",
1988
+ "let finished = false",
1989
+ "function done(code) { if (finished) return; finished = true; keeper.ref(); process.send({ type: 'hookExit', code }) }",
1990
+ "process.exit = code => done(code === undefined ? (process.exitCode || 0) : Number(code))",
1991
+ "process.once('beforeExit', code => done(code))",
1992
+ "process.once('uncaughtException', () => done(1))",
1993
+ "process.once('unhandledRejection', () => done(1))",
1994
+ "import(require('node:url').pathToFileURL(require('node:path').resolve(process.argv[1])).href).catch(() => done(1))",
1995
+ ].join(';')
1996
+ return new Promise((resolve, reject) => {
1997
+ const root = spawn(process.execPath, ['-e', supervisor, ...args], {
1998
+ env, windowsHide: true,
1999
+ // owner: this shim, which SIGKILLs the whole group in beginCleanup and, when that fails or the supervisor died first, in cleanupFailed; dies when the helper reports its exit, on an error, a timeout or the hard deadline (descendants that left the group, or the shim itself being SIGKILLed, are not covered).
2000
+ detached: process.platform !== 'win32',
2001
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
2002
+ })
2003
+ let helperCode
2004
+ let rootClosed = false
2005
+ let cleaning = false
2006
+ let cleaned = false
2007
+ let settled = false
2008
+ let failure
2009
+ let outputBytes = 0
2010
+ const output = []
2011
+ let stderrBytes = 0
2012
+ let timer
2013
+ let hardTimer
2014
+
2015
+ function alive(pid) {
2016
+ if (!pid) return false
2017
+ try { process.kill(pid, 0); return true } catch (error) { return error.code === 'EPERM' }
2018
+ }
2019
+
2020
+ function finish(error) {
2021
+ if (settled) return
2022
+ settled = true
2023
+ clearTimeout(timer)
2024
+ clearTimeout(hardTimer)
2025
+ root.removeListener('message', onMessage)
2026
+ root.removeListener('exit', onExit)
2027
+ root.removeListener('close', onClose)
2028
+ root.stdout.removeListener('data', onOutput)
2029
+ root.stderr.removeListener('data', onStderr)
2030
+ root.stdin.destroy()
2031
+ root.stdout.destroy()
2032
+ root.stderr.destroy()
2033
+ if (root.connected) root.disconnect()
2034
+ // owner: this shim, which has killed the group by now (beginCleanup, or the group kill in cleanupFailed); dies when that kill lands, and the unref only lets the shim exit.
2035
+ root.unref()
2036
+ if (error) reject(new Error(error))
2037
+ else resolve(Buffer.concat(output, outputBytes).toString('utf8'))
2038
+ }
2039
+
2040
+ function complete() {
2041
+ // close, not exit, proves all buffered stdout was drained. The hard deadline
2042
+ // below also covers an escaped descendant retaining a pipe indefinitely.
2043
+ if (!cleaned || !rootClosed) return
2044
+ if (!failure && (helperCode !== 0 || outputBytes === 0)) failure = 'hook helper exited without a response'
2045
+ finish(failure)
2046
+ }
2047
+
2048
+ function killGroup() { process.kill(-root.pid, 'SIGKILL') }
2049
+
2050
+ function cleanupFailed(reason) {
2051
+ // The group outlives its leader while any member runs, so this reaches descendants even after the supervisor died; ESRCH means no member is left. Descendants that left the group are still unverified.
2052
+ if (root.pid) {
2053
+ try { killGroup() } catch (error) {
2054
+ if (error.code !== 'ESRCH') reason += '; group kill failed'
2055
+ }
2056
+ }
2057
+ // Verify the known owned PIDs before falling back to direct kills. Unknown
2058
+ // descendants cannot be certified clean when the tree utility has failed.
2059
+ const remaining = [root.pid].filter(alive)
2060
+ for (const pid of remaining) {
2061
+ try { process.kill(pid, 'SIGKILL') } catch (error) {
2062
+ if (error.code !== 'ESRCH') reason += '; direct kill failed for PID ' + pid
2063
+ }
2064
+ }
2065
+ finish((failure ? failure + '; ' : '') + 'process-tree cleanup failed: ' + reason
2066
+ + '; remaining owned PIDs before fallback: ' + (remaining.join(',') || 'none')
2067
+ + '; remaining known PIDs after fallback: ' + (remaining.filter(alive).join(',') || 'none')
2068
+ + '; descendant termination not verified')
2069
+ }
2070
+
2071
+ function beginCleanup(reason) {
2072
+ if (reason && !failure) failure = reason
2073
+ if (settled || cleaning) return
2074
+ cleaning = true
2075
+ clearTimeout(timer)
2076
+ if (!root.pid) { finish(failure || 'hook helper could not run'); return }
2077
+ try {
2078
+ killGroup()
2079
+ cleaned = true
2080
+ complete()
2081
+ } catch (error) {
2082
+ cleanupFailed(error.code || 'group kill failed')
2083
+ }
2084
+ }
2085
+
2086
+ function onOutput(chunk) {
2087
+ outputBytes += chunk.length
2088
+ if (outputBytes > SESSION_START_MAX_INPUT_BYTES) { beginCleanup('hook helper output exceeded 1 MiB'); return }
2089
+ output.push(chunk)
2090
+ }
2091
+
2092
+ function onStderr(chunk) {
2093
+ stderrBytes += chunk.length
2094
+ if (stderrBytes > SESSION_START_MAX_INPUT_BYTES) beginCleanup('hook helper stderr exceeded 1 MiB')
2095
+ }
2096
+
2097
+ function onMessage(message) {
2098
+ if (message && message.type === 'hookExit') {
2099
+ helperCode = message.code
2100
+ beginCleanup(message.code === 0 ? undefined : 'hook helper exited without a response')
2101
+ }
2102
+ }
2103
+
2104
+ function onExit() {
2105
+ if (!cleaning) cleanupFailed('supervisor exited before tree cleanup')
2106
+ }
2107
+
2108
+ function onClose() { rootClosed = true; complete() }
2109
+
2110
+ root.on('message', onMessage)
2111
+ root.once('exit', onExit)
2112
+ root.once('close', onClose)
2113
+ root.once('error', () => beginCleanup('hook helper could not run'))
2114
+ root.stdin.on('error', () => beginCleanup('hook helper input failed'))
2115
+ root.stdout.on('data', onOutput)
2116
+ root.stderr.on('data', onStderr)
2117
+ root.stdout.on('error', () => beginCleanup('hook helper stdout failed'))
2118
+ root.stderr.on('error', () => beginCleanup('hook helper stderr failed'))
2119
+ const remaining = deadline - Date.now()
2120
+ // Reserve the final second of the single budget for owned-tree teardown.
2121
+ timer = setTimeout(() => beginCleanup('hook helper timed out'), Math.max(0, remaining - SESSION_START_CLEANUP_RESERVE_MS))
2122
+ hardTimer = setTimeout(() => cleanupFailed('overall deadline exceeded'), Math.max(0, remaining))
2123
+ root.stdin.end(JSON.stringify(payload))
2124
+ })
2125
+ }
2126
+
2127
+ function superviseWindowsSessionStart({ args, env, payload, deadline }) {
2128
+ const { spawn } = require('node:child_process')
2129
+ const path = require('node:path')
2130
+ // Pure path arithmetic on the trusted installed entry, never PATH lookup or
2131
+ // synchronous filesystem probing in the externally visible parent.
2132
+ const binary = path.join(path.dirname(args[0]), 'native', 'win32-' + process.arch, 'tg-hook.exe')
2133
+ return new Promise((resolve, reject) => {
2134
+ // owner: the native tg-hook client, which holds a strict Job Object and ends every descendant when this shim closes its handle; dies when the job closes or the deadline passes.
2135
+ const root = spawn(binary, ['--session-start-owner-v1', String(deadline), process.execPath, ...args], {
2136
+ env, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
2137
+ })
2138
+ const output = []
2139
+ const diagnostics = []
2140
+ let outputBytes = 0
2141
+ let diagnosticBytes = 0
2142
+ let settled = false
2143
+ let timer
2144
+ function finish(error, candidate) {
2145
+ if (settled) return
2146
+ settled = true
2147
+ clearTimeout(timer)
2148
+ root.stdin.destroy()
2149
+ root.stdout.destroy()
2150
+ root.stderr.destroy()
2151
+ // Killing the owner closes the sole non-inherited job handle. Never
2152
+ // traverse a dead canonical PID or launch an unowned Windows fallback.
2153
+ if (error && root.pid) root.kill('SIGKILL')
2154
+ // owner: this shim, which kills the native owner on any error (its job handle closing ends the tree) and finds it closed on success; dies when that kill lands or the owner has exited.
2155
+ root.unref()
2156
+ if (error) reject(new Error(error))
2157
+ else resolve(JSON.stringify(candidate))
2158
+ }
2159
+ root.once('error', error => finish('native startup owner unavailable: ' + error.message))
2160
+ root.stdin.on('error', error => finish('native startup owner input failed: ' + error.message))
2161
+ root.stdout.on('error', error => finish('native startup owner stdout failed: ' + error.message))
2162
+ root.stderr.on('error', error => finish('native startup owner stderr failed: ' + error.message))
2163
+ root.stdout.on('data', chunk => {
2164
+ outputBytes += chunk.length
2165
+ if (outputBytes > SESSION_START_MAX_INPUT_BYTES + 4096) finish('native startup owner output exceeded 1 MiB')
2166
+ else output.push(chunk)
2167
+ })
2168
+ root.stderr.on('data', chunk => {
2169
+ diagnosticBytes += chunk.length
2170
+ if (diagnosticBytes > SESSION_START_MAX_INPUT_BYTES) finish('native startup owner stderr exceeded 1 MiB')
2171
+ else diagnostics.push(chunk)
2172
+ })
2173
+ root.once('close', code => {
2174
+ if (settled) return
2175
+ const diagnostic = Buffer.concat(diagnostics).toString('utf8').trim()
2176
+ if (code !== 0) { finish('native startup owner failed: ' + (diagnostic || 'exit ' + code)); return }
2177
+ let ack
2178
+ try { ack = JSON.parse(Buffer.concat(output).toString('utf8')) } catch {
2179
+ finish('native startup owner returned invalid protocol JSON'); return
2180
+ }
2181
+ if (!ack || ack.protocol !== 'tg-session-start-owner-v1' || ack.capability !== 'atomic-job-list'
2182
+ || ack.activeProcesses !== 0 || ack.rootExited !== true || ack.pipesDrained !== true
2183
+ || !ack.candidate || typeof ack.candidate !== 'object' || Array.isArray(ack.candidate)) {
2184
+ finish('native startup owner incompatible protocol or unverified cleanup'); return
2185
+ }
2186
+ if (ack.workerWake === true) {
2187
+ // The installed native entry owns this shim too. Relay only a bounded
2188
+ // request; it, not an inner owner, must launch outside every startup job.
2189
+ if (process.argv[4] && process.argv[4].startsWith('--session-start-deadline=')) {
2190
+ process.stderr.write('\\x1eTG_WORKER_WAKE_V1\\x1e\\n')
2191
+ } else if (process.env.TOKEN_GOAT_NO_WORKER_SPAWN !== '1') {
2192
+ const workerEnv = Object.assign({}, process.env)
2193
+ delete workerEnv.TG_SESSION_START_DEFER_WORKER
2194
+ try {
2195
+ const worker = spawn(process.execPath, [args[0], '--worker-daemon', '--claim-worker'], {
2196
+ env: workerEnv, cwd: require('node:os').tmpdir(), windowsHide: true, stdio: 'ignore',
2197
+ // owner: none, the daemon outlives this shim on purpose; dies when its pid file or data directory is gone, when it sits idle, or on worker stop.
2198
+ detached: true,
2199
+ })
2200
+ worker.on('error', () => {})
2201
+ // owner: none, the daemon outlives this shim on purpose; dies when its pid file or data directory is gone, when it sits idle, or on worker stop.
2202
+ worker.unref()
2203
+ } catch {
2204
+ // Queue remains durable; ordinary lifecycle activation can retry.
2205
+ }
2206
+ }
2207
+ }
2208
+ finish(undefined, ack.candidate)
2209
+ })
2210
+ timer = setTimeout(() => finish('native startup owner overall deadline exceeded'), Math.max(0, deadline - Date.now()))
2211
+ root.stdin.end(JSON.stringify(payload))
2212
+ })
2213
+ }
2214
+ `;
2215
+
1713
2216
  // src/bridges/shrink_block.ts
1714
2217
  init_define_import_meta_env();
1715
2218
  var MATERIALIZE_SHRUNK_IMAGE_JS = `// Best-effort sweep of previously materialized shrunk copies in the OS temp dir. The temp file only needs to outlive the single tool call whose path argument was rewritten to it, so anything older than an hour is finished with; the "token-goat-shrink-" prefix check confines the sweep to this mechanism's own files (same defense-in-depth rule as pruneShrinkCache in src/image_shrink.ts). Throttled per process; runs only when a shrink payload actually arrives, so the common no-image path never pays for it.
@@ -1761,12 +2264,17 @@ var COPILOT_CLI_HOOK_SCRIPT = `#!/usr/bin/env node
1761
2264
  // token-goat Copilot CLI hook shim. Translates Copilot's hook event names and
1762
2265
  // request/response schema to/from token-goat's internal hook protocol.
1763
2266
  'use strict'
2267
+ ${SHIM_MAX_BUFFER_CONST}
1764
2268
  const { spawnSync } = require('node:child_process')
1765
2269
  const fs = require('node:fs')
1766
2270
  const os = require('node:os')
1767
2271
  const path = require('node:path')
1768
2272
  const { pathToFileURL } = require('node:url')
1769
2273
 
2274
+ // How long the fallback spawn of the real hook may run before the shim answers an empty fail-open '{}' for it. The hook entries token-goat installs give Copilot 60 s (HOOK_TIMEOUT_SEC in copilot_cli_install.ts); the number is SHIM_FALLBACK_TIMEOUT_MS.copilot in shim_common.ts, and a hook Copilot kills fails open the same way, so a cap of a few seconds only discarded the verdict of a cold start that was merely slow (bundle load, DB open, a loaded machine). Kept under that 60 s so the shim, not Copilot, ends a hook that really is stuck.
2275
+ ${shimFallbackTimeoutConst("copilot")}
2276
+ ${COPILOT_SESSION_START_HELPERS}
2277
+
1770
2278
  // Copilot event name -> token-goat internal HookEventName (src/types.ts's
1771
2279
  // HOOK_EVENTS). Only these nine have a token-goat handler; every other real
1772
2280
  // Copilot event (sessionEnd, errorOccurred, notification, permissionRequest)
@@ -1815,6 +2323,7 @@ const COPILOT_TO_TG_EVENT = {
1815
2323
  // one of these keys is answered with nothing, so a Copilot build that also
1816
2324
  // honored the PascalCase spelling could not brief a subagent twice.
1817
2325
  const VSCODE_ONLY_TO_TG_EVENT = {
2326
+ SessionStart: 'session_start',
1818
2327
  SubagentStart: 'subagent_start',
1819
2328
  }
1820
2329
 
@@ -2074,9 +2583,9 @@ async function relayVscode(entryPath, tgEvent, payload) {
2074
2583
  input: JSON.stringify(input),
2075
2584
  encoding: 'utf8',
2076
2585
  windowsHide: true,
2077
- timeout: 3000,
2586
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
2078
2587
  killSignal: 'SIGKILL',
2079
- maxBuffer: 32 * 1024 * 1024,
2588
+ maxBuffer: SHIM_MAX_BUFFER_BYTES,
2080
2589
  env: Object.assign({}, process.env, { TOKEN_GOAT_HARNESS_OVERRIDE: 'vscode', TOKEN_GOAT_VSCODE_HOOKS_DIR: __dirname }),
2081
2590
  }
2082
2591
  const res = entryPath
@@ -2094,6 +2603,7 @@ async function relayVscode(entryPath, tgEvent, payload) {
2094
2603
  }
2095
2604
 
2096
2605
  async function main() {
2606
+ const startedAt = Math.floor(require('node:perf_hooks').performance.timeOrigin)
2097
2607
  const copilotEvent = process.argv[2] || ''
2098
2608
 
2099
2609
  // ownGet, not a bare lookup: the map is a plain object literal, so eight keys it never declares ('constructor', 'toString', '__proto__' and the rest of Object.prototype) come back truthy and sail past the check below. tgEvent is then a Function, and 'token-goat hook ' + tgEvent is concatenated into a shell command string by the fallback at the bottom of this shim. No member of Object.prototype stringifies with a shell metacharacter, so this was a failed spawn rather than a second command -- but argv reaching a shell string through a gate that reads like it stops it is the shape worth closing, not the payload that happened to be harmless. The four shims built on shim_common.ts validate against a closed Set, which has no equivalent hole.
@@ -2104,24 +2614,56 @@ async function main() {
2104
2614
  return
2105
2615
  }
2106
2616
 
2617
+ let sessionStartDeadline
2618
+ if (tgEvent === 'session_start') {
2619
+ const inherited = process.argv[4]
2620
+ const epoch = inherited === undefined ? startedAt + sessionStartBudgetMs()
2621
+ : inherited.startsWith('--session-start-deadline=') ? Number(inherited.slice('--session-start-deadline='.length)) : NaN
2622
+ if (!Number.isSafeInteger(epoch) || epoch <= 0 || epoch > startedAt + sessionStartBudgetMs()) {
2623
+ failOpenSessionStart('invalid inherited startup deadline')
2624
+ return
2625
+ }
2626
+ sessionStartDeadline = epoch
2627
+ }
2107
2628
  let raw = ''
2108
- try {
2109
- raw = require('node:fs').readFileSync(0, 'utf8')
2110
- } catch {
2111
- process.stdout.write('{}')
2112
- return
2629
+ if (sessionStartDeadline !== undefined) {
2630
+ try {
2631
+ raw = await readSessionStartInput(sessionStartDeadline - Date.now())
2632
+ } catch (error) {
2633
+ failOpenSessionStart(error && error.message ? error.message : 'input could not be read')
2634
+ return
2635
+ }
2636
+ } else {
2637
+ try {
2638
+ raw = require('node:fs').readFileSync(0, 'utf8')
2639
+ } catch {
2640
+ process.stdout.write('{}')
2641
+ return
2642
+ }
2113
2643
  }
2114
2644
 
2115
2645
  let payload
2116
2646
  try {
2117
2647
  payload = JSON.parse(raw)
2118
2648
  } catch {
2119
- process.stdout.write('{}')
2649
+ if (sessionStartDeadline !== undefined) failOpenSessionStart('input contained malformed JSON')
2650
+ else process.stdout.write('{}')
2120
2651
  return
2121
2652
  }
2122
2653
 
2123
2654
  if (isVscodePayload(payload)) {
2124
- process.stdout.write(await relayVscode(process.argv[3], tgEvent, payload))
2655
+ if (sessionStartDeadline === undefined) {
2656
+ process.stdout.write(await relayVscode(process.argv[3], tgEvent, payload))
2657
+ } else {
2658
+ try {
2659
+ const input = typeof payload.session_id === 'string' && payload.session_id !== ''
2660
+ ? payload
2661
+ : Object.assign({}, payload, { session_id: stableFallbackSessionId(process.cwd()) })
2662
+ process.stdout.write(await runBoundedSessionStart(process.argv[3], tgEvent, input, 'vscode', sessionStartDeadline))
2663
+ } catch (error) {
2664
+ failOpenSessionStart(error && error.message ? error.message : 'hook helper failed')
2665
+ }
2666
+ }
2125
2667
  return
2126
2668
  }
2127
2669
  if (vscodeOnly) {
@@ -2214,23 +2756,28 @@ async function main() {
2214
2756
  // template string) is safe here specifically because entryPath and tgEvent never touch a
2215
2757
  // shell -- no DEP0190 concern, unlike the shell:true fallback below.
2216
2758
  const entryPath = process.argv[3]
2217
- // Try the in-process hook lib first (tryInProcess above), which avoids spawning a
2218
- // second node process altogether. If that's unavailable, invoking the entry directly
2219
- // via process.execPath sidesteps PATH/cmd.exe resolution for this inner call, per the
2220
- // note above this function's original single-spawn form documented. A 3000ms
2221
- // timeout/killSignal on both spawnSync fallbacks keeps them well under Copilot CLI's
2222
- // own hook timeout budget (~30000ms), so token-goat degrades to its own fail-open
2223
- // '{}' rather than being force-killed by Copilot first.
2224
- let stdout = await tryInProcess(entryPath, tgEvent, canonical, 'copilot_cli')
2759
+ // SessionStart bypasses the resident server and in-process path: only a child-process deadline can interrupt synchronous handler or SQLite work. TOKEN_GOAT_HOOK_SERVER=0 below also prevents that child from leaving a server process behind.
2760
+ let stdout
2761
+ if (sessionStartDeadline !== undefined) {
2762
+ try {
2763
+ stdout = await runBoundedSessionStart(entryPath, tgEvent, canonical, 'copilot_cli', sessionStartDeadline)
2764
+ } catch (error) {
2765
+ failOpenSessionStart(error && error.message ? error.message : 'hook helper failed')
2766
+ return
2767
+ }
2768
+ } else {
2769
+ // Other events retain the in-process fast path and existing fallback behavior.
2770
+ stdout = await tryInProcess(entryPath, tgEvent, canonical, 'copilot_cli')
2771
+ }
2225
2772
  if (stdout === undefined) {
2226
2773
  const res = entryPath
2227
2774
  ? spawnSync(process.execPath, [entryPath, 'hook', tgEvent], {
2228
2775
  input: JSON.stringify(canonical),
2229
2776
  encoding: 'utf8',
2230
2777
  windowsHide: true,
2231
- timeout: 3000,
2778
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
2232
2779
  killSignal: 'SIGKILL',
2233
- maxBuffer: 32 * 1024 * 1024,
2780
+ maxBuffer: SHIM_MAX_BUFFER_BYTES,
2234
2781
  env: Object.assign({}, process.env, { TOKEN_GOAT_HARNESS_OVERRIDE: 'copilot_cli', TOKEN_GOAT_COPILOT_HOOKS_DIR: __dirname }),
2235
2782
  })
2236
2783
  : spawnSync('token-goat hook ' + tgEvent, {
@@ -2238,9 +2785,9 @@ async function main() {
2238
2785
  encoding: 'utf8',
2239
2786
  shell: true,
2240
2787
  windowsHide: true,
2241
- timeout: 3000,
2788
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
2242
2789
  killSignal: 'SIGKILL',
2243
- maxBuffer: 32 * 1024 * 1024,
2790
+ maxBuffer: SHIM_MAX_BUFFER_BYTES,
2244
2791
  env: Object.assign({}, process.env, { TOKEN_GOAT_HARNESS_OVERRIDE: 'copilot_cli', TOKEN_GOAT_COPILOT_HOOKS_DIR: __dirname }),
2245
2792
  })
2246
2793
  if (res.status !== 0 || !res.stdout) {
@@ -2586,7 +3133,7 @@ function installCopilotMcpServer() {
2586
3133
  const config = readServersJson(mcpPath, LABEL);
2587
3134
  const current = serversOf(config, mcpPath, LABEL, MCP_SERVERS_KEY)[TOKEN_GOAT_ENTRY_KEY];
2588
3135
  if (current !== void 0 && !isCopilotManagedServer(current) && !isReadmeServer(current)) {
2589
- throw new Error(`Copilot CLI mcp-config.json already has a "${TOKEN_GOAT_ENTRY_KEY}" MCP server entry token-goat did not write, at ${mcpPath}; remove it manually first`);
3136
+ throw new Error(`Copilot CLI mcp-config.json already has a ${echoedValue(TOKEN_GOAT_ENTRY_KEY)} MCP server entry token-goat did not write, at ${mcpPath}; remove it manually first`);
2590
3137
  }
2591
3138
  const nextText = setTokenGoatServer(config.text, copilotManagedServer(), MCP_SERVERS_KEY);
2592
3139
  if (nextText === config.text) return false;
@@ -2656,8 +3203,8 @@ function visualStudioDuplicateNote(projectRoot = process.cwd()) {
2656
3203
  const found = visualStudioManagedRegistrations(projectRoot);
2657
3204
  if (found.length < 2) return null;
2658
3205
  const lead = `Note: Visual Studio reads ${found.join(" and ")}, and each registers token-goat, so it will list the token-goat server more than once for this solution.`;
2659
- if (!found.includes(visualStudioSolutionVscodeMcpPath(projectRoot))) return `${lead} Keep one: run "token-goat uninstall --visualstudio" or "token-goat uninstall --visualstudio -p".`;
2660
- return `${lead} Keep one: if you do not use VS Code in this folder, run "token-goat uninstall --vscode -p"; otherwise run "token-goat uninstall --visualstudio${found.includes(visualStudioUserMcpPath()) ? "" : " -p"}".`;
3206
+ if (!found.includes(visualStudioSolutionVscodeMcpPath(projectRoot))) return `${lead} Keep one: run ${fencedCommand("token-goat uninstall --visualstudio")} or ${fencedCommand("token-goat uninstall --visualstudio -p")}.`;
3207
+ return `${lead} Keep one: if you do not use VS Code in this folder, run ${fencedCommand("token-goat uninstall --vscode -p")}; otherwise run ${fencedCommand("token-goat uninstall --visualstudio" + (found.includes(visualStudioUserMcpPath()) ? "" : " -p"))}.`;
2661
3208
  }
2662
3209
  function visualStudioMcpStatus(opts = {}) {
2663
3210
  const checkedPaths = [visualStudioUserMcpPath()];
@@ -2708,7 +3255,7 @@ function installVisualStudioScoped(opts) {
2708
3255
  if (visualStudioOtherScopeHasManagedServer(opts)) {
2709
3256
  const otherScope = scope === "project" ? "user" : "project";
2710
3257
  throw new Error(
2711
- `token-goat is already registered in Visual Studio ${otherScope} scope (${otherScopeMcpPath(opts)}). Visual Studio reads both files, so installing into ${scope} scope too would register it twice. Run "token-goat uninstall --visualstudio${otherScope === "project" ? " --project" : ""}" first if you want to move it, or drop --visualstudio from this run.`
3258
+ `token-goat is already registered in Visual Studio ${otherScope} scope (${otherScopeMcpPath(opts)}). Visual Studio reads both files, so installing into ${scope} scope too would register it twice. Run ${fencedCommand("token-goat uninstall --visualstudio" + (otherScope === "project" ? " --project" : ""))} first if you want to move it, or drop --visualstudio from this run.`
2712
3259
  );
2713
3260
  }
2714
3261
  const mcpExisted = fs6.existsSync(mcpPath);
@@ -2926,6 +3473,20 @@ function releaseCopilotHooksFile(hooksDir, owner, keepBackups = false) {
2926
3473
  function installCopilotCli(opts = {}) {
2927
3474
  return withInstallScope(projectScopeRoot(opts), () => installCopilotCliScoped(opts));
2928
3475
  }
3476
+ function syncCopilotHooksAndInstructions(opts) {
3477
+ const hooks = installCopilotHooksFile(copilotCliHooksDir(opts), "copilot");
3478
+ const instructionsPath = copilotCliInstructionsPath(opts);
3479
+ const instructionsExisted = fs7.existsSync(instructionsPath);
3480
+ const instructionsChanged = writeCopilotInstructionsBlock(instructionsPath);
3481
+ if (!instructionsExisted) recordCreatedConfig(instructionsPath);
3482
+ return { hooks, instructionsChanged };
3483
+ }
3484
+ function repairCopilotCliHooks(opts = {}) {
3485
+ return withInstallScope(projectScopeRoot(opts), () => {
3486
+ const { hooks, instructionsChanged } = syncCopilotHooksAndInstructions(opts);
3487
+ return hooks.changed || instructionsChanged;
3488
+ });
3489
+ }
2929
3490
  function installCopilotCliScoped(opts) {
2930
3491
  const instructionsPath = copilotCliInstructionsPath(opts);
2931
3492
  const userScope = opts.local !== true;
@@ -2933,10 +3494,8 @@ function installCopilotCliScoped(opts) {
2933
3494
  const scopeDirExisted = fs7.existsSync(scopeDir);
2934
3495
  const mcpChanged = userScope ? installCopilotMcpServer() : false;
2935
3496
  if (!scopeDirExisted && fs7.existsSync(scopeDir)) recordCreatedConfig(scopeDir);
2936
- const hooks = installCopilotHooksFile(copilotCliHooksDir(opts), "copilot");
2937
- const instructionsExisted = fs7.existsSync(instructionsPath);
2938
- let instructionsChanged = writeCopilotInstructionsBlock(instructionsPath);
2939
- if (!instructionsExisted) recordCreatedConfig(instructionsPath);
3497
+ const { hooks, instructionsChanged: scopeInstructionsChanged } = syncCopilotHooksAndInstructions(opts);
3498
+ let instructionsChanged = scopeInstructionsChanged;
2940
3499
  if (userScope) {
2941
3500
  const projectRoot = path7.resolve(opts.projectRoot ?? process.cwd());
2942
3501
  const projectInstructions = copilotCliInstructionsPath({ local: true, projectRoot });
@@ -3001,6 +3560,7 @@ export {
3001
3560
  SHIM_VALID_HOOK_EVENTS,
3002
3561
  SHIM_TRY_IN_PROCESS,
3003
3562
  SHIM_SPAWN_LADDER,
3563
+ shimFallbackTimeoutConst,
3004
3564
  SHIM_MAX_BUFFER_CONST,
3005
3565
  CLAUDECODE_HOOK_SCRIPT,
3006
3566
  buildGuidanceBody,
@@ -3024,6 +3584,7 @@ export {
3024
3584
  uninstallHooks,
3025
3585
  finishHookUninstall,
3026
3586
  isInstalled,
3587
+ claudeHooksInstalledAnyScope,
3027
3588
  hookEventGaps,
3028
3589
  claudeMdPath,
3029
3590
  installedClaudeMdBlock,
@@ -3070,11 +3631,9 @@ export {
3070
3631
  installVisualStudio,
3071
3632
  uninstallVisualStudio,
3072
3633
  canonicalProjectRoot,
3073
- copilotCliHooksDir,
3074
3634
  copilotCliConfigPath,
3075
3635
  copilotCliScriptPath,
3076
3636
  copilotCliInstructionsPath,
3077
- writeCopilotInstructionsBlock,
3078
3637
  wiredCopilotHookWords,
3079
3638
  HOOKS_SCRIPT_FILE,
3080
3639
  LEGACY_HOOKS_SCRIPT_FILE,
@@ -3082,6 +3641,7 @@ export {
3082
3641
  installCopilotHooksFile,
3083
3642
  releaseCopilotHooksFile,
3084
3643
  installCopilotCli,
3644
+ repairCopilotCliHooks,
3085
3645
  uninstallCopilotCli,
3086
3646
  isCopilotCliInstalled
3087
3647
  };