@amg555/frameforge-mcp 2.7.6 → 2.7.8

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 (1141) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/package.json +1 -1
  4. package/plugins/video-pipeline/.claude-plugin/plugin.json +1 -1
  5. package/plugins/video-pipeline/skills/.upstream-skills.json +1 -1
  6. package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +1 -1
  7. package/scripts/deep-validate.js +2 -2
  8. package/src/mcp-server/server.js +5 -5
  9. package/src/skills/sync-upstream.js +3 -3
  10. package/.agent/skills/ai-video-editor/README.md +0 -32
  11. package/.agent/skills/ai-video-editor/SKILL.md +0 -152
  12. package/.agent/skills/ai-video-editor/assets/fonts/fonts.css +0 -2409
  13. package/.agent/skills/ai-video-editor/assets/fonts/ttf/anton-400.ttf +0 -0
  14. package/.agent/skills/ai-video-editor/assets/fonts/ttf/archivo-800.ttf +0 -0
  15. package/.agent/skills/ai-video-editor/assets/fonts/ttf/cabin-sketch-700.ttf +0 -0
  16. package/.agent/skills/ai-video-editor/assets/fonts/ttf/dm-sans-700.ttf +0 -0
  17. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-600.ttf +0 -0
  18. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-700.ttf +0 -0
  19. package/.agent/skills/ai-video-editor/assets/fonts/ttf/fredoka-700.ttf +0 -0
  20. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-700.ttf +0 -0
  21. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-800.ttf +0 -0
  22. package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-tight-800.ttf +0 -0
  23. package/.agent/skills/ai-video-editor/assets/fonts/ttf/kalam-700.ttf +0 -0
  24. package/.agent/skills/ai-video-editor/assets/fonts/ttf/noto-sans-devanagari-700.ttf +0 -0
  25. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-400-normal.woff2 +0 -0
  26. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-500-normal.woff2 +0 -0
  27. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-600-normal.woff2 +0 -0
  28. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-700-normal.woff2 +0 -0
  29. package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-800-normal.woff2 +0 -0
  30. package/.agent/skills/ai-video-editor/engine/hidpi.js +0 -117
  31. package/.agent/skills/ai-video-editor/engine/page.html +0 -43
  32. package/.agent/skills/ai-video-editor/engine/player.js +0 -145
  33. package/.agent/skills/ai-video-editor/references/example-story-warm-canvas.js +0 -95
  34. package/.agent/skills/ai-video-editor/references/story-guide.md +0 -112
  35. package/.agent/skills/ai-video-editor/requirements.txt +0 -4
  36. package/.agent/skills/ai-video-editor/styles/chalk-talk/base.js +0 -490
  37. package/.agent/skills/ai-video-editor/styles/chalk-talk/style.json +0 -14
  38. package/.agent/skills/ai-video-editor/styles/chalk-talk/style.md +0 -41
  39. package/.agent/skills/ai-video-editor/styles/clean-tech/base.js +0 -447
  40. package/.agent/skills/ai-video-editor/styles/clean-tech/style.json +0 -15
  41. package/.agent/skills/ai-video-editor/styles/clean-tech/style.md +0 -38
  42. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/base.js +0 -698
  43. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.json +0 -16
  44. package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.md +0 -39
  45. package/.agent/skills/ai-video-editor/styles/data-story/base.js +0 -432
  46. package/.agent/skills/ai-video-editor/styles/data-story/style.json +0 -14
  47. package/.agent/skills/ai-video-editor/styles/data-story/style.md +0 -38
  48. package/.agent/skills/ai-video-editor/styles/index.json +0 -103
  49. package/.agent/skills/ai-video-editor/styles/pop-info/base.js +0 -570
  50. package/.agent/skills/ai-video-editor/styles/pop-info/style.json +0 -13
  51. package/.agent/skills/ai-video-editor/styles/pop-info/style.md +0 -37
  52. package/.agent/skills/ai-video-editor/styles/software-walkthrough/base.js +0 -544
  53. package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.json +0 -15
  54. package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.md +0 -38
  55. package/.agent/skills/ai-video-editor/styles/warm-canvas/base.js +0 -610
  56. package/.agent/skills/ai-video-editor/styles/warm-canvas/style.json +0 -14
  57. package/.agent/skills/ai-video-editor/styles/warm-canvas/style.md +0 -37
  58. package/.agent/skills/faceless-shorts/README.md +0 -34
  59. package/.agent/skills/faceless-shorts/SKILL.md +0 -124
  60. package/.agent/skills/faceless-shorts/styles/deep-dive/STYLE.md +0 -58
  61. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/cues.json +0 -140
  62. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/lines.json +0 -67
  63. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.json +0 -614
  64. package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.txt +0 -102
  65. package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
  66. package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
  67. package/.agent/skills/faceless-shorts/styles/deep-dive/example/index.html +0 -16
  68. package/.agent/skills/faceless-shorts/styles/deep-dive/example/script.json +0 -27
  69. package/.agent/skills/faceless-shorts/styles/deep-dive/example/sheet.html +0 -10
  70. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/board.js +0 -62
  71. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/captions.js +0 -68
  72. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/engine.js +0 -65
  73. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/human.js +0 -128
  74. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/lib.js +0 -44
  75. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/look2d.js +0 -33
  76. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sdf.js +0 -79
  77. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sets.js +0 -107
  78. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/timing.js +0 -23
  79. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/main.js +0 -56
  80. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/sheet.js +0 -79
  81. package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/shots.js +0 -136
  82. package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/capture.mjs +0 -122
  83. package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/mix.py +0 -172
  84. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +0 -21
  85. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  86. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  87. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  88. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  89. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  90. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +0 -95
  91. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  92. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  93. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  94. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +0 -145
  95. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +0 -45
  96. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  97. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +0 -85
  98. package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/three.module.js +0 -6
  99. package/.agent/skills/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
  100. package/.agent/skills/faceless-shorts/styles/inside-out/STYLE.md +0 -97
  101. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/cues.json +0 -178
  102. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/lines.json +0 -73
  103. package/.agent/skills/faceless-shorts/styles/inside-out/example/data/words.json +0 -662
  104. package/.agent/skills/faceless-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
  105. package/.agent/skills/faceless-shorts/styles/inside-out/example/index.html +0 -16
  106. package/.agent/skills/faceless-shorts/styles/inside-out/example/script.json +0 -26
  107. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/captions.js +0 -68
  108. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/kit.js +0 -345
  109. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/lib.js +0 -44
  110. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/timing.js +0 -23
  111. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/main.js +0 -62
  112. package/.agent/skills/faceless-shorts/styles/inside-out/example/src/shots.js +0 -195
  113. package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/capture.mjs +0 -122
  114. package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/mix.py +0 -172
  115. package/.agent/skills/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
  116. package/.agent/skills/faceless-shorts/styles/night-file/STYLE.md +0 -36
  117. package/.agent/skills/faceless-shorts/styles/night-file/example/data/cues.json +0 -146
  118. package/.agent/skills/faceless-shorts/styles/night-file/example/data/lines.json +0 -73
  119. package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.json +0 -572
  120. package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.txt +0 -95
  121. package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
  122. package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
  123. package/.agent/skills/faceless-shorts/styles/night-file/example/index.html +0 -16
  124. package/.agent/skills/faceless-shorts/styles/night-file/example/script.json +0 -26
  125. package/.agent/skills/faceless-shorts/styles/night-file/example/sheet.html +0 -11
  126. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/board.js +0 -62
  127. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/captions.js +0 -49
  128. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/engine.js +0 -65
  129. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/human.js +0 -128
  130. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/kit.js +0 -151
  131. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/lib.js +0 -44
  132. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sdf.js +0 -79
  133. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sets3d.js +0 -106
  134. package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/timing.js +0 -23
  135. package/.agent/skills/faceless-shorts/styles/night-file/example/src/main.js +0 -56
  136. package/.agent/skills/faceless-shorts/styles/night-file/example/src/sheet.js +0 -86
  137. package/.agent/skills/faceless-shorts/styles/night-file/example/src/shots.js +0 -130
  138. package/.agent/skills/faceless-shorts/styles/night-file/example/tools/capture.mjs +0 -122
  139. package/.agent/skills/faceless-shorts/styles/night-file/example/tools/mix.py +0 -172
  140. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/LICENSE-three.txt +0 -21
  141. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  142. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  143. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  144. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  145. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  146. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +0 -95
  147. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  148. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  149. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  150. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +0 -145
  151. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +0 -45
  152. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  153. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +0 -85
  154. package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/three.module.js +0 -6
  155. package/.agent/skills/faceless-shorts/styles/night-file/style-sheet.png +0 -0
  156. package/.agent/skills/faceless-shorts/styles/paper-atlas/STYLE.md +0 -30
  157. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/cues.json +0 -167
  158. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/lines.json +0 -67
  159. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.json +0 -614
  160. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.txt +0 -102
  161. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
  162. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
  163. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/index.html +0 -13
  164. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/script.json +0 -26
  165. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/sheet.html +0 -10
  166. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/board.js +0 -62
  167. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/captions.js +0 -47
  168. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/kit.js +0 -152
  169. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/lib.js +0 -44
  170. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/timing.js +0 -23
  171. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/main.js +0 -62
  172. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/sheet.js +0 -61
  173. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/shots.js +0 -139
  174. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/capture.mjs +0 -122
  175. package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/mix.py +0 -172
  176. package/.agent/skills/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
  177. package/.agent/skills/faceless-shorts/styles/stick-story/STYLE.md +0 -32
  178. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/cues.json +0 -182
  179. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/lines.json +0 -79
  180. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.json +0 -662
  181. package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.txt +0 -110
  182. package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
  183. package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
  184. package/.agent/skills/faceless-shorts/styles/stick-story/example/index.html +0 -12
  185. package/.agent/skills/faceless-shorts/styles/stick-story/example/script.json +0 -27
  186. package/.agent/skills/faceless-shorts/styles/stick-story/example/sheet.html +0 -9
  187. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/board.js +0 -62
  188. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/captions.js +0 -47
  189. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/kit.js +0 -166
  190. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/lib.js +0 -44
  191. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/timing.js +0 -23
  192. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/main.js +0 -62
  193. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/sheet.js +0 -64
  194. package/.agent/skills/faceless-shorts/styles/stick-story/example/src/shots.js +0 -148
  195. package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/capture.mjs +0 -122
  196. package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/mix.py +0 -172
  197. package/.agent/skills/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
  198. package/.agent/skills/faceless-shorts/styles/tall-tale/STYLE.md +0 -51
  199. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/cues.json +0 -271
  200. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/lines.json +0 -127
  201. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.json +0 -908
  202. package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.txt +0 -151
  203. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
  204. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
  205. package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
  206. package/.agent/skills/faceless-shorts/styles/tall-tale/example/index.html +0 -17
  207. package/.agent/skills/faceless-shorts/styles/tall-tale/example/script.json +0 -37
  208. package/.agent/skills/faceless-shorts/styles/tall-tale/example/sheet.html +0 -11
  209. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/board.js +0 -62
  210. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/captions.js +0 -56
  211. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/engine.js +0 -65
  212. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/lib.js +0 -44
  213. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/props.js +0 -166
  214. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/timing.js +0 -23
  215. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/toon.js +0 -352
  216. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/world.js +0 -340
  217. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/main.js +0 -56
  218. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/sheet.js +0 -99
  219. package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/shots.js +0 -377
  220. package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/capture.mjs +0 -122
  221. package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/mix.py +0 -204
  222. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +0 -21
  223. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +0 -144
  224. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +0 -141
  225. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
  226. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +0 -104
  227. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +0 -97
  228. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +0 -95
  229. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +0 -99
  230. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
  231. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
  232. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +0 -145
  233. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +0 -45
  234. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
  235. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +0 -85
  236. package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/three.module.js +0 -6
  237. package/.agent/skills/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
  238. package/.agent/skills/gstack-autoplan/SKILL.md +0 -972
  239. package/.agent/skills/gstack-autoplan/SKILL.md.tmpl +0 -468
  240. package/.agent/skills/gstack-autoplan/bin/guard-journal.ts +0 -115
  241. package/.agent/skills/gstack-autoplan/bin/guard-log.ts +0 -40
  242. package/.agent/skills/gstack-autoplan/bin/guard-reasons.ts +0 -92
  243. package/.agent/skills/gstack-autoplan/bin/owned-read.ts +0 -72
  244. package/.agent/skills/gstack-autoplan/bin/phase-publication-hook +0 -15
  245. package/.agent/skills/gstack-autoplan/bin/phase-publication-hook.ts +0 -769
  246. package/.agent/skills/gstack-autoplan/sections/ceo-phase.md +0 -195
  247. package/.agent/skills/gstack-autoplan/sections/ceo-phase.md.tmpl +0 -147
  248. package/.agent/skills/gstack-autoplan/sections/design-phase.md +0 -128
  249. package/.agent/skills/gstack-autoplan/sections/design-phase.md.tmpl +0 -82
  250. package/.agent/skills/gstack-autoplan/sections/dx-phase.md +0 -155
  251. package/.agent/skills/gstack-autoplan/sections/dx-phase.md.tmpl +0 -109
  252. package/.agent/skills/gstack-autoplan/sections/eng-phase.md +0 -164
  253. package/.agent/skills/gstack-autoplan/sections/eng-phase.md.tmpl +0 -116
  254. package/.agent/skills/gstack-autoplan/sections/manifest.json +0 -50
  255. package/.agent/skills/gstack-autoplan/sections/phase-close.md +0 -70
  256. package/.agent/skills/gstack-autoplan/sections/phase-close.md.tmpl +0 -68
  257. package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md +0 -71
  258. package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md.tmpl +0 -1
  259. package/.agent/skills/gstack-autoplan/sections/unattended.md +0 -11
  260. package/.agent/skills/gstack-autoplan/sections/unattended.md.tmpl +0 -9
  261. package/.agent/skills/gstack-benchmark/SKILL.md +0 -456
  262. package/.agent/skills/gstack-benchmark/SKILL.md.tmpl +0 -249
  263. package/.agent/skills/gstack-benchmark-models/SKILL.md +0 -285
  264. package/.agent/skills/gstack-benchmark-models/SKILL.md.tmpl +0 -155
  265. package/.agent/skills/gstack-browse/PLAN-snapshot-dropdown-interactive.md +0 -102
  266. package/.agent/skills/gstack-browse/SKILL.md +0 -470
  267. package/.agent/skills/gstack-browse/SKILL.md.tmpl +0 -129
  268. package/.agent/skills/gstack-browse/bin/find-browse +0 -21
  269. package/.agent/skills/gstack-browse/bin/remote-slug +0 -18
  270. package/.agent/skills/gstack-browse/sections/command-list.md +0 -176
  271. package/.agent/skills/gstack-browse/sections/command-list.md.tmpl +0 -7
  272. package/.agent/skills/gstack-browse/sections/manifest.json +0 -14
  273. package/.agent/skills/gstack-browse/src/activity.ts +0 -209
  274. package/.agent/skills/gstack-browse/src/audit.ts +0 -69
  275. package/.agent/skills/gstack-browse/src/browse-client.ts +0 -264
  276. package/.agent/skills/gstack-browse/src/browser-manager.ts +0 -2334
  277. package/.agent/skills/gstack-browse/src/browser-skill-commands.ts +0 -466
  278. package/.agent/skills/gstack-browse/src/browser-skill-write.ts +0 -216
  279. package/.agent/skills/gstack-browse/src/browser-skills.ts +0 -420
  280. package/.agent/skills/gstack-browse/src/buffers.ts +0 -137
  281. package/.agent/skills/gstack-browse/src/bun-child-config.ts +0 -35
  282. package/.agent/skills/gstack-browse/src/bun-polyfill.cjs +0 -276
  283. package/.agent/skills/gstack-browse/src/cdp-allowlist.ts +0 -235
  284. package/.agent/skills/gstack-browse/src/cdp-bridge.ts +0 -180
  285. package/.agent/skills/gstack-browse/src/cdp-commands.ts +0 -64
  286. package/.agent/skills/gstack-browse/src/cdp-inspector.ts +0 -824
  287. package/.agent/skills/gstack-browse/src/chromium-profiles.ts +0 -212
  288. package/.agent/skills/gstack-browse/src/claude-bin.ts +0 -2
  289. package/.agent/skills/gstack-browse/src/cli.ts +0 -2254
  290. package/.agent/skills/gstack-browse/src/commands.ts +0 -296
  291. package/.agent/skills/gstack-browse/src/config.ts +0 -307
  292. package/.agent/skills/gstack-browse/src/content-security.ts +0 -373
  293. package/.agent/skills/gstack-browse/src/cookie-auth-verification.ts +0 -225
  294. package/.agent/skills/gstack-browse/src/cookie-database.ts +0 -73
  295. package/.agent/skills/gstack-browse/src/cookie-import-browser.ts +0 -929
  296. package/.agent/skills/gstack-browse/src/cookie-import-native-integrity.ts +0 -95
  297. package/.agent/skills/gstack-browse/src/cookie-import-native-job.ts +0 -161
  298. package/.agent/skills/gstack-browse/src/cookie-import-native-qualification.json +0 -1
  299. package/.agent/skills/gstack-browse/src/cookie-import-native-worker.ts +0 -268
  300. package/.agent/skills/gstack-browse/src/cookie-import-native.ts +0 -166
  301. package/.agent/skills/gstack-browse/src/cookie-import-operation.ts +0 -232
  302. package/.agent/skills/gstack-browse/src/cookie-picker-routes.ts +0 -343
  303. package/.agent/skills/gstack-browse/src/cookie-picker-ui.ts +0 -717
  304. package/.agent/skills/gstack-browse/src/domain-skill-commands.ts +0 -301
  305. package/.agent/skills/gstack-browse/src/domain-skills.ts +0 -438
  306. package/.agent/skills/gstack-browse/src/error-handling.ts +0 -2
  307. package/.agent/skills/gstack-browse/src/extension-id.ts +0 -25
  308. package/.agent/skills/gstack-browse/src/file-permissions.ts +0 -370
  309. package/.agent/skills/gstack-browse/src/find-browse.ts +0 -118
  310. package/.agent/skills/gstack-browse/src/find-security-sidecar.ts +0 -78
  311. package/.agent/skills/gstack-browse/src/media-extract.ts +0 -177
  312. package/.agent/skills/gstack-browse/src/memory-command.ts +0 -115
  313. package/.agent/skills/gstack-browse/src/memory-snapshot.ts +0 -73
  314. package/.agent/skills/gstack-browse/src/meta-commands.ts +0 -1150
  315. package/.agent/skills/gstack-browse/src/network-capture.ts +0 -179
  316. package/.agent/skills/gstack-browse/src/path-security.ts +0 -132
  317. package/.agent/skills/gstack-browse/src/platform.ts +0 -38
  318. package/.agent/skills/gstack-browse/src/port-allocator.ts +0 -140
  319. package/.agent/skills/gstack-browse/src/project-slug.ts +0 -36
  320. package/.agent/skills/gstack-browse/src/proxy-config.ts +0 -283
  321. package/.agent/skills/gstack-browse/src/proxy-redact.ts +0 -46
  322. package/.agent/skills/gstack-browse/src/pty-session-cookie.ts +0 -64
  323. package/.agent/skills/gstack-browse/src/pty-session-lease.ts +0 -137
  324. package/.agent/skills/gstack-browse/src/read-commands.ts +0 -756
  325. package/.agent/skills/gstack-browse/src/routes/activity.ts +0 -63
  326. package/.agent/skills/gstack-browse/src/routes/commands.ts +0 -154
  327. package/.agent/skills/gstack-browse/src/routes/core.ts +0 -123
  328. package/.agent/skills/gstack-browse/src/routes/files.ts +0 -47
  329. package/.agent/skills/gstack-browse/src/routes/index.ts +0 -27
  330. package/.agent/skills/gstack-browse/src/routes/inspector.ts +0 -132
  331. package/.agent/skills/gstack-browse/src/routes/pairing.ts +0 -158
  332. package/.agent/skills/gstack-browse/src/routes/pty.ts +0 -254
  333. package/.agent/skills/gstack-browse/src/routes/table.ts +0 -182
  334. package/.agent/skills/gstack-browse/src/routes/tokens.ts +0 -86
  335. package/.agent/skills/gstack-browse/src/routes/tunnel.ts +0 -62
  336. package/.agent/skills/gstack-browse/src/sanitize.ts +0 -49
  337. package/.agent/skills/gstack-browse/src/screenshot-size-guard.ts +0 -106
  338. package/.agent/skills/gstack-browse/src/security-classifier.ts +0 -258
  339. package/.agent/skills/gstack-browse/src/security-sidecar-client.ts +0 -234
  340. package/.agent/skills/gstack-browse/src/security-sidecar-entry.ts +0 -120
  341. package/.agent/skills/gstack-browse/src/security.ts +0 -328
  342. package/.agent/skills/gstack-browse/src/server.ts +0 -2220
  343. package/.agent/skills/gstack-browse/src/session-cookie-store.ts +0 -124
  344. package/.agent/skills/gstack-browse/src/session-persist.ts +0 -178
  345. package/.agent/skills/gstack-browse/src/skill-token.ts +0 -91
  346. package/.agent/skills/gstack-browse/src/snapshot-flags.ts +0 -30
  347. package/.agent/skills/gstack-browse/src/snapshot.ts +0 -656
  348. package/.agent/skills/gstack-browse/src/socks-bridge.ts +0 -314
  349. package/.agent/skills/gstack-browse/src/sse-helpers.ts +0 -147
  350. package/.agent/skills/gstack-browse/src/sse-session-cookie.ts +0 -65
  351. package/.agent/skills/gstack-browse/src/stealth.ts +0 -574
  352. package/.agent/skills/gstack-browse/src/tab-session.ts +0 -210
  353. package/.agent/skills/gstack-browse/src/telemetry.ts +0 -115
  354. package/.agent/skills/gstack-browse/src/terminal-agent-control.ts +0 -379
  355. package/.agent/skills/gstack-browse/src/terminal-agent.ts +0 -1124
  356. package/.agent/skills/gstack-browse/src/terminal-pty-lifecycle.ts +0 -144
  357. package/.agent/skills/gstack-browse/src/token-registry.ts +0 -721
  358. package/.agent/skills/gstack-browse/src/tunnel-denial-log.ts +0 -97
  359. package/.agent/skills/gstack-browse/src/url-validation.ts +0 -299
  360. package/.agent/skills/gstack-browse/src/welcome.html +0 -237
  361. package/.agent/skills/gstack-browse/src/write-commands.ts +0 -1438
  362. package/.agent/skills/gstack-browse/src/xprotect-heal.ts +0 -439
  363. package/.agent/skills/gstack-browse/src/xvfb.ts +0 -313
  364. package/.agent/skills/gstack-browse/test/activity.test.ts +0 -120
  365. package/.agent/skills/gstack-browse/test/adversarial-security.test.ts +0 -32
  366. package/.agent/skills/gstack-browse/test/batch.test.ts +0 -267
  367. package/.agent/skills/gstack-browse/test/bridge-chromium-e2e.test.ts +0 -205
  368. package/.agent/skills/gstack-browse/test/browse-client.test.ts +0 -300
  369. package/.agent/skills/gstack-browse/test/browse-eval-wrapping.test.ts +0 -86
  370. package/.agent/skills/gstack-browse/test/browser-manager-custom-chromium.test.ts +0 -67
  371. package/.agent/skills/gstack-browse/test/browser-manager-unit.test.ts +0 -335
  372. package/.agent/skills/gstack-browse/test/browser-skill-commands.test.ts +0 -433
  373. package/.agent/skills/gstack-browse/test/browser-skill-write.test.ts +0 -350
  374. package/.agent/skills/gstack-browse/test/browser-skills-e2e.test.ts +0 -101
  375. package/.agent/skills/gstack-browse/test/browser-skills-storage.test.ts +0 -283
  376. package/.agent/skills/gstack-browse/test/build-command-response.test.ts +0 -68
  377. package/.agent/skills/gstack-browse/test/build.test.ts +0 -52
  378. package/.agent/skills/gstack-browse/test/bun-child-config.test.ts +0 -83
  379. package/.agent/skills/gstack-browse/test/bun-polyfill.test.ts +0 -402
  380. package/.agent/skills/gstack-browse/test/busy-daemon-iron-rule.test.ts +0 -270
  381. package/.agent/skills/gstack-browse/test/busy-daemon-recovery.test.ts +0 -134
  382. package/.agent/skills/gstack-browse/test/cdp-allowlist.test.ts +0 -109
  383. package/.agent/skills/gstack-browse/test/cdp-e2e.test.ts +0 -117
  384. package/.agent/skills/gstack-browse/test/cdp-inspector-history-cap.test.ts +0 -95
  385. package/.agent/skills/gstack-browse/test/cdp-mutex.test.ts +0 -113
  386. package/.agent/skills/gstack-browse/test/cdp-session-cleanup.test.ts +0 -171
  387. package/.agent/skills/gstack-browse/test/chromium-profile-isolation.test.ts +0 -418
  388. package/.agent/skills/gstack-browse/test/claude-bin.test.ts +0 -95
  389. package/.agent/skills/gstack-browse/test/cli-chain-stdin.test.ts +0 -122
  390. package/.agent/skills/gstack-browse/test/cli-config-mismatch-message.test.ts +0 -72
  391. package/.agent/skills/gstack-browse/test/cli-lock.test.ts +0 -120
  392. package/.agent/skills/gstack-browse/test/cli-loopback-no-proxy.test.ts +0 -77
  393. package/.agent/skills/gstack-browse/test/cli-setsid-daemonize.test.ts +0 -75
  394. package/.agent/skills/gstack-browse/test/cli-start-final-healthcheck.test.ts +0 -77
  395. package/.agent/skills/gstack-browse/test/cli-supervisor.test.ts +0 -194
  396. package/.agent/skills/gstack-browse/test/commands.test.ts +0 -2779
  397. package/.agent/skills/gstack-browse/test/compare-board.test.ts +0 -382
  398. package/.agent/skills/gstack-browse/test/config.test.ts +0 -519
  399. package/.agent/skills/gstack-browse/test/content-security.test.ts +0 -681
  400. package/.agent/skills/gstack-browse/test/cookie-auth-verification.test.ts +0 -760
  401. package/.agent/skills/gstack-browse/test/cookie-credential-deadline.test.ts +0 -225
  402. package/.agent/skills/gstack-browse/test/cookie-database.test.ts +0 -277
  403. package/.agent/skills/gstack-browse/test/cookie-fixture-delete-lease.test.ts +0 -256
  404. package/.agent/skills/gstack-browse/test/cookie-import-browser.test.ts +0 -523
  405. package/.agent/skills/gstack-browse/test/cookie-import-native-job.test.ts +0 -1645
  406. package/.agent/skills/gstack-browse/test/cookie-import-native-qualification.ts +0 -128
  407. package/.agent/skills/gstack-browse/test/cookie-import-native.test.ts +0 -436
  408. package/.agent/skills/gstack-browse/test/cookie-import-node.test.ts +0 -166
  409. package/.agent/skills/gstack-browse/test/cookie-import-operation.test.ts +0 -452
  410. package/.agent/skills/gstack-browse/test/cookie-import-reliability.test.ts +0 -659
  411. package/.agent/skills/gstack-browse/test/cookie-import-transport.test.ts +0 -38
  412. package/.agent/skills/gstack-browse/test/cookie-picker-binding-browser.test.ts +0 -96
  413. package/.agent/skills/gstack-browse/test/cookie-picker-binding.test.ts +0 -155
  414. package/.agent/skills/gstack-browse/test/cookie-picker-csrf-browser.test.ts +0 -50
  415. package/.agent/skills/gstack-browse/test/cookie-picker-routes.test.ts +0 -460
  416. package/.agent/skills/gstack-browse/test/cookie-picker-ui.test.ts +0 -535
  417. package/.agent/skills/gstack-browse/test/daemon-log-hygiene.test.ts +0 -135
  418. package/.agent/skills/gstack-browse/test/daemon-mismatch-refuse.test.ts +0 -178
  419. package/.agent/skills/gstack-browse/test/data-platform.test.ts +0 -176
  420. package/.agent/skills/gstack-browse/test/dia-gui-readiness.test.ts +0 -174
  421. package/.agent/skills/gstack-browse/test/dia-launch-comparison.test.ts +0 -180
  422. package/.agent/skills/gstack-browse/test/dia-macos-qualification.test.ts +0 -1695
  423. package/.agent/skills/gstack-browse/test/domain-skills-e2e.test.ts +0 -142
  424. package/.agent/skills/gstack-browse/test/domain-skills-storage.test.ts +0 -263
  425. package/.agent/skills/gstack-browse/test/dotenv-isolation.test.ts +0 -121
  426. package/.agent/skills/gstack-browse/test/dual-listener.test.ts +0 -411
  427. package/.agent/skills/gstack-browse/test/dx-polish.test.ts +0 -101
  428. package/.agent/skills/gstack-browse/test/error-handling.test.ts +0 -47
  429. package/.agent/skills/gstack-browse/test/extension-sender-auth.test.ts +0 -285
  430. package/.agent/skills/gstack-browse/test/extension-token.test.ts +0 -232
  431. package/.agent/skills/gstack-browse/test/file-drop.test.ts +0 -271
  432. package/.agent/skills/gstack-browse/test/file-permissions.test.ts +0 -346
  433. package/.agent/skills/gstack-browse/test/fill-change-event.test.ts +0 -78
  434. package/.agent/skills/gstack-browse/test/find-browse.test.ts +0 -61
  435. package/.agent/skills/gstack-browse/test/findport.test.ts +0 -232
  436. package/.agent/skills/gstack-browse/test/fixtures/basic.html +0 -33
  437. package/.agent/skills/gstack-browse/test/fixtures/change-only-validator.html +0 -31
  438. package/.agent/skills/gstack-browse/test/fixtures/cursor-interactive.html +0 -22
  439. package/.agent/skills/gstack-browse/test/fixtures/dialog.html +0 -15
  440. package/.agent/skills/gstack-browse/test/fixtures/dropdown.html +0 -61
  441. package/.agent/skills/gstack-browse/test/fixtures/empty.html +0 -2
  442. package/.agent/skills/gstack-browse/test/fixtures/forms.html +0 -55
  443. package/.agent/skills/gstack-browse/test/fixtures/iframe.html +0 -30
  444. package/.agent/skills/gstack-browse/test/fixtures/injection-combined.html +0 -58
  445. package/.agent/skills/gstack-browse/test/fixtures/injection-hidden.html +0 -62
  446. package/.agent/skills/gstack-browse/test/fixtures/injection-social.html +0 -25
  447. package/.agent/skills/gstack-browse/test/fixtures/injection-visible.html +0 -26
  448. package/.agent/skills/gstack-browse/test/fixtures/media-page.html +0 -67
  449. package/.agent/skills/gstack-browse/test/fixtures/mock-claude/claude +0 -185
  450. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-delete-lease.ts +0 -125
  451. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-file-owners.ts +0 -132
  452. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-launch.cjs +0 -139
  453. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process-observer.ts +0 -150
  454. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process.cjs +0 -13
  455. package/.agent/skills/gstack-browse/test/fixtures/native-cookie-remove-fixture.cjs +0 -42
  456. package/.agent/skills/gstack-browse/test/fixtures/network-idle.html +0 -30
  457. package/.agent/skills/gstack-browse/test/fixtures/qa-eval-checkout.html +0 -108
  458. package/.agent/skills/gstack-browse/test/fixtures/qa-eval-spa.html +0 -98
  459. package/.agent/skills/gstack-browse/test/fixtures/qa-eval.html +0 -51
  460. package/.agent/skills/gstack-browse/test/fixtures/qa-only.html +0 -13
  461. package/.agent/skills/gstack-browse/test/fixtures/responsive.html +0 -49
  462. package/.agent/skills/gstack-browse/test/fixtures/route-dispatch-allowlist.json +0 -22
  463. package/.agent/skills/gstack-browse/test/fixtures/snapshot-ambiguous.html +0 -24
  464. package/.agent/skills/gstack-browse/test/fixtures/snapshot.html +0 -55
  465. package/.agent/skills/gstack-browse/test/fixtures/spa.html +0 -24
  466. package/.agent/skills/gstack-browse/test/fixtures/states.html +0 -17
  467. package/.agent/skills/gstack-browse/test/fixtures/upload-path-validation.ts +0 -91
  468. package/.agent/skills/gstack-browse/test/fixtures/upload.html +0 -25
  469. package/.agent/skills/gstack-browse/test/from-file-path-validation.test.ts +0 -68
  470. package/.agent/skills/gstack-browse/test/gstack-config.test.ts +0 -252
  471. package/.agent/skills/gstack-browse/test/gstack-update-check.test.ts +0 -614
  472. package/.agent/skills/gstack-browse/test/handoff.test.ts +0 -508
  473. package/.agent/skills/gstack-browse/test/headless-gpu-and-reap.test.ts +0 -286
  474. package/.agent/skills/gstack-browse/test/headless-launch-options.test.ts +0 -93
  475. package/.agent/skills/gstack-browse/test/launch-signal-flags.test.ts +0 -75
  476. package/.agent/skills/gstack-browse/test/learnings-injection.test.ts +0 -33
  477. package/.agent/skills/gstack-browse/test/media-extract-unit.test.ts +0 -294
  478. package/.agent/skills/gstack-browse/test/memory-command.test.ts +0 -219
  479. package/.agent/skills/gstack-browse/test/memory-leak-reproducer.test.ts +0 -133
  480. package/.agent/skills/gstack-browse/test/navigation-guard.test.ts +0 -106
  481. package/.agent/skills/gstack-browse/test/output-path-symlinks.test.ts +0 -76
  482. package/.agent/skills/gstack-browse/test/pair-agent-e2e.test.ts +0 -793
  483. package/.agent/skills/gstack-browse/test/pair-agent-optin-gate.test.ts +0 -171
  484. package/.agent/skills/gstack-browse/test/pair-agent-tunnel-eval.test.ts +0 -501
  485. package/.agent/skills/gstack-browse/test/path-security-realpath.test.ts +0 -89
  486. package/.agent/skills/gstack-browse/test/path-validation.test.ts +0 -268
  487. package/.agent/skills/gstack-browse/test/pdf-flags.test.ts +0 -86
  488. package/.agent/skills/gstack-browse/test/platform.test.ts +0 -37
  489. package/.agent/skills/gstack-browse/test/playwright-core-patch.test.ts +0 -62
  490. package/.agent/skills/gstack-browse/test/poisoned-bundle-probe.test.ts +0 -148
  491. package/.agent/skills/gstack-browse/test/process-liveness-windows.test.ts +0 -176
  492. package/.agent/skills/gstack-browse/test/proxy-config.test.ts +0 -189
  493. package/.agent/skills/gstack-browse/test/proxy-env-daemon.test.ts +0 -137
  494. package/.agent/skills/gstack-browse/test/proxy-env.test.ts +0 -189
  495. package/.agent/skills/gstack-browse/test/proxy-redact.test.ts +0 -64
  496. package/.agent/skills/gstack-browse/test/pty-inject-scan.test.ts +0 -153
  497. package/.agent/skills/gstack-browse/test/pty-session-lease.test.ts +0 -98
  498. package/.agent/skills/gstack-browse/test/rebrand-signed-bundle.test.ts +0 -53
  499. package/.agent/skills/gstack-browse/test/regression-pr1169-pdf-from-file-invalid-json.test.ts +0 -84
  500. package/.agent/skills/gstack-browse/test/restart-env.test.ts +0 -39
  501. package/.agent/skills/gstack-browse/test/route-test-harness.ts +0 -121
  502. package/.agent/skills/gstack-browse/test/runtime-root-server-bundle.test.ts +0 -100
  503. package/.agent/skills/gstack-browse/test/sanitize.test.ts +0 -112
  504. package/.agent/skills/gstack-browse/test/screenshot-size-guard.test.ts +0 -118
  505. package/.agent/skills/gstack-browse/test/security-adversarial-fixes.test.ts +0 -101
  506. package/.agent/skills/gstack-browse/test/security-adversarial.test.ts +0 -342
  507. package/.agent/skills/gstack-browse/test/security-audit-r2.test.ts +0 -432
  508. package/.agent/skills/gstack-browse/test/security-bench.test.ts +0 -159
  509. package/.agent/skills/gstack-browse/test/security-classifier-download-cleanup.test.ts +0 -138
  510. package/.agent/skills/gstack-browse/test/security-classifier.test.ts +0 -29
  511. package/.agent/skills/gstack-browse/test/security-integration.test.ts +0 -184
  512. package/.agent/skills/gstack-browse/test/security-live-playwright.test.ts +0 -195
  513. package/.agent/skills/gstack-browse/test/security-sidecar-client.test.ts +0 -66
  514. package/.agent/skills/gstack-browse/test/security.test.ts +0 -268
  515. package/.agent/skills/gstack-browse/test/server-auth.test.ts +0 -600
  516. package/.agent/skills/gstack-browse/test/server-embedder-terminal-port.test.ts +0 -301
  517. package/.agent/skills/gstack-browse/test/server-factory.test.ts +0 -640
  518. package/.agent/skills/gstack-browse/test/server-flush-trackers.test.ts +0 -73
  519. package/.agent/skills/gstack-browse/test/server-launch-resolution.test.ts +0 -49
  520. package/.agent/skills/gstack-browse/test/server-lock-errors.test.ts +0 -124
  521. package/.agent/skills/gstack-browse/test/server-no-import-side-effects.test.ts +0 -110
  522. package/.agent/skills/gstack-browse/test/server-proxy-fail-fast.test.ts +0 -98
  523. package/.agent/skills/gstack-browse/test/server-pty-lease-routes.test.ts +0 -156
  524. package/.agent/skills/gstack-browse/test/server-route-auth-blackbox.test.ts +0 -270
  525. package/.agent/skills/gstack-browse/test/server-route-dispatch-ratchet.test.ts +0 -129
  526. package/.agent/skills/gstack-browse/test/server-route-table.test.ts +0 -238
  527. package/.agent/skills/gstack-browse/test/server-sanitize-surrogates.test.ts +0 -182
  528. package/.agent/skills/gstack-browse/test/server-tmp-state-path.test.ts +0 -110
  529. package/.agent/skills/gstack-browse/test/session-cookie-store.test.ts +0 -159
  530. package/.agent/skills/gstack-browse/test/session-persist.test.ts +0 -287
  531. package/.agent/skills/gstack-browse/test/sidebar-tabs.test.ts +0 -268
  532. package/.agent/skills/gstack-browse/test/sidebar-ux.test.ts +0 -1006
  533. package/.agent/skills/gstack-browse/test/sidepanel-font-size.test.ts +0 -147
  534. package/.agent/skills/gstack-browse/test/sidepanel-patient-autoconnect.test.ts +0 -70
  535. package/.agent/skills/gstack-browse/test/sidepanel-reattach.test.ts +0 -93
  536. package/.agent/skills/gstack-browse/test/sidepanel-restart-dispose.test.ts +0 -106
  537. package/.agent/skills/gstack-browse/test/skill-token.test.ts +0 -167
  538. package/.agent/skills/gstack-browse/test/snapshot.test.ts +0 -593
  539. package/.agent/skills/gstack-browse/test/socks-bridge.test.ts +0 -461
  540. package/.agent/skills/gstack-browse/test/sse-helpers.test.ts +0 -194
  541. package/.agent/skills/gstack-browse/test/sse-session-cookie.test.ts +0 -155
  542. package/.agent/skills/gstack-browse/test/state-ttl.test.ts +0 -35
  543. package/.agent/skills/gstack-browse/test/stealth-extended.test.ts +0 -127
  544. package/.agent/skills/gstack-browse/test/stealth-layer-c.test.ts +0 -284
  545. package/.agent/skills/gstack-browse/test/stealth-webdriver.test.ts +0 -394
  546. package/.agent/skills/gstack-browse/test/stop-ack-before-shutdown.test.ts +0 -23
  547. package/.agent/skills/gstack-browse/test/stop-dead-daemon.test.ts +0 -153
  548. package/.agent/skills/gstack-browse/test/tab-each.test.ts +0 -196
  549. package/.agent/skills/gstack-browse/test/tab-guardrail.test.ts +0 -118
  550. package/.agent/skills/gstack-browse/test/tab-isolation.test.ts +0 -309
  551. package/.agent/skills/gstack-browse/test/tab-session-frame-detach.test.ts +0 -154
  552. package/.agent/skills/gstack-browse/test/telemetry-optout.test.ts +0 -171
  553. package/.agent/skills/gstack-browse/test/telemetry.test.ts +0 -77
  554. package/.agent/skills/gstack-browse/test/temp-dirs.test.ts +0 -129
  555. package/.agent/skills/gstack-browse/test/terminal-agent-detach-reattach.test.ts +0 -84
  556. package/.agent/skills/gstack-browse/test/terminal-agent-import.test.ts +0 -42
  557. package/.agent/skills/gstack-browse/test/terminal-agent-integration.test.ts +0 -565
  558. package/.agent/skills/gstack-browse/test/terminal-agent-keepalive.test.ts +0 -88
  559. package/.agent/skills/gstack-browse/test/terminal-agent-lifecycle.test.ts +0 -371
  560. package/.agent/skills/gstack-browse/test/terminal-agent-native-observation.test.ts +0 -66
  561. package/.agent/skills/gstack-browse/test/terminal-agent-owner-watchdog.test.ts +0 -81
  562. package/.agent/skills/gstack-browse/test/terminal-agent-pid-identity.test.ts +0 -161
  563. package/.agent/skills/gstack-browse/test/terminal-agent-port-range.test.ts +0 -80
  564. package/.agent/skills/gstack-browse/test/terminal-agent-publication-lock.test.ts +0 -289
  565. package/.agent/skills/gstack-browse/test/terminal-agent-ring-buffer-runtime.test.ts +0 -157
  566. package/.agent/skills/gstack-browse/test/terminal-agent-session-routing.test.ts +0 -96
  567. package/.agent/skills/gstack-browse/test/terminal-agent-watchdog.test.ts +0 -101
  568. package/.agent/skills/gstack-browse/test/terminal-agent.test.ts +0 -259
  569. package/.agent/skills/gstack-browse/test/terminal-pty-lifecycle.test.ts +0 -212
  570. package/.agent/skills/gstack-browse/test/test-server.ts +0 -57
  571. package/.agent/skills/gstack-browse/test/token-registry.test.ts +0 -615
  572. package/.agent/skills/gstack-browse/test/tunnel-gate-unit.test.ts +0 -129
  573. package/.agent/skills/gstack-browse/test/tunnel-revoke-cli.test.ts +0 -455
  574. package/.agent/skills/gstack-browse/test/url-validation.test.ts +0 -384
  575. package/.agent/skills/gstack-browse/test/watch.test.ts +0 -129
  576. package/.agent/skills/gstack-browse/test/watchdog.test.ts +0 -352
  577. package/.agent/skills/gstack-browse/test/welcome-page.test.ts +0 -143
  578. package/.agent/skills/gstack-browse/test/windows-spawn-hide.test.ts +0 -167
  579. package/.agent/skills/gstack-browse/test/xprotect-heal.test.ts +0 -457
  580. package/.agent/skills/gstack-browse/test/xvfb.test.ts +0 -603
  581. package/.agent/skills/gstack-canary/SKILL.md +0 -699
  582. package/.agent/skills/gstack-canary/SKILL.md.tmpl +0 -254
  583. package/.agent/skills/gstack-careful/SKILL.md +0 -96
  584. package/.agent/skills/gstack-careful/SKILL.md.tmpl +0 -107
  585. package/.agent/skills/gstack-careful/bin/check-careful.sh +0 -407
  586. package/.agent/skills/gstack-careful/bin/hook-extract.sh +0 -198
  587. package/.agent/skills/gstack-codex/SKILL.md +0 -889
  588. package/.agent/skills/gstack-codex/SKILL.md.tmpl +0 -358
  589. package/.agent/skills/gstack-codex/sections/challenge-mode.md +0 -150
  590. package/.agent/skills/gstack-codex/sections/challenge-mode.md.tmpl +0 -136
  591. package/.agent/skills/gstack-codex/sections/consult-mode.md +0 -221
  592. package/.agent/skills/gstack-codex/sections/consult-mode.md.tmpl +0 -207
  593. package/.agent/skills/gstack-codex/sections/manifest.json +0 -26
  594. package/.agent/skills/gstack-codex/sections/review-mode.md +0 -265
  595. package/.agent/skills/gstack-codex/sections/review-mode.md.tmpl +0 -246
  596. package/.agent/skills/gstack-context-restore/SKILL.md +0 -612
  597. package/.agent/skills/gstack-context-restore/SKILL.md.tmpl +0 -289
  598. package/.agent/skills/gstack-context-save/SKILL.md +0 -631
  599. package/.agent/skills/gstack-context-save/SKILL.md.tmpl +0 -307
  600. package/.agent/skills/gstack-cso/ACKNOWLEDGEMENTS.md +0 -17
  601. package/.agent/skills/gstack-cso/SKILL.md +0 -166
  602. package/.agent/skills/gstack-cso/SKILL.md.tmpl +0 -162
  603. package/.agent/skills/gstack-cso/sections/audit-phases.md +0 -116
  604. package/.agent/skills/gstack-cso/sections/audit-phases.md.tmpl +0 -114
  605. package/.agent/skills/gstack-cso/sections/manifest.json +0 -14
  606. package/.agent/skills/gstack-design-consultation/SKILL.md +0 -795
  607. package/.agent/skills/gstack-design-consultation/SKILL.md.tmpl +0 -234
  608. package/.agent/skills/gstack-design-consultation/sections/manifest.json +0 -14
  609. package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md +0 -595
  610. package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md.tmpl +0 -350
  611. package/.agent/skills/gstack-design-html/SKILL.md +0 -866
  612. package/.agent/skills/gstack-design-html/SKILL.md.tmpl +0 -476
  613. package/.agent/skills/gstack-design-html/sections/detector-install-offer.md +0 -40
  614. package/.agent/skills/gstack-design-html/sections/detector-install-offer.md.tmpl +0 -6
  615. package/.agent/skills/gstack-design-html/sections/doctrine.md +0 -86
  616. package/.agent/skills/gstack-design-html/sections/doctrine.md.tmpl +0 -1
  617. package/.agent/skills/gstack-design-html/sections/manifest.json +0 -26
  618. package/.agent/skills/gstack-design-html/sections/pretext-patterns.md +0 -145
  619. package/.agent/skills/gstack-design-html/sections/pretext-patterns.md.tmpl +0 -143
  620. package/.agent/skills/gstack-design-html/vendor/pretext.js +0 -5
  621. package/.agent/skills/gstack-design-review/SKILL.md +0 -1957
  622. package/.agent/skills/gstack-design-review/SKILL.md.tmpl +0 -344
  623. package/.agent/skills/gstack-design-shotgun/SKILL.md +0 -885
  624. package/.agent/skills/gstack-design-shotgun/SKILL.md.tmpl +0 -310
  625. package/.agent/skills/gstack-design-shotgun/sections/doctrine.md +0 -86
  626. package/.agent/skills/gstack-design-shotgun/sections/doctrine.md.tmpl +0 -1
  627. package/.agent/skills/gstack-design-shotgun/sections/manifest.json +0 -14
  628. package/.agent/skills/gstack-deslop-shared-libs/SKILL.md +0 -209
  629. package/.agent/skills/gstack-deslop-shared-libs/SKILL.md.tmpl +0 -181
  630. package/.agent/skills/gstack-devex-review/SKILL.md +0 -1107
  631. package/.agent/skills/gstack-devex-review/SKILL.md.tmpl +0 -241
  632. package/.agent/skills/gstack-diagram/SKILL.md +0 -295
  633. package/.agent/skills/gstack-diagram/SKILL.md.tmpl +0 -175
  634. package/.agent/skills/gstack-document-generate/SKILL.md +0 -814
  635. package/.agent/skills/gstack-document-generate/SKILL.md.tmpl +0 -460
  636. package/.agent/skills/gstack-document-release/SKILL.md +0 -557
  637. package/.agent/skills/gstack-document-release/SKILL.md.tmpl +0 -188
  638. package/.agent/skills/gstack-document-release/sections/audit-scope.md +0 -51
  639. package/.agent/skills/gstack-document-release/sections/audit-scope.md.tmpl +0 -49
  640. package/.agent/skills/gstack-document-release/sections/manifest.json +0 -20
  641. package/.agent/skills/gstack-document-release/sections/release-body.md +0 -640
  642. package/.agent/skills/gstack-document-release/sections/release-body.md.tmpl +0 -428
  643. package/.agent/skills/gstack-eval-plan/SKILL.md +0 -472
  644. package/.agent/skills/gstack-eval-plan/SKILL.md.tmpl +0 -157
  645. package/.agent/skills/gstack-freeze/SKILL.md +0 -79
  646. package/.agent/skills/gstack-freeze/SKILL.md.tmpl +0 -96
  647. package/.agent/skills/gstack-freeze/bin/check-freeze.sh +0 -210
  648. package/.agent/skills/gstack-freeze/bin/freeze-state.sh +0 -68
  649. package/.agent/skills/gstack-gstack-upgrade/SKILL.md +0 -410
  650. package/.agent/skills/gstack-gstack-upgrade/SKILL.md.tmpl +0 -413
  651. package/.agent/skills/gstack-gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
  652. package/.agent/skills/gstack-gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
  653. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
  654. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
  655. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
  656. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
  657. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
  658. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
  659. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
  660. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
  661. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
  662. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
  663. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
  664. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
  665. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
  666. package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
  667. package/.agent/skills/gstack-guard/SKILL.md +0 -60
  668. package/.agent/skills/gstack-guard/SKILL.md.tmpl +0 -88
  669. package/.agent/skills/gstack-health/SKILL.md +0 -710
  670. package/.agent/skills/gstack-health/SKILL.md.tmpl +0 -386
  671. package/.agent/skills/gstack-investigate/SKILL.md +0 -706
  672. package/.agent/skills/gstack-investigate/SKILL.md.tmpl +0 -305
  673. package/.agent/skills/gstack-investigate/sections/backlog.md +0 -31
  674. package/.agent/skills/gstack-investigate/sections/backlog.md.tmpl +0 -29
  675. package/.agent/skills/gstack-investigate/sections/eval-bisect.md +0 -25
  676. package/.agent/skills/gstack-investigate/sections/eval-bisect.md.tmpl +0 -23
  677. package/.agent/skills/gstack-investigate/sections/flake.md +0 -50
  678. package/.agent/skills/gstack-investigate/sections/flake.md.tmpl +0 -43
  679. package/.agent/skills/gstack-investigate/sections/manifest.json +0 -38
  680. package/.agent/skills/gstack-investigate/sections/polluter.md +0 -33
  681. package/.agent/skills/gstack-investigate/sections/polluter.md.tmpl +0 -31
  682. package/.agent/skills/gstack-investigate/sections/race.md +0 -27
  683. package/.agent/skills/gstack-investigate/sections/race.md.tmpl +0 -25
  684. package/.agent/skills/gstack-ios-clean/SKILL.md +0 -427
  685. package/.agent/skills/gstack-ios-clean/SKILL.md.tmpl +0 -107
  686. package/.agent/skills/gstack-ios-design-review/SKILL.md +0 -434
  687. package/.agent/skills/gstack-ios-design-review/SKILL.md.tmpl +0 -111
  688. package/.agent/skills/gstack-ios-fix/SKILL.md +0 -423
  689. package/.agent/skills/gstack-ios-fix/SKILL.md.tmpl +0 -103
  690. package/.agent/skills/gstack-ios-qa/SKILL.md +0 -643
  691. package/.agent/skills/gstack-ios-qa/SKILL.md.tmpl +0 -297
  692. package/.agent/skills/gstack-ios-qa/daemon/src/allowlist.ts +0 -114
  693. package/.agent/skills/gstack-ios-qa/daemon/src/audit.ts +0 -97
  694. package/.agent/skills/gstack-ios-qa/daemon/src/auth-mint.ts +0 -89
  695. package/.agent/skills/gstack-ios-qa/daemon/src/cli-mint.ts +0 -149
  696. package/.agent/skills/gstack-ios-qa/daemon/src/devicectl.ts +0 -335
  697. package/.agent/skills/gstack-ios-qa/daemon/src/index.ts +0 -697
  698. package/.agent/skills/gstack-ios-qa/daemon/src/proxy.ts +0 -160
  699. package/.agent/skills/gstack-ios-qa/daemon/src/session-tokens.ts +0 -126
  700. package/.agent/skills/gstack-ios-qa/daemon/src/single-instance.ts +0 -171
  701. package/.agent/skills/gstack-ios-qa/daemon/src/tailscale-localapi.ts +0 -120
  702. package/.agent/skills/gstack-ios-qa/daemon/src/tunnel-bootstrap.ts +0 -459
  703. package/.agent/skills/gstack-ios-qa/daemon/src/types.ts +0 -91
  704. package/.agent/skills/gstack-ios-qa/daemon/test/allowlist.test.ts +0 -146
  705. package/.agent/skills/gstack-ios-qa/daemon/test/audit.test.ts +0 -111
  706. package/.agent/skills/gstack-ios-qa/daemon/test/auth-mint.test.ts +0 -106
  707. package/.agent/skills/gstack-ios-qa/daemon/test/cli-mint.test.ts +0 -120
  708. package/.agent/skills/gstack-ios-qa/daemon/test/daemon-integration.test.ts +0 -855
  709. package/.agent/skills/gstack-ios-qa/daemon/test/fake-device.ts +0 -238
  710. package/.agent/skills/gstack-ios-qa/daemon/test/proxy-classify.test.ts +0 -47
  711. package/.agent/skills/gstack-ios-qa/daemon/test/proxy-transport.test.ts +0 -499
  712. package/.agent/skills/gstack-ios-qa/daemon/test/session-tokens.test.ts +0 -156
  713. package/.agent/skills/gstack-ios-qa/daemon/test/single-instance.test.ts +0 -96
  714. package/.agent/skills/gstack-ios-qa/daemon/test/tailscale-localapi.test.ts +0 -55
  715. package/.agent/skills/gstack-ios-qa/daemon/test/tunnel-bootstrap.test.ts +0 -1030
  716. package/.agent/skills/gstack-ios-qa/templates/Bridges.swift.template +0 -474
  717. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeManager.swift.template +0 -38
  718. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.h.template +0 -38
  719. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.m.template +0 -365
  720. package/.agent/skills/gstack-ios-qa/templates/DebugBridgeWiring.swift.template +0 -59
  721. package/.agent/skills/gstack-ios-qa/templates/DebugOverlay.swift.template +0 -137
  722. package/.agent/skills/gstack-ios-qa/templates/Package.swift.template +0 -82
  723. package/.agent/skills/gstack-ios-qa/templates/StateAccessor.swift.template +0 -41
  724. package/.agent/skills/gstack-ios-qa/templates/StateServer.swift.template +0 -625
  725. package/.agent/skills/gstack-ios-sync/SKILL.md +0 -418
  726. package/.agent/skills/gstack-ios-sync/SKILL.md.tmpl +0 -99
  727. package/.agent/skills/gstack-land-and-deploy/SKILL.md +0 -1028
  728. package/.agent/skills/gstack-land-and-deploy/SKILL.md.tmpl +0 -509
  729. package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md +0 -188
  730. package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md.tmpl +0 -150
  731. package/.agent/skills/gstack-land-and-deploy/sections/manifest.json +0 -26
  732. package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md +0 -250
  733. package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md.tmpl +0 -248
  734. package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md +0 -308
  735. package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md.tmpl +0 -306
  736. package/.agent/skills/gstack-landing-report/SKILL.md +0 -488
  737. package/.agent/skills/gstack-landing-report/SKILL.md.tmpl +0 -164
  738. package/.agent/skills/gstack-learn/SKILL.md +0 -519
  739. package/.agent/skills/gstack-learn/SKILL.md.tmpl +0 -197
  740. package/.agent/skills/gstack-make-pdf/SKILL.md +0 -409
  741. package/.agent/skills/gstack-make-pdf/SKILL.md.tmpl +0 -253
  742. package/.agent/skills/gstack-make-pdf/src/asideClient.ts +0 -114
  743. package/.agent/skills/gstack-make-pdf/src/cli.ts +0 -294
  744. package/.agent/skills/gstack-make-pdf/src/commands.ts +0 -62
  745. package/.agent/skills/gstack-make-pdf/src/diagram-prepass.ts +0 -834
  746. package/.agent/skills/gstack-make-pdf/src/html-to-docx.d.ts +0 -7
  747. package/.agent/skills/gstack-make-pdf/src/image-policy.ts +0 -236
  748. package/.agent/skills/gstack-make-pdf/src/image-size.ts +0 -117
  749. package/.agent/skills/gstack-make-pdf/src/orchestrator.ts +0 -339
  750. package/.agent/skills/gstack-make-pdf/src/pdftotext.ts +0 -318
  751. package/.agent/skills/gstack-make-pdf/src/print-css.ts +0 -452
  752. package/.agent/skills/gstack-make-pdf/src/render.ts +0 -636
  753. package/.agent/skills/gstack-make-pdf/src/setup.ts +0 -113
  754. package/.agent/skills/gstack-make-pdf/src/smartypants.ts +0 -105
  755. package/.agent/skills/gstack-make-pdf/src/toc-pages.ts +0 -197
  756. package/.agent/skills/gstack-make-pdf/src/types.ts +0 -109
  757. package/.agent/skills/gstack-make-pdf/test/asideClient.test.ts +0 -153
  758. package/.agent/skills/gstack-make-pdf/test/cli-args.test.ts +0 -94
  759. package/.agent/skills/gstack-make-pdf/test/cli-exit-codes.test.ts +0 -329
  760. package/.agent/skills/gstack-make-pdf/test/diagram-prepass.test.ts +0 -743
  761. package/.agent/skills/gstack-make-pdf/test/e2e/ci-prereqs.test.ts +0 -48
  762. package/.agent/skills/gstack-make-pdf/test/e2e/combined-gate.test.ts +0 -74
  763. package/.agent/skills/gstack-make-pdf/test/e2e/diagram-gate.test.ts +0 -173
  764. package/.agent/skills/gstack-make-pdf/test/e2e/emoji-gate.test.ts +0 -191
  765. package/.agent/skills/gstack-make-pdf/test/e2e/format-gate.test.ts +0 -130
  766. package/.agent/skills/gstack-make-pdf/test/e2e/landscape-gate.test.ts +0 -150
  767. package/.agent/skills/gstack-make-pdf/test/e2e/toc-gate.test.ts +0 -143
  768. package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.expected.txt +0 -20
  769. package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.md +0 -30
  770. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/huge-noise.png +0 -0
  771. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/red-box.png +0 -0
  772. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-arch.png +0 -0
  773. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-screenshot.png +0 -0
  774. package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-gate.md +0 -48
  775. package/.agent/skills/gstack-make-pdf/test/fixtures/emoji-gate.md +0 -12
  776. package/.agent/skills/gstack-make-pdf/test/fixtures/landscape-gate.md +0 -52
  777. package/.agent/skills/gstack-make-pdf/test/image-policy.test.ts +0 -215
  778. package/.agent/skills/gstack-make-pdf/test/pdftotext.test.ts +0 -327
  779. package/.agent/skills/gstack-make-pdf/test/render-offline-sanitize.test.ts +0 -268
  780. package/.agent/skills/gstack-make-pdf/test/render.test.ts +0 -603
  781. package/.agent/skills/gstack-make-pdf/test/setup-smoke.test.ts +0 -259
  782. package/.agent/skills/gstack-make-pdf/test/toc-anchors.test.ts +0 -95
  783. package/.agent/skills/gstack-make-pdf/test/toc-pages.test.ts +0 -148
  784. package/.agent/skills/gstack-office-hours/SKILL.md +0 -1324
  785. package/.agent/skills/gstack-office-hours/SKILL.md.tmpl +0 -372
  786. package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md +0 -706
  787. package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md.tmpl +0 -547
  788. package/.agent/skills/gstack-office-hours/sections/manifest.json +0 -26
  789. package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md +0 -163
  790. package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md.tmpl +0 -161
  791. package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md +0 -43
  792. package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md.tmpl +0 -41
  793. package/.agent/skills/gstack-open-gstack-browser/SKILL.md +0 -414
  794. package/.agent/skills/gstack-open-gstack-browser/SKILL.md.tmpl +0 -248
  795. package/.agent/skills/gstack-pair-agent/SKILL.md +0 -730
  796. package/.agent/skills/gstack-pair-agent/SKILL.md.tmpl +0 -366
  797. package/.agent/skills/gstack-plan-ceo-review/SKILL.md +0 -1245
  798. package/.agent/skills/gstack-plan-ceo-review/SKILL.md.tmpl +0 -596
  799. package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook +0 -5
  800. package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook.ts +0 -34
  801. package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md +0 -37
  802. package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md.tmpl +0 -35
  803. package/.agent/skills/gstack-plan-ceo-review/sections/manifest.json +0 -23
  804. package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md +0 -1207
  805. package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md.tmpl +0 -605
  806. package/.agent/skills/gstack-plan-design-review/SKILL.md +0 -1230
  807. package/.agent/skills/gstack-plan-design-review/SKILL.md.tmpl +0 -344
  808. package/.agent/skills/gstack-plan-design-review/sections/checklist.md +0 -27
  809. package/.agent/skills/gstack-plan-design-review/sections/checklist.md.tmpl +0 -25
  810. package/.agent/skills/gstack-plan-design-review/sections/manifest.json +0 -23
  811. package/.agent/skills/gstack-plan-design-review/sections/review-sections.md +0 -727
  812. package/.agent/skills/gstack-plan-design-review/sections/review-sections.md.tmpl +0 -281
  813. package/.agent/skills/gstack-plan-devex-review/SKILL.md +0 -1072
  814. package/.agent/skills/gstack-plan-devex-review/SKILL.md.tmpl +0 -459
  815. package/.agent/skills/gstack-plan-devex-review/dx-hall-of-fame.md +0 -127
  816. package/.agent/skills/gstack-plan-devex-review/sections/checklist.md +0 -33
  817. package/.agent/skills/gstack-plan-devex-review/sections/checklist.md.tmpl +0 -31
  818. package/.agent/skills/gstack-plan-devex-review/sections/manifest.json +0 -23
  819. package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md +0 -1037
  820. package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md.tmpl +0 -446
  821. package/.agent/skills/gstack-plan-eng-review/SKILL.md +0 -702
  822. package/.agent/skills/gstack-plan-eng-review/SKILL.md.tmpl +0 -191
  823. package/.agent/skills/gstack-plan-eng-review/sections/checklist.md +0 -34
  824. package/.agent/skills/gstack-plan-eng-review/sections/checklist.md.tmpl +0 -32
  825. package/.agent/skills/gstack-plan-eng-review/sections/manifest.json +0 -23
  826. package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md +0 -1545
  827. package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md.tmpl +0 -614
  828. package/.agent/skills/gstack-plan-tune/SKILL.md +0 -986
  829. package/.agent/skills/gstack-plan-tune/SKILL.md.tmpl +0 -669
  830. package/.agent/skills/gstack-qa/SKILL.md +0 -781
  831. package/.agent/skills/gstack-qa/SKILL.md.tmpl +0 -305
  832. package/.agent/skills/gstack-qa/references/issue-taxonomy.md +0 -85
  833. package/.agent/skills/gstack-qa/sections/browser-setup.md +0 -114
  834. package/.agent/skills/gstack-qa/sections/browser-setup.md.tmpl +0 -28
  835. package/.agent/skills/gstack-qa/sections/browser-verify.md +0 -19
  836. package/.agent/skills/gstack-qa/sections/browser-verify.md.tmpl +0 -17
  837. package/.agent/skills/gstack-qa/sections/exploratory.md +0 -92
  838. package/.agent/skills/gstack-qa/sections/exploratory.md.tmpl +0 -1
  839. package/.agent/skills/gstack-qa/sections/manifest.json +0 -50
  840. package/.agent/skills/gstack-qa/sections/qa-patterns.md +0 -226
  841. package/.agent/skills/gstack-qa/sections/qa-patterns.md.tmpl +0 -1
  842. package/.agent/skills/gstack-qa/sections/scope.md +0 -23
  843. package/.agent/skills/gstack-qa/sections/scope.md.tmpl +0 -1
  844. package/.agent/skills/gstack-qa/sections/system-functional.md +0 -61
  845. package/.agent/skills/gstack-qa/sections/system-functional.md.tmpl +0 -1
  846. package/.agent/skills/gstack-qa/sections/test-bootstrap.md +0 -86
  847. package/.agent/skills/gstack-qa/sections/test-bootstrap.md.tmpl +0 -74
  848. package/.agent/skills/gstack-qa/templates/functional-report-template.md +0 -55
  849. package/.agent/skills/gstack-qa/templates/qa-report-template.md +0 -124
  850. package/.agent/skills/gstack-qa-only/SKILL.md +0 -595
  851. package/.agent/skills/gstack-qa-only/SKILL.md.tmpl +0 -214
  852. package/.agent/skills/gstack-qa-only/sections/exploratory.md +0 -107
  853. package/.agent/skills/gstack-qa-only/sections/exploratory.md.tmpl +0 -1
  854. package/.agent/skills/gstack-qa-only/sections/manifest.json +0 -19
  855. package/.agent/skills/gstack-qa-only/sections/reporting.md +0 -78
  856. package/.agent/skills/gstack-qa-only/sections/reporting.md.tmpl +0 -76
  857. package/.agent/skills/gstack-retro/SKILL.md +0 -1178
  858. package/.agent/skills/gstack-retro/SKILL.md.tmpl +0 -769
  859. package/.agent/skills/gstack-retro/sections/manifest.json +0 -14
  860. package/.agent/skills/gstack-retro/sections/report-format.md +0 -127
  861. package/.agent/skills/gstack-retro/sections/report-format.md.tmpl +0 -125
  862. package/.agent/skills/gstack-review/SKILL.md +0 -1199
  863. package/.agent/skills/gstack-review/SKILL.md.tmpl +0 -482
  864. package/.agent/skills/gstack-review/TODOS-format.md +0 -61
  865. package/.agent/skills/gstack-review/checklist.md +0 -222
  866. package/.agent/skills/gstack-review/design-checklist.md +0 -193
  867. package/.agent/skills/gstack-review/gate-integrity.md +0 -171
  868. package/.agent/skills/gstack-review/greptile-triage.md +0 -261
  869. package/.agent/skills/gstack-review/sections/adversarial.md +0 -296
  870. package/.agent/skills/gstack-review/sections/adversarial.md.tmpl +0 -1
  871. package/.agent/skills/gstack-review/sections/manifest.json +0 -32
  872. package/.agent/skills/gstack-review/sections/plan-completion.md +0 -232
  873. package/.agent/skills/gstack-review/sections/plan-completion.md.tmpl +0 -3
  874. package/.agent/skills/gstack-review/sections/review-army.md +0 -263
  875. package/.agent/skills/gstack-review/sections/review-army.md.tmpl +0 -1
  876. package/.agent/skills/gstack-review/sections/shared-code-reuse.md +0 -34
  877. package/.agent/skills/gstack-review/sections/shared-code-reuse.md.tmpl +0 -1
  878. package/.agent/skills/gstack-review/specialists/api-contract.md +0 -49
  879. package/.agent/skills/gstack-review/specialists/data-migration.md +0 -48
  880. package/.agent/skills/gstack-review/specialists/maintainability.md +0 -46
  881. package/.agent/skills/gstack-review/specialists/performance.md +0 -52
  882. package/.agent/skills/gstack-review/specialists/red-team.md +0 -45
  883. package/.agent/skills/gstack-review/specialists/security.md +0 -65
  884. package/.agent/skills/gstack-review/specialists/simplification.md +0 -49
  885. package/.agent/skills/gstack-review/specialists/testing.md +0 -160
  886. package/.agent/skills/gstack-scrape/SKILL.md +0 -431
  887. package/.agent/skills/gstack-scrape/SKILL.md.tmpl +0 -187
  888. package/.agent/skills/gstack-setup-browser-cookies/SKILL.md +0 -231
  889. package/.agent/skills/gstack-setup-browser-cookies/SKILL.md.tmpl +0 -64
  890. package/.agent/skills/gstack-setup-deploy/SKILL.md +0 -605
  891. package/.agent/skills/gstack-setup-deploy/SKILL.md.tmpl +0 -250
  892. package/.agent/skills/gstack-setup-gbrain/SKILL.md +0 -1074
  893. package/.agent/skills/gstack-setup-gbrain/SKILL.md.tmpl +0 -742
  894. package/.agent/skills/gstack-setup-gbrain/memory.md +0 -426
  895. package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md +0 -269
  896. package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md.tmpl +0 -267
  897. package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md +0 -40
  898. package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md.tmpl +0 -38
  899. package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md +0 -71
  900. package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md.tmpl +0 -69
  901. package/.agent/skills/gstack-setup-gbrain/sections/manifest.json +0 -32
  902. package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md +0 -105
  903. package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md.tmpl +0 -103
  904. package/.agent/skills/gstack-ship/SKILL.md +0 -1220
  905. package/.agent/skills/gstack-ship/SKILL.md.tmpl +0 -694
  906. package/.agent/skills/gstack-ship/sections/adversarial.md +0 -362
  907. package/.agent/skills/gstack-ship/sections/adversarial.md.tmpl +0 -22
  908. package/.agent/skills/gstack-ship/sections/apple-release.md +0 -98
  909. package/.agent/skills/gstack-ship/sections/apple-release.md.tmpl +0 -96
  910. package/.agent/skills/gstack-ship/sections/changelog.md +0 -47
  911. package/.agent/skills/gstack-ship/sections/changelog.md.tmpl +0 -5
  912. package/.agent/skills/gstack-ship/sections/documentation.md +0 -118
  913. package/.agent/skills/gstack-ship/sections/documentation.md.tmpl +0 -116
  914. package/.agent/skills/gstack-ship/sections/greptile-early.md +0 -57
  915. package/.agent/skills/gstack-ship/sections/greptile-early.md.tmpl +0 -48
  916. package/.agent/skills/gstack-ship/sections/greptile.md +0 -95
  917. package/.agent/skills/gstack-ship/sections/greptile.md.tmpl +0 -93
  918. package/.agent/skills/gstack-ship/sections/handoff.md +0 -22
  919. package/.agent/skills/gstack-ship/sections/handoff.md.tmpl +0 -20
  920. package/.agent/skills/gstack-ship/sections/manifest.json +0 -92
  921. package/.agent/skills/gstack-ship/sections/measure.md +0 -66
  922. package/.agent/skills/gstack-ship/sections/measure.md.tmpl +0 -64
  923. package/.agent/skills/gstack-ship/sections/plan-completion.md +0 -387
  924. package/.agent/skills/gstack-ship/sections/plan-completion.md.tmpl +0 -67
  925. package/.agent/skills/gstack-ship/sections/pr-body.md +0 -258
  926. package/.agent/skills/gstack-ship/sections/pr-body.md.tmpl +0 -241
  927. package/.agent/skills/gstack-ship/sections/review-army.md +0 -728
  928. package/.agent/skills/gstack-ship/sections/review-army.md.tmpl +0 -171
  929. package/.agent/skills/gstack-ship/sections/shared-code-reuse.md +0 -34
  930. package/.agent/skills/gstack-ship/sections/shared-code-reuse.md.tmpl +0 -1
  931. package/.agent/skills/gstack-ship/sections/test-coverage.md +0 -478
  932. package/.agent/skills/gstack-ship/sections/test-coverage.md.tmpl +0 -118
  933. package/.agent/skills/gstack-ship/sections/tests.md +0 -453
  934. package/.agent/skills/gstack-ship/sections/tests.md.tmpl +0 -144
  935. package/.agent/skills/gstack-skillify/SKILL.md +0 -790
  936. package/.agent/skills/gstack-skillify/SKILL.md.tmpl +0 -437
  937. package/.agent/skills/gstack-spec/SKILL.md +0 -893
  938. package/.agent/skills/gstack-spec/SKILL.md.tmpl +0 -498
  939. package/.agent/skills/gstack-spec/sections/gate-and-file.md +0 -470
  940. package/.agent/skills/gstack-spec/sections/gate-and-file.md.tmpl +0 -319
  941. package/.agent/skills/gstack-spec/sections/manifest.json +0 -14
  942. package/.agent/skills/gstack-sync-gbrain/SKILL.md +0 -935
  943. package/.agent/skills/gstack-sync-gbrain/SKILL.md.tmpl +0 -615
  944. package/.agent/skills/gstack-test-audit/SKILL.md +0 -500
  945. package/.agent/skills/gstack-test-audit/SKILL.md.tmpl +0 -149
  946. package/.agent/skills/gstack-unfreeze/SKILL.md +0 -39
  947. package/.agent/skills/gstack-unfreeze/SKILL.md.tmpl +0 -39
  948. package/.agent/skills/gstack-upgrade/SKILL.md +0 -410
  949. package/.agent/skills/gstack-upgrade/SKILL.md.tmpl +0 -413
  950. package/.agent/skills/gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
  951. package/.agent/skills/gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
  952. package/.agent/skills/gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
  953. package/.agent/skills/gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
  954. package/.agent/skills/gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
  955. package/.agent/skills/gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
  956. package/.agent/skills/gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
  957. package/.agent/skills/gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
  958. package/.agent/skills/gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
  959. package/.agent/skills/gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
  960. package/.agent/skills/gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
  961. package/.agent/skills/gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
  962. package/.agent/skills/gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
  963. package/.agent/skills/gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
  964. package/.agent/skills/gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
  965. package/.agent/skills/gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
  966. package/.agent/skills/html-renderer/SKILL.md +0 -336
  967. package/.agent/skills/html-renderer/references/animation-patterns.md +0 -609
  968. package/.agent/skills/html-renderer/references/character-animation.md +0 -388
  969. package/.agent/skills/html-renderer/references/collage-broll.md +0 -41
  970. package/.agent/skills/html-renderer/references/design.md +0 -217
  971. package/.agent/skills/html-renderer/references/directing.md +0 -165
  972. package/.agent/skills/html-renderer/references/earshot-audio.md +0 -201
  973. package/.agent/skills/html-renderer/references/engine-api.md +0 -480
  974. package/.agent/skills/html-renderer/references/film-styles.md +0 -95
  975. package/.agent/skills/html-renderer/references/hyperframes-motion.md +0 -208
  976. package/.agent/skills/html-renderer/references/motion-craft.md +0 -92
  977. package/.agent/skills/html-renderer/references/onetake-camera.md +0 -59
  978. package/.agent/skills/html-renderer/references/procedural-sfx.md +0 -147
  979. package/.agent/skills/html-renderer/references/remotion-patterns.md +0 -159
  980. package/.agent/skills/html-renderer/references/render-contract.md +0 -66
  981. package/.agent/skills/motion-styles/LIBRARY.md +0 -896
  982. package/.agent/skills/motion-styles/README.md +0 -29
  983. package/.agent/skills/motion-styles/SKILL.md +0 -70
  984. package/.agent/skills/motion-styles/catalog.json +0 -2253
  985. package/.agent/skills/motion-styles/prompts/bio-glow.md +0 -149
  986. package/.agent/skills/motion-styles/prompts/blueprint.md +0 -145
  987. package/.agent/skills/motion-styles/prompts/breaking-desk.md +0 -147
  988. package/.agent/skills/motion-styles/prompts/callout-kit.md +0 -147
  989. package/.agent/skills/motion-styles/prompts/chalk-talk.md +0 -153
  990. package/.agent/skills/motion-styles/prompts/chat-story.md +0 -143
  991. package/.agent/skills/motion-styles/prompts/clay-diorama.md +0 -149
  992. package/.agent/skills/motion-styles/prompts/clean-tech.md +0 -147
  993. package/.agent/skills/motion-styles/prompts/comic-panels.md +0 -145
  994. package/.agent/skills/motion-styles/prompts/cosmic-scale.md +0 -147
  995. package/.agent/skills/motion-styles/prompts/creator-motion.md +0 -149
  996. package/.agent/skills/motion-styles/prompts/data-city.md +0 -149
  997. package/.agent/skills/motion-styles/prompts/data-story.md +0 -147
  998. package/.agent/skills/motion-styles/prompts/deep-layers.md +0 -147
  999. package/.agent/skills/motion-styles/prompts/doc-cinema.md +0 -145
  1000. package/.agent/skills/motion-styles/prompts/evidence-board.md +0 -145
  1001. package/.agent/skills/motion-styles/prompts/exploded-view.md +0 -149
  1002. package/.agent/skills/motion-styles/prompts/glass-icons.md +0 -147
  1003. package/.agent/skills/motion-styles/prompts/glitch-pack.md +0 -145
  1004. package/.agent/skills/motion-styles/prompts/globe-arcs.md +0 -147
  1005. package/.agent/skills/motion-styles/prompts/headline-backdrop.md +0 -145
  1006. package/.agent/skills/motion-styles/prompts/iso-cutaway.md +0 -147
  1007. package/.agent/skills/motion-styles/prompts/isotype.md +0 -145
  1008. package/.agent/skills/motion-styles/prompts/karaoke-subs.md +0 -145
  1009. package/.agent/skills/motion-styles/prompts/low-poly-atlas.md +0 -149
  1010. package/.agent/skills/motion-styles/prompts/lower-thirds.md +0 -147
  1011. package/.agent/skills/motion-styles/prompts/marker-board.md +0 -145
  1012. package/.agent/skills/motion-styles/prompts/market-tape.md +0 -145
  1013. package/.agent/skills/motion-styles/prompts/math-canvas.md +0 -145
  1014. package/.agent/skills/motion-styles/prompts/myth-or-fact.md +0 -145
  1015. package/.agent/skills/motion-styles/prompts/neon-line.md +0 -145
  1016. package/.agent/skills/motion-styles/prompts/paper-craft.md +0 -145
  1017. package/.agent/skills/motion-styles/prompts/parchment-map.md +0 -147
  1018. package/.agent/skills/motion-styles/prompts/physics-toybox.md +0 -147
  1019. package/.agent/skills/motion-styles/prompts/pixel-quest.md +0 -145
  1020. package/.agent/skills/motion-styles/prompts/podcast-clip.md +0 -147
  1021. package/.agent/skills/motion-styles/prompts/pop-info.md +0 -145
  1022. package/.agent/skills/motion-styles/prompts/product-turntable.md +0 -149
  1023. package/.agent/skills/motion-styles/prompts/punch-captions.md +0 -145
  1024. package/.agent/skills/motion-styles/prompts/quiz-countdown.md +0 -145
  1025. package/.agent/skills/motion-styles/prompts/quote-cinema.md +0 -145
  1026. package/.agent/skills/motion-styles/prompts/recipe-steps.md +0 -145
  1027. package/.agent/skills/motion-styles/prompts/rubber-hose-reel.md +0 -145
  1028. package/.agent/skills/motion-styles/prompts/shadow-puppets.md +0 -145
  1029. package/.agent/skills/motion-styles/prompts/size-lineup.md +0 -149
  1030. package/.agent/skills/motion-styles/prompts/software-walkthrough.md +0 -147
  1031. package/.agent/skills/motion-styles/prompts/stack-split.md +0 -145
  1032. package/.agent/skills/motion-styles/prompts/sticker-pop.md +0 -145
  1033. package/.agent/skills/motion-styles/prompts/storybook.md +0 -145
  1034. package/.agent/skills/motion-styles/prompts/swiss-grid.md +0 -145
  1035. package/.agent/skills/motion-styles/prompts/synth-grid.md +0 -147
  1036. package/.agent/skills/motion-styles/prompts/tactics-board.md +0 -145
  1037. package/.agent/skills/motion-styles/prompts/terminal.md +0 -145
  1038. package/.agent/skills/motion-styles/prompts/tier-list.md +0 -143
  1039. package/.agent/skills/motion-styles/prompts/timeline-rail.md +0 -145
  1040. package/.agent/skills/motion-styles/prompts/top-5-countdown.md +0 -145
  1041. package/.agent/skills/motion-styles/prompts/versus-split.md +0 -145
  1042. package/.agent/skills/motion-styles/prompts/vhs-camcorder.md +0 -145
  1043. package/.agent/skills/motion-styles/prompts/visual-novel.md +0 -145
  1044. package/.agent/skills/motion-styles/prompts/warm-canvas.md +0 -145
  1045. package/.agent/skills/motion-styles/prompts/woodcut-tale.md +0 -145
  1046. package/.agent/skills/motion-styles/prompts/zine-collage.md +0 -145
  1047. package/.agent/skills/motion-styles/sources/bio-glow.html +0 -639
  1048. package/.agent/skills/motion-styles/sources/blueprint.html +0 -617
  1049. package/.agent/skills/motion-styles/sources/breaking-desk.html +0 -585
  1050. package/.agent/skills/motion-styles/sources/callout-kit.html +0 -586
  1051. package/.agent/skills/motion-styles/sources/chalk-talk.html +0 -597
  1052. package/.agent/skills/motion-styles/sources/chat-story.html +0 -597
  1053. package/.agent/skills/motion-styles/sources/clay-diorama.html +0 -681
  1054. package/.agent/skills/motion-styles/sources/clean-tech.html +0 -554
  1055. package/.agent/skills/motion-styles/sources/comic-panels.html +0 -608
  1056. package/.agent/skills/motion-styles/sources/cosmic-scale.html +0 -628
  1057. package/.agent/skills/motion-styles/sources/creator-motion.html +0 -805
  1058. package/.agent/skills/motion-styles/sources/data-city.html +0 -613
  1059. package/.agent/skills/motion-styles/sources/data-story.html +0 -539
  1060. package/.agent/skills/motion-styles/sources/deep-layers.html +0 -624
  1061. package/.agent/skills/motion-styles/sources/doc-cinema.html +0 -590
  1062. package/.agent/skills/motion-styles/sources/evidence-board.html +0 -610
  1063. package/.agent/skills/motion-styles/sources/exploded-view.html +0 -618
  1064. package/.agent/skills/motion-styles/sources/glass-icons.html +0 -612
  1065. package/.agent/skills/motion-styles/sources/glitch-pack.html +0 -583
  1066. package/.agent/skills/motion-styles/sources/globe-arcs.html +0 -621
  1067. package/.agent/skills/motion-styles/sources/headline-backdrop.html +0 -584
  1068. package/.agent/skills/motion-styles/sources/iso-cutaway.html +0 -621
  1069. package/.agent/skills/motion-styles/sources/isotype.html +0 -588
  1070. package/.agent/skills/motion-styles/sources/karaoke-subs.html +0 -577
  1071. package/.agent/skills/motion-styles/sources/low-poly-atlas.html +0 -621
  1072. package/.agent/skills/motion-styles/sources/lower-thirds.html +0 -582
  1073. package/.agent/skills/motion-styles/sources/marker-board.html +0 -598
  1074. package/.agent/skills/motion-styles/sources/market-tape.html +0 -598
  1075. package/.agent/skills/motion-styles/sources/math-canvas.html +0 -592
  1076. package/.agent/skills/motion-styles/sources/myth-or-fact.html +0 -587
  1077. package/.agent/skills/motion-styles/sources/neon-line.html +0 -597
  1078. package/.agent/skills/motion-styles/sources/paper-craft.html +0 -612
  1079. package/.agent/skills/motion-styles/sources/parchment-map.html +0 -624
  1080. package/.agent/skills/motion-styles/sources/physics-toybox.html +0 -617
  1081. package/.agent/skills/motion-styles/sources/pixel-quest.html +0 -609
  1082. package/.agent/skills/motion-styles/sources/podcast-clip.html +0 -585
  1083. package/.agent/skills/motion-styles/sources/pop-info.html +0 -677
  1084. package/.agent/skills/motion-styles/sources/product-turntable.html +0 -611
  1085. package/.agent/skills/motion-styles/sources/punch-captions.html +0 -583
  1086. package/.agent/skills/motion-styles/sources/quiz-countdown.html +0 -593
  1087. package/.agent/skills/motion-styles/sources/quote-cinema.html +0 -581
  1088. package/.agent/skills/motion-styles/sources/recipe-steps.html +0 -599
  1089. package/.agent/skills/motion-styles/sources/rubber-hose-reel.html +0 -627
  1090. package/.agent/skills/motion-styles/sources/shadow-puppets.html +0 -609
  1091. package/.agent/skills/motion-styles/sources/size-lineup.html +0 -620
  1092. package/.agent/skills/motion-styles/sources/software-walkthrough.html +0 -651
  1093. package/.agent/skills/motion-styles/sources/stack-split.html +0 -588
  1094. package/.agent/skills/motion-styles/sources/sticker-pop.html +0 -587
  1095. package/.agent/skills/motion-styles/sources/storybook.html +0 -600
  1096. package/.agent/skills/motion-styles/sources/swiss-grid.html +0 -590
  1097. package/.agent/skills/motion-styles/sources/synth-grid.html +0 -614
  1098. package/.agent/skills/motion-styles/sources/tactics-board.html +0 -608
  1099. package/.agent/skills/motion-styles/sources/terminal.html +0 -593
  1100. package/.agent/skills/motion-styles/sources/tier-list.html +0 -600
  1101. package/.agent/skills/motion-styles/sources/timeline-rail.html +0 -598
  1102. package/.agent/skills/motion-styles/sources/top-5-countdown.html +0 -589
  1103. package/.agent/skills/motion-styles/sources/versus-split.html +0 -592
  1104. package/.agent/skills/motion-styles/sources/vhs-camcorder.html +0 -587
  1105. package/.agent/skills/motion-styles/sources/visual-novel.html +0 -623
  1106. package/.agent/skills/motion-styles/sources/warm-canvas.html +0 -742
  1107. package/.agent/skills/motion-styles/sources/woodcut-tale.html +0 -598
  1108. package/.agent/skills/motion-styles/sources/zine-collage.html +0 -594
  1109. package/.agent/skills/reverse-engineer-anything/SKILL.md +0 -356
  1110. package/.agent/skills/reverse-engineer-anything/references/android-applications.md +0 -36
  1111. package/.agent/skills/reverse-engineer-anything/references/connection-and-recovery.md +0 -127
  1112. package/.agent/skills/reverse-engineer-anything/references/evidence-workflows.md +0 -71
  1113. package/.agent/skills/reverse-engineer-anything/references/javascript-applications.md +0 -218
  1114. package/.agent/skills/reverse-engineer-anything/references/native-and-artifacts.md +0 -297
  1115. package/.agent/skills/reverse-engineer-anything/references/runtime-observation.md +0 -78
  1116. package/.agent/skills/video-pipeline/SKILL.md +0 -384
  1117. package/.agent/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +0 -258
  1118. package/.agent/skills/video-pipeline/assets/project/STORYBOARD.md +0 -77
  1119. package/.agent/skills/video-pipeline/assets/project/index.html +0 -80
  1120. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +0 -43
  1121. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +0 -75
  1122. package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +0 -63
  1123. package/.agent/skills/video-pipeline/assets/project/reel/engine.css +0 -140
  1124. package/.agent/skills/video-pipeline/assets/project/reel/engine.js +0 -2143
  1125. package/.agent/skills/video-pipeline/assets/project/reel/virtual-clock.js +0 -168
  1126. package/.agent/skills/video-pipeline/references/mcp-tools.md +0 -441
  1127. package/.agent/skills/video-pipeline/references/motion-craft.md +0 -91
  1128. package/.agent/skills/video-pipeline/references/providers.md +0 -174
  1129. package/.agent/skills/video-pipeline/references/render-contract.md +0 -65
  1130. package/plugins/aiinsight-shorts/styles/deep-dive/style-sheet.png +0 -0
  1131. package/plugins/aiinsight-shorts/styles/inside-out/style-sheet.png +0 -0
  1132. package/plugins/aiinsight-shorts/styles/night-file/style-sheet.png +0 -0
  1133. package/plugins/aiinsight-shorts/styles/paper-atlas/style-sheet.png +0 -0
  1134. package/plugins/aiinsight-shorts/styles/stick-story/style-sheet.png +0 -0
  1135. package/plugins/aiinsight-shorts/styles/tall-tale/style-sheet.png +0 -0
  1136. package/plugins/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
  1137. package/plugins/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
  1138. package/plugins/faceless-shorts/styles/night-file/style-sheet.png +0 -0
  1139. package/plugins/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
  1140. package/plugins/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
  1141. package/plugins/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
@@ -1,2334 +0,0 @@
1
- /**
2
- * Browser lifecycle manager
3
- *
4
- * Chromium crash handling:
5
- * browser.on('disconnected') → log error → process.exit(1)
6
- * CLI detects dead server → auto-restarts on next command
7
- * We do NOT try to self-heal — don't hide failure.
8
- *
9
- * Dialog handling:
10
- * page.on('dialog') → auto-accept by default → store in dialog buffer
11
- * Prevents browser lockup from alert/confirm/prompt
12
- *
13
- * Context recreation (useragent):
14
- * recreateContext() saves cookies/storage/URLs, creates new context,
15
- * restores state. Falls back to clean slate on any failure.
16
- */
17
-
18
- import type { ChildProcess } from 'node:child_process';
19
- import { chromium, type Browser, type BrowserContext, type BrowserContextOptions, type Page, type Locator, type Cookie } from 'playwright';
20
- import { writeSecureFile, mkdirSecure } from './file-permissions';
21
- import { addConsoleEntry, addNetworkEntry, addDialogEntry, networkBuffer, type DialogEntry } from './buffers';
22
- import { emitActivity } from './activity';
23
- import { validateNavigationUrl, blockedNavigationReason } from './url-validation';
24
- import { TabSession, type RefEntry } from './tab-session';
25
- import { resolveChromiumProfile, cleanSingletonLocks, resolveConfig } from './config';
26
- import { ensureProjectProfile } from './chromium-profiles';
27
- import { launchWithXProtectHeal } from './xprotect-heal';
28
- import { readPidStartTime, shouldSpawnXvfb, spawnFreeXvfb, xvfbInstallHint, type XvfbHandle } from './xvfb';
29
- import { withCdpSession } from './cdp-bridge';
30
- import type { MemorySnapshot, MemoryStructureStats, MemoryTabSnapshot, MemoryProcess } from './memory-snapshot';
31
-
32
- /**
33
- * Headless GPU flags (#2709): on macOS 26 / Apple Silicon the headless-shell
34
- * GPU process can peg ~800% CPU indefinitely after real page work, and
35
- * --disable-gpu alone is not enough — the software-compositing GPU process
36
- * still spawns and still spins. The reporter validated this exact flag set
37
- * drops it to 0.0% with screenshots still working. darwin-gated: on Linux CI
38
- * and Windows the GPU process behaves, and the flags are a mild automation
39
- * tell. GSTACK_DISABLE_GPU=off opts out. Pure function (platform + env
40
- * injected) so the darwin behavior is unit-testable on any host. Deliberately
41
- * NOT in buildGStackLaunchArgs: that feeds the headed/GBrowser paths too,
42
- * where GPU-off is user-visibly wrong.
43
- */
44
- export function headlessGpuArgs(platform: string, env: NodeJS.ProcessEnv): string[] {
45
- if (platform !== 'darwin') return [];
46
- if ((env.GSTACK_DISABLE_GPU || '').toLowerCase() === 'off') return [];
47
- return [
48
- '--disable-gpu',
49
- '--disable-software-rasterizer',
50
- '--disable-gpu-compositing',
51
- '--disable-gpu-watchdog',
52
- ];
53
- }
54
-
55
- /**
56
- * Detect whether GSTACK_CHROMIUM_PATH points at a custom Chromium build that
57
- * already bakes the gstack extension in as a component extension (e.g.,
58
- * GStack Browser.app / GBrowser). Passing --load-extension against such a
59
- * binary triggers a ServiceWorkerState::SetWorkerId DCHECK because two
60
- * copies of the same service worker try to register.
61
- *
62
- * Resolution:
63
- * 1. GSTACK_CHROMIUM_KIND === 'custom-extension-baked' (preferred, explicit)
64
- * 2. GSTACK_CHROMIUM_PATH path substring contains 'GBrowser' or 'gbrowser'
65
- * (fallback for callers that only set the path)
66
- */
67
- export function isCustomChromium(): boolean {
68
- if (process.env.GSTACK_CHROMIUM_KIND === 'custom-extension-baked') return true;
69
- const p = process.env.GSTACK_CHROMIUM_PATH || '';
70
- return p.includes('GBrowser') || p.includes('gbrowser');
71
- }
72
-
73
- /**
74
- * Decide whether Playwright should request Chromium's sandbox.
75
- *
76
- * Returns false on Windows (Bun→Node→Chromium chain breaks the sandbox,
77
- * GitHub #276) and on Linux under root / CI / container (sandbox needs
78
- * unprivileged user namespaces, which are missing for root and typically
79
- * disabled in containers).
80
- *
81
- * When false, Playwright auto-adds --no-sandbox to the launch args — the
82
- * desired behavior in those environments. When true, Playwright does NOT
83
- * add --no-sandbox, which keeps Chromium's "unsupported command-line flag"
84
- * yellow infobar from appearing on every headed launch.
85
- *
86
- * The headless launch path also pushes an explicit '--no-sandbox' into args
87
- * when CI/CONTAINER/root is set; that push is now defensively redundant
88
- * (Playwright will add it anyway when this returns false) and harmless.
89
- */
90
- export function shouldEnableChromiumSandbox(): boolean {
91
- if (process.platform === 'win32') return false;
92
- // Explicit user override for Ubuntu/AppArmor and similar environments where
93
- // unprivileged Chromium sandboxing is blocked even for normal users (the
94
- // sandbox needs unprivileged user namespaces that the host policy denies,
95
- // so /qa hangs without --no-sandbox). Setting GSTACK_CHROMIUM_NO_SANDBOX=1
96
- // forces the sandbox off without changing the default for everyone else.
97
- // See #1562.
98
- if (process.env.GSTACK_CHROMIUM_NO_SANDBOX === '1') return false;
99
- const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
100
- return !(process.env.CI || process.env.CONTAINER || isRoot);
101
- }
102
-
103
- /**
104
- * Thrown by probePoisonedChromiumBundle() when it finds — and removes — a
105
- * Chromium bundle poisoned by the pre-v1.64 in-place rebrand (#2242).
106
- * Call sites rethrow on `instanceof` (never message-string sniffing) so the
107
- * actionable remediation reaches the user instead of being swallowed by the
108
- * probe's fall-through-on-failure catch.
109
- */
110
- export class PoisonedBundleError extends Error {
111
- constructor(message: string) {
112
- super(message);
113
- this.name = 'PoisonedBundleError';
114
- }
115
- }
116
-
117
- /**
118
- * Self-heal probe for bundles the OLD (pre-v1.64) rebrand code already
119
- * poisoned (#2242): the mutation lives in the SHARED Playwright cache, so
120
- * deleting the rebrand code fixes fresh installs only, and the documented
121
- * deploy paths never run upgrade migrations. Detect the mutated plist and
122
- * remove the bundle so the next `playwright install chromium` (or the
123
- * upgrade migration) re-fetches a clean one.
124
- *
125
- * Removal scope: when the .app sits in the standard Playwright cache layout
126
- * (chromium-<rev>/chrome-mac/<name>.app), the WHOLE chromium-<rev> revision
127
- * dir is removed — Playwright's INSTALLATION_COMPLETE marker lives there,
128
- * and `playwright install chromium` treats its presence as "is already
129
- * downloaded", so removing only the .app would turn our own remediation
130
- * command into a no-op that leaves the user with no browser at all. Outside
131
- * that layout, the .app plus any sibling INSTALLATION_COMPLETE /
132
- * DEPENDENCIES_VALIDATED markers are removed.
133
- *
134
- * Caller contract: pass ONLY Playwright-cache executables
135
- * (chromium.executablePath()). A bundle supplied via GSTACK_CHROMIUM_PATH
136
- * belongs to the wrapper/embedder — its plist legitimately says "GStack
137
- * Browser" — and must never be deleted. Both call sites (launchHeaded and
138
- * handoff) honor this, and as a second belt the probe refuses to act on the
139
- * GSTACK_CHROMIUM_PATH executable itself.
140
- *
141
- * @param chromiumExecutablePath the Chromium binary inside the .app
142
- * (…/<name>.app/Contents/MacOS/<name>), as returned by
143
- * chromium.executablePath().
144
- * @throws PoisonedBundleError after removing a poisoned bundle — the
145
- * message carries the re-fetch command for the user.
146
- */
147
- export function probePoisonedChromiumBundle(chromiumExecutablePath: string): void {
148
- const fs = require('fs');
149
- const path = require('path');
150
-
151
- // Belt to the caller contract: never act on the custom/embedder bundle.
152
- const customPath = process.env.GSTACK_CHROMIUM_PATH;
153
- if (customPath && path.resolve(chromiumExecutablePath) === path.resolve(customPath)) {
154
- return;
155
- }
156
-
157
- const chromeContentsDir = path.resolve(path.dirname(chromiumExecutablePath), '..');
158
- const chromePlist = path.join(chromeContentsDir, 'Info.plist');
159
- if (!fs.existsSync(chromePlist)) return;
160
- if (!fs.readFileSync(chromePlist, 'utf-8').includes('GStack Browser')) return;
161
-
162
- const appDir = path.resolve(chromeContentsDir, '..');
163
- const revisionDir = path.resolve(appDir, '..', '..');
164
- if (/^chromium-\d+$/.test(path.basename(revisionDir))) {
165
- fs.rmSync(revisionDir, { recursive: true, force: true });
166
- } else {
167
- fs.rmSync(appDir, { recursive: true, force: true });
168
- for (const marker of ['INSTALLATION_COMPLETE', 'DEPENDENCIES_VALIDATED']) {
169
- fs.rmSync(path.join(path.dirname(appDir), marker), { force: true });
170
- }
171
- }
172
- throw new PoisonedBundleError(
173
- 'Chromium bundle was mutated by a previous gstack version (broken codesign seal — ' +
174
- 'GPU exit_code=5 on macOS 26). The poisoned bundle has been removed. ' +
175
- 'Re-fetch a clean one with: bunx playwright install chromium — then retry.',
176
- );
177
- }
178
-
179
- /** Playwright's public Browser type omits `process()`, which only browsers we launched provide. */
180
- function launchedProcess(browser: Browser | null | undefined): ChildProcess | null {
181
- const withProcess = browser as (Browser & { process?: () => ChildProcess | null }) | null | undefined;
182
- return typeof withProcess?.process === 'function' ? withProcess.process() : null;
183
- }
184
-
185
- /**
186
- * #2709: PID of the Chromium browser process gstack launched, read over CDP
187
- * (`SystemInfo.getProcessInfo`, type "browser") because Playwright 1.62's
188
- * Browser has no `process()`. Call it only for a launch gstack owns; a
189
- * connected browser belongs to someone else and must never be reaped. Bounded:
190
- * a hung or failed CDP call logs once and returns null, which means no reap.
191
- */
192
- export async function launchedChromiumPid(browser: Browser, timeoutMs = 2_000): Promise<number | null> {
193
- let timer: ReturnType<typeof setTimeout> | undefined;
194
- const query = async (): Promise<number | null> => {
195
- const session = await browser.newBrowserCDPSession();
196
- try {
197
- const { processInfo } = await session.send('SystemInfo.getProcessInfo');
198
- const pid = processInfo.find((p) => p.type === 'browser')?.id;
199
- return Number.isInteger(pid) && pid! > 0 ? pid! : null;
200
- } finally {
201
- await session.detach().catch(() => {});
202
- }
203
- };
204
- try {
205
- return await Promise.race([
206
- query(),
207
- new Promise<never>((_, reject) => {
208
- timer = setTimeout(() => reject(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
209
- }),
210
- ]);
211
- } catch (err) {
212
- const msg = err instanceof Error ? err.message : String(err);
213
- console.warn(`[browse] Could not record the Chromium PID (CDP SystemInfo.getProcessInfo: ${msg}); browse stop cannot reap a surviving Chromium.`);
214
- return null;
215
- } finally {
216
- clearTimeout(timer);
217
- }
218
- }
219
-
220
- /**
221
- * Resolve why the underlying Chromium ChildProcess is going away.
222
- *
223
- * The 'disconnected' Playwright event fires before the child process emits
224
- * its own 'exit' in most cases, so .exitCode is null at that moment. Wait
225
- * briefly (capped at 1s) for the exit then read .exitCode + .signalCode:
226
- *
227
- * exitCode === 0 && no signal → 'clean' (user Cmd+Q, normal shutdown)
228
- * anything else → 'crash' (signal-kill, SIGSEGV, OOM, non-zero exit)
229
- *
230
- * Process supervisors (gbrowser's gbd HealthMonitor in cmd/gbd/health.go)
231
- * read our exit code to decide whether to restart. The two callers in this
232
- * file ride on top of this: a 'clean' result exits with code 0 (gbd skips
233
- * restart, treats as user-intent); a 'crash' result keeps the existing
234
- * per-path exit semantics (launch→1, launchHeaded→2, handoff→1) and gbd
235
- * restarts on backoff.
236
- */
237
- export async function resolveDisconnectCause(browser: Browser | null): Promise<'clean' | 'crash'> {
238
- // `.process()` only exists on browsers we launched ourselves. A browser
239
- // obtained via connectOverCDP() (or a stub in tests) has no such method —
240
- // calling it blind throws inside the disconnect handler, which killed the
241
- // whole daemon with "browser?.process is not a function".
242
- const proc = launchedProcess(browser);
243
- if (proc && proc.exitCode === null && proc.signalCode === null) {
244
- await new Promise<void>((resolve) => {
245
- const timer = setTimeout(resolve, 1000);
246
- proc.once('exit', () => {
247
- clearTimeout(timer);
248
- resolve();
249
- });
250
- });
251
- }
252
- return proc?.exitCode === 0 && proc?.signalCode == null ? 'clean' : 'crash';
253
- }
254
-
255
- /**
256
- * Exit-on-disconnect is DAEMON-ONLY semantics. The standalone server
257
- * entrypoint opts in via markDaemonProcess() (under its import.meta.main
258
- * gate, same contract as its signal handlers); embedders — gbrowser
259
- * phoenix, and every test that launches a BrowserManager in-process —
260
- * must never have a Chromium crash process.exit() their HOST. Observed
261
- * live before this flag: a test-launched browser died mid-suite and the
262
- * exit(1) killed the whole bun shard with no terminal summary (the
263
- * truncation class the strict runner exists to catch).
264
- */
265
- let daemonProcess = false;
266
- export function markDaemonProcess(): void {
267
- daemonProcess = true;
268
- }
269
-
270
- /**
271
- * Headless `launch()` disconnect handler. In the standalone daemon: exits 0
272
- * on clean user-quit, 1 on crash. Embedded contexts get the log line only.
273
- * Inlined into the launch() body via a one-line dispatch so
274
- * browser-manager's flow stays grep-friendly.
275
- */
276
- export async function handleChromiumDisconnect(browser: Browser | null): Promise<void> {
277
- const cause = await resolveDisconnectCause(browser);
278
- if (!daemonProcess) {
279
- console.error(`[browse] Chromium disconnected (${cause}) in an embedded context — host process continues.`);
280
- return;
281
- }
282
- if (cause === 'clean') {
283
- console.error('[browse] Chromium closed cleanly (user-initiated quit). Server exiting (0).');
284
- process.exit(0);
285
- }
286
- console.error('[browse] FATAL: Chromium process crashed or was killed. Server exiting (1).');
287
- console.error('[browse] Console/network logs flushed to .gstack/browse-*.log');
288
- process.exit(1);
289
- }
290
-
291
- export type { RefEntry };
292
-
293
- // Re-export TabSession for consumers
294
- export { TabSession };
295
-
296
- export interface BrowserState {
297
- cookies: Cookie[];
298
- pages: Array<{
299
- url: string;
300
- isActive: boolean;
301
- storage: { localStorage: Record<string, string>; sessionStorage: Record<string, string> } | null;
302
- /**
303
- * HTML content loaded via load-html (setContent), replayed after context recreation.
304
- * In-memory only — never persisted to disk (HTML may contain secrets or customer data).
305
- */
306
- loadedHtml?: string;
307
- loadedHtmlWaitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
308
- /**
309
- * Tab owner clientId for multi-agent isolation. Survives context recreation so
310
- * scoped agents don't get locked out of their own tabs after viewport --scale.
311
- * In-memory only.
312
- */
313
- owner?: string;
314
- }>;
315
- }
316
-
317
- export class BrowserManager {
318
- private browser: Browser | null = null;
319
- private context: BrowserContext | null = null;
320
- // Proxy config applied to chromium.launch() when set (D8). Set by server.ts
321
- // at startup based on BROWSE_PROXY_URL. For SOCKS5 with auth, server.ts
322
- // points this at the local bridge (socks5://127.0.0.1:<bridgePort>); for
323
- // HTTP/HTTPS or unauth SOCKS5, it's the upstream URL directly.
324
- private proxyConfig: { server: string; bypass?: string; username?: string; password?: string } | null = null;
325
- private pages: Map<number, Page> = new Map();
326
- private tabSessions: Map<number, TabSession> = new Map();
327
- private activeTabId: number = 0;
328
- private nextTabId: number = 1;
329
- private extraHeaders: Record<string, string> = {};
330
- private customUserAgent: string | null = null;
331
- // #2709: identity of the Chromium child WE launched (headless path). The
332
- // headless launch has no userDataDir, so killOrphanChromium's SingletonLock
333
- // walk is a structural no-op for it — `browse stop` reported success while
334
- // the orphaned GPU process kept spinning. PID alone is not identity (reuse);
335
- // start time makes the later reap safe (same contract as xvfbPid/xvfbStartTime).
336
- private chromiumProcInfo: { pid: number; startTime: string } | null = null;
337
-
338
- /** PID + start-time of the launched Chromium child, when we own one. */
339
- getChromiumProcInfo(): { pid: number; startTime: string } | null {
340
- return this.chromiumProcInfo;
341
- }
342
-
343
- // ─── Viewport + deviceScaleFactor (context options) ──────────
344
- // Tracked at the manager level so recreateContext() preserves them.
345
- // deviceScaleFactor is a *context* option, not a page-level setter — changes
346
- // require recreateContext(). Viewport width/height can change on-page, but we
347
- // track the latest so context recreation restores it instead of hardcoding 1280x720.
348
- private deviceScaleFactor: number = 1;
349
- private currentViewport: { width: number; height: number } = { width: 1280, height: 720 };
350
-
351
- /** Server port — set after server starts, used by cookie-import-browser command */
352
- public serverPort: number = 0;
353
-
354
- // ─── Tab Ownership (multi-agent isolation) ──────────────
355
- // Maps tabId → clientId. Unowned tabs (not in this map) are root-only for writes.
356
- private tabOwnership: Map<number, string> = new Map();
357
- /** Headless launch with BROWSE_EXTENSIONS_DIR: a persistent context (#2281). */
358
- private extensionContext = false;
359
- /** Pending and recorded navigation-guard verdicts per page (D2). */
360
- private navigationGuards = new WeakMap<Page, { pending: Set<Promise<void>>; blocked: string | null }>();
361
- // Our own 'disconnected' listeners per browser. Detach only these: removing
362
- // every 'disconnected' listener also strips Playwright's internal one that
363
- // resolves browser.close(), so each close waited the full race timeout (and
364
- // Playwright 1.62 has no Browser.process() for the SIGKILL fallback).
365
- private disconnectHandlers = new WeakMap<Browser, Array<() => void>>();
366
- private onBrowserDisconnected(browser: Browser | null | undefined, handler: () => void): void {
367
- if (!browser) return;
368
- browser.on('disconnected', handler);
369
- this.disconnectHandlers.set(browser, [...(this.disconnectHandlers.get(browser) ?? []), handler]);
370
- }
371
- private detachDisconnectHandlers(browser: Browser | null | undefined): void {
372
- if (!browser) return;
373
- for (const handler of this.disconnectHandlers.get(browser) ?? []) browser.off('disconnected', handler);
374
- this.disconnectHandlers.delete(browser);
375
- }
376
-
377
- // ─── Dialog Handling (global, not per-tab) ──────────────────
378
- private dialogAutoAccept: boolean = true;
379
- private dialogPromptText: string | null = null;
380
-
381
- // ─── Cookie Origin Tracking ────────────────────────────────
382
- private cookieImportedDomains: Set<string> = new Set();
383
-
384
- // ─── Handoff State ─────────────────────────────────────────
385
- private isHeaded: boolean = false;
386
- private consecutiveFailures: number = 0;
387
-
388
- // ─── Watch Mode ─────────────────────────────────────────
389
- private watching = false;
390
- public watchInterval: ReturnType<typeof setInterval> | null = null;
391
- private watchSnapshots: string[] = [];
392
- private watchStartTime: number = 0;
393
-
394
- // ─── Headed State ────────────────────────────────────────
395
- private connectionMode: 'launched' | 'headed' = 'launched';
396
-
397
- /**
398
- * Fired when a RUNNING daemon is promoted to headed mode (see handoff()),
399
- * as opposed to starting headed. The server uses it to cancel the
400
- * parent-process watchdog, which was registered on the assumption that mode
401
- * is fixed at boot and would otherwise kill the freshly handed-off browser
402
- * the next time the spawning shell exits.
403
- */
404
- onHeadedPromotion?: () => void;
405
- private intentionalDisconnect = false;
406
- private xvfb: XvfbHandle | null = null;
407
- private displayAllocation: Promise<void> | null = null;
408
- private handoffPending: Promise<string> | null = null;
409
- private closing = false;
410
- private handoffPrevious: { browser: Browser; pages: Map<number, Page>; tabSessions: Map<number, TabSession>; activeTabId: number } | null = null;
411
-
412
- getXvfbHandle(): XvfbHandle | null { return this.xvfb; }
413
-
414
- async ensureHeadedDisplay(): Promise<void> {
415
- if (this.closing) throw new Error('Browser is shutting down');
416
- if (this.xvfb) return;
417
- if (this.displayAllocation) return this.displayAllocation;
418
- if (!shouldSpawnXvfb({ ...process.env, BROWSE_HEADED: '1' }, process.platform).spawn) return;
419
- this.displayAllocation = (async () => {
420
- try {
421
- const handle = await spawnFreeXvfb();
422
- if (this.closing) {
423
- handle.close();
424
- throw new Error('Browser is shutting down');
425
- }
426
- this.xvfb = handle;
427
- } catch (err) {
428
- throw new Error(`${err instanceof Error ? err.message : String(err)}. ${xvfbInstallHint()}`);
429
- }
430
- })();
431
- try { await this.displayAllocation; }
432
- finally { this.displayAllocation = null; }
433
- }
434
-
435
- closeOwnedDisplay(): void {
436
- this.xvfb?.close();
437
- this.xvfb = null;
438
- }
439
-
440
- // ─── Tab Count Guardrail (D5 + Codex single-tab flag) ───────
441
- // Idempotent threshold trackers: each guardrail fires exactly once per
442
- // upward crossing of its threshold and re-arms when the tab count drops
443
- // back below. Pre-guardrail, nothing tracked tab count growth and a
444
- // user could accumulate hundreds of tabs (each holding 50–300 MB of
445
- // Chromium-side RSS) without warning until the OS OOM-killer fired.
446
- // The toast UX lives in the sidebar (extension/sidepanel.js); the
447
- // server-side responsibility is the audit-trail activity entry that
448
- // appears in the activity feed even when the sidebar is closed.
449
- private static readonly TAB_GUARDRAIL_SOFT = 50;
450
- private static readonly TAB_GUARDRAIL_HARD = 200;
451
- private tabGuardrailSoftHit = false;
452
- private tabGuardrailHardHit = false;
453
-
454
- /**
455
- * Called from context.on('page') after a new tab is tracked. Emits at
456
- * most one activity entry per upward crossing of each threshold.
457
- */
458
- private checkTabGuardrails(): void {
459
- const total = this.pages.size;
460
- if (!this.tabGuardrailSoftHit && total >= BrowserManager.TAB_GUARDRAIL_SOFT) {
461
- this.tabGuardrailSoftHit = true;
462
- const msg = `Tab count crossed ${BrowserManager.TAB_GUARDRAIL_SOFT} (now ${total}). Consider closing unused tabs — each Chromium tab holds 50–300 MB.`;
463
- console.warn(`[browse] ${msg}`);
464
- emitActivity({ type: 'error', command: 'tab-guardrail', error: msg, tabs: total });
465
- }
466
- if (!this.tabGuardrailHardHit && total >= BrowserManager.TAB_GUARDRAIL_HARD) {
467
- this.tabGuardrailHardHit = true;
468
- const msg = `Tab count crossed ${BrowserManager.TAB_GUARDRAIL_HARD} (now ${total}). OOM risk imminent. Open the sidebar to see top RAM consumers.`;
469
- console.error(`[browse] ${msg}`);
470
- emitActivity({ type: 'error', command: 'tab-guardrail', error: msg, tabs: total });
471
- }
472
- }
473
-
474
- /** Called from page.on('close') so the guardrails re-arm. */
475
- private recheckTabGuardrailsOnClose(): void {
476
- const total = this.pages.size;
477
- if (this.tabGuardrailSoftHit && total < BrowserManager.TAB_GUARDRAIL_SOFT) {
478
- this.tabGuardrailSoftHit = false;
479
- }
480
- if (this.tabGuardrailHardHit && total < BrowserManager.TAB_GUARDRAIL_HARD) {
481
- this.tabGuardrailHardHit = false;
482
- }
483
- }
484
-
485
- // Called when the headed browser disconnects without intentional teardown
486
- // (user closed the window). Wired up by server.ts to run full cleanup
487
- // (terminal agent, state file, profile locks) before exiting with code 2.
488
- // Returns void or a Promise; rejections are caught and fall back to exit(2).
489
- // `exitCode` is the resolved process exit code from the disconnect cause:
490
- // 0 on clean user-initiated quit (e.g., Cmd+Q on headed Chromium), 2 on
491
- // crash/signal-kill. Callers (server.ts) forward it to their shutdown
492
- // pipeline so process supervisors (gbrowser's gbd) read the right signal.
493
- public onDisconnect: ((exitCode?: number) => void | Promise<void>) | null = null;
494
-
495
- getConnectionMode(): 'launched' | 'headed' { return this.connectionMode; }
496
-
497
- // ─── Watch Mode Methods ─────────────────────────────────
498
- isWatching(): boolean { return this.watching; }
499
-
500
- startWatch(): void {
501
- this.watching = true;
502
- this.watchSnapshots = [];
503
- this.watchStartTime = Date.now();
504
- }
505
-
506
- stopWatch(): { snapshots: string[]; duration: number } {
507
- this.watching = false;
508
- if (this.watchInterval) {
509
- clearInterval(this.watchInterval);
510
- this.watchInterval = null;
511
- }
512
- const snapshots = this.watchSnapshots;
513
- const duration = Date.now() - this.watchStartTime;
514
- this.watchSnapshots = [];
515
- this.watchStartTime = 0;
516
- return { snapshots, duration };
517
- }
518
-
519
- addWatchSnapshot(snapshot: string): void {
520
- this.watchSnapshots.push(snapshot);
521
- }
522
-
523
- /**
524
- * Find the gstack Chrome extension directory.
525
- * Checks: repo root /extension, global install, dev install.
526
- */
527
- private findExtensionPath(): string | null {
528
- const fs = require('fs');
529
- const path = require('path');
530
- const candidates = [
531
- // Explicit override via env var (used by GStack Browser.app bundle)
532
- process.env.BROWSE_EXTENSIONS_DIR || '',
533
- // Relative to this source file (dev mode: browse/src/ -> ../../extension)
534
- path.resolve(__dirname, '..', '..', 'extension'),
535
- // Global gstack install
536
- path.join(process.env.HOME || '', '.claude', 'skills', 'gstack', 'extension'),
537
- // Git repo root (detected via BROWSE_STATE_FILE location)
538
- (() => {
539
- const stateFile = process.env.BROWSE_STATE_FILE || '';
540
- if (stateFile) {
541
- const repoRoot = path.resolve(path.dirname(stateFile), '..');
542
- return path.join(repoRoot, '.claude', 'skills', 'gstack', 'extension');
543
- }
544
- return '';
545
- })(),
546
- ].filter(Boolean);
547
-
548
- for (const candidate of candidates) {
549
- try {
550
- if (fs.existsSync(path.join(candidate, 'manifest.json'))) {
551
- return candidate;
552
- }
553
- } catch (err: any) {
554
- if (err?.code !== 'ENOENT' && err?.code !== 'EACCES') throw err;
555
- }
556
- }
557
- return null;
558
- }
559
-
560
- /**
561
- * Set the proxy config applied to chromium.launch() in launch() and
562
- * launchHeaded(). Called by server.ts at startup once the (optional) SOCKS5
563
- * bridge is up.
564
- */
565
- setProxyConfig(cfg: { server: string; bypass?: string; username?: string; password?: string } | null): void {
566
- this.proxyConfig = cfg;
567
- }
568
-
569
- /**
570
- * Get the ref map for external consumers (e.g., /refs endpoint).
571
- */
572
- getRefMap(): Array<{ ref: string; role: string; name: string }> {
573
- try {
574
- return this.getActiveSession().getRefEntries();
575
- } catch {
576
- return [];
577
- }
578
- }
579
-
580
- async launch() {
581
- this.closing = false;
582
- // ─── Extension Support ────────────────────────────────────
583
- // BROWSE_EXTENSIONS_DIR points to an unpacked Chrome extension directory.
584
- // Extensions only run in a persistent context (launch() + newContext()
585
- // isolates them), so that path uses launchPersistentContext with Chrome's
586
- // new headless mode: no window, unlike the old off-screen window that
587
- // macOS still showed (#2281, #432).
588
- const extensionsDir = process.env.BROWSE_EXTENSIONS_DIR;
589
- const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs, STEALTH_IGNORE_DEFAULT_ARGS } = await import('./stealth');
590
- const launchArgs: string[] = [...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs()];
591
- // #2771/#1968: honor GSTACK_CHROMIUM_PATH headless too (NixOS, macOS 13).
592
- // That bundle belongs to the user, so the XProtect heal never touches it,
593
- // and a launch failure names it.
594
- const executablePath = process.env.GSTACK_CHROMIUM_PATH || undefined;
595
- const nameCustomChromium = (err: unknown): never => {
596
- if (!executablePath) throw err;
597
- const message = err instanceof Error ? err.message : String(err);
598
- throw new Error(`Chromium at GSTACK_CHROMIUM_PATH=${executablePath} failed to launch: ${message}\nFix: point GSTACK_CHROMIUM_PATH at a working Chromium or Chrome binary, or unset it to use Playwright's bundled Chromium.`, { cause: err });
599
- };
600
-
601
- // Docker/CI/root: Chromium sandbox requires unprivileged user namespaces which
602
- // are typically disabled in containers and are never available for the root
603
- // user on Linux. Detect all three cases and add --no-sandbox automatically.
604
- const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
605
- if (process.env.CI || process.env.CONTAINER || isRoot) {
606
- launchArgs.push('--no-sandbox');
607
- }
608
-
609
- if (extensionsDir) {
610
- // Skip --load-extension when running against a custom Chromium build that
611
- // already bakes the extension in (e.g., GBrowser / GStack Browser.app).
612
- // Loading it twice causes a ServiceWorkerState::SetWorkerId DCHECK crash.
613
- if (!isCustomChromium()) {
614
- launchArgs.push(
615
- `--disable-extensions-except=${extensionsDir}`,
616
- `--load-extension=${extensionsDir}`,
617
- );
618
- }
619
- launchArgs.push('--headless=new');
620
- console.log(`[browse] Extensions loaded from: ${extensionsDir}`);
621
- }
622
-
623
- // #2709: headless-only GPU flags; headed/GBrowser sessions keep the GPU.
624
- launchArgs.push(...headlessGpuArgs(process.platform, process.env));
625
-
626
- const contextOptions: BrowserContextOptions = {
627
- viewport: { width: this.currentViewport.width, height: this.currentViewport.height },
628
- deviceScaleFactor: this.deviceScaleFactor,
629
- };
630
- if (this.customUserAgent) {
631
- contextOptions.userAgent = this.customUserAgent;
632
- }
633
-
634
- if (extensionsDir) {
635
- const profileDir = require('path').join(resolveConfig().stateDir, 'extensions', 'chromium-profile');
636
- require('fs').mkdirSync(profileDir, { recursive: true, mode: 0o700 });
637
- cleanSingletonLocks(profileDir);
638
- this.context = await launchWithXProtectHeal(() => chromium.launchPersistentContext(profileDir, {
639
- headless: false, // --headless=new in launchArgs: windowless, extensions run
640
- handleSIGINT: false,
641
- handleSIGTERM: false,
642
- handleSIGHUP: false,
643
- chromiumSandbox: shouldEnableChromiumSandbox(),
644
- args: launchArgs,
645
- ignoreDefaultArgs: STEALTH_IGNORE_DEFAULT_ARGS,
646
- ...contextOptions,
647
- ...(executablePath ? { executablePath } : {}),
648
- ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
649
- }), { usesCustomExecutable: Boolean(executablePath) }).catch(nameCustomChromium);
650
- this.extensionContext = true;
651
- this.browser = this.context.browser();
652
- this.onBrowserDisconnected(this.browser, () => {
653
- void handleChromiumDisconnect(this.browser);
654
- });
655
- for (const page of this.context.pages()) await page.close().catch(() => {});
656
- if (Object.keys(this.extraHeaders).length > 0) {
657
- await this.context.setExtraHTTPHeaders(this.extraHeaders);
658
- }
659
- const { applyStealth } = await import('./stealth');
660
- await applyStealth(this.context);
661
- await this.newTab();
662
- return;
663
- }
664
-
665
- // XProtect self-heal wrapper (P0 #2554): a macOS definition update can
666
- // start SIGKILLing the pinned Chromium at spawn. On the classified
667
- // signature, clear quarantine on the Playwright cache + force-reinstall
668
- // once, then retry this launch once. A GSTACK_CHROMIUM_PATH bundle is
669
- // scoped out of the heal.
670
- this.browser = await launchWithXProtectHeal(() => chromium.launch({
671
- headless: true,
672
- // #2220: the daemon owns signal policy, not Playwright. Playwright's
673
- // default handlers close Chromium the moment THIS process receives
674
- // SIGINT/SIGTERM/SIGHUP — which fights the deliberate headless
675
- // SIGTERM-ignore in server.ts (the daemon survives the signal but
676
- // loses its browser out from under it). All three are false; server.ts
677
- // routes the signals it actually honors through activeShutdown, which
678
- // closes Chromium itself.
679
- handleSIGINT: false,
680
- handleSIGTERM: false,
681
- handleSIGHUP: false,
682
- // On Windows, Chromium's sandbox fails when the server is spawned through
683
- // the Bun→Node process chain (GitHub #276). Disable it — local daemon
684
- // browsing user-specified URLs has marginal sandbox benefit. Also disabled
685
- // on Linux root/CI/container, where the sandbox requires unprivileged user
686
- // namespaces that aren't available.
687
- chromiumSandbox: shouldEnableChromiumSandbox(),
688
- ...(launchArgs.length > 0 ? { args: launchArgs } : {}),
689
- ...(executablePath ? { executablePath } : {}),
690
- ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
691
- }), { usesCustomExecutable: Boolean(executablePath) }).catch(nameCustomChromium);
692
-
693
- // Chromium disconnect → distinguish clean user-quit from crash. Both
694
- // events look identical to Playwright (one 'disconnected' fires), but
695
- // the underlying ChildProcess exit code separates them:
696
- // exitCode === 0 → clean quit (user Cmd+Q on macOS, normal shutdown)
697
- // exitCode !== 0 → crash, signal-kill, or OOM
698
- // Process supervisors (gbrowser's gbd) consume our exit code: code 0
699
- // means "user wanted this, don't restart"; non-zero means "crash, please
700
- // bring me back." Without this distinction every Cmd+Q gets treated as
701
- // a crash and the user-visible window keeps respawning.
702
- this.onBrowserDisconnected(this.browser, () => {
703
- void handleChromiumDisconnect(this.browser);
704
- });
705
-
706
- // #2709: record the child's identity so the CLI can reap a survivor after
707
- // daemon shutdown. We launched this browser, so its PID is ours to record.
708
- {
709
- const pid = await launchedChromiumPid(this.browser);
710
- this.chromiumProcInfo = pid ? { pid, startTime: readPidStartTime(pid) } : null;
711
- }
712
-
713
- this.context = await this.browser.newContext(contextOptions);
714
-
715
- if (Object.keys(this.extraHeaders).length > 0) {
716
- await this.context.setExtraHTTPHeaders(this.extraHeaders);
717
- }
718
-
719
- // Apply Layer C stealth (applyStealth): masks navigator.webdriver,
720
- // restores window.chrome.* shape, aligns Notification.permission, sets
721
- // per-install hardware, and strips automation globals + the Permissions
722
- // notifications tell. We still do NOT fake navigator.plugins/languages —
723
- // faking those to fixed values flags more bot-like, not less (D7).
724
- const { applyStealth } = await import('./stealth');
725
- await applyStealth(this.context);
726
-
727
- // Create first tab
728
- await this.newTab();
729
- }
730
-
731
- // ─── Headed Mode ─────────────────────────────────────────────
732
- /**
733
- * Launch Playwright's bundled Chromium in headed mode with the gstack
734
- * Chrome extension auto-loaded. Uses launchPersistentContext() which
735
- * is required for extension loading (launch() + newContext() can't
736
- * load extensions).
737
- *
738
- * The browser launches headed with a visible window — the user sees
739
- * every action Claude takes in real time.
740
- */
741
- async launchHeaded(authToken?: string): Promise<void> {
742
- this.closing = false;
743
- await this.ensureHeadedDisplay();
744
- // Clear old state before repopulating
745
- this.pages.clear();
746
- this.tabSessions.clear();
747
- this.nextTabId = 1;
748
-
749
- // Find the gstack extension directory for auto-loading
750
- const extensionPath = this.findExtensionPath();
751
- const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
752
- const launchArgs = [
753
- '--hide-crash-restore-bubble',
754
- // Anti-bot-detection: --disable-blink-features=AutomationControlled (and any
755
- // future blink-level tells) via the shared STEALTH_LAUNCH_ARGS constant — the
756
- // same flag launch() and handoff() use, kept in one place instead of a literal.
757
- ...STEALTH_LAUNCH_ARGS,
758
- // GStack Pack 1: per-install hardware/GPU/UA-CH overrides for the
759
- // C++ patches in gbrowser's Chromium build. Each switch is a no-op
760
- // on Chromium builds without the corresponding patch (the patch's
761
- // empty-fallback returns native), so this is safe on stock Playwright
762
- // Chromium too.
763
- ...buildGStackLaunchArgs(),
764
- ];
765
- if (extensionPath) {
766
- // Skip --load-extension when running against a custom Chromium build
767
- // that already bakes the extension in as a component extension
768
- // (gbrowser / GStack Browser.app). Loading it twice causes a
769
- // ServiceWorkerState::SetWorkerId DCHECK crash.
770
- if (!isCustomChromium()) {
771
- launchArgs.push(`--disable-extensions-except=${extensionPath}`);
772
- launchArgs.push(`--load-extension=${extensionPath}`);
773
- }
774
- // Write auth token for extension bootstrap (still required even when
775
- // the extension is component-baked — it reads ~/.gstack/.auth.json at
776
- // startup to learn how to call the daemon).
777
- // Write to ~/.gstack/.auth.json (not the extension dir, which may be read-only
778
- // in .app bundles and breaks codesigning).
779
- if (authToken) {
780
- const fs = require('fs');
781
- const path = require('path');
782
- const gstackDir = path.join(process.env.HOME || '/tmp', '.gstack');
783
- mkdirSecure(gstackDir);
784
- const authFile = path.join(gstackDir, '.auth.json');
785
- try {
786
- writeSecureFile(authFile, JSON.stringify({ token: authToken, port: this.serverPort || 34567 }));
787
- } catch (err: any) {
788
- console.warn(`[browse] Could not write .auth.json: ${err.message}`);
789
- }
790
- }
791
- }
792
-
793
- // Launch headed Chromium via Playwright's persistent context.
794
- // Extensions REQUIRE launchPersistentContext (not launch + newContext).
795
- // Real Chrome (executablePath/channel) silently blocks --load-extension,
796
- // so we use Playwright's bundled Chromium which reliably loads extensions.
797
- const fs = require('fs');
798
- const path = require('path');
799
- const userDataDir = resolveChromiumProfile();
800
- ensureProjectProfile(userDataDir);
801
- fs.mkdirSync(userDataDir, { recursive: true });
802
-
803
- // Pre-launch cleanup of stale SingletonLock/Socket/Cookie. Chromium's
804
- // ProcessSingleton refuses to start when these exist from a prior crash
805
- // (SIGKILL, hard crash) — the lockfiles point at a PID that may no longer
806
- // exist. Shutdown cleanup doesn't run on hard crashes, so we clean here
807
- // too. Safe under external coordination: gbd.lock for gbrowser,
808
- // single-instance CLI check for gstack.
809
- cleanSingletonLocks(userDataDir);
810
-
811
- // Support custom Chromium binary via GSTACK_CHROMIUM_PATH env var.
812
- // Used by GStack Browser.app to point at the bundled Chromium.
813
- const executablePath = process.env.GSTACK_CHROMIUM_PATH || undefined;
814
-
815
- // NOTE (#2242): the in-place "rebrand" that patched the Chromium .app's
816
- // Info.plist (global "Google Chrome for Testing" → "GStack Browser"
817
- // replace) and overwrote its Resources/*.icns is deliberately GONE.
818
- // Chrome for Testing is a code-signed bundle: the global replace renamed
819
- // CFBundleExecutable to a binary that doesn't exist and the plist/icon
820
- // writes broke the codesign seal — GPU process exit_code=5, headed mode
821
- // dead on macOS 26 (#2242, #2138, #2139). Branding belongs in the
822
- // GStack Browser.app wrapper (GSTACK_CHROMIUM_PATH), never in a mutation
823
- // of the signed bundle. Do not reintroduce writes into the Chromium
824
- // bundle here — browse/test/rebrand-signed-bundle.test.ts fails CI if
825
- // you do.
826
- //
827
- // Self-heal for bundles the OLD code already poisoned: probe the
828
- // Playwright-cache bundle and remove it when the mutated plist is
829
- // present (see probePoisonedChromiumBundle for the removal-scope
830
- // rationale). Scoped to the Playwright cache copy — a
831
- // GSTACK_CHROMIUM_PATH bundle belongs to the wrapper/embedder and is
832
- // never probed.
833
- if (!executablePath) {
834
- try {
835
- probePoisonedChromiumBundle(chromium.executablePath());
836
- } catch (err: unknown) {
837
- if (err instanceof PoisonedBundleError) throw err;
838
- // Probe failures (no bundle yet, EACCES) fall through to launch,
839
- // which produces its own actionable error.
840
- }
841
- }
842
-
843
- // Build custom user agent: report as stock Chrome with the version
844
- // matching the underlying Chromium binary. D6 (codex #18 correction):
845
- // the previous "GStackBrowser" branding suffix was itself a high-entropy
846
- // classifier — sites grepping UA for known browser strings caught us
847
- // immediately. Branding still lives in the wrapper .app name + Dock icon
848
- // + tray; it does NOT need to be in the UA string for the product to be
849
- // "GBrowser." Removing it resolves the "looks like Chrome but identifies
850
- // as GStackBrowser" contradiction codex flagged.
851
- let customUA: string | undefined;
852
- if (!this.customUserAgent) {
853
- // Detect Chrome version from the Chromium binary
854
- const chromePath = executablePath || chromium.executablePath();
855
- try {
856
- const versionProc = Bun.spawnSync([chromePath, '--version'], {
857
- stdout: 'pipe', stderr: 'pipe', timeout: 5000,
858
- });
859
- const versionOutput = versionProc.stdout.toString().trim();
860
- // Output like: "Google Chrome for Testing 145.0.6422.0" or "Chromium 145.0.6422.0"
861
- const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+\.\d+)/);
862
- const chromeVersion = versionMatch ? versionMatch[1] : '131.0.0.0';
863
- customUA = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
864
- } catch {
865
- // Fallback: generic modern Chrome UA
866
- customUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
867
- }
868
- }
869
-
870
- // T1: strip Playwright's automation-tell defaults. STEALTH_IGNORE_DEFAULT_ARGS
871
- // covers the originals (extension-loading blockers) plus --enable-automation
872
- // (kills the "Chrome is being controlled by automated test software" infobar
873
- // and the chrome-runtime shape changes Playwright otherwise triggers) and
874
- // three more (--disable-popup-blocking, --disable-component-update,
875
- // --disable-default-apps — each a documented automation tell per Patchright).
876
- const { STEALTH_IGNORE_DEFAULT_ARGS } = await import('./stealth');
877
- // XProtect self-heal wrapper (P0 #2554). usesCustomExecutable scopes the
878
- // heal out when GSTACK_CHROMIUM_PATH supplies the bundle — that bundle
879
- // belongs to the wrapper/embedder and is never quarantine-cleared or
880
- // reinstalled over (probePoisonedChromiumBundle's scope contract).
881
- this.context = await launchWithXProtectHeal(() => chromium.launchPersistentContext(userDataDir, {
882
- headless: false,
883
- ...(this.xvfb ? { env: { ...process.env, DISPLAY: this.xvfb.display } } : {}),
884
- // #2220: daemon owns signal policy — see launch() for the rationale.
885
- handleSIGINT: false,
886
- handleSIGTERM: false,
887
- handleSIGHUP: false,
888
- // Match the sandbox policy used by launch() above. Without this,
889
- // Playwright auto-adds --no-sandbox on every headed launch and the user
890
- // sees Chromium's "unsupported command-line flag" yellow infobar.
891
- chromiumSandbox: shouldEnableChromiumSandbox(),
892
- args: launchArgs,
893
- viewport: null, // Use browser's default viewport (real window size)
894
- userAgent: this.customUserAgent || customUA,
895
- ...(executablePath ? { executablePath } : {}),
896
- ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
897
- ignoreDefaultArgs: STEALTH_IGNORE_DEFAULT_ARGS,
898
- }), { usesCustomExecutable: Boolean(executablePath) });
899
- this.browser = this.context.browser();
900
- this.connectionMode = 'headed';
901
- this.intentionalDisconnect = false;
902
-
903
- // ─── Anti-bot-detection patches ───────────────────────────────
904
- // Apply Layer C stealth (applyStealth): masks navigator.webdriver,
905
- // restores window.chrome.* shape, aligns Notification.permission, sets
906
- // per-install hardware, and strips automation runtime artifacts (cdc_/
907
- // __webdriver globals + the Permissions notifications 'denied' tell).
908
- // We still do NOT fake navigator.plugins/languages — faking those flags
909
- // more bot-like, not less (D7). The cdc/Permissions cleanup moved into
910
- // applyStealth so headless launch() and handoff() get it too, not just
911
- // this headed path.
912
- const { applyStealth } = await import('./stealth');
913
- await applyStealth(this.context);
914
-
915
- // Inject visual indicator — subtle top-edge amber gradient
916
- // Extension's content script handles the floating pill
917
- const indicatorScript = () => {
918
- const injectIndicator = () => {
919
- if (document.getElementById('gstack-ctrl')) return;
920
-
921
- const topLine = document.createElement('div');
922
- topLine.id = 'gstack-ctrl';
923
- topLine.style.cssText = `
924
- position: fixed; top: 0; left: 0; right: 0; height: 2px;
925
- background: linear-gradient(90deg, #F59E0B, #FBBF24, #F59E0B);
926
- background-size: 200% 100%;
927
- animation: gstack-shimmer 3s linear infinite;
928
- pointer-events: none; z-index: 2147483647;
929
- opacity: 0.8;
930
- `;
931
-
932
- const style = document.createElement('style');
933
- style.textContent = `
934
- @keyframes gstack-shimmer {
935
- 0% { background-position: 200% 0; }
936
- 100% { background-position: -200% 0; }
937
- }
938
- @media (prefers-reduced-motion: reduce) {
939
- #gstack-ctrl { animation: none !important; }
940
- }
941
- `;
942
-
943
- document.documentElement.appendChild(style);
944
- document.documentElement.appendChild(topLine);
945
- };
946
- if (document.readyState === 'loading') {
947
- document.addEventListener('DOMContentLoaded', injectIndicator);
948
- } else {
949
- injectIndicator();
950
- }
951
- };
952
- await this.context.addInitScript(indicatorScript);
953
-
954
- // Track user-created tabs automatically (Cmd+T, link opens in new tab, etc.)
955
- this.context.on('page', (page) => {
956
- const id = this.nextTabId++;
957
- this.pages.set(id, page);
958
- this.tabSessions.set(id, new TabSession(page));
959
- this.activeTabId = id;
960
- this.wirePageEvents(page);
961
- // Inject indicator on the new tab
962
- page.evaluate(indicatorScript).catch(() => {});
963
- console.log(`[browse] New tab detected (id=${id}, total=${this.pages.size})`);
964
- this.checkTabGuardrails();
965
- });
966
-
967
- // Persistent context opens a default page — adopt it instead of creating a new one
968
- const existingPages = this.context.pages();
969
- if (existingPages.length > 0) {
970
- const page = existingPages[0];
971
- const id = this.nextTabId++;
972
- this.pages.set(id, page);
973
- this.tabSessions.set(id, new TabSession(page));
974
- this.activeTabId = id;
975
- this.wirePageEvents(page);
976
- // Inject indicator on restored page (addInitScript only fires on new navigations)
977
- try {
978
- await page.evaluate(indicatorScript);
979
- } catch {}
980
- } else {
981
- await this.newTab();
982
- }
983
-
984
- // Browser disconnect handler — distinguish user Cmd+Q from real crash.
985
- // Clean exit (Chromium exit code 0) → process.exit(0) so process
986
- // supervisors (gbrowser's gbd) treat it as user intent and skip the
987
- // restart loop. Crash → process.exit(2) preserves the legacy headed
988
- // semantics that's distinct from launch()'s code 1.
989
- // Always calls onDisconnect() first to trigger full shutdown (kill
990
- // terminal agent, save session, clean profile locks + state file) so
991
- // crashes don't strand resources either.
992
- if (this.browser) {
993
- this.onBrowserDisconnected(this.browser, () => {
994
- if (this.intentionalDisconnect) return;
995
- const browserRef = this.browser;
996
- void (async () => {
997
- const cause = await resolveDisconnectCause(browserRef);
998
- const exitCode = cause === 'clean' ? 0 : 2;
999
- if (cause === 'clean') {
1000
- console.error('[browse] Real browser closed cleanly (user-initiated quit). Server exiting (0).');
1001
- } else {
1002
- console.error('[browse] Real browser disconnected (crash or kill). Server exiting (2).');
1003
- console.error('[browse] Run `$B connect` to reconnect.');
1004
- }
1005
- if (!this.onDisconnect) {
1006
- process.exit(exitCode);
1007
- return;
1008
- }
1009
- try {
1010
- const result = this.onDisconnect(exitCode);
1011
- if (result && typeof (result as Promise<void>).catch === 'function') {
1012
- (result as Promise<void>).catch((err) => {
1013
- console.error('[browse] onDisconnect rejected:', err);
1014
- process.exit(exitCode);
1015
- });
1016
- }
1017
- // onDisconnect is responsible for exit on the success path.
1018
- } catch (err) {
1019
- console.error('[browse] onDisconnect threw:', err);
1020
- process.exit(exitCode);
1021
- }
1022
- })();
1023
- });
1024
- }
1025
-
1026
- // Headed mode defaults
1027
- this.dialogAutoAccept = false; // Don't dismiss user's real dialogs
1028
- this.isHeaded = true;
1029
- this.consecutiveFailures = 0;
1030
- }
1031
-
1032
- // How long close() waits for a graceful shutdown before falling back to
1033
- // SIGKILL (launched mode) or abandoning the context close (headed mode).
1034
- // A field, not a literal, so the SIGKILL fallback is unit-testable without
1035
- // a 5-second wait.
1036
- private closeRaceMs = 5000;
1037
-
1038
- async close() {
1039
- this.closing = true;
1040
- const previousBrowser = this.handoffPrevious?.browser;
1041
- const currentBrowser = this.browser;
1042
- // unref'd race timer: without unref, every successful close still pins
1043
- // the caller's event loop for the full window.
1044
- const raceTimeout = (ms: number) => new Promise<false>((resolve) => {
1045
- const t = setTimeout(() => resolve(false), ms);
1046
- (t as { unref?: () => void }).unref?.();
1047
- });
1048
- if (this.browser || ((this.connectionMode === 'headed' || this.handoffPrevious) && this.context)) {
1049
- if (this.connectionMode === 'headed' || this.handoffPrevious || this.extensionContext) {
1050
- // Headed/persistent context mode: close the context (which closes the browser)
1051
- this.intentionalDisconnect = true;
1052
- this.detachDisconnectHandlers(this.browser);
1053
- await Promise.race([
1054
- this.context ? this.context.close() : Promise.resolve(),
1055
- raceTimeout(this.closeRaceMs),
1056
- ]).catch(() => {});
1057
- } else if (this.browser) {
1058
- // Launched mode: close the browser we spawned.
1059
- this.detachDisconnectHandlers(this.browser);
1060
- // Grab the child handle BEFORE the race: nulling this.browser after a
1061
- // race-timeout used to ABANDON a live Chromium whose sockets kept the
1062
- // caller's event loop (and keep-alive connections into test servers)
1063
- // open forever — the intermittent whole-suite wedge. If graceful close
1064
- // doesn't finish in time, the child gets SIGKILL, not freedom.
1065
- const child = launchedProcess(this.browser);
1066
- const closed = await Promise.race([
1067
- this.browser.close().then(() => true as const),
1068
- raceTimeout(this.closeRaceMs),
1069
- ]).catch(() => false as const);
1070
- if (closed === false && child && child.exitCode === null && !child.killed) {
1071
- try { child.kill('SIGKILL'); } catch { /* already gone */ }
1072
- }
1073
- }
1074
- this.browser = null;
1075
- }
1076
- if (previousBrowser && previousBrowser !== currentBrowser) {
1077
- this.detachDisconnectHandlers(previousBrowser);
1078
- const child = launchedProcess(previousBrowser);
1079
- const closed = await Promise.race([
1080
- previousBrowser.close().then(() => true), raceTimeout(this.closeRaceMs),
1081
- ]).catch(() => false);
1082
- if (!closed && child && child.exitCode === null && !child.killed) {
1083
- try { child.kill('SIGKILL'); } catch {}
1084
- }
1085
- }
1086
- await this.displayAllocation?.catch(() => {});
1087
- this.closeOwnedDisplay();
1088
- }
1089
-
1090
- /** Health check — verifies Chromium is connected AND responsive */
1091
- async isHealthy(): Promise<boolean> {
1092
- if (!this.browser || !this.browser.isConnected()) return false;
1093
- try {
1094
- const page = this.pages.get(this.activeTabId);
1095
- if (!page) return true; // connected but no pages — still healthy
1096
- await Promise.race([
1097
- page.evaluate('1'),
1098
- new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000)),
1099
- ]);
1100
- return true;
1101
- } catch {
1102
- return false;
1103
- }
1104
- }
1105
-
1106
- // ─── Tab Management ────────────────────────────────────────
1107
- async newTab(url?: string, clientId?: string): Promise<number> {
1108
- if (!this.context) throw new Error('Browser not launched');
1109
-
1110
- // Validate URL before allocating page to avoid zombie tabs on rejection.
1111
- // Use the normalized return value for navigation — it handles file://./x and
1112
- // file://<segment> cwd-relative forms that the standard URL parser doesn't.
1113
- let normalizedUrl: string | undefined;
1114
- if (url) {
1115
- normalizedUrl = await validateNavigationUrl(url);
1116
- }
1117
-
1118
- const page = await this.context.newPage();
1119
- const id = this.nextTabId++;
1120
- this.pages.set(id, page);
1121
- this.tabSessions.set(id, new TabSession(page));
1122
- this.activeTabId = id;
1123
-
1124
- // Record tab ownership for multi-agent isolation
1125
- if (clientId) {
1126
- this.tabOwnership.set(id, clientId);
1127
- }
1128
-
1129
- // Wire up console/network/dialog capture
1130
- this.wirePageEvents(page);
1131
-
1132
- if (normalizedUrl) {
1133
- await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
1134
- }
1135
-
1136
- return id;
1137
- }
1138
-
1139
- async closeTab(id?: number): Promise<void> {
1140
- const tabId = id ?? this.activeTabId;
1141
- const page = this.pages.get(tabId);
1142
- if (!page) throw new Error(`Tab ${tabId} not found`);
1143
-
1144
- // Capture BEFORE close(): the page 'close' event handler wired in
1145
- // wirePageEvents() can fire while page.close() is awaited. It removes
1146
- // the tab from the maps and reassigns activeTabId (to 0 when no tabs
1147
- // remain), so a post-close `tabId === this.activeTabId` check is
1148
- // order-dependent — whether the event dispatches before or after
1149
- // close() resolves varies across Playwright/Chromium versions and
1150
- // machines, and losing the race means the last-tab auto-create below
1151
- // never runs, leaving the manager with zero tabs.
1152
- const wasActive = tabId === this.activeTabId;
1153
-
1154
- await page.close();
1155
- this.pages.delete(tabId);
1156
- this.tabSessions.delete(tabId);
1157
- this.tabOwnership.delete(tabId);
1158
-
1159
- // Switch to another tab if we closed the active one
1160
- if (wasActive) {
1161
- const remaining = [...this.pages.keys()];
1162
- if (remaining.length === 0) {
1163
- // No tabs left — create a new blank one
1164
- await this.newTab();
1165
- } else if (!this.pages.has(this.activeTabId)) {
1166
- // The 'close' handler may have already switched to a valid tab;
1167
- // only reassign when activeTabId no longer points at a live tab.
1168
- this.activeTabId = remaining[remaining.length - 1];
1169
- }
1170
- }
1171
- }
1172
-
1173
- switchTab(id: number, opts?: { bringToFront?: boolean }): void {
1174
- if (!this.tabSessions.has(id)) throw new Error(`Tab ${id} not found`);
1175
- this.activeTabId = id;
1176
- // Only bring to front when explicitly requested (user-initiated tab switch).
1177
- // Internal tab pinning (BROWSE_TAB) should NOT steal focus.
1178
- if (opts?.bringToFront !== false) {
1179
- const page = this.pages.get(id);
1180
- if (page) page.bringToFront().catch(() => {});
1181
- }
1182
- }
1183
-
1184
- /**
1185
- * Sync activeTabId to match the tab whose URL matches the Chrome extension's
1186
- * active tab. Called on every /sidebar-tabs poll so manual tab switches in
1187
- * the browser are detected within ~2s.
1188
- */
1189
- syncActiveTabByUrl(activeUrl: string): void {
1190
- if (!activeUrl || this.pages.size <= 1) return;
1191
- // Try exact match first, then fuzzy match (origin+pathname, ignoring query/fragment)
1192
- let fuzzyId: number | null = null;
1193
- let activeOriginPath = '';
1194
- try {
1195
- const u = new URL(activeUrl);
1196
- activeOriginPath = u.origin + u.pathname;
1197
- } catch (err: any) {
1198
- if (!(err instanceof TypeError)) throw err;
1199
- }
1200
-
1201
- for (const [id, page] of this.pages) {
1202
- try {
1203
- const pageUrl = page.url();
1204
- // Exact match — best case
1205
- if (pageUrl === activeUrl && id !== this.activeTabId) {
1206
- this.activeTabId = id;
1207
- return;
1208
- }
1209
- // Fuzzy match — origin+pathname (handles query param / fragment differences)
1210
- if (activeOriginPath && fuzzyId === null && id !== this.activeTabId) {
1211
- try {
1212
- const pu = new URL(pageUrl);
1213
- if (pu.origin + pu.pathname === activeOriginPath) {
1214
- fuzzyId = id;
1215
- }
1216
- } catch (err: any) {
1217
- if (!(err instanceof TypeError)) throw err;
1218
- }
1219
- }
1220
- } catch {}
1221
- }
1222
- // Fall back to fuzzy match
1223
- if (fuzzyId !== null) {
1224
- this.activeTabId = fuzzyId;
1225
- }
1226
- }
1227
-
1228
- getActiveTabId(): number {
1229
- return this.activeTabId;
1230
- }
1231
-
1232
- getTabCount(): number {
1233
- return this.pages.size;
1234
- }
1235
-
1236
- // ─── Tab Ownership (multi-agent isolation) ──────────────
1237
-
1238
- /** Get the owner of a tab, or null if unowned (root-only for writes). */
1239
- getTabOwner(tabId: number): string | null {
1240
- return this.tabOwnership.get(tabId) || null;
1241
- }
1242
-
1243
- /**
1244
- * Check if a client can access a tab.
1245
- *
1246
- * Two policies, distinguished by `options.ownOnly`:
1247
- *
1248
- * - **own-only (pair-agent over tunnel):** the strict mode. Token must own
1249
- * the target tab for any access (reads or writes). Unowned user tabs
1250
- * and tabs owned by other clients are off-limits. Remote agents must
1251
- * `newtab` first to get a tab they can drive.
1252
- *
1253
- * - **shared (local skill spawns, default scoped tokens):** permissive on
1254
- * tab access. The token can read/write any tab — capability is gated
1255
- * elsewhere (scope checks at /command, rate limits, the dual-listener
1256
- * allowlist for tunnel-bound traffic). Tab ownership is not a security
1257
- * boundary for shared tokens; it only matters for pair-agent isolation.
1258
- * This matches the contract documented in `skill-token.ts:79`
1259
- * ("skill scripts may switch tabs as needed").
1260
- *
1261
- * Root is unconstrained.
1262
- *
1263
- * `isWrite` is preserved in the signature for callers that want to log or
1264
- * branch on it elsewhere, but the access decision itself only depends on
1265
- * `ownOnly` + ownership map state.
1266
- */
1267
- checkTabAccess(tabId: number, clientId: string, options: { isWrite?: boolean; ownOnly?: boolean } = {}): boolean {
1268
- if (clientId === 'root') return true;
1269
- if (options.ownOnly) {
1270
- const owner = this.tabOwnership.get(tabId);
1271
- return owner === clientId;
1272
- }
1273
- return true;
1274
- }
1275
-
1276
- /** Transfer tab ownership to a different client. */
1277
- transferTab(tabId: number, toClientId: string): void {
1278
- if (!this.pages.has(tabId)) throw new Error(`Tab ${tabId} not found`);
1279
- this.tabOwnership.set(tabId, toClientId);
1280
- }
1281
-
1282
- /**
1283
- * Release all tab ownership held by a client (called on revoke / reducing
1284
- * re-pair). Deletes the ownership entries so an own-only client re-pairing
1285
- * under the same name can no longer inherit the revoked agent's
1286
- * authenticated tabs (checkTabAccess gates own-only on owner === clientId).
1287
- * Tabs are NOT closed — leaving the page open is the local human's call, not
1288
- * the daemon's; the residual is a root-only tab. Returns the released ids.
1289
- */
1290
- releaseClientTabs(clientId: string): number[] {
1291
- const released: number[] = [];
1292
- for (const [tabId, owner] of this.tabOwnership) {
1293
- if (owner === clientId) {
1294
- this.tabOwnership.delete(tabId);
1295
- released.push(tabId);
1296
- }
1297
- }
1298
- return released;
1299
- }
1300
-
1301
- async getTabListWithTitles(): Promise<Array<{ id: number; url: string; title: string; active: boolean }>> {
1302
- const tabs: Array<{ id: number; url: string; title: string; active: boolean }> = [];
1303
- for (const [id, page] of this.pages) {
1304
- tabs.push({
1305
- id,
1306
- url: page.url(),
1307
- title: await page.title().catch(() => ''),
1308
- active: id === this.activeTabId,
1309
- });
1310
- }
1311
- return tabs;
1312
- }
1313
-
1314
- // ─── Session Access ────────────────────────────────────────
1315
- /** Get the TabSession for the active tab. */
1316
- getActiveSession(): TabSession {
1317
- const session = this.tabSessions.get(this.activeTabId);
1318
- if (!session) throw new Error('No active page. Use "browse goto <url>" first.');
1319
- return session;
1320
- }
1321
-
1322
- /** Get a TabSession by tab ID. Used by /batch for parallel tab execution. */
1323
- getSession(tabId: number): TabSession {
1324
- const session = this.tabSessions.get(tabId);
1325
- if (!session) throw new Error(`Tab ${tabId} not found`);
1326
- return session;
1327
- }
1328
-
1329
- /** Get the underlying Page for a tab id. Returns null if the tab doesn't exist.
1330
- * Used by the CDP bridge (cdp-bridge.ts) to mint per-tab CDPSessions. */
1331
- getPageForTab(tabId: number): Page | null {
1332
- return this.pages.get(tabId) ?? null;
1333
- }
1334
-
1335
- // ─── Two-tier mutex (Codex T7) ─────────────────────────────
1336
- // Per-tab and global locks for the CDP bridge. tab-scoped methods take the
1337
- // per-tab mutex; browser-scoped methods take the global lock that blocks all
1338
- // tab mutexes. Hard timeout on acquire so silent deadlock can't happen.
1339
- // Every caller MUST use try { ... } finally { release() }.
1340
-
1341
- private tabLocks: Map<number, Promise<void>> = new Map();
1342
- private globalCdpLockTail: Promise<void> = Promise.resolve();
1343
-
1344
- /**
1345
- * Acquire the per-tab CDP lock with a timeout. Returns a release fn.
1346
- * Locks chain: each acquire waits on the prior tail's resolution.
1347
- * Browser-scoped global lock takes precedence: while the global lock is
1348
- * held, no tab lock can be acquired (and vice versa).
1349
- */
1350
- async acquireTabLock(tabId: number, timeoutMs: number): Promise<() => void> {
1351
- const existing = this.tabLocks.get(tabId) ?? Promise.resolve();
1352
- // Wait for any held global lock first (cross-tier serialization).
1353
- const tail = Promise.all([existing, this.globalCdpLockTail]).then(() => undefined);
1354
- let release!: () => void;
1355
- const next = new Promise<void>((resolve) => { release = resolve; });
1356
- this.tabLocks.set(tabId, tail.then(() => next));
1357
-
1358
- const timeoutPromise = new Promise<never>((_, reject) =>
1359
- setTimeout(() => reject(new Error(
1360
- `CDPMutexAcquireTimeout: tab ${tabId} lock not acquired within ${timeoutMs}ms.\n` +
1361
- 'Cause: a prior CDP or browser-scoped operation has held the lock too long.\n' +
1362
- 'Action: retry; if this repeats, the prior operation may be hung — file a bug.'
1363
- )), timeoutMs),
1364
- );
1365
- try {
1366
- await Promise.race([tail, timeoutPromise]);
1367
- } catch (e) {
1368
- // Acquisition failed; release the slot we reserved so we don't deadlock the queue.
1369
- release();
1370
- throw e;
1371
- }
1372
- return release;
1373
- }
1374
-
1375
- /**
1376
- * Acquire the global CDP lock. Blocks until all tab locks are released, and
1377
- * blocks new tab-lock acquisitions until released.
1378
- */
1379
- async acquireGlobalCdpLock(timeoutMs: number): Promise<() => void> {
1380
- const allTabTails = Array.from(this.tabLocks.values());
1381
- const priorGlobal = this.globalCdpLockTail;
1382
- const allPrior = Promise.all([priorGlobal, ...allTabTails]).then(() => undefined);
1383
- let release!: () => void;
1384
- const next = new Promise<void>((resolve) => { release = resolve; });
1385
- this.globalCdpLockTail = allPrior.then(() => next);
1386
-
1387
- const timeoutPromise = new Promise<never>((_, reject) =>
1388
- setTimeout(() => reject(new Error(
1389
- `CDPMutexAcquireTimeout: global CDP lock not acquired within ${timeoutMs}ms.\n` +
1390
- 'Cause: in-flight tab operations have not completed.\n' +
1391
- 'Action: retry; if this repeats, file a bug — a tab op may be hung.'
1392
- )), timeoutMs),
1393
- );
1394
- try {
1395
- await Promise.race([allPrior, timeoutPromise]);
1396
- } catch (e) {
1397
- release();
1398
- throw e;
1399
- }
1400
- return release;
1401
- }
1402
-
1403
- // ─── Page Access (delegates to active session) ─────────────
1404
- getPage(): Page {
1405
- return this.getActiveSession().page;
1406
- }
1407
-
1408
- getCurrentUrl(): string {
1409
- try {
1410
- return this.getPage().url();
1411
- } catch {
1412
- return 'about:blank';
1413
- }
1414
- }
1415
-
1416
- /**
1417
- * Diagnostic for `$B memory` and the /memory endpoint.
1418
- *
1419
- * Collects:
1420
- * - Bun process memory (cross-platform, accurate, no shelling).
1421
- * - Per-tab JS heap via CDP Performance.getMetrics — the most portable
1422
- * per-tab signal CDP exposes. Misses native/GPU/Skia/cache memory
1423
- * (Codex flag on the eng-review; see follow-up TODO "native/GPU
1424
- * memory breakdown").
1425
- * - Chromium process tree via SystemInfo.getProcessInfo — PID + type
1426
- * + CPU time. Per-process RSS is NOT exposed via CDP and the eng
1427
- * review (D2 USE_CDP) explicitly chose CDP over shelling to `ps`,
1428
- * so RSS columns are absent and `notes[]` says why.
1429
- *
1430
- * `structures` is passed in by the caller (read-commands / server) so
1431
- * browser-manager doesn't take a hard dep on every buffer-owning module.
1432
- */
1433
- async getMemorySnapshot(structures: MemoryStructureStats): Promise<MemorySnapshot> {
1434
- const bunMem = process.memoryUsage();
1435
- const notes: string[] = [];
1436
-
1437
- // Per-tab JS heap. Lazy: only the pages we already track. A target
1438
- // that died mid-snapshot is omitted, never throws.
1439
- const tabs: MemoryTabSnapshot[] = [];
1440
- for (const [id, page] of this.pages) {
1441
- try {
1442
- const url = (() => { try { return page.url(); } catch { return ''; } })();
1443
- const title = await page.title().catch(() => '');
1444
- const metrics = await withCdpSession(page, async (session) => {
1445
- await session.send('Performance.enable').catch(() => undefined);
1446
- const result = await session.send('Performance.getMetrics');
1447
- return ((result as { metrics?: Array<{ name: string; value: number }> }).metrics) ?? [];
1448
- });
1449
- const mm: Record<string, number> = {};
1450
- for (const m of metrics) mm[m.name] = m.value;
1451
- tabs.push({
1452
- id,
1453
- url,
1454
- title,
1455
- jsHeapUsed: mm.JSHeapUsedSize ?? 0,
1456
- jsHeapTotal: mm.JSHeapTotalSize ?? 0,
1457
- documents: mm.Documents ?? 0,
1458
- nodes: mm.Nodes ?? 0,
1459
- listeners: mm.JSEventListeners ?? 0,
1460
- });
1461
- } catch {
1462
- // Target died or CDP unavailable mid-snapshot — skip this tab.
1463
- }
1464
- }
1465
-
1466
- // Chromium process tree. Browser handle may be on the `browser` field
1467
- // (launched mode) or accessible via `context.browser()` (persistent
1468
- // context / headed mode); try both.
1469
- let processes: MemoryProcess[] | null = null;
1470
- const browser: Browser | null = this.browser ?? (this.context ? this.context.browser() : null);
1471
- if (browser) {
1472
- try {
1473
- // `newBrowserCDPSession` is browser-wide. Not exposed on every
1474
- // Playwright TypeScript surface, but present at runtime on the
1475
- // Browser instance — use a typed cast to avoid the @ts-expect-error.
1476
- type BrowserWithCDP = Browser & {
1477
- newBrowserCDPSession?: () => Promise<{
1478
- send: (method: string, params?: unknown) => Promise<unknown>;
1479
- detach: () => Promise<void>;
1480
- }>;
1481
- };
1482
- const maybeFactory = (browser as BrowserWithCDP).newBrowserCDPSession;
1483
- if (typeof maybeFactory === 'function') {
1484
- const browserSession = await maybeFactory.call(browser);
1485
- try {
1486
- const info = (await browserSession.send('SystemInfo.getProcessInfo')) as {
1487
- processInfo?: Array<{ id: number; type: string; cpuTime: number }>;
1488
- };
1489
- processes = (info.processInfo ?? []).map((p) => ({
1490
- id: p.id,
1491
- type: p.type,
1492
- cpuTime: p.cpuTime,
1493
- }));
1494
- notes.push(
1495
- 'Per-Chromium-process RSS not collected — SystemInfo.getProcessInfo exposes PID+type+CPU only. ' +
1496
- 'See follow-up TODO "native/GPU memory breakdown" for the deferred fix.',
1497
- );
1498
- } finally {
1499
- await browserSession.detach().catch(() => undefined);
1500
- }
1501
- } else {
1502
- notes.push('Playwright build does not expose newBrowserCDPSession; per-process info skipped.');
1503
- }
1504
- } catch (err: any) {
1505
- notes.push(`CDP browser session unavailable: ${err?.message ?? String(err)}`);
1506
- }
1507
- } else {
1508
- notes.push('Browser handle unavailable (server connection mode); per-process info skipped.');
1509
- }
1510
-
1511
- return {
1512
- bunServer: {
1513
- rss: bunMem.rss,
1514
- heapUsed: bunMem.heapUsed,
1515
- heapTotal: bunMem.heapTotal,
1516
- external: bunMem.external,
1517
- },
1518
- tabs,
1519
- processes,
1520
- structures,
1521
- capturedAt: Date.now(),
1522
- notes,
1523
- };
1524
- }
1525
-
1526
- // ─── Ref Map (delegates to active session) ──────────────────
1527
- setRefMap(refs: Map<string, RefEntry>) {
1528
- this.getActiveSession().setRefMap(refs);
1529
- }
1530
-
1531
- clearRefs() {
1532
- this.getActiveSession().clearRefs();
1533
- }
1534
-
1535
- async resolveRef(selector: string): Promise<{ locator: Locator } | { selector: string }> {
1536
- return this.getActiveSession().resolveRef(selector);
1537
- }
1538
-
1539
- getRefRole(selector: string): string | null {
1540
- return this.getActiveSession().getRefRole(selector);
1541
- }
1542
-
1543
- getRefCount(): number {
1544
- return this.getActiveSession().getRefCount();
1545
- }
1546
-
1547
- // ─── Snapshot Diffing (delegates to active session) ─────────
1548
- setLastSnapshot(text: string | null) {
1549
- this.getActiveSession().setLastSnapshot(text);
1550
- }
1551
-
1552
- getLastSnapshot(): string | null {
1553
- return this.getActiveSession().getLastSnapshot();
1554
- }
1555
-
1556
- // ─── Dialog Control ───────────────────────────────────────
1557
- setDialogAutoAccept(accept: boolean) {
1558
- this.dialogAutoAccept = accept;
1559
- }
1560
-
1561
- getDialogAutoAccept(): boolean {
1562
- return this.dialogAutoAccept;
1563
- }
1564
-
1565
- setDialogPromptText(text: string | null) {
1566
- this.dialogPromptText = text;
1567
- }
1568
-
1569
- getDialogPromptText(): string | null {
1570
- return this.dialogPromptText;
1571
- }
1572
-
1573
- // ─── Cookie Origin Tracking ────────────────────────────────
1574
- trackCookieImportDomains(domains: string[]): void {
1575
- for (const d of domains) this.cookieImportedDomains.add(d);
1576
- }
1577
-
1578
- getCookieImportedDomains(): ReadonlySet<string> {
1579
- return this.cookieImportedDomains;
1580
- }
1581
-
1582
- hasCookieImports(): boolean {
1583
- return this.cookieImportedDomains.size > 0;
1584
- }
1585
-
1586
- // ─── Viewport ──────────────────────────────────────────────
1587
- async setViewport(width: number, height: number) {
1588
- this.currentViewport = { width, height };
1589
- await this.getPage().setViewportSize({ width, height });
1590
- }
1591
-
1592
- /**
1593
- * Undo a `viewport WxH` pin. Headless contexts restore the 1280x720 launch
1594
- * default. Headed contexts launch with `viewport: null`, but Playwright has no
1595
- * way to drop a page's emulated size, so the active tab gets a fresh page in
1596
- * the same context (cookies and storage persist, refs reset as on navigation).
1597
- */
1598
- async resetViewport(): Promise<'window' | 'default'> {
1599
- this.currentViewport = { width: 1280, height: 720 };
1600
- if (this.connectionMode !== 'headed') {
1601
- await this.getPage().setViewportSize(this.currentViewport);
1602
- return 'default';
1603
- }
1604
- if (!this.context) throw new Error('Browser not launched');
1605
-
1606
- const tabId = this.activeTabId;
1607
- const pinned = this.getPage();
1608
- const loaded = this.getActiveSession().getLoadedHtml();
1609
- const url = pinned.url();
1610
- const page = await this.context.newPage();
1611
- const session = new TabSession(page);
1612
- this.wirePageEvents(page);
1613
- try {
1614
- if (loaded) await session.setTabContent(loaded.html, { waitUntil: loaded.waitUntil });
1615
- else if (url !== 'about:blank') await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
1616
- } catch (err) {
1617
- await page.close().catch(() => {});
1618
- throw err;
1619
- }
1620
- this.pages.set(tabId, page);
1621
- this.tabSessions.set(tabId, session);
1622
- await pinned.close().catch(() => {});
1623
- await page.bringToFront().catch(() => {});
1624
- return 'window';
1625
- }
1626
-
1627
- // ─── Extra Headers ─────────────────────────────────────────
1628
- async setExtraHeader(name: string, value: string) {
1629
- this.extraHeaders[name] = value;
1630
- if (this.context) {
1631
- await this.context.setExtraHTTPHeaders(this.extraHeaders);
1632
- }
1633
- }
1634
-
1635
- // ─── User Agent ────────────────────────────────────────────
1636
- setUserAgent(ua: string) {
1637
- this.customUserAgent = ua;
1638
- }
1639
-
1640
- getUserAgent(): string | null {
1641
- return this.customUserAgent;
1642
- }
1643
-
1644
- // ─── Lifecycle helpers ───────────────────────────────
1645
- /**
1646
- * Close all open pages and clear the pages map.
1647
- * Used by state load to replace the current session.
1648
- */
1649
- async closeAllPages(): Promise<void> {
1650
- for (const page of this.pages.values()) {
1651
- await page.close().catch(() => {});
1652
- }
1653
- this.pages.clear();
1654
- this.tabSessions.clear();
1655
- }
1656
-
1657
- // ─── Frame context (delegates to active session) ────────────
1658
- setFrame(frame: import('playwright').Frame | null): void {
1659
- this.getActiveSession().setFrame(frame);
1660
- }
1661
-
1662
- getFrame(): import('playwright').Frame | null {
1663
- return this.getActiveSession().getFrame();
1664
- }
1665
-
1666
- getActiveFrameOrPage(): import('playwright').Page | import('playwright').Frame {
1667
- return this.getActiveSession().getActiveFrameOrPage();
1668
- }
1669
-
1670
- // ─── State Save/Restore (shared by recreateContext + handoff) ─
1671
- /**
1672
- * Capture browser state: cookies, localStorage, sessionStorage, URLs, active tab.
1673
- * Skips pages that fail storage reads (e.g., already closed).
1674
- */
1675
- async saveState(): Promise<BrowserState> {
1676
- if (!this.context) throw new Error('Browser not launched');
1677
-
1678
- const cookies = await this.context.cookies();
1679
- const pages: BrowserState['pages'] = [];
1680
-
1681
- for (const [id, page] of this.pages) {
1682
- const url = page.url();
1683
- let storage = null;
1684
- try {
1685
- storage = await page.evaluate(() => ({
1686
- localStorage: { ...localStorage },
1687
- sessionStorage: { ...sessionStorage },
1688
- }));
1689
- } catch {}
1690
-
1691
- // Capture load-html content so a later context recreation (viewport --scale)
1692
- // can replay it via setTabContent. Never persisted to disk.
1693
- const session = this.tabSessions.get(id);
1694
- const loaded = session?.getLoadedHtml();
1695
- // Preserve tab ownership through recreation so scoped agents aren't locked out.
1696
- const owner = this.tabOwnership.get(id);
1697
-
1698
- pages.push({
1699
- url: url === 'about:blank' ? '' : url,
1700
- isActive: id === this.activeTabId,
1701
- storage,
1702
- loadedHtml: loaded?.html,
1703
- loadedHtmlWaitUntil: loaded?.waitUntil,
1704
- owner,
1705
- });
1706
- }
1707
-
1708
- return { cookies, pages };
1709
- }
1710
-
1711
- /**
1712
- * Restore browser state into the current context: cookies, pages, storage.
1713
- * Navigates to saved URLs, restores storage, wires page events.
1714
- * Failures on individual pages are swallowed — partial restore is better than none.
1715
- */
1716
- async restoreState(state: BrowserState): Promise<void> {
1717
- if (!this.context) throw new Error('Browser not launched');
1718
-
1719
- // Restore cookies
1720
- if (state.cookies.length > 0) {
1721
- await this.context.addCookies(state.cookies);
1722
- }
1723
-
1724
- // Clear stale ownership — the old tab IDs are gone. We'll re-add per-tab
1725
- // owners below as each saved tab gets a fresh ID. Without this reset, old
1726
- // tabId → clientId entries would linger and match new tabs with the same
1727
- // sequential IDs, silently granting ownership to the wrong clients.
1728
- this.tabOwnership.clear();
1729
-
1730
- // Re-create pages
1731
- let activeId: number | null = null;
1732
- for (const saved of state.pages) {
1733
- const page = await this.context.newPage();
1734
- const id = this.nextTabId++;
1735
- this.pages.set(id, page);
1736
- const newSession = new TabSession(page);
1737
- this.tabSessions.set(id, newSession);
1738
- this.wirePageEvents(page);
1739
-
1740
- // Restore tab ownership for the new ID — preserves scoped-agent isolation
1741
- // across context recreation (viewport --scale, user-agent change, handoff).
1742
- if (saved.owner) {
1743
- this.tabOwnership.set(id, saved.owner);
1744
- }
1745
-
1746
- if (saved.loadedHtml) {
1747
- // Replay load-html content via setTabContent — this rehydrates
1748
- // TabSession.loadedHtml so the next saveState sees it. page.setContent()
1749
- // alone would restore the DOM but lose the replay metadata.
1750
- try {
1751
- await newSession.setTabContent(saved.loadedHtml, { waitUntil: saved.loadedHtmlWaitUntil });
1752
- } catch (err: any) {
1753
- console.warn(`[browse] Failed to replay loadedHtml for tab ${id}: ${err.message}`);
1754
- }
1755
- } else if (saved.url) {
1756
- // Validate the saved URL before navigating — the state file is user-writable and
1757
- // a tampered URL could navigate to cloud metadata endpoints. Use the normalized
1758
- // return value so file:// forms get consistent treatment with live goto.
1759
- let normalizedUrl: string;
1760
- try {
1761
- normalizedUrl = await validateNavigationUrl(saved.url);
1762
- } catch (err: any) {
1763
- console.warn(`[browse] Skipping invalid URL in state file: ${saved.url} — ${err.message}`);
1764
- continue;
1765
- }
1766
- await page.goto(normalizedUrl, { waitUntil: 'domcontentloaded', timeout: 15000 }).catch(() => {});
1767
- }
1768
-
1769
- if (saved.storage) {
1770
- try {
1771
- await page.evaluate((s: { localStorage: Record<string, string>; sessionStorage: Record<string, string> }) => {
1772
- if (s.localStorage) {
1773
- for (const [k, v] of Object.entries(s.localStorage)) {
1774
- localStorage.setItem(k, v);
1775
- }
1776
- }
1777
- if (s.sessionStorage) {
1778
- for (const [k, v] of Object.entries(s.sessionStorage)) {
1779
- sessionStorage.setItem(k, v);
1780
- }
1781
- }
1782
- }, saved.storage);
1783
- } catch {}
1784
- }
1785
-
1786
- if (saved.isActive) activeId = id;
1787
- }
1788
-
1789
- // If no pages were saved, create a blank one
1790
- if (this.pages.size === 0) {
1791
- await this.newTab();
1792
- } else {
1793
- this.activeTabId = activeId ?? [...this.pages.keys()][0];
1794
- }
1795
-
1796
- // Clear refs — pages are new, locators are stale
1797
- this.clearRefs();
1798
- }
1799
-
1800
- /**
1801
- * Recreate the browser context to apply user agent changes.
1802
- * Saves and restores cookies, localStorage, sessionStorage, and open pages.
1803
- * Falls back to a clean slate on any failure.
1804
- */
1805
- async recreateContext(): Promise<string | null> {
1806
- if (this.connectionMode === 'headed') {
1807
- throw new Error('Cannot recreate context in headed mode. Use disconnect first.');
1808
- }
1809
- if (this.extensionContext) {
1810
- throw new Error('Cannot recreate context while BROWSE_EXTENSIONS_DIR extensions are loaded (persistent context). Restart browse with the new settings instead.');
1811
- }
1812
- if (!this.browser || !this.context) {
1813
- throw new Error('Browser not launched');
1814
- }
1815
-
1816
- try {
1817
- // 1. Save state
1818
- const state = await this.saveState();
1819
-
1820
- // 2. Close old pages and context
1821
- for (const page of this.pages.values()) {
1822
- await page.close().catch(() => {});
1823
- }
1824
- this.pages.clear();
1825
- this.tabSessions.clear();
1826
- await this.context.close().catch(() => {});
1827
-
1828
- // 3. Create new context with updated settings
1829
- const contextOptions: BrowserContextOptions = {
1830
- viewport: { width: this.currentViewport.width, height: this.currentViewport.height },
1831
- deviceScaleFactor: this.deviceScaleFactor,
1832
- };
1833
- if (this.customUserAgent) {
1834
- contextOptions.userAgent = this.customUserAgent;
1835
- }
1836
- this.context = await this.browser.newContext(contextOptions);
1837
-
1838
- // Re-apply stealth: newContext() is a fresh context with no init scripts,
1839
- // so a useragent / viewport --scale rebuild would otherwise drop the
1840
- // webdriver mask, window.chrome.* shape, hardware spoof, and cdc/
1841
- // Permissions cleanup on every restored page. Must run before
1842
- // restoreState() navigates the restored tabs.
1843
- const { applyStealth } = await import('./stealth');
1844
- await applyStealth(this.context);
1845
-
1846
- if (Object.keys(this.extraHeaders).length > 0) {
1847
- await this.context.setExtraHTTPHeaders(this.extraHeaders);
1848
- }
1849
-
1850
- // 4. Restore state
1851
- await this.restoreState(state);
1852
-
1853
- return null; // success
1854
- } catch (err: unknown) {
1855
- // Fallback: create a clean context + blank tab
1856
- try {
1857
- this.pages.clear();
1858
- this.tabSessions.clear();
1859
- if (this.context) await this.context.close().catch(() => {});
1860
-
1861
- const contextOptions: BrowserContextOptions = {
1862
- viewport: { width: this.currentViewport.width, height: this.currentViewport.height },
1863
- deviceScaleFactor: this.deviceScaleFactor,
1864
- };
1865
- if (this.customUserAgent) {
1866
- contextOptions.userAgent = this.customUserAgent;
1867
- }
1868
- this.context = await this.browser!.newContext(contextOptions);
1869
- // Stealth applies to the fallback blank context too.
1870
- const { applyStealth } = await import('./stealth');
1871
- await applyStealth(this.context);
1872
- await this.newTab();
1873
- this.clearRefs();
1874
- } catch {
1875
- // If even the fallback fails, we're in trouble — but browser is still alive
1876
- }
1877
- return `Context recreation failed: ${err instanceof Error ? err.message : String(err)}. Browser reset to blank tab.`;
1878
- }
1879
- }
1880
-
1881
- /**
1882
- * Change deviceScaleFactor + viewport size atomically.
1883
- *
1884
- * deviceScaleFactor is a context-level option, so Playwright requires a full context
1885
- * recreation. This method validates the input, stores the new values, calls
1886
- * recreateContext(), and rolls back the fields on failure so a bad call doesn't
1887
- * leave the manager in an inconsistent state.
1888
- *
1889
- * Returns null on success, or an error string if the new context couldn't be built
1890
- * (state may have been lost, per recreateContext's fallback behavior).
1891
- */
1892
- async setDeviceScaleFactor(scale: number, width: number, height: number): Promise<string | null> {
1893
- if (!Number.isFinite(scale)) {
1894
- throw new Error(`viewport --scale: value must be a finite number, got ${scale}`);
1895
- }
1896
- if (scale < 1 || scale > 3) {
1897
- throw new Error(`viewport --scale: value must be between 1 and 3 (gstack policy cap), got ${scale}`);
1898
- }
1899
- if (this.connectionMode === 'headed') {
1900
- throw new Error('viewport --scale is not supported in headed mode — scale is controlled by the real browser window.');
1901
- }
1902
-
1903
- const prevScale = this.deviceScaleFactor;
1904
- const prevViewport = { ...this.currentViewport };
1905
- this.deviceScaleFactor = scale;
1906
- this.currentViewport = { width, height };
1907
-
1908
- const err = await this.recreateContext();
1909
- if (err !== null) {
1910
- // recreateContext's fallback path built a blank context using the NEW scale +
1911
- // viewport (the fields we just set). Rolling the fields back without a second
1912
- // recreate would leave the live context at new-scale while state says old-scale.
1913
- // Roll back fields FIRST, then force a second recreate against the old values
1914
- // so live state matches tracked state.
1915
- this.deviceScaleFactor = prevScale;
1916
- this.currentViewport = prevViewport;
1917
- const rollbackErr = await this.recreateContext();
1918
- if (rollbackErr !== null) {
1919
- // Second recreate also failed — we're in a clean blank slate via fallback, but
1920
- // with old scale. Return the original error so the caller sees the primary failure.
1921
- return `${err} (rollback also encountered: ${rollbackErr})`;
1922
- }
1923
- return err;
1924
- }
1925
- return null;
1926
- }
1927
-
1928
- /** Read current deviceScaleFactor (for tests + debug). */
1929
- getDeviceScaleFactor(): number {
1930
- return this.deviceScaleFactor;
1931
- }
1932
-
1933
- /** Read current tracked viewport (for tests + `viewport --scale` size fallback). */
1934
- getCurrentViewport(): { width: number; height: number } {
1935
- return { ...this.currentViewport };
1936
- }
1937
-
1938
- // ─── Handoff: Headless → Headed ─────────────────────────────
1939
- /**
1940
- * Hand off browser control to the user by relaunching in headed mode.
1941
- *
1942
- * Flow (launch-first-close-second for safe rollback):
1943
- * 1. Save state from current headless browser
1944
- * 2. Launch NEW headed browser
1945
- * 3. Restore state into new browser
1946
- * 4. Close OLD headless browser
1947
- * If step 2 fails → return error, headless browser untouched
1948
- */
1949
- async handoff(message: string): Promise<string> {
1950
- if (this.handoffPending) return this.handoffPending;
1951
- this.handoffPending = this.promoteToHeaded(message);
1952
- try { return await this.handoffPending; }
1953
- finally { this.handoffPending = null; }
1954
- }
1955
-
1956
- private async promoteToHeaded(message: string): Promise<string> {
1957
- if (this.closing) return 'ERROR: Browser is shutting down';
1958
- if (this.connectionMode === 'headed' || this.isHeaded) {
1959
- return `HANDOFF: Already in headed mode at ${this.getCurrentUrl()}`;
1960
- }
1961
- if (!this.browser || !this.context) {
1962
- throw new Error('Browser not launched');
1963
- }
1964
-
1965
- // 1. Save state from current browser
1966
- const state = await this.saveState();
1967
- if (this.closing) return 'ERROR: Browser is shutting down';
1968
- const currentUrl = this.getCurrentUrl();
1969
- const previousDisplay = this.xvfb;
1970
-
1971
- // 2. Launch new headed browser with extension (same as launchHeaded)
1972
- // Uses launchPersistentContext so the extension auto-loads.
1973
- let newContext: BrowserContext;
1974
- try {
1975
- await this.ensureHeadedDisplay();
1976
- const fs = require('fs');
1977
- const path = require('path');
1978
- const extensionPath = this.findExtensionPath();
1979
- const { STEALTH_LAUNCH_ARGS, buildGStackLaunchArgs } = await import('./stealth');
1980
- // Same blink-level stealth flags as launch()/launchHeaded(). Without
1981
- // STEALTH_LAUNCH_ARGS the handed-off browser kept the AutomationControlled
1982
- // tell that the other two paths strip.
1983
- const launchArgs: string[] = ['--hide-crash-restore-bubble', ...STEALTH_LAUNCH_ARGS, ...buildGStackLaunchArgs()];
1984
- if (extensionPath) {
1985
- launchArgs.push(`--disable-extensions-except=${extensionPath}`);
1986
- launchArgs.push(`--load-extension=${extensionPath}`);
1987
- // Auth token is served via POST /extension-token (pinned-origin
1988
- // bootstrap, no file write needed). /health is liveness-only.
1989
- console.log(`[browse] Handoff: loading extension from ${extensionPath}`);
1990
- } else {
1991
- console.log('[browse] Handoff: extension not found — headed mode without side panel');
1992
- }
1993
-
1994
- // Same profile resolution + singleton-lock cleanup as launchHeaded().
1995
- // This path previously hardcoded ~/.gstack/chromium-profile, silently
1996
- // ignoring $CHROMIUM_PROFILE / $GSTACK_HOME and skipping the lock
1997
- // cleanup — the third shipped drift between the three launch paths.
1998
- const userDataDir = resolveChromiumProfile();
1999
- ensureProjectProfile(userDataDir);
2000
- fs.mkdirSync(userDataDir, { recursive: true });
2001
- cleanSingletonLocks(userDataDir);
2002
-
2003
- // Self-heal probe (#2242): handoff always launches the Playwright-cache
2004
- // bundle (this launchPersistentContext call passes no executablePath),
2005
- // so a bundle poisoned by the old in-place rebrand would GPU-crash here
2006
- // exactly like launchHeaded(). Same probe, same contract: a
2007
- // GSTACK_CHROMIUM_PATH bundle is never passed in. The rethrown typed
2008
- // error surfaces through the outer catch as the actionable
2009
- // "Cannot open headed browser" message, headless browser untouched.
2010
- try {
2011
- probePoisonedChromiumBundle(chromium.executablePath());
2012
- } catch (err: unknown) {
2013
- if (err instanceof PoisonedBundleError) throw err;
2014
- // Probe failures (no bundle yet, EACCES) fall through to launch.
2015
- }
2016
-
2017
- // T1: same automation-tell-stripping defaults as launchHeaded().
2018
- // The handoff path (headless → headed re-launch) takes the same
2019
- // anti-detection posture.
2020
- const { STEALTH_IGNORE_DEFAULT_ARGS } = await import('./stealth');
2021
- // XProtect self-heal wrapper (P0 #2554): handoff always launches the
2022
- // Playwright-cache bundle (no executablePath), so the heal applies
2023
- // exactly as in launch()/launchHeaded().
2024
- newContext = await launchWithXProtectHeal(() => chromium.launchPersistentContext(userDataDir, {
2025
- headless: false,
2026
- ...(this.xvfb ? { env: { ...process.env, DISPLAY: this.xvfb.display } } : {}),
2027
- // #2220: daemon owns signal policy — see launch() for the rationale.
2028
- handleSIGINT: false,
2029
- handleSIGTERM: false,
2030
- handleSIGHUP: false,
2031
- // Match the sandbox policy used by launchHeaded() / launch(). The
2032
- // handoff path is the headless→headed re-launch and shares the same
2033
- // anti-detection posture, including no spurious --no-sandbox infobar.
2034
- chromiumSandbox: shouldEnableChromiumSandbox(),
2035
- args: launchArgs,
2036
- viewport: null,
2037
- ...(this.proxyConfig ? { proxy: this.proxyConfig } : {}),
2038
- ignoreDefaultArgs: STEALTH_IGNORE_DEFAULT_ARGS,
2039
- timeout: 15000,
2040
- }));
2041
- if (this.closing) {
2042
- await newContext.close().catch(() => {});
2043
- throw new Error('Browser is shutting down');
2044
- }
2045
- } catch (err: unknown) {
2046
- if (!previousDisplay) this.closeOwnedDisplay();
2047
- if (this.closing) return 'ERROR: Browser is shutting down';
2048
- const msg = err instanceof Error ? err.message : String(err);
2049
- return `ERROR: Cannot open headed browser — ${msg}. Headless browser still running.`;
2050
- }
2051
-
2052
- const previous = {
2053
- browser: this.browser, context: this.context,
2054
- pages: this.pages, tabSessions: this.tabSessions, tabOwnership: this.tabOwnership,
2055
- activeTabId: this.activeTabId, nextTabId: this.nextTabId,
2056
- connectionMode: this.connectionMode, isHeaded: this.isHeaded,
2057
- dialogAutoAccept: this.dialogAutoAccept, intentionalDisconnect: this.intentionalDisconnect,
2058
- chromiumProcInfo: this.chromiumProcInfo,
2059
- tabGuardrailSoftHit: this.tabGuardrailSoftHit, tabGuardrailHardHit: this.tabGuardrailHardHit,
2060
- };
2061
- this.handoffPrevious = previous;
2062
- try {
2063
- this.context = newContext;
2064
- this.browser = newContext.browser();
2065
- this.pages = new Map();
2066
- this.tabSessions = new Map();
2067
- this.tabOwnership = new Map();
2068
-
2069
- // Same Layer C stealth as launch()/launchHeaded(). Must run BEFORE
2070
- // restoreState() navigates so the init scripts apply to the restored
2071
- // pages — without this the handed-off browser had cmdline args but no
2072
- // JS stealth (no webdriver mask, no chrome.* shape, no toString proxy).
2073
- const { applyStealth } = await import('./stealth');
2074
- await applyStealth(newContext);
2075
-
2076
- if (Object.keys(this.extraHeaders).length > 0) {
2077
- await newContext.setExtraHTTPHeaders(this.extraHeaders);
2078
- }
2079
-
2080
- await this.restoreState(state);
2081
- if (this.closing) throw new Error('Browser is shutting down');
2082
-
2083
- if (this.browser) {
2084
- const browserRef = this.browser;
2085
- this.onBrowserDisconnected(this.browser, () => {
2086
- if (this.intentionalDisconnect) return;
2087
- void handleChromiumDisconnect(browserRef);
2088
- });
2089
- }
2090
-
2091
- this.connectionMode = 'headed';
2092
- this.isHeaded = true;
2093
- this.dialogAutoAccept = false; // User controls dialogs in headed mode
2094
- this.chromiumProcInfo = null;
2095
- try { this.onHeadedPromotion?.(); }
2096
- catch (err) { console.warn('[browse] Headed promotion callback failed:', err); }
2097
-
2098
- // 4. Close old headless browser (fire-and-forget)
2099
- this.detachDisconnectHandlers(previous.browser);
2100
- previous.browser.close().catch(() => {});
2101
-
2102
- return [
2103
- `HANDOFF: Browser opened at ${currentUrl}`,
2104
- ...(this.xvfb ? ['DISPLAY: Off-screen Xvfb; a separate remote desktop is required for human interaction.'] : []),
2105
- `MESSAGE: ${message}`,
2106
- `STATUS: Waiting for user. Run 'resume' when done.`,
2107
- ].join('\n');
2108
- } catch (err: unknown) {
2109
- await newContext.close().catch(() => {});
2110
- if (!this.closing) {
2111
- Object.assign(this, previous);
2112
- this.recheckTabGuardrailsOnClose();
2113
- }
2114
- if (!previousDisplay) this.closeOwnedDisplay();
2115
- if (this.closing) return 'ERROR: Browser is shutting down';
2116
- const msg = err instanceof Error ? err.message : String(err);
2117
- return `ERROR: Handoff failed during state restore — ${msg}. Headless browser still running.`;
2118
- } finally {
2119
- this.handoffPrevious = null;
2120
- }
2121
- }
2122
-
2123
- /**
2124
- * Resume AI control after user handoff.
2125
- * Clears stale refs and resets failure counter.
2126
- * The meta-command handler calls handleSnapshot() after this.
2127
- */
2128
- resume(): void {
2129
- // Clear refs and frame on the active session
2130
- try {
2131
- const session = this.getActiveSession();
2132
- session.clearRefs();
2133
- session.setFrame(null);
2134
- } catch {}
2135
- this.resetFailures();
2136
- }
2137
-
2138
- getIsHeaded(): boolean {
2139
- return this.isHeaded;
2140
- }
2141
-
2142
- // ─── Auto-handoff Hint (consecutive failure tracking) ───────
2143
- incrementFailures(): void {
2144
- this.consecutiveFailures++;
2145
- }
2146
-
2147
- resetFailures(): void {
2148
- this.consecutiveFailures = 0;
2149
- }
2150
-
2151
- getFailureHint(): string | null {
2152
- if (this.consecutiveFailures >= 3 && !this.isHeaded) {
2153
- return `HINT: ${this.consecutiveFailures} consecutive failures. Consider using 'handoff' to let the user help.`;
2154
- }
2155
- return null;
2156
- }
2157
-
2158
- /**
2159
- * Run a command and surface any navigation the guard blocked while it ran.
2160
- * The block reason replaces the command's own result or error (often
2161
- * "navigation interrupted"), so the caller learns why the tab is blank.
2162
- */
2163
- async failIfNavigationBlocked<T>(page: Page, work: Promise<T>): Promise<T> {
2164
- const outcome = await work.then((value) => ({ value }), (error) => ({ error }));
2165
- const guard = this.navigationGuards.get(page);
2166
- while (guard && guard.pending.size > 0) await Promise.all([...guard.pending]);
2167
- const blocked = guard?.blocked ?? null;
2168
- if (guard) guard.blocked = null;
2169
- if (blocked) throw new Error(blocked);
2170
- if ('error' in outcome) throw outcome.error;
2171
- return outcome.value;
2172
- }
2173
-
2174
- /**
2175
- * D2: validateNavigationUrl only sees explicit navigations. Redirect hops and
2176
- * page-driven navigations (links, scripts, forms, frames) arrive here as
2177
- * navigation requests; one that targets a blocked address blanks the tab and
2178
- * is reported by failIfNavigationBlocked.
2179
- */
2180
- private guardNavigations(page: Page): void {
2181
- if (this.navigationGuards.has(page)) return;
2182
- const guard = { pending: new Set<Promise<void>>(), blocked: null as string | null };
2183
- this.navigationGuards.set(page, guard);
2184
- page.on('request', (req) => {
2185
- if (!req.isNavigationRequest()) return;
2186
- const check: Promise<void> = blockedNavigationReason(req.url()).then(async (reason) => {
2187
- if (!reason || page.isClosed()) return;
2188
- guard.blocked = reason;
2189
- console.warn(`[browse] ${reason} (navigation from ${req.frame().url() || 'a new page'}; tab reset to about:blank)`);
2190
- // A target that fails fast commits its error page around the reset, so
2191
- // let it settle (bounded; a hanging target is cut off by the reset),
2192
- // then blank the tab until it stays blank (about:blank loads at once).
2193
- await Promise.race([req.response().catch(() => null), new Promise((resolve) => setTimeout(resolve, 1_000))]);
2194
- // Leaving an error page can commit a second about:blank after the reset
2195
- // resolves; wait for the main frame to stay quiet so the next command's
2196
- // navigation is not interrupted by it.
2197
- for (let attempt = 0; attempt < 3 && !page.isClosed(); attempt++) {
2198
- await page.goto('about:blank', { waitUntil: 'load', timeout: 5_000 }).catch(() => {});
2199
- const late = await page.waitForEvent('framenavigated', { predicate: (frame) => frame === page.mainFrame(), timeout: 250 })
2200
- .then(() => true, () => false);
2201
- if (!late && page.url() === 'about:blank') break;
2202
- }
2203
- }).finally(() => { guard.pending.delete(check); });
2204
- guard.pending.add(check);
2205
- });
2206
- }
2207
-
2208
- // ─── Console/Network/Dialog/Ref Wiring ────────────────────
2209
- private wirePageEvents(page: Page) {
2210
- const pages = this.pages;
2211
- const tabSessions = this.tabSessions;
2212
- this.guardNavigations(page);
2213
- // Track tab close — remove from pages and sessions maps, switch to another tab
2214
- page.on('close', () => {
2215
- for (const [id, p] of pages) {
2216
- if (p === page) {
2217
- pages.delete(id);
2218
- tabSessions.delete(id);
2219
- console.log(`[browse] Tab closed (id=${id}, remaining=${pages.size})`);
2220
- // If the closed tab was active, switch to another
2221
- const remaining = [...pages.keys()];
2222
- const fallback = remaining.length > 0 ? remaining[remaining.length - 1]! : 0;
2223
- if (pages === this.pages) {
2224
- if (this.activeTabId === id) this.activeTabId = fallback;
2225
- } else if (this.handoffPrevious?.pages === pages && this.handoffPrevious.activeTabId === id) {
2226
- this.handoffPrevious.activeTabId = fallback;
2227
- }
2228
- break;
2229
- }
2230
- }
2231
- if (pages === this.pages) this.recheckTabGuardrailsOnClose();
2232
- });
2233
-
2234
- // Clear ref map on navigation — refs point to stale elements after page change
2235
- // (lastSnapshot is NOT cleared — it's a text baseline for diffing)
2236
- page.on('framenavigated', (frame) => {
2237
- if (frame === page.mainFrame()) {
2238
- // Find the TabSession for this page and clear its per-tab state
2239
- for (const session of tabSessions.values()) {
2240
- if (session.page === page) {
2241
- session.onMainFrameNavigated();
2242
- break;
2243
- }
2244
- }
2245
- }
2246
- });
2247
-
2248
- // ─── Dialog auto-handling (prevents browser lockup) ─────
2249
- page.on('dialog', async (dialog) => {
2250
- const entry: DialogEntry = {
2251
- timestamp: Date.now(),
2252
- type: dialog.type(),
2253
- message: dialog.message(),
2254
- defaultValue: dialog.defaultValue() || undefined,
2255
- action: this.dialogAutoAccept ? 'accepted' : 'dismissed',
2256
- response: this.dialogAutoAccept ? (this.dialogPromptText ?? undefined) : undefined,
2257
- };
2258
- addDialogEntry(entry);
2259
-
2260
- try {
2261
- if (this.dialogAutoAccept) {
2262
- await dialog.accept(this.dialogPromptText ?? undefined);
2263
- } else {
2264
- await dialog.dismiss();
2265
- }
2266
- } catch {
2267
- // Dialog may have been dismissed by navigation
2268
- }
2269
- });
2270
-
2271
- page.on('console', (msg) => {
2272
- addConsoleEntry({
2273
- timestamp: Date.now(),
2274
- level: msg.type(),
2275
- text: msg.text(),
2276
- });
2277
- });
2278
-
2279
- page.on('request', (req) => {
2280
- addNetworkEntry({
2281
- timestamp: Date.now(),
2282
- method: req.method(),
2283
- url: req.url(),
2284
- });
2285
- });
2286
-
2287
- page.on('response', (res) => {
2288
- // Find matching request entry and update it (backward scan)
2289
- const url = res.url();
2290
- const status = res.status();
2291
- for (let i = networkBuffer.length - 1; i >= 0; i--) {
2292
- const entry = networkBuffer.get(i);
2293
- if (entry && entry.url === url && !entry.status) {
2294
- networkBuffer.set(i, { ...entry, status, duration: Date.now() - entry.timestamp });
2295
- break;
2296
- }
2297
- }
2298
- });
2299
-
2300
- // Capture response sizes via requestfinished — but DO NOT call
2301
- // response.body() here. Pre-fix, this listener materialized every
2302
- // response body across CDP just to read .length: multi-GB/hour of
2303
- // Buffer churn on long-lived headed Chromium with media-heavy
2304
- // pages, the primary Bun-side accelerant on the gbrowser-OOM
2305
- // investigation. req.sizes() pulls from the Network.loadingFinished
2306
- // event Chromium already emits — accurate for chunked transfer,
2307
- // gzip-compressed responses, and streaming media, all the cases
2308
- // where the previous Content-Length-header approach would have
2309
- // missed the size.
2310
- //
2311
- // The "single context-level CDP listener" architecture (D10's
2312
- // stretch goal — would reduce per-page listener count from N to 1
2313
- // via Target.setAutoAttach) is deferred. TODOS.md tracks it.
2314
- page.on('requestfinished', async (req) => {
2315
- try {
2316
- const sizes = await req.sizes().catch(() => null);
2317
- if (!sizes) return;
2318
- const url = req.url();
2319
- const size = sizes.responseBodySize ?? 0;
2320
- for (let i = networkBuffer.length - 1; i >= 0; i--) {
2321
- const entry = networkBuffer.get(i);
2322
- if (entry && entry.url === url && !entry.size) {
2323
- networkBuffer.set(i, { ...entry, size });
2324
- break;
2325
- }
2326
- }
2327
- } catch {
2328
- // Best-effort: requestfinished fires for aborted/cached requests too,
2329
- // where sizes() is unavailable. Missing size is acceptable; an
2330
- // unbounded throw would noise the console for every cache hit.
2331
- }
2332
- });
2333
- }
2334
- }