@ashx-j/lunr 0.2.13 → 0.2.15

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 (335) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +10 -7
  3. package/dist/builtin-extensions/ashxj-tui.d.ts +16 -1
  4. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.js +48 -24
  6. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  7. package/dist/builtin-extensions/index.d.ts.map +1 -1
  8. package/dist/builtin-extensions/index.js +2 -0
  9. package/dist/builtin-extensions/index.js.map +1 -1
  10. package/dist/builtin-extensions/lunr-settings-tools.d.ts +4 -0
  11. package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +1 -0
  12. package/dist/builtin-extensions/lunr-settings-tools.js +120 -0
  13. package/dist/builtin-extensions/lunr-settings-tools.js.map +1 -0
  14. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts +2 -2
  15. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.d.ts.map +1 -1
  16. package/dist/builtin-extensions/pi-prompt-template-model/subagent-runtime.js.map +1 -1
  17. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js +3 -3
  19. package/dist/builtin-extensions/pi-prompt-template-model/subagent-step.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts +2 -2
  21. package/dist/builtin-extensions/pi-subagents/src/api/delegation.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/api/delegation.js +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/api/delegation.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -0
  25. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +29 -23
  27. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +5 -11
  29. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +12 -15
  31. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  33. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +14 -27
  35. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +9 -4
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +2 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +16 -3
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts +1 -0
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts.map +1 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js +7 -4
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js.map +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js +1 -0
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js +3 -0
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.d.ts.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js +3 -3
  57. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js.map +1 -1
  58. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +2 -2
  60. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +3 -0
  63. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js +3 -35
  66. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +7 -8
  69. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  71. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +2 -0
  72. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +1 -0
  74. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  75. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +20 -21
  76. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +12 -1
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +42 -17
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +2 -0
  82. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  83. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  84. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +2 -1
  85. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  86. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +23 -1
  87. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  88. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +3 -1
  89. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
  90. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +15 -4
  91. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
  92. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +14 -3
  93. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  94. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
  95. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  96. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts +3 -3
  97. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
  98. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js +7 -6
  99. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
  100. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.d.ts.map +1 -1
  101. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js +3 -3
  102. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-request.js.map +1 -1
  103. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +1 -1
  104. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  105. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +6 -4
  106. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  107. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +1 -1
  108. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  109. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +15 -10
  110. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  111. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.d.ts.map +1 -1
  112. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js +5 -4
  113. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js.map +1 -1
  114. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -2
  115. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  116. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +86 -52
  117. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  118. package/dist/catalog/models.json +1 -1
  119. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  120. package/dist/catalog/providers/anthropic.json +1 -1
  121. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  122. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  123. package/dist/catalog/providers/fireworks.json +1 -1
  124. package/dist/catalog/providers/github-copilot.json +1 -1
  125. package/dist/catalog/providers/google-vertex.json +1 -1
  126. package/dist/catalog/providers/google.json +1 -1
  127. package/dist/catalog/providers/groq.json +1 -1
  128. package/dist/catalog/providers/huggingface.json +1 -1
  129. package/dist/catalog/providers/nvidia.json +1 -1
  130. package/dist/catalog/providers/openai-codex.json +1 -1
  131. package/dist/catalog/providers/openai.json +1 -1
  132. package/dist/catalog/providers/opencode-go.json +1 -1
  133. package/dist/catalog/providers/opencode.json +1 -1
  134. package/dist/catalog/providers/openrouter.json +1 -1
  135. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  136. package/dist/catalog/publication.json +131 -3
  137. package/dist/cli/list-models.d.ts.map +1 -1
  138. package/dist/cli/list-models.js +1 -0
  139. package/dist/cli/list-models.js.map +1 -1
  140. package/dist/cli.d.ts.map +1 -1
  141. package/dist/cli.js +52 -27
  142. package/dist/cli.js.map +1 -1
  143. package/dist/core/agent-session-runtime.d.ts +5 -1
  144. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  145. package/dist/core/agent-session-runtime.js +7 -2
  146. package/dist/core/agent-session-runtime.js.map +1 -1
  147. package/dist/core/agent-session.d.ts +2 -0
  148. package/dist/core/agent-session.d.ts.map +1 -1
  149. package/dist/core/agent-session.js +18 -6
  150. package/dist/core/agent-session.js.map +1 -1
  151. package/dist/core/catalog-cache-scope.d.ts +3 -0
  152. package/dist/core/catalog-cache-scope.d.ts.map +1 -0
  153. package/dist/core/catalog-cache-scope.js +12 -0
  154. package/dist/core/catalog-cache-scope.js.map +1 -0
  155. package/dist/core/catalog-merge.d.ts +2 -0
  156. package/dist/core/catalog-merge.d.ts.map +1 -1
  157. package/dist/core/catalog-merge.js +29 -2
  158. package/dist/core/catalog-merge.js.map +1 -1
  159. package/dist/core/catalog-refresh-polling.d.ts +8 -0
  160. package/dist/core/catalog-refresh-polling.d.ts.map +1 -0
  161. package/dist/core/catalog-refresh-polling.js +30 -0
  162. package/dist/core/catalog-refresh-polling.js.map +1 -0
  163. package/dist/core/codex-catalog.d.ts +3 -0
  164. package/dist/core/codex-catalog.d.ts.map +1 -0
  165. package/dist/core/codex-catalog.js +15 -0
  166. package/dist/core/codex-catalog.js.map +1 -0
  167. package/dist/core/context-breakdown.d.ts +2 -0
  168. package/dist/core/context-breakdown.d.ts.map +1 -1
  169. package/dist/core/context-breakdown.js +6 -2
  170. package/dist/core/context-breakdown.js.map +1 -1
  171. package/dist/core/customize.d.ts +2 -4
  172. package/dist/core/customize.d.ts.map +1 -1
  173. package/dist/core/customize.js +2 -8
  174. package/dist/core/customize.js.map +1 -1
  175. package/dist/core/footer-data-provider.d.ts +3 -4
  176. package/dist/core/footer-data-provider.d.ts.map +1 -1
  177. package/dist/core/footer-data-provider.js +54 -82
  178. package/dist/core/footer-data-provider.js.map +1 -1
  179. package/dist/core/injected-prompt.d.ts +3 -3
  180. package/dist/core/injected-prompt.d.ts.map +1 -1
  181. package/dist/core/injected-prompt.js +2 -7
  182. package/dist/core/injected-prompt.js.map +1 -1
  183. package/dist/core/large-subagent-launch.d.ts +13 -0
  184. package/dist/core/large-subagent-launch.d.ts.map +1 -0
  185. package/dist/core/large-subagent-launch.js +54 -0
  186. package/dist/core/large-subagent-launch.js.map +1 -0
  187. package/dist/core/live-catalog.d.ts +5 -2
  188. package/dist/core/live-catalog.d.ts.map +1 -1
  189. package/dist/core/live-catalog.js +108 -27
  190. package/dist/core/live-catalog.js.map +1 -1
  191. package/dist/core/model-instructions.d.ts +24 -0
  192. package/dist/core/model-instructions.d.ts.map +1 -0
  193. package/dist/core/model-instructions.js +123 -0
  194. package/dist/core/model-instructions.js.map +1 -0
  195. package/dist/core/model-runtime.d.ts +8 -0
  196. package/dist/core/model-runtime.d.ts.map +1 -1
  197. package/dist/core/model-runtime.js +64 -12
  198. package/dist/core/model-runtime.js.map +1 -1
  199. package/dist/core/official-catalog.d.ts +4 -0
  200. package/dist/core/official-catalog.d.ts.map +1 -1
  201. package/dist/core/official-catalog.js +86 -21
  202. package/dist/core/official-catalog.js.map +1 -1
  203. package/dist/core/permissions.d.ts +10 -11
  204. package/dist/core/permissions.d.ts.map +1 -1
  205. package/dist/core/permissions.js +107 -40
  206. package/dist/core/permissions.js.map +1 -1
  207. package/dist/core/plan-mode.d.ts.map +1 -1
  208. package/dist/core/plan-mode.js +10 -1
  209. package/dist/core/plan-mode.js.map +1 -1
  210. package/dist/core/resource-loader.d.ts +3 -0
  211. package/dist/core/resource-loader.d.ts.map +1 -1
  212. package/dist/core/resource-loader.js +13 -4
  213. package/dist/core/resource-loader.js.map +1 -1
  214. package/dist/core/runtime-bridges.d.ts +4 -0
  215. package/dist/core/runtime-bridges.d.ts.map +1 -0
  216. package/dist/core/runtime-bridges.js +16 -0
  217. package/dist/core/runtime-bridges.js.map +1 -0
  218. package/dist/core/sdk.d.ts.map +1 -1
  219. package/dist/core/sdk.js +1 -0
  220. package/dist/core/sdk.js.map +1 -1
  221. package/dist/core/settings-manager.d.ts +13 -6
  222. package/dist/core/settings-manager.d.ts.map +1 -1
  223. package/dist/core/settings-manager.js +32 -17
  224. package/dist/core/settings-manager.js.map +1 -1
  225. package/dist/core/settings-tools-bridge.d.ts +35 -0
  226. package/dist/core/settings-tools-bridge.d.ts.map +1 -0
  227. package/dist/core/settings-tools-bridge.js +56 -0
  228. package/dist/core/settings-tools-bridge.js.map +1 -0
  229. package/dist/core/slash-commands.d.ts.map +1 -1
  230. package/dist/core/slash-commands.js +1 -1
  231. package/dist/core/slash-commands.js.map +1 -1
  232. package/dist/core/system-prompt.d.ts.map +1 -1
  233. package/dist/core/system-prompt.js +1 -1
  234. package/dist/core/system-prompt.js.map +1 -1
  235. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  236. package/dist/gateway/agent-bridge.js +3 -2
  237. package/dist/gateway/agent-bridge.js.map +1 -1
  238. package/dist/gateway/approval.d.ts.map +1 -1
  239. package/dist/gateway/approval.js +4 -3
  240. package/dist/gateway/approval.js.map +1 -1
  241. package/dist/gateway/commands.d.ts +1 -1
  242. package/dist/gateway/commands.d.ts.map +1 -1
  243. package/dist/gateway/commands.js +1 -18
  244. package/dist/gateway/commands.js.map +1 -1
  245. package/dist/gateway/cron.d.ts.map +1 -1
  246. package/dist/gateway/cron.js +3 -2
  247. package/dist/gateway/cron.js.map +1 -1
  248. package/dist/gateway/router.d.ts +1 -1
  249. package/dist/gateway/router.d.ts.map +1 -1
  250. package/dist/gateway/router.js +1 -1
  251. package/dist/gateway/router.js.map +1 -1
  252. package/dist/main.d.ts +2 -0
  253. package/dist/main.d.ts.map +1 -1
  254. package/dist/main.js +108 -58
  255. package/dist/main.js.map +1 -1
  256. package/dist/migrations.d.ts +2 -0
  257. package/dist/migrations.d.ts.map +1 -1
  258. package/dist/migrations.js +18 -4
  259. package/dist/migrations.js.map +1 -1
  260. package/dist/modes/interactive/components/assistant-message.d.ts +1 -3
  261. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/assistant-message.js +4 -32
  263. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  264. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  265. package/dist/modes/interactive/components/custom-editor.js +2 -1
  266. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  267. package/dist/modes/interactive/components/injected-prompt-message.d.ts +1 -1
  268. package/dist/modes/interactive/components/injected-prompt-message.d.ts.map +1 -1
  269. package/dist/modes/interactive/components/injected-prompt-message.js +1 -1
  270. package/dist/modes/interactive/components/injected-prompt-message.js.map +1 -1
  271. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/model-selector.js +4 -1
  273. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  274. package/dist/modes/interactive/components/plan-message.d.ts +1 -3
  275. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -1
  276. package/dist/modes/interactive/components/plan-message.js +1 -16
  277. package/dist/modes/interactive/components/plan-message.js.map +1 -1
  278. package/dist/modes/interactive/components/settings-selector.d.ts +8 -5
  279. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  280. package/dist/modes/interactive/components/settings-selector.js +71 -31
  281. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  282. package/dist/modes/interactive/components/user-message.d.ts +1 -3
  283. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/user-message.js +2 -19
  285. package/dist/modes/interactive/components/user-message.js.map +1 -1
  286. package/dist/modes/interactive/interactive-mode.d.ts +13 -4
  287. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  288. package/dist/modes/interactive/interactive-mode.js +163 -177
  289. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  290. package/dist/modes/interactive/theme/moon.json +0 -1
  291. package/dist/modes/interactive/theme/theme-schema.json +3 -3
  292. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  293. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  294. package/dist/modes/interactive/theme/theme.js +95 -90
  295. package/dist/modes/interactive/theme/theme.js.map +1 -1
  296. package/dist/startup/interactive-view.d.ts +55 -0
  297. package/dist/startup/interactive-view.d.ts.map +1 -0
  298. package/dist/startup/interactive-view.js +332 -0
  299. package/dist/startup/interactive-view.js.map +1 -0
  300. package/dist/startup/launch-routing.d.ts +8 -0
  301. package/dist/startup/launch-routing.d.ts.map +1 -0
  302. package/dist/startup/launch-routing.js +29 -0
  303. package/dist/startup/launch-routing.js.map +1 -0
  304. package/dist/startup/startup-milestones.d.ts +4 -0
  305. package/dist/startup/startup-milestones.d.ts.map +1 -0
  306. package/dist/startup/startup-milestones.js +14 -0
  307. package/dist/startup/startup-milestones.js.map +1 -0
  308. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  309. package/dist/utils/syntax-highlight.js +10 -4
  310. package/dist/utils/syntax-highlight.js.map +1 -1
  311. package/docs/features.md +9 -9
  312. package/docs/interactive-startup.md +58 -0
  313. package/docs/keybindings.md +1 -1
  314. package/docs/model-catalog.md +29 -0
  315. package/docs/quickstart.md +3 -2
  316. package/docs/settings.md +4 -4
  317. package/docs/terminal-setup.md +2 -2
  318. package/docs/termux.md +1 -1
  319. package/docs/usage.md +4 -3
  320. package/docs/windows.md +1 -1
  321. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  322. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  323. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  324. package/examples/extensions/gondolin/package-lock.json +2 -2
  325. package/examples/extensions/gondolin/package.json +1 -1
  326. package/examples/extensions/sandbox/package-lock.json +2 -2
  327. package/examples/extensions/sandbox/package.json +1 -1
  328. package/examples/extensions/subagent/README.md +1 -1
  329. package/examples/extensions/with-deps/package-lock.json +2 -2
  330. package/examples/extensions/with-deps/package.json +1 -1
  331. package/package.json +16 -16
  332. package/dist/core/swarm.d.ts +0 -54
  333. package/dist/core/swarm.d.ts.map +0 -1
  334. package/dist/core/swarm.js +0 -119
  335. package/dist/core/swarm.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"footer-data-provider.d.ts","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"AA4DA,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAqBlF;AAkED;;;GAGG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAEhD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,cAAc,CAAoE;IAC1F,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,qBAAqB,CAA4D;IACzF,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,yBAAyB,CAA0B;IAC3D,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,qBAAqB,CAAyB;IACtD,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,YAAY,CAA8C;IAClE,OAAO,CAAC,oBAAoB,CAA8C;IAC1E,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,GAAG,EAAE,MAAM,EAItB;IAED,2EAA2E;IAC3E,YAAY,IAAI,MAAM,GAAG,IAAI,CAK5B;IAED,mFAAmF;IACnF,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAK1D;IAED,wDAAwD;IACxD,oBAAoB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAElD;IAED,qEAAqE;IACrE,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG/C;IAED,qCAAqC;IACrC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAM9D;IAED,yCAAyC;IACzC,sBAAsB,IAAI,IAAI,CAE7B;IAED,4EAA4E;IAC5E,yBAAyB,IAAI,MAAM,CAElC;IAED,gDAAgD;IAChD,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7C;IAED,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAgBxB;IAED,wBAAwB;IACxB,OAAO,IAAI,IAAI,CAQd;IAED,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,eAAe;YAYT,qBAAqB;IA+BnC,OAAO,CAAC,oBAAoB;YAcd,qBAAqB;IAgBnC,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,eAAe;CA2EvB;AAED,yGAAyG;AACzG,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,cAAc,GAAG,gBAAgB,GAAG,sBAAsB,GAAG,2BAA2B,GAAG,gBAAgB,CAC3G,CAAC","sourcesContent":["import { type ExecFileException, execFile, spawnSync } from \"child_process\";\nimport { existsSync, type FSWatcher, readFileSync, type Stats, statSync, unwatchFile, watchFile } from \"fs\";\nimport { dirname, join, resolve } from \"path\";\nimport { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from \"../utils/fs-watch.ts\";\n\ntype GitPaths = {\n\trepoDir: string;\n\tcommonGitDir: string;\n\theadPath: string;\n};\n\n/**\n * Find git metadata paths by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nfunction findGitPaths(cwd: string): GitPaths | null {\n\tlet dir = cwd;\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\tif (existsSync(gitPath)) {\n\t\t\ttry {\n\t\t\t\tconst stat = statSync(gitPath);\n\t\t\t\tif (stat.isFile()) {\n\t\t\t\t\tconst content = readFileSync(gitPath, \"utf8\").trim();\n\t\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\t\tconst gitDir = resolve(dir, content.slice(8).trim());\n\t\t\t\t\t\tconst headPath = join(gitDir, \"HEAD\");\n\t\t\t\t\t\tif (!existsSync(headPath)) return null;\n\t\t\t\t\t\tconst commonDirPath = join(gitDir, \"commondir\");\n\t\t\t\t\t\tconst commonGitDir = existsSync(commonDirPath)\n\t\t\t\t\t\t\t? resolve(gitDir, readFileSync(commonDirPath, \"utf8\").trim())\n\t\t\t\t\t\t\t: gitDir;\n\t\t\t\t\t\treturn { repoDir: dir, commonGitDir, headPath };\n\t\t\t\t\t}\n\t\t\t\t} else if (stat.isDirectory()) {\n\t\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\t\tif (!existsSync(headPath)) return null;\n\t\t\t\t\treturn { repoDir: dir, commonGitDir: gitPath, headPath };\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/** Ask git for the current branch. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitSync(repoDir: string): string | null {\n\tconst result = spawnSync(\"git\", [\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"], {\n\t\tcwd: repoDir,\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n\tconst branch = result.status === 0 ? result.stdout.trim() : \"\";\n\treturn branch || null;\n}\n\n/** Sum added/removed columns from `git diff --numstat`. Binary rows (`-`) are ignored. */\nexport function parseGitNumstat(stdout: string): { added: number; removed: number } {\n\tlet added = 0;\n\tlet removed = 0;\n\tfor (const line of stdout.split(/\\r?\\n/)) {\n\t\tif (!line) continue;\n\t\tconst tab1 = line.indexOf(\"\\t\");\n\t\tif (tab1 <= 0) continue;\n\t\tconst tab2 = line.indexOf(\"\\t\", tab1 + 1);\n\t\tif (tab2 < 0) continue;\n\t\tconst addTok = line.slice(0, tab1);\n\t\tconst delTok = line.slice(tab1 + 1, tab2);\n\t\tif (addTok !== \"-\") {\n\t\t\tconst n = Number(addTok);\n\t\t\tif (Number.isFinite(n)) added += n;\n\t\t}\n\t\tif (delTok !== \"-\") {\n\t\t\tconst n = Number(delTok);\n\t\t\tif (Number.isFinite(n)) removed += n;\n\t\t}\n\t}\n\treturn { added, removed };\n}\n\nfunction resolveDiffstatWithGitSync(repoDir: string): { added: number; removed: number } | null {\n\tconst result = spawnSync(\"git\", [\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"], {\n\t\tcwd: repoDir,\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n\tif (result.status !== 0) return null;\n\treturn parseGitNumstat(result.stdout ?? \"\");\n}\n\nfunction resolveDiffstatWithGitAsync(repoDir: string): Promise<{ added: number; removed: number } | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolvePromise(parseGitNumstat(stdout));\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** Ask git for the current branch asynchronously. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitAsync(repoDir: string): Promise<string | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst branch = stdout.trim();\n\t\t\t\tresolvePromise(branch || null);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction isWslEnvironment(): boolean {\n\treturn process.platform === \"linux\" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n}\n\nfunction isWindowsMountedRepoPath(repoDir: string): boolean {\n\treturn /^\\/mnt\\/[a-z](?:\\/|$)/i.test(repoDir);\n}\n\nfunction shouldPollGitHead(repoDir: string): boolean {\n\treturn isWslEnvironment() && isWindowsMountedRepoPath(repoDir);\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate cwd: string;\n\tprivate static readonly WATCH_DEBOUNCE_MS = 500;\n\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate cachedDiffstat: { added: number; removed: number } | null | undefined = undefined;\n\tprivate gitPaths: GitPaths | null | undefined = undefined;\n\tprivate headWatcher: FSWatcher | null = null;\n\tprivate headWatchFilePath: string | null = null;\n\tprivate headWatchFileListener: ((current: Stats, previous: Stats) => void) | null = null;\n\tprivate reftableWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListPath: string | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\tprivate refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate gitWatcherRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate refreshInFlight = false;\n\tprivate refreshPending = false;\n\tprivate disposed = false;\n\n\tconstructor(cwd: string) {\n\t\tthis.cwd = cwd;\n\t\tthis.gitPaths = findGitPaths(cwd);\n\t\tthis.setupGitWatcher();\n\t}\n\n\t/** Current git branch, null if not in repo, \"detached\" if detached HEAD */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch === undefined) {\n\t\t\tthis.cachedBranch = this.resolveGitBranchSync();\n\t\t}\n\t\treturn this.cachedBranch;\n\t}\n\n\t/** Staged + unstaged added/removed vs HEAD. Null when not a repo or git failed. */\n\tgetGitDiffstat(): { added: number; removed: number } | null {\n\t\tif (this.cachedDiffstat === undefined) {\n\t\t\tthis.cachedDiffstat = this.gitPaths ? resolveDiffstatWithGitSync(this.gitPaths.repoDir) : null;\n\t\t}\n\t\treturn this.cachedDiffstat;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\tsetCwd(cwd: string): void {\n\t\tif (this.cwd === cwd) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.cwd = cwd;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.cachedBranch = undefined;\n\t\tthis.cachedDiffstat = undefined;\n\t\tthis.gitPaths = findGitPaths(cwd);\n\t\tthis.setupGitWatcher();\n\t\tthis.notifyBranchChange();\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate notifyBranchChange(): void {\n\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.disposed || this.refreshTimer) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshTimer = setTimeout(() => {\n\t\t\tthis.refreshTimer = null;\n\t\t\tvoid this.refreshGitBranchAsync();\n\t\t}, FooterDataProvider.WATCH_DEBOUNCE_MS);\n\t}\n\n\tprivate async refreshGitBranchAsync(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refreshInFlight = true;\n\t\ttry {\n\t\t\tconst nextBranch = await this.resolveGitBranchAsync();\n\t\t\tconst nextDiff = this.gitPaths ? await resolveDiffstatWithGitAsync(this.gitPaths.repoDir) : null;\n\t\t\tif (this.disposed) return;\n\t\t\tconst branchChanged = this.cachedBranch !== undefined && this.cachedBranch !== nextBranch;\n\t\t\tconst prevDiff = this.cachedDiffstat;\n\t\t\tconst diffChanged =\n\t\t\t\tprevDiff !== undefined &&\n\t\t\t\t(prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed);\n\t\t\tthis.cachedBranch = nextBranch;\n\t\t\tthis.cachedDiffstat = nextDiff;\n\t\t\tif (branchChanged || diffChanged) {\n\t\t\t\tthis.notifyBranchChange();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.refreshInFlight = false;\n\t\t\tif (this.refreshPending && !this.disposed) {\n\t\t\t\tthis.refreshPending = false;\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate resolveGitBranchSync(): string | null {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = readFileSync(this.gitPaths.headPath, \"utf8\").trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\" ? (resolveBranchWithGitSync(this.gitPaths.repoDir) ?? \"detached\") : branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async resolveGitBranchAsync(): Promise<string | null> {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = readFileSync(this.gitPaths.headPath, \"utf8\").trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\"\n\t\t\t\t\t? ((await resolveBranchWithGitAsync(this.gitPaths.repoDir)) ?? \"detached\")\n\t\t\t\t\t: branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate clearGitWatchers(): void {\n\t\tcloseWatcher(this.headWatcher);\n\t\tthis.headWatcher = null;\n\t\tif (this.headWatchFilePath && this.headWatchFileListener) {\n\t\t\tunwatchFile(this.headWatchFilePath, this.headWatchFileListener);\n\t\t\tthis.headWatchFilePath = null;\n\t\t\tthis.headWatchFileListener = null;\n\t\t}\n\t\tcloseWatcher(this.reftableWatcher);\n\t\tthis.reftableWatcher = null;\n\t\tcloseWatcher(this.reftableTablesListWatcher);\n\t\tthis.reftableTablesListWatcher = null;\n\t\tif (this.reftableTablesListPath) {\n\t\t\tunwatchFile(this.reftableTablesListPath);\n\t\t\tthis.reftableTablesListPath = null;\n\t\t}\n\t\tif (this.gitWatcherRetryTimer) {\n\t\t\tclearTimeout(this.gitWatcherRetryTimer);\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t}\n\t}\n\n\tprivate scheduleGitWatcherRetry(): void {\n\t\tif (this.disposed || this.gitWatcherRetryTimer) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.gitWatcherRetryTimer = setTimeout(() => {\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t\tthis.setupGitWatcher();\n\t\t}, FS_WATCH_RETRY_DELAY_MS);\n\t}\n\n\tprivate handleGitWatcherError(): void {\n\t\tthis.clearGitWatchers();\n\t\tthis.scheduleGitWatcherRetry();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tthis.clearGitWatchers();\n\t\tif (!this.gitPaths) return;\n\n\t\tconst pollGitHead = shouldPollGitHead(this.gitPaths.repoDir);\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tthis.headWatcher = watchWithErrorHandler(\n\t\t\tdirname(this.gitPaths.headPath),\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (!filename || filename === \"HEAD\" || filename === \"index\") {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => this.handleGitWatcherError(),\n\t\t);\n\t\tif (pollGitHead) {\n\t\t\tthis.headWatchFilePath = this.gitPaths.headPath;\n\t\t\tthis.headWatchFileListener = (current, previous) => {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t) {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t};\n\t\t\twatchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener);\n\t\t}\n\t\tif (!this.headWatcher && !pollGitHead) {\n\t\t\treturn;\n\t\t}\n\n\t\t// In reftable repos, branch switches update files in the reftable directory\n\t\t// instead of HEAD. Watch it separately so the footer picks up those changes.\n\t\tconst reftableDir = join(this.gitPaths.commonGitDir, \"reftable\");\n\t\tif (existsSync(reftableDir)) {\n\t\t\tthis.reftableWatcher = watchWithErrorHandler(\n\t\t\t\treftableDir,\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t},\n\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t);\n\t\t\tif (!this.reftableWatcher) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tablesListPath = join(reftableDir, \"tables.list\");\n\t\t\tif (existsSync(tablesListPath)) {\n\t\t\t\tthis.reftableTablesListPath = tablesListPath;\n\t\t\t\tthis.reftableTablesListWatcher = watchWithErrorHandler(\n\t\t\t\t\ttablesListPath,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t},\n\t\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t\t);\n\t\t\t\tif (!this.reftableTablesListWatcher) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twatchFile(tablesListPath, { interval: 250 }, (current, previous) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getGitDiffstat\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"onBranchChange\"\n>;\n"]}
1
+ {"version":3,"file":"footer-data-provider.d.ts","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"AA8CA,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAqBlF;AA0DD;;;GAGG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAEhD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,cAAc,CAAoE;IAC1F,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,qBAAqB,CAA4D;IACzF,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,yBAAyB,CAA0B;IAC3D,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,qBAAqB,CAAyB;IACtD,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,YAAY,CAA8C;IAClE,OAAO,CAAC,oBAAoB,CAA8C;IAC1E,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,GAAG,EAAE,MAAM,EAEtB;IAED,8FAA8F;IAC9F,YAAY,IAAI,MAAM,GAAG,IAAI,CAK5B;IAED,6FAA6F;IAC7F,cAAc,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAK1D;IAED,wDAAwD;IACxD,oBAAoB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAElD;IAED,qEAAqE;IACrE,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG/C;IAED,qCAAqC;IACrC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAM9D;IAED,yCAAyC;IACzC,sBAAsB,IAAI,IAAI,CAE7B;IAED,4EAA4E;IAC5E,yBAAyB,IAAI,MAAM,CAElC;IAED,gDAAgD;IAChD,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7C;IAED,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAgBxB;IAED,wBAAwB;IACxB,OAAO,IAAI,IAAI,CAQd;IAED,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,eAAe;YAYT,cAAc;YAuCd,qBAAqB;IAgBnC,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,eAAe;CA2EvB;AAED,yGAAyG;AACzG,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,cAAc,GAAG,gBAAgB,GAAG,sBAAsB,GAAG,2BAA2B,GAAG,gBAAgB,CAC3G,CAAC","sourcesContent":["import { type ExecFileException, execFile } from \"child_process\";\nimport { existsSync, type FSWatcher, type Stats, unwatchFile, watchFile } from \"fs\";\nimport { readFile, stat } from \"fs/promises\";\nimport { dirname, join, resolve } from \"path\";\nimport { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from \"../utils/fs-watch.ts\";\n\ntype GitPaths = {\n\trepoDir: string;\n\tcommonGitDir: string;\n\theadPath: string;\n};\n\n/**\n * Find git metadata paths by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nasync function findGitPaths(cwd: string): Promise<GitPaths | null> {\n\tlet dir = cwd;\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\ttry {\n\t\t\tconst gitStat = await stat(gitPath);\n\t\t\tif (gitStat.isFile()) {\n\t\t\t\tconst content = (await readFile(gitPath, \"utf8\")).trim();\n\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\tconst gitDir = resolve(dir, content.slice(8).trim());\n\t\t\t\t\tconst headPath = join(gitDir, \"HEAD\");\n\t\t\t\t\tawait stat(headPath);\n\t\t\t\t\tlet commonGitDir = gitDir;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcommonGitDir = resolve(gitDir, (await readFile(join(gitDir, \"commondir\"), \"utf8\")).trim());\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { repoDir: dir, commonGitDir, headPath };\n\t\t\t\t}\n\t\t\t} else if (gitStat.isDirectory()) {\n\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\tawait stat(headPath);\n\t\t\t\treturn { repoDir: dir, commonGitDir: gitPath, headPath };\n\t\t\t}\n\t\t} catch {}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/** Sum added/removed columns from `git diff --numstat`. Binary rows (`-`) are ignored. */\nexport function parseGitNumstat(stdout: string): { added: number; removed: number } {\n\tlet added = 0;\n\tlet removed = 0;\n\tfor (const line of stdout.split(/\\r?\\n/)) {\n\t\tif (!line) continue;\n\t\tconst tab1 = line.indexOf(\"\\t\");\n\t\tif (tab1 <= 0) continue;\n\t\tconst tab2 = line.indexOf(\"\\t\", tab1 + 1);\n\t\tif (tab2 < 0) continue;\n\t\tconst addTok = line.slice(0, tab1);\n\t\tconst delTok = line.slice(tab1 + 1, tab2);\n\t\tif (addTok !== \"-\") {\n\t\t\tconst n = Number(addTok);\n\t\t\tif (Number.isFinite(n)) added += n;\n\t\t}\n\t\tif (delTok !== \"-\") {\n\t\t\tconst n = Number(delTok);\n\t\t\tif (Number.isFinite(n)) removed += n;\n\t\t}\n\t}\n\treturn { added, removed };\n}\n\nfunction resolveDiffstatWithGitAsync(repoDir: string): Promise<{ added: number; removed: number } | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\ttimeout: 3000,\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolvePromise(parseGitNumstat(stdout));\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** Ask git for the current branch asynchronously. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitAsync(repoDir: string): Promise<string | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\ttimeout: 3000,\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst branch = stdout.trim();\n\t\t\t\tresolvePromise(branch || null);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction isWslEnvironment(): boolean {\n\treturn process.platform === \"linux\" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n}\n\nfunction isWindowsMountedRepoPath(repoDir: string): boolean {\n\treturn /^\\/mnt\\/[a-z](?:\\/|$)/i.test(repoDir);\n}\n\nfunction shouldPollGitHead(repoDir: string): boolean {\n\treturn isWslEnvironment() && isWindowsMountedRepoPath(repoDir);\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate cwd: string;\n\tprivate static readonly WATCH_DEBOUNCE_MS = 500;\n\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate cachedDiffstat: { added: number; removed: number } | null | undefined = undefined;\n\tprivate gitPaths: GitPaths | null | undefined = undefined;\n\tprivate headWatcher: FSWatcher | null = null;\n\tprivate headWatchFilePath: string | null = null;\n\tprivate headWatchFileListener: ((current: Stats, previous: Stats) => void) | null = null;\n\tprivate reftableWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListPath: string | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\tprivate refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate gitWatcherRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate refreshInFlight = false;\n\tprivate refreshPending = false;\n\tprivate disposed = false;\n\n\tconstructor(cwd: string) {\n\t\tthis.cwd = cwd;\n\t}\n\n\t/** Current git branch, null until the asynchronous lookup completes or when not in a repo. */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch === undefined && !this.refreshInFlight) {\n\t\t\tvoid this.refreshGitData();\n\t\t}\n\t\treturn this.cachedBranch ?? null;\n\t}\n\n\t/** Staged + unstaged added/removed vs HEAD. Null until the asynchronous lookup completes. */\n\tgetGitDiffstat(): { added: number; removed: number } | null {\n\t\tif (this.cachedDiffstat === undefined && !this.refreshInFlight) {\n\t\t\tvoid this.refreshGitData();\n\t\t}\n\t\treturn this.cachedDiffstat ?? null;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\tsetCwd(cwd: string): void {\n\t\tif (this.cwd === cwd) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.cwd = cwd;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.cachedBranch = undefined;\n\t\tthis.cachedDiffstat = undefined;\n\t\tthis.gitPaths = undefined;\n\t\tif (this.refreshInFlight) this.refreshPending = true;\n\t\tthis.notifyBranchChange();\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate notifyBranchChange(): void {\n\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.disposed || this.refreshTimer) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshTimer = setTimeout(() => {\n\t\t\tthis.refreshTimer = null;\n\t\t\tvoid this.refreshGitData();\n\t\t}, FooterDataProvider.WATCH_DEBOUNCE_MS);\n\t}\n\n\tprivate async refreshGitData(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refreshInFlight = true;\n\t\tconst cwd = this.cwd;\n\t\ttry {\n\t\t\tconst gitPaths = this.gitPaths === undefined ? await findGitPaths(cwd) : this.gitPaths;\n\t\t\tif (this.disposed || this.cwd !== cwd) return;\n\t\t\tif (this.gitPaths === undefined) {\n\t\t\t\tthis.gitPaths = gitPaths;\n\t\t\t\tthis.setupGitWatcher();\n\t\t\t}\n\t\t\tconst [nextBranch, nextDiff] = await Promise.all([\n\t\t\t\tthis.resolveGitBranchAsync(),\n\t\t\t\tgitPaths ? resolveDiffstatWithGitAsync(gitPaths.repoDir) : null,\n\t\t\t]);\n\t\t\tif (this.disposed || this.cwd !== cwd) return;\n\t\t\tconst prevBranch = this.cachedBranch;\n\t\t\tconst prevDiff = this.cachedDiffstat;\n\t\t\tconst branchChanged = prevBranch !== nextBranch;\n\t\t\tconst diffChanged = prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed;\n\t\t\tthis.cachedBranch = nextBranch;\n\t\t\tthis.cachedDiffstat = nextDiff;\n\t\t\tif (branchChanged || diffChanged) {\n\t\t\t\tthis.notifyBranchChange();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.refreshInFlight = false;\n\t\t\tif (this.refreshPending && !this.disposed) {\n\t\t\t\tthis.refreshPending = false;\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async resolveGitBranchAsync(): Promise<string | null> {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = (await readFile(this.gitPaths.headPath, \"utf8\")).trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\"\n\t\t\t\t\t? ((await resolveBranchWithGitAsync(this.gitPaths.repoDir)) ?? \"detached\")\n\t\t\t\t\t: branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate clearGitWatchers(): void {\n\t\tcloseWatcher(this.headWatcher);\n\t\tthis.headWatcher = null;\n\t\tif (this.headWatchFilePath && this.headWatchFileListener) {\n\t\t\tunwatchFile(this.headWatchFilePath, this.headWatchFileListener);\n\t\t\tthis.headWatchFilePath = null;\n\t\t\tthis.headWatchFileListener = null;\n\t\t}\n\t\tcloseWatcher(this.reftableWatcher);\n\t\tthis.reftableWatcher = null;\n\t\tcloseWatcher(this.reftableTablesListWatcher);\n\t\tthis.reftableTablesListWatcher = null;\n\t\tif (this.reftableTablesListPath) {\n\t\t\tunwatchFile(this.reftableTablesListPath);\n\t\t\tthis.reftableTablesListPath = null;\n\t\t}\n\t\tif (this.gitWatcherRetryTimer) {\n\t\t\tclearTimeout(this.gitWatcherRetryTimer);\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t}\n\t}\n\n\tprivate scheduleGitWatcherRetry(): void {\n\t\tif (this.disposed || this.gitWatcherRetryTimer) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.gitWatcherRetryTimer = setTimeout(() => {\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t\tthis.setupGitWatcher();\n\t\t}, FS_WATCH_RETRY_DELAY_MS);\n\t}\n\n\tprivate handleGitWatcherError(): void {\n\t\tthis.clearGitWatchers();\n\t\tthis.scheduleGitWatcherRetry();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tthis.clearGitWatchers();\n\t\tif (!this.gitPaths) return;\n\n\t\tconst pollGitHead = shouldPollGitHead(this.gitPaths.repoDir);\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tthis.headWatcher = watchWithErrorHandler(\n\t\t\tdirname(this.gitPaths.headPath),\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (!filename || filename === \"HEAD\" || filename === \"index\") {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => this.handleGitWatcherError(),\n\t\t);\n\t\tif (pollGitHead) {\n\t\t\tthis.headWatchFilePath = this.gitPaths.headPath;\n\t\t\tthis.headWatchFileListener = (current, previous) => {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t) {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t};\n\t\t\twatchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener);\n\t\t}\n\t\tif (!this.headWatcher && !pollGitHead) {\n\t\t\treturn;\n\t\t}\n\n\t\t// In reftable repos, branch switches update files in the reftable directory\n\t\t// instead of HEAD. Watch it separately so the footer picks up those changes.\n\t\tconst reftableDir = join(this.gitPaths.commonGitDir, \"reftable\");\n\t\tif (existsSync(reftableDir)) {\n\t\t\tthis.reftableWatcher = watchWithErrorHandler(\n\t\t\t\treftableDir,\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t},\n\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t);\n\t\t\tif (!this.reftableWatcher) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tablesListPath = join(reftableDir, \"tables.list\");\n\t\t\tif (existsSync(tablesListPath)) {\n\t\t\t\tthis.reftableTablesListPath = tablesListPath;\n\t\t\t\tthis.reftableTablesListWatcher = watchWithErrorHandler(\n\t\t\t\t\ttablesListPath,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t},\n\t\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t\t);\n\t\t\t\tif (!this.reftableTablesListWatcher) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twatchFile(tablesListPath, { interval: 250 }, (current, previous) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getGitDiffstat\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"onBranchChange\"\n>;\n"]}
@@ -1,59 +1,45 @@
1
- import { execFile, spawnSync } from "child_process";
2
- import { existsSync, readFileSync, statSync, unwatchFile, watchFile } from "fs";
1
+ import { execFile } from "child_process";
2
+ import { existsSync, unwatchFile, watchFile } from "fs";
3
+ import { readFile, stat } from "fs/promises";
3
4
  import { dirname, join, resolve } from "path";
4
5
  import { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from "../utils/fs-watch.js";
5
6
  /**
6
7
  * Find git metadata paths by walking up from cwd.
7
8
  * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).
8
9
  */
9
- function findGitPaths(cwd) {
10
+ async function findGitPaths(cwd) {
10
11
  let dir = cwd;
11
12
  while (true) {
12
13
  const gitPath = join(dir, ".git");
13
- if (existsSync(gitPath)) {
14
- try {
15
- const stat = statSync(gitPath);
16
- if (stat.isFile()) {
17
- const content = readFileSync(gitPath, "utf8").trim();
18
- if (content.startsWith("gitdir: ")) {
19
- const gitDir = resolve(dir, content.slice(8).trim());
20
- const headPath = join(gitDir, "HEAD");
21
- if (!existsSync(headPath))
22
- return null;
23
- const commonDirPath = join(gitDir, "commondir");
24
- const commonGitDir = existsSync(commonDirPath)
25
- ? resolve(gitDir, readFileSync(commonDirPath, "utf8").trim())
26
- : gitDir;
27
- return { repoDir: dir, commonGitDir, headPath };
14
+ try {
15
+ const gitStat = await stat(gitPath);
16
+ if (gitStat.isFile()) {
17
+ const content = (await readFile(gitPath, "utf8")).trim();
18
+ if (content.startsWith("gitdir: ")) {
19
+ const gitDir = resolve(dir, content.slice(8).trim());
20
+ const headPath = join(gitDir, "HEAD");
21
+ await stat(headPath);
22
+ let commonGitDir = gitDir;
23
+ try {
24
+ commonGitDir = resolve(gitDir, (await readFile(join(gitDir, "commondir"), "utf8")).trim());
28
25
  }
29
- }
30
- else if (stat.isDirectory()) {
31
- const headPath = join(gitPath, "HEAD");
32
- if (!existsSync(headPath))
33
- return null;
34
- return { repoDir: dir, commonGitDir: gitPath, headPath };
26
+ catch { }
27
+ return { repoDir: dir, commonGitDir, headPath };
35
28
  }
36
29
  }
37
- catch {
38
- return null;
30
+ else if (gitStat.isDirectory()) {
31
+ const headPath = join(gitPath, "HEAD");
32
+ await stat(headPath);
33
+ return { repoDir: dir, commonGitDir: gitPath, headPath };
39
34
  }
40
35
  }
36
+ catch { }
41
37
  const parent = dirname(dir);
42
38
  if (parent === dir)
43
39
  return null;
44
40
  dir = parent;
45
41
  }
46
42
  }
47
- /** Ask git for the current branch. Returns null on detached HEAD or if git is unavailable. */
48
- function resolveBranchWithGitSync(repoDir) {
49
- const result = spawnSync("git", ["--no-optional-locks", "symbolic-ref", "--quiet", "--short", "HEAD"], {
50
- cwd: repoDir,
51
- encoding: "utf8",
52
- stdio: ["ignore", "pipe", "ignore"],
53
- });
54
- const branch = result.status === 0 ? result.stdout.trim() : "";
55
- return branch || null;
56
- }
57
43
  /** Sum added/removed columns from `git diff --numstat`. Binary rows (`-`) are ignored. */
58
44
  export function parseGitNumstat(stdout) {
59
45
  let added = 0;
@@ -82,21 +68,12 @@ export function parseGitNumstat(stdout) {
82
68
  }
83
69
  return { added, removed };
84
70
  }
85
- function resolveDiffstatWithGitSync(repoDir) {
86
- const result = spawnSync("git", ["--no-optional-locks", "diff", "--numstat", "HEAD"], {
87
- cwd: repoDir,
88
- encoding: "utf8",
89
- stdio: ["ignore", "pipe", "ignore"],
90
- });
91
- if (result.status !== 0)
92
- return null;
93
- return parseGitNumstat(result.stdout ?? "");
94
- }
95
71
  function resolveDiffstatWithGitAsync(repoDir) {
96
72
  return new Promise((resolvePromise) => {
97
73
  execFile("git", ["--no-optional-locks", "diff", "--numstat", "HEAD"], {
98
74
  cwd: repoDir,
99
75
  encoding: "utf8",
76
+ timeout: 3000,
100
77
  }, (error, stdout) => {
101
78
  if (error) {
102
79
  resolvePromise(null);
@@ -112,6 +89,7 @@ function resolveBranchWithGitAsync(repoDir) {
112
89
  execFile("git", ["--no-optional-locks", "symbolic-ref", "--quiet", "--short", "HEAD"], {
113
90
  cwd: repoDir,
114
91
  encoding: "utf8",
92
+ timeout: 3000,
115
93
  }, (error, stdout) => {
116
94
  if (error) {
117
95
  resolvePromise(null);
@@ -157,22 +135,20 @@ export class FooterDataProvider {
157
135
  disposed = false;
158
136
  constructor(cwd) {
159
137
  this.cwd = cwd;
160
- this.gitPaths = findGitPaths(cwd);
161
- this.setupGitWatcher();
162
138
  }
163
- /** Current git branch, null if not in repo, "detached" if detached HEAD */
139
+ /** Current git branch, null until the asynchronous lookup completes or when not in a repo. */
164
140
  getGitBranch() {
165
- if (this.cachedBranch === undefined) {
166
- this.cachedBranch = this.resolveGitBranchSync();
141
+ if (this.cachedBranch === undefined && !this.refreshInFlight) {
142
+ void this.refreshGitData();
167
143
  }
168
- return this.cachedBranch;
144
+ return this.cachedBranch ?? null;
169
145
  }
170
- /** Staged + unstaged added/removed vs HEAD. Null when not a repo or git failed. */
146
+ /** Staged + unstaged added/removed vs HEAD. Null until the asynchronous lookup completes. */
171
147
  getGitDiffstat() {
172
- if (this.cachedDiffstat === undefined) {
173
- this.cachedDiffstat = this.gitPaths ? resolveDiffstatWithGitSync(this.gitPaths.repoDir) : null;
148
+ if (this.cachedDiffstat === undefined && !this.refreshInFlight) {
149
+ void this.refreshGitData();
174
150
  }
175
- return this.cachedDiffstat;
151
+ return this.cachedDiffstat ?? null;
176
152
  }
177
153
  /** Extension status texts set via ctx.ui.setStatus() */
178
154
  getExtensionStatuses() {
@@ -216,8 +192,9 @@ export class FooterDataProvider {
216
192
  this.clearGitWatchers();
217
193
  this.cachedBranch = undefined;
218
194
  this.cachedDiffstat = undefined;
219
- this.gitPaths = findGitPaths(cwd);
220
- this.setupGitWatcher();
195
+ this.gitPaths = undefined;
196
+ if (this.refreshInFlight)
197
+ this.refreshPending = true;
221
198
  this.notifyBranchChange();
222
199
  }
223
200
  /** Internal: cleanup */
@@ -243,10 +220,10 @@ export class FooterDataProvider {
243
220
  }
244
221
  this.refreshTimer = setTimeout(() => {
245
222
  this.refreshTimer = null;
246
- void this.refreshGitBranchAsync();
223
+ void this.refreshGitData();
247
224
  }, FooterDataProvider.WATCH_DEBOUNCE_MS);
248
225
  }
249
- async refreshGitBranchAsync() {
226
+ async refreshGitData() {
250
227
  if (this.disposed)
251
228
  return;
252
229
  if (this.refreshInFlight) {
@@ -254,15 +231,25 @@ export class FooterDataProvider {
254
231
  return;
255
232
  }
256
233
  this.refreshInFlight = true;
234
+ const cwd = this.cwd;
257
235
  try {
258
- const nextBranch = await this.resolveGitBranchAsync();
259
- const nextDiff = this.gitPaths ? await resolveDiffstatWithGitAsync(this.gitPaths.repoDir) : null;
260
- if (this.disposed)
236
+ const gitPaths = this.gitPaths === undefined ? await findGitPaths(cwd) : this.gitPaths;
237
+ if (this.disposed || this.cwd !== cwd)
261
238
  return;
262
- const branchChanged = this.cachedBranch !== undefined && this.cachedBranch !== nextBranch;
239
+ if (this.gitPaths === undefined) {
240
+ this.gitPaths = gitPaths;
241
+ this.setupGitWatcher();
242
+ }
243
+ const [nextBranch, nextDiff] = await Promise.all([
244
+ this.resolveGitBranchAsync(),
245
+ gitPaths ? resolveDiffstatWithGitAsync(gitPaths.repoDir) : null,
246
+ ]);
247
+ if (this.disposed || this.cwd !== cwd)
248
+ return;
249
+ const prevBranch = this.cachedBranch;
263
250
  const prevDiff = this.cachedDiffstat;
264
- const diffChanged = prevDiff !== undefined &&
265
- (prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed);
251
+ const branchChanged = prevBranch !== nextBranch;
252
+ const diffChanged = prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed;
266
253
  this.cachedBranch = nextBranch;
267
254
  this.cachedDiffstat = nextDiff;
268
255
  if (branchChanged || diffChanged) {
@@ -277,26 +264,11 @@ export class FooterDataProvider {
277
264
  }
278
265
  }
279
266
  }
280
- resolveGitBranchSync() {
281
- try {
282
- if (!this.gitPaths)
283
- return null;
284
- const content = readFileSync(this.gitPaths.headPath, "utf8").trim();
285
- if (content.startsWith("ref: refs/heads/")) {
286
- const branch = content.slice(16);
287
- return branch === ".invalid" ? (resolveBranchWithGitSync(this.gitPaths.repoDir) ?? "detached") : branch;
288
- }
289
- return "detached";
290
- }
291
- catch {
292
- return null;
293
- }
294
- }
295
267
  async resolveGitBranchAsync() {
296
268
  try {
297
269
  if (!this.gitPaths)
298
270
  return null;
299
- const content = readFileSync(this.gitPaths.headPath, "utf8").trim();
271
+ const content = (await readFile(this.gitPaths.headPath, "utf8")).trim();
300
272
  if (content.startsWith("ref: refs/heads/")) {
301
273
  const branch = content.slice(16);
302
274
  return branch === ".invalid"
@@ -1 +1 @@
1
- {"version":3,"file":"footer-data-provider.js","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ExecFileException, execFile, spawnSync } from \"child_process\";\nimport { existsSync, type FSWatcher, readFileSync, type Stats, statSync, unwatchFile, watchFile } from \"fs\";\nimport { dirname, join, resolve } from \"path\";\nimport { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from \"../utils/fs-watch.ts\";\n\ntype GitPaths = {\n\trepoDir: string;\n\tcommonGitDir: string;\n\theadPath: string;\n};\n\n/**\n * Find git metadata paths by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nfunction findGitPaths(cwd: string): GitPaths | null {\n\tlet dir = cwd;\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\tif (existsSync(gitPath)) {\n\t\t\ttry {\n\t\t\t\tconst stat = statSync(gitPath);\n\t\t\t\tif (stat.isFile()) {\n\t\t\t\t\tconst content = readFileSync(gitPath, \"utf8\").trim();\n\t\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\t\tconst gitDir = resolve(dir, content.slice(8).trim());\n\t\t\t\t\t\tconst headPath = join(gitDir, \"HEAD\");\n\t\t\t\t\t\tif (!existsSync(headPath)) return null;\n\t\t\t\t\t\tconst commonDirPath = join(gitDir, \"commondir\");\n\t\t\t\t\t\tconst commonGitDir = existsSync(commonDirPath)\n\t\t\t\t\t\t\t? resolve(gitDir, readFileSync(commonDirPath, \"utf8\").trim())\n\t\t\t\t\t\t\t: gitDir;\n\t\t\t\t\t\treturn { repoDir: dir, commonGitDir, headPath };\n\t\t\t\t\t}\n\t\t\t\t} else if (stat.isDirectory()) {\n\t\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\t\tif (!existsSync(headPath)) return null;\n\t\t\t\t\treturn { repoDir: dir, commonGitDir: gitPath, headPath };\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/** Ask git for the current branch. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitSync(repoDir: string): string | null {\n\tconst result = spawnSync(\"git\", [\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"], {\n\t\tcwd: repoDir,\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n\tconst branch = result.status === 0 ? result.stdout.trim() : \"\";\n\treturn branch || null;\n}\n\n/** Sum added/removed columns from `git diff --numstat`. Binary rows (`-`) are ignored. */\nexport function parseGitNumstat(stdout: string): { added: number; removed: number } {\n\tlet added = 0;\n\tlet removed = 0;\n\tfor (const line of stdout.split(/\\r?\\n/)) {\n\t\tif (!line) continue;\n\t\tconst tab1 = line.indexOf(\"\\t\");\n\t\tif (tab1 <= 0) continue;\n\t\tconst tab2 = line.indexOf(\"\\t\", tab1 + 1);\n\t\tif (tab2 < 0) continue;\n\t\tconst addTok = line.slice(0, tab1);\n\t\tconst delTok = line.slice(tab1 + 1, tab2);\n\t\tif (addTok !== \"-\") {\n\t\t\tconst n = Number(addTok);\n\t\t\tif (Number.isFinite(n)) added += n;\n\t\t}\n\t\tif (delTok !== \"-\") {\n\t\t\tconst n = Number(delTok);\n\t\t\tif (Number.isFinite(n)) removed += n;\n\t\t}\n\t}\n\treturn { added, removed };\n}\n\nfunction resolveDiffstatWithGitSync(repoDir: string): { added: number; removed: number } | null {\n\tconst result = spawnSync(\"git\", [\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"], {\n\t\tcwd: repoDir,\n\t\tencoding: \"utf8\",\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n\tif (result.status !== 0) return null;\n\treturn parseGitNumstat(result.stdout ?? \"\");\n}\n\nfunction resolveDiffstatWithGitAsync(repoDir: string): Promise<{ added: number; removed: number } | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolvePromise(parseGitNumstat(stdout));\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** Ask git for the current branch asynchronously. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitAsync(repoDir: string): Promise<string | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst branch = stdout.trim();\n\t\t\t\tresolvePromise(branch || null);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction isWslEnvironment(): boolean {\n\treturn process.platform === \"linux\" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n}\n\nfunction isWindowsMountedRepoPath(repoDir: string): boolean {\n\treturn /^\\/mnt\\/[a-z](?:\\/|$)/i.test(repoDir);\n}\n\nfunction shouldPollGitHead(repoDir: string): boolean {\n\treturn isWslEnvironment() && isWindowsMountedRepoPath(repoDir);\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate cwd: string;\n\tprivate static readonly WATCH_DEBOUNCE_MS = 500;\n\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate cachedDiffstat: { added: number; removed: number } | null | undefined = undefined;\n\tprivate gitPaths: GitPaths | null | undefined = undefined;\n\tprivate headWatcher: FSWatcher | null = null;\n\tprivate headWatchFilePath: string | null = null;\n\tprivate headWatchFileListener: ((current: Stats, previous: Stats) => void) | null = null;\n\tprivate reftableWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListPath: string | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\tprivate refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate gitWatcherRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate refreshInFlight = false;\n\tprivate refreshPending = false;\n\tprivate disposed = false;\n\n\tconstructor(cwd: string) {\n\t\tthis.cwd = cwd;\n\t\tthis.gitPaths = findGitPaths(cwd);\n\t\tthis.setupGitWatcher();\n\t}\n\n\t/** Current git branch, null if not in repo, \"detached\" if detached HEAD */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch === undefined) {\n\t\t\tthis.cachedBranch = this.resolveGitBranchSync();\n\t\t}\n\t\treturn this.cachedBranch;\n\t}\n\n\t/** Staged + unstaged added/removed vs HEAD. Null when not a repo or git failed. */\n\tgetGitDiffstat(): { added: number; removed: number } | null {\n\t\tif (this.cachedDiffstat === undefined) {\n\t\t\tthis.cachedDiffstat = this.gitPaths ? resolveDiffstatWithGitSync(this.gitPaths.repoDir) : null;\n\t\t}\n\t\treturn this.cachedDiffstat;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\tsetCwd(cwd: string): void {\n\t\tif (this.cwd === cwd) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.cwd = cwd;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.cachedBranch = undefined;\n\t\tthis.cachedDiffstat = undefined;\n\t\tthis.gitPaths = findGitPaths(cwd);\n\t\tthis.setupGitWatcher();\n\t\tthis.notifyBranchChange();\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate notifyBranchChange(): void {\n\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.disposed || this.refreshTimer) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshTimer = setTimeout(() => {\n\t\t\tthis.refreshTimer = null;\n\t\t\tvoid this.refreshGitBranchAsync();\n\t\t}, FooterDataProvider.WATCH_DEBOUNCE_MS);\n\t}\n\n\tprivate async refreshGitBranchAsync(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refreshInFlight = true;\n\t\ttry {\n\t\t\tconst nextBranch = await this.resolveGitBranchAsync();\n\t\t\tconst nextDiff = this.gitPaths ? await resolveDiffstatWithGitAsync(this.gitPaths.repoDir) : null;\n\t\t\tif (this.disposed) return;\n\t\t\tconst branchChanged = this.cachedBranch !== undefined && this.cachedBranch !== nextBranch;\n\t\t\tconst prevDiff = this.cachedDiffstat;\n\t\t\tconst diffChanged =\n\t\t\t\tprevDiff !== undefined &&\n\t\t\t\t(prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed);\n\t\t\tthis.cachedBranch = nextBranch;\n\t\t\tthis.cachedDiffstat = nextDiff;\n\t\t\tif (branchChanged || diffChanged) {\n\t\t\t\tthis.notifyBranchChange();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.refreshInFlight = false;\n\t\t\tif (this.refreshPending && !this.disposed) {\n\t\t\t\tthis.refreshPending = false;\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate resolveGitBranchSync(): string | null {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = readFileSync(this.gitPaths.headPath, \"utf8\").trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\" ? (resolveBranchWithGitSync(this.gitPaths.repoDir) ?? \"detached\") : branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async resolveGitBranchAsync(): Promise<string | null> {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = readFileSync(this.gitPaths.headPath, \"utf8\").trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\"\n\t\t\t\t\t? ((await resolveBranchWithGitAsync(this.gitPaths.repoDir)) ?? \"detached\")\n\t\t\t\t\t: branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate clearGitWatchers(): void {\n\t\tcloseWatcher(this.headWatcher);\n\t\tthis.headWatcher = null;\n\t\tif (this.headWatchFilePath && this.headWatchFileListener) {\n\t\t\tunwatchFile(this.headWatchFilePath, this.headWatchFileListener);\n\t\t\tthis.headWatchFilePath = null;\n\t\t\tthis.headWatchFileListener = null;\n\t\t}\n\t\tcloseWatcher(this.reftableWatcher);\n\t\tthis.reftableWatcher = null;\n\t\tcloseWatcher(this.reftableTablesListWatcher);\n\t\tthis.reftableTablesListWatcher = null;\n\t\tif (this.reftableTablesListPath) {\n\t\t\tunwatchFile(this.reftableTablesListPath);\n\t\t\tthis.reftableTablesListPath = null;\n\t\t}\n\t\tif (this.gitWatcherRetryTimer) {\n\t\t\tclearTimeout(this.gitWatcherRetryTimer);\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t}\n\t}\n\n\tprivate scheduleGitWatcherRetry(): void {\n\t\tif (this.disposed || this.gitWatcherRetryTimer) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.gitWatcherRetryTimer = setTimeout(() => {\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t\tthis.setupGitWatcher();\n\t\t}, FS_WATCH_RETRY_DELAY_MS);\n\t}\n\n\tprivate handleGitWatcherError(): void {\n\t\tthis.clearGitWatchers();\n\t\tthis.scheduleGitWatcherRetry();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tthis.clearGitWatchers();\n\t\tif (!this.gitPaths) return;\n\n\t\tconst pollGitHead = shouldPollGitHead(this.gitPaths.repoDir);\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tthis.headWatcher = watchWithErrorHandler(\n\t\t\tdirname(this.gitPaths.headPath),\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (!filename || filename === \"HEAD\" || filename === \"index\") {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => this.handleGitWatcherError(),\n\t\t);\n\t\tif (pollGitHead) {\n\t\t\tthis.headWatchFilePath = this.gitPaths.headPath;\n\t\t\tthis.headWatchFileListener = (current, previous) => {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t) {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t};\n\t\t\twatchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener);\n\t\t}\n\t\tif (!this.headWatcher && !pollGitHead) {\n\t\t\treturn;\n\t\t}\n\n\t\t// In reftable repos, branch switches update files in the reftable directory\n\t\t// instead of HEAD. Watch it separately so the footer picks up those changes.\n\t\tconst reftableDir = join(this.gitPaths.commonGitDir, \"reftable\");\n\t\tif (existsSync(reftableDir)) {\n\t\t\tthis.reftableWatcher = watchWithErrorHandler(\n\t\t\t\treftableDir,\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t},\n\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t);\n\t\t\tif (!this.reftableWatcher) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tablesListPath = join(reftableDir, \"tables.list\");\n\t\t\tif (existsSync(tablesListPath)) {\n\t\t\t\tthis.reftableTablesListPath = tablesListPath;\n\t\t\t\tthis.reftableTablesListWatcher = watchWithErrorHandler(\n\t\t\t\t\ttablesListPath,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t},\n\t\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t\t);\n\t\t\t\tif (!this.reftableTablesListWatcher) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twatchFile(tablesListPath, { interval: 250 }, (current, previous) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getGitDiffstat\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"onBranchChange\"\n>;\n"]}
1
+ {"version":3,"file":"footer-data-provider.js","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ExecFileException, execFile } from \"child_process\";\nimport { existsSync, type FSWatcher, type Stats, unwatchFile, watchFile } from \"fs\";\nimport { readFile, stat } from \"fs/promises\";\nimport { dirname, join, resolve } from \"path\";\nimport { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from \"../utils/fs-watch.ts\";\n\ntype GitPaths = {\n\trepoDir: string;\n\tcommonGitDir: string;\n\theadPath: string;\n};\n\n/**\n * Find git metadata paths by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nasync function findGitPaths(cwd: string): Promise<GitPaths | null> {\n\tlet dir = cwd;\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\ttry {\n\t\t\tconst gitStat = await stat(gitPath);\n\t\t\tif (gitStat.isFile()) {\n\t\t\t\tconst content = (await readFile(gitPath, \"utf8\")).trim();\n\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\tconst gitDir = resolve(dir, content.slice(8).trim());\n\t\t\t\t\tconst headPath = join(gitDir, \"HEAD\");\n\t\t\t\t\tawait stat(headPath);\n\t\t\t\t\tlet commonGitDir = gitDir;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcommonGitDir = resolve(gitDir, (await readFile(join(gitDir, \"commondir\"), \"utf8\")).trim());\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { repoDir: dir, commonGitDir, headPath };\n\t\t\t\t}\n\t\t\t} else if (gitStat.isDirectory()) {\n\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\tawait stat(headPath);\n\t\t\t\treturn { repoDir: dir, commonGitDir: gitPath, headPath };\n\t\t\t}\n\t\t} catch {}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/** Sum added/removed columns from `git diff --numstat`. Binary rows (`-`) are ignored. */\nexport function parseGitNumstat(stdout: string): { added: number; removed: number } {\n\tlet added = 0;\n\tlet removed = 0;\n\tfor (const line of stdout.split(/\\r?\\n/)) {\n\t\tif (!line) continue;\n\t\tconst tab1 = line.indexOf(\"\\t\");\n\t\tif (tab1 <= 0) continue;\n\t\tconst tab2 = line.indexOf(\"\\t\", tab1 + 1);\n\t\tif (tab2 < 0) continue;\n\t\tconst addTok = line.slice(0, tab1);\n\t\tconst delTok = line.slice(tab1 + 1, tab2);\n\t\tif (addTok !== \"-\") {\n\t\t\tconst n = Number(addTok);\n\t\t\tif (Number.isFinite(n)) added += n;\n\t\t}\n\t\tif (delTok !== \"-\") {\n\t\t\tconst n = Number(delTok);\n\t\t\tif (Number.isFinite(n)) removed += n;\n\t\t}\n\t}\n\treturn { added, removed };\n}\n\nfunction resolveDiffstatWithGitAsync(repoDir: string): Promise<{ added: number; removed: number } | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"diff\", \"--numstat\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\ttimeout: 3000,\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolvePromise(parseGitNumstat(stdout));\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** Ask git for the current branch asynchronously. Returns null on detached HEAD or if git is unavailable. */\nfunction resolveBranchWithGitAsync(repoDir: string): Promise<string | null> {\n\treturn new Promise((resolvePromise) => {\n\t\texecFile(\n\t\t\t\"git\",\n\t\t\t[\"--no-optional-locks\", \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"],\n\t\t\t{\n\t\t\t\tcwd: repoDir,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\ttimeout: 3000,\n\t\t\t},\n\t\t\t(error: ExecFileException | null, stdout: string) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tresolvePromise(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst branch = stdout.trim();\n\t\t\t\tresolvePromise(branch || null);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction isWslEnvironment(): boolean {\n\treturn process.platform === \"linux\" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n}\n\nfunction isWindowsMountedRepoPath(repoDir: string): boolean {\n\treturn /^\\/mnt\\/[a-z](?:\\/|$)/i.test(repoDir);\n}\n\nfunction shouldPollGitHead(repoDir: string): boolean {\n\treturn isWslEnvironment() && isWindowsMountedRepoPath(repoDir);\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate cwd: string;\n\tprivate static readonly WATCH_DEBOUNCE_MS = 500;\n\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate cachedDiffstat: { added: number; removed: number } | null | undefined = undefined;\n\tprivate gitPaths: GitPaths | null | undefined = undefined;\n\tprivate headWatcher: FSWatcher | null = null;\n\tprivate headWatchFilePath: string | null = null;\n\tprivate headWatchFileListener: ((current: Stats, previous: Stats) => void) | null = null;\n\tprivate reftableWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListWatcher: FSWatcher | null = null;\n\tprivate reftableTablesListPath: string | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\tprivate refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate gitWatcherRetryTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate refreshInFlight = false;\n\tprivate refreshPending = false;\n\tprivate disposed = false;\n\n\tconstructor(cwd: string) {\n\t\tthis.cwd = cwd;\n\t}\n\n\t/** Current git branch, null until the asynchronous lookup completes or when not in a repo. */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch === undefined && !this.refreshInFlight) {\n\t\t\tvoid this.refreshGitData();\n\t\t}\n\t\treturn this.cachedBranch ?? null;\n\t}\n\n\t/** Staged + unstaged added/removed vs HEAD. Null until the asynchronous lookup completes. */\n\tgetGitDiffstat(): { added: number; removed: number } | null {\n\t\tif (this.cachedDiffstat === undefined && !this.refreshInFlight) {\n\t\t\tvoid this.refreshGitData();\n\t\t}\n\t\treturn this.cachedDiffstat ?? null;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\tsetCwd(cwd: string): void {\n\t\tif (this.cwd === cwd) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.cwd = cwd;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.cachedBranch = undefined;\n\t\tthis.cachedDiffstat = undefined;\n\t\tthis.gitPaths = undefined;\n\t\tif (this.refreshInFlight) this.refreshPending = true;\n\t\tthis.notifyBranchChange();\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tif (this.refreshTimer) {\n\t\t\tclearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = null;\n\t\t}\n\t\tthis.clearGitWatchers();\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate notifyBranchChange(): void {\n\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.disposed || this.refreshTimer) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshTimer = setTimeout(() => {\n\t\t\tthis.refreshTimer = null;\n\t\t\tvoid this.refreshGitData();\n\t\t}, FooterDataProvider.WATCH_DEBOUNCE_MS);\n\t}\n\n\tprivate async refreshGitData(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tif (this.refreshInFlight) {\n\t\t\tthis.refreshPending = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.refreshInFlight = true;\n\t\tconst cwd = this.cwd;\n\t\ttry {\n\t\t\tconst gitPaths = this.gitPaths === undefined ? await findGitPaths(cwd) : this.gitPaths;\n\t\t\tif (this.disposed || this.cwd !== cwd) return;\n\t\t\tif (this.gitPaths === undefined) {\n\t\t\t\tthis.gitPaths = gitPaths;\n\t\t\t\tthis.setupGitWatcher();\n\t\t\t}\n\t\t\tconst [nextBranch, nextDiff] = await Promise.all([\n\t\t\t\tthis.resolveGitBranchAsync(),\n\t\t\t\tgitPaths ? resolveDiffstatWithGitAsync(gitPaths.repoDir) : null,\n\t\t\t]);\n\t\t\tif (this.disposed || this.cwd !== cwd) return;\n\t\t\tconst prevBranch = this.cachedBranch;\n\t\t\tconst prevDiff = this.cachedDiffstat;\n\t\t\tconst branchChanged = prevBranch !== nextBranch;\n\t\t\tconst diffChanged = prevDiff?.added !== nextDiff?.added || prevDiff?.removed !== nextDiff?.removed;\n\t\t\tthis.cachedBranch = nextBranch;\n\t\t\tthis.cachedDiffstat = nextDiff;\n\t\t\tif (branchChanged || diffChanged) {\n\t\t\t\tthis.notifyBranchChange();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.refreshInFlight = false;\n\t\t\tif (this.refreshPending && !this.disposed) {\n\t\t\t\tthis.refreshPending = false;\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async resolveGitBranchAsync(): Promise<string | null> {\n\t\ttry {\n\t\t\tif (!this.gitPaths) return null;\n\t\t\tconst content = (await readFile(this.gitPaths.headPath, \"utf8\")).trim();\n\t\t\tif (content.startsWith(\"ref: refs/heads/\")) {\n\t\t\t\tconst branch = content.slice(16);\n\t\t\t\treturn branch === \".invalid\"\n\t\t\t\t\t? ((await resolveBranchWithGitAsync(this.gitPaths.repoDir)) ?? \"detached\")\n\t\t\t\t\t: branch;\n\t\t\t}\n\t\t\treturn \"detached\";\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate clearGitWatchers(): void {\n\t\tcloseWatcher(this.headWatcher);\n\t\tthis.headWatcher = null;\n\t\tif (this.headWatchFilePath && this.headWatchFileListener) {\n\t\t\tunwatchFile(this.headWatchFilePath, this.headWatchFileListener);\n\t\t\tthis.headWatchFilePath = null;\n\t\t\tthis.headWatchFileListener = null;\n\t\t}\n\t\tcloseWatcher(this.reftableWatcher);\n\t\tthis.reftableWatcher = null;\n\t\tcloseWatcher(this.reftableTablesListWatcher);\n\t\tthis.reftableTablesListWatcher = null;\n\t\tif (this.reftableTablesListPath) {\n\t\t\tunwatchFile(this.reftableTablesListPath);\n\t\t\tthis.reftableTablesListPath = null;\n\t\t}\n\t\tif (this.gitWatcherRetryTimer) {\n\t\t\tclearTimeout(this.gitWatcherRetryTimer);\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t}\n\t}\n\n\tprivate scheduleGitWatcherRetry(): void {\n\t\tif (this.disposed || this.gitWatcherRetryTimer) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.gitWatcherRetryTimer = setTimeout(() => {\n\t\t\tthis.gitWatcherRetryTimer = null;\n\t\t\tthis.setupGitWatcher();\n\t\t}, FS_WATCH_RETRY_DELAY_MS);\n\t}\n\n\tprivate handleGitWatcherError(): void {\n\t\tthis.clearGitWatchers();\n\t\tthis.scheduleGitWatcherRetry();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tthis.clearGitWatchers();\n\t\tif (!this.gitPaths) return;\n\n\t\tconst pollGitHead = shouldPollGitHead(this.gitPaths.repoDir);\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tthis.headWatcher = watchWithErrorHandler(\n\t\t\tdirname(this.gitPaths.headPath),\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (!filename || filename === \"HEAD\" || filename === \"index\") {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => this.handleGitWatcherError(),\n\t\t);\n\t\tif (pollGitHead) {\n\t\t\tthis.headWatchFilePath = this.gitPaths.headPath;\n\t\t\tthis.headWatchFileListener = (current, previous) => {\n\t\t\t\tif (\n\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t) {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t}\n\t\t\t};\n\t\t\twatchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener);\n\t\t}\n\t\tif (!this.headWatcher && !pollGitHead) {\n\t\t\treturn;\n\t\t}\n\n\t\t// In reftable repos, branch switches update files in the reftable directory\n\t\t// instead of HEAD. Watch it separately so the footer picks up those changes.\n\t\tconst reftableDir = join(this.gitPaths.commonGitDir, \"reftable\");\n\t\tif (existsSync(reftableDir)) {\n\t\t\tthis.reftableWatcher = watchWithErrorHandler(\n\t\t\t\treftableDir,\n\t\t\t\t() => {\n\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t},\n\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t);\n\t\t\tif (!this.reftableWatcher) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst tablesListPath = join(reftableDir, \"tables.list\");\n\t\t\tif (existsSync(tablesListPath)) {\n\t\t\t\tthis.reftableTablesListPath = tablesListPath;\n\t\t\t\tthis.reftableTablesListWatcher = watchWithErrorHandler(\n\t\t\t\t\ttablesListPath,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t},\n\t\t\t\t\t() => this.handleGitWatcherError(),\n\t\t\t\t);\n\t\t\t\tif (!this.reftableTablesListWatcher) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twatchFile(tablesListPath, { interval: 250 }, (current, previous) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcurrent.mtimeMs !== previous.mtimeMs ||\n\t\t\t\t\t\tcurrent.ctimeMs !== previous.ctimeMs ||\n\t\t\t\t\t\tcurrent.size !== previous.size\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.scheduleRefresh();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getGitDiffstat\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"onBranchChange\"\n>;\n"]}
@@ -1,15 +1,15 @@
1
1
  /**
2
- * lunr: detect injected prompts (/swarm, /research, /goal) so the transcript can
2
+ * lunr: detect injected prompts (/research, /goal) so the transcript can
3
3
  * render them collapsed instead of dumping the full multi-line prompt verbatim.
4
4
  *
5
5
  * These prompts become plain {role:"user"} messages (PromptOptions.source is not
6
6
  * persisted), so detection is keyed on stable markers that already exist in the
7
- * prompt bodies: the `[SWARM MODE]` / `[DEEP RESEARCH]` literal prefixes and the
7
+ * prompt bodies: the `[DEEP RESEARCH]` literal prefix and the
8
8
  * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time
9
9
  * only — the model still receives the full prompt unchanged, and old session
10
10
  * transcripts are cleaned up retroactively with no schema migration.
11
11
  */
12
- export type InjectedPromptKind = "swarm" | "research" | "goal";
12
+ export type InjectedPromptKind = "research" | "goal";
13
13
  export interface InjectedPromptInfo {
14
14
  kind: InjectedPromptKind;
15
15
  /** One-line summary extracted from the prompt body (task / question / objective). */
@@ -1 +1 @@
1
- {"version":3,"file":"injected-prompt.d.ts","sourceRoot":"","sources":["../../src/core/injected-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;CAChB;AA+BD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAoBjF;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAIrE,CAAC","sourcesContent":["/**\n * lunr: detect injected prompts (/swarm, /research, /goal) so the transcript can\n * render them collapsed instead of dumping the full multi-line prompt verbatim.\n *\n * These prompts become plain {role:\"user\"} messages (PromptOptions.source is not\n * persisted), so detection is keyed on stable markers that already exist in the\n * prompt bodies: the `[SWARM MODE]` / `[DEEP RESEARCH]` literal prefixes and the\n * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time\n * only — the model still receives the full prompt unchanged, and old session\n * transcripts are cleaned up retroactively with no schema migration.\n */\n\nexport type InjectedPromptKind = \"swarm\" | \"research\" | \"goal\";\n\nexport interface InjectedPromptInfo {\n\tkind: InjectedPromptKind;\n\t/** One-line summary extracted from the prompt body (task / question / objective). */\n\tsummary: string;\n}\n\nfunction firstNonEmptyLine(text: string): string | undefined {\n\tfor (const raw of text.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (line) return line;\n\t}\n\treturn undefined;\n}\n\n/** Extract the value following a `Label:` prefix on the first line, else undefined. */\nfunction extractAfterLabel(text: string, label: string): string | undefined {\n\tconst idx = text.indexOf(label);\n\tif (idx === -1) return undefined;\n\tconst rest = text.slice(idx + label.length).split(/\\r?\\n/)[0] ?? \"\";\n\tconst trimmed = rest.trim();\n\treturn trimmed || undefined;\n}\n\n/** Extract the contents of a `<goal_objective>…</goal_objective>` block. */\nfunction extractGoalObjective(text: string): string | undefined {\n\tconst open = text.indexOf(\"<goal_objective>\");\n\tif (open === -1) return undefined;\n\tconst close = text.indexOf(\"</goal_objective>\", open);\n\tif (close === -1) return undefined;\n\tconst inner = text.slice(open + \"<goal_objective>\".length, close);\n\tconst trimmed = inner.trim();\n\t// Unescape the minimal XML escaping the goal prompt builder applies.\n\treturn trimmed.replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").trim() || undefined;\n}\n\nexport function detectInjectedPrompt(text: string): InjectedPromptInfo | undefined {\n\tif (!text) return undefined;\n\n\tif (text.startsWith(\"[SWARM MODE]\")) {\n\t\tconst summary = extractAfterLabel(text, \"Task:\") ?? firstNonEmptyLine(text.slice(\"[SWARM MODE]\".length).trim());\n\t\treturn { kind: \"swarm\", summary: summary ?? \"swarm task\" };\n\t}\n\n\tif (text.startsWith(\"[DEEP RESEARCH]\")) {\n\t\tconst summary =\n\t\t\textractAfterLabel(text, \"Question:\") ?? firstNonEmptyLine(text.slice(\"[DEEP RESEARCH]\".length).trim());\n\t\treturn { kind: \"research\", summary: summary ?? \"research question\" };\n\t}\n\n\tif (text.includes(\"pi-goal-prompt:\")) {\n\t\tconst objective = extractGoalObjective(text);\n\t\treturn { kind: \"goal\", summary: objective ?? \"goal\" };\n\t}\n\n\treturn undefined;\n}\n\nexport const INJECTED_PROMPT_LABELS: Record<InjectedPromptKind, string> = {\n\tswarm: \"swarm\",\n\tresearch: \"research\",\n\tgoal: \"goal\",\n};\n"]}
1
+ {"version":3,"file":"injected-prompt.d.ts","sourceRoot":"","sources":["../../src/core/injected-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;CAChB;AA+BD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAejF;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAGrE,CAAC","sourcesContent":["/**\n * lunr: detect injected prompts (/research, /goal) so the transcript can\n * render them collapsed instead of dumping the full multi-line prompt verbatim.\n *\n * These prompts become plain {role:\"user\"} messages (PromptOptions.source is not\n * persisted), so detection is keyed on stable markers that already exist in the\n * prompt bodies: the `[DEEP RESEARCH]` literal prefix and the\n * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time\n * only — the model still receives the full prompt unchanged, and old session\n * transcripts are cleaned up retroactively with no schema migration.\n */\n\nexport type InjectedPromptKind = \"research\" | \"goal\";\n\nexport interface InjectedPromptInfo {\n\tkind: InjectedPromptKind;\n\t/** One-line summary extracted from the prompt body (task / question / objective). */\n\tsummary: string;\n}\n\nfunction firstNonEmptyLine(text: string): string | undefined {\n\tfor (const raw of text.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (line) return line;\n\t}\n\treturn undefined;\n}\n\n/** Extract the value following a `Label:` prefix on the first line, else undefined. */\nfunction extractAfterLabel(text: string, label: string): string | undefined {\n\tconst idx = text.indexOf(label);\n\tif (idx === -1) return undefined;\n\tconst rest = text.slice(idx + label.length).split(/\\r?\\n/)[0] ?? \"\";\n\tconst trimmed = rest.trim();\n\treturn trimmed || undefined;\n}\n\n/** Extract the contents of a `<goal_objective>…</goal_objective>` block. */\nfunction extractGoalObjective(text: string): string | undefined {\n\tconst open = text.indexOf(\"<goal_objective>\");\n\tif (open === -1) return undefined;\n\tconst close = text.indexOf(\"</goal_objective>\", open);\n\tif (close === -1) return undefined;\n\tconst inner = text.slice(open + \"<goal_objective>\".length, close);\n\tconst trimmed = inner.trim();\n\t// Unescape the minimal XML escaping the goal prompt builder applies.\n\treturn trimmed.replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").trim() || undefined;\n}\n\nexport function detectInjectedPrompt(text: string): InjectedPromptInfo | undefined {\n\tif (!text) return undefined;\n\n\tif (text.startsWith(\"[DEEP RESEARCH]\")) {\n\t\tconst summary =\n\t\t\textractAfterLabel(text, \"Question:\") ?? firstNonEmptyLine(text.slice(\"[DEEP RESEARCH]\".length).trim());\n\t\treturn { kind: \"research\", summary: summary ?? \"research question\" };\n\t}\n\n\tif (text.includes(\"pi-goal-prompt:\")) {\n\t\tconst objective = extractGoalObjective(text);\n\t\treturn { kind: \"goal\", summary: objective ?? \"goal\" };\n\t}\n\n\treturn undefined;\n}\n\nexport const INJECTED_PROMPT_LABELS: Record<InjectedPromptKind, string> = {\n\tresearch: \"research\",\n\tgoal: \"goal\",\n};\n"]}
@@ -1,10 +1,10 @@
1
1
  /**
2
- * lunr: detect injected prompts (/swarm, /research, /goal) so the transcript can
2
+ * lunr: detect injected prompts (/research, /goal) so the transcript can
3
3
  * render them collapsed instead of dumping the full multi-line prompt verbatim.
4
4
  *
5
5
  * These prompts become plain {role:"user"} messages (PromptOptions.source is not
6
6
  * persisted), so detection is keyed on stable markers that already exist in the
7
- * prompt bodies: the `[SWARM MODE]` / `[DEEP RESEARCH]` literal prefixes and the
7
+ * prompt bodies: the `[DEEP RESEARCH]` literal prefix and the
8
8
  * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time
9
9
  * only — the model still receives the full prompt unchanged, and old session
10
10
  * transcripts are cleaned up retroactively with no schema migration.
@@ -42,10 +42,6 @@ function extractGoalObjective(text) {
42
42
  export function detectInjectedPrompt(text) {
43
43
  if (!text)
44
44
  return undefined;
45
- if (text.startsWith("[SWARM MODE]")) {
46
- const summary = extractAfterLabel(text, "Task:") ?? firstNonEmptyLine(text.slice("[SWARM MODE]".length).trim());
47
- return { kind: "swarm", summary: summary ?? "swarm task" };
48
- }
49
45
  if (text.startsWith("[DEEP RESEARCH]")) {
50
46
  const summary = extractAfterLabel(text, "Question:") ?? firstNonEmptyLine(text.slice("[DEEP RESEARCH]".length).trim());
51
47
  return { kind: "research", summary: summary ?? "research question" };
@@ -57,7 +53,6 @@ export function detectInjectedPrompt(text) {
57
53
  return undefined;
58
54
  }
59
55
  export const INJECTED_PROMPT_LABELS = {
60
- swarm: "swarm",
61
56
  research: "research",
62
57
  goal: "goal",
63
58
  };
@@ -1 +1 @@
1
- {"version":3,"file":"injected-prompt.js","sourceRoot":"","sources":["../../src/core/injected-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunr: detect injected prompts (/swarm, /research, /goal) so the transcript can\n * render them collapsed instead of dumping the full multi-line prompt verbatim.\n *\n * These prompts become plain {role:\"user\"} messages (PromptOptions.source is not\n * persisted), so detection is keyed on stable markers that already exist in the\n * prompt bodies: the `[SWARM MODE]` / `[DEEP RESEARCH]` literal prefixes and the\n * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time\n * only — the model still receives the full prompt unchanged, and old session\n * transcripts are cleaned up retroactively with no schema migration.\n */\n\nexport type InjectedPromptKind = \"swarm\" | \"research\" | \"goal\";\n\nexport interface InjectedPromptInfo {\n\tkind: InjectedPromptKind;\n\t/** One-line summary extracted from the prompt body (task / question / objective). */\n\tsummary: string;\n}\n\nfunction firstNonEmptyLine(text: string): string | undefined {\n\tfor (const raw of text.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (line) return line;\n\t}\n\treturn undefined;\n}\n\n/** Extract the value following a `Label:` prefix on the first line, else undefined. */\nfunction extractAfterLabel(text: string, label: string): string | undefined {\n\tconst idx = text.indexOf(label);\n\tif (idx === -1) return undefined;\n\tconst rest = text.slice(idx + label.length).split(/\\r?\\n/)[0] ?? \"\";\n\tconst trimmed = rest.trim();\n\treturn trimmed || undefined;\n}\n\n/** Extract the contents of a `<goal_objective>…</goal_objective>` block. */\nfunction extractGoalObjective(text: string): string | undefined {\n\tconst open = text.indexOf(\"<goal_objective>\");\n\tif (open === -1) return undefined;\n\tconst close = text.indexOf(\"</goal_objective>\", open);\n\tif (close === -1) return undefined;\n\tconst inner = text.slice(open + \"<goal_objective>\".length, close);\n\tconst trimmed = inner.trim();\n\t// Unescape the minimal XML escaping the goal prompt builder applies.\n\treturn trimmed.replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").trim() || undefined;\n}\n\nexport function detectInjectedPrompt(text: string): InjectedPromptInfo | undefined {\n\tif (!text) return undefined;\n\n\tif (text.startsWith(\"[SWARM MODE]\")) {\n\t\tconst summary = extractAfterLabel(text, \"Task:\") ?? firstNonEmptyLine(text.slice(\"[SWARM MODE]\".length).trim());\n\t\treturn { kind: \"swarm\", summary: summary ?? \"swarm task\" };\n\t}\n\n\tif (text.startsWith(\"[DEEP RESEARCH]\")) {\n\t\tconst summary =\n\t\t\textractAfterLabel(text, \"Question:\") ?? firstNonEmptyLine(text.slice(\"[DEEP RESEARCH]\".length).trim());\n\t\treturn { kind: \"research\", summary: summary ?? \"research question\" };\n\t}\n\n\tif (text.includes(\"pi-goal-prompt:\")) {\n\t\tconst objective = extractGoalObjective(text);\n\t\treturn { kind: \"goal\", summary: objective ?? \"goal\" };\n\t}\n\n\treturn undefined;\n}\n\nexport const INJECTED_PROMPT_LABELS: Record<InjectedPromptKind, string> = {\n\tswarm: \"swarm\",\n\tresearch: \"research\",\n\tgoal: \"goal\",\n};\n"]}
1
+ {"version":3,"file":"injected-prompt.js","sourceRoot":"","sources":["../../src/core/injected-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,SAAS,iBAAiB,CAAC,IAAY,EAAsB;IAC5D,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,uFAAuF;AACvF,SAAS,iBAAiB,CAAC,IAAY,EAAE,KAAa,EAAsB;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,OAAO,IAAI,SAAS,CAAC;AAAA,CAC5B;AAED,8EAA4E;AAC5E,SAAS,oBAAoB,CAAC,IAAY,EAAsB;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9C,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,qEAAqE;IACrE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;AAAA,CACtG;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAkC;IAClF,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,IAAI,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACxC,MAAM,OAAO,GACZ,iBAAiB,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACxG,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,CAAC;IACtE,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACtC,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,IAAI,MAAM,EAAE,CAAC;IACvD,CAAC;IAED,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAuC;IACzE,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,MAAM;CACZ,CAAC","sourcesContent":["/**\n * lunr: detect injected prompts (/research, /goal) so the transcript can\n * render them collapsed instead of dumping the full multi-line prompt verbatim.\n *\n * These prompts become plain {role:\"user\"} messages (PromptOptions.source is not\n * persisted), so detection is keyed on stable markers that already exist in the\n * prompt bodies: the `[DEEP RESEARCH]` literal prefix and the\n * goal extension's `<!-- pi-goal-prompt:… -->` HTML comment. This is render-time\n * only — the model still receives the full prompt unchanged, and old session\n * transcripts are cleaned up retroactively with no schema migration.\n */\n\nexport type InjectedPromptKind = \"research\" | \"goal\";\n\nexport interface InjectedPromptInfo {\n\tkind: InjectedPromptKind;\n\t/** One-line summary extracted from the prompt body (task / question / objective). */\n\tsummary: string;\n}\n\nfunction firstNonEmptyLine(text: string): string | undefined {\n\tfor (const raw of text.split(/\\r?\\n/)) {\n\t\tconst line = raw.trim();\n\t\tif (line) return line;\n\t}\n\treturn undefined;\n}\n\n/** Extract the value following a `Label:` prefix on the first line, else undefined. */\nfunction extractAfterLabel(text: string, label: string): string | undefined {\n\tconst idx = text.indexOf(label);\n\tif (idx === -1) return undefined;\n\tconst rest = text.slice(idx + label.length).split(/\\r?\\n/)[0] ?? \"\";\n\tconst trimmed = rest.trim();\n\treturn trimmed || undefined;\n}\n\n/** Extract the contents of a `<goal_objective>…</goal_objective>` block. */\nfunction extractGoalObjective(text: string): string | undefined {\n\tconst open = text.indexOf(\"<goal_objective>\");\n\tif (open === -1) return undefined;\n\tconst close = text.indexOf(\"</goal_objective>\", open);\n\tif (close === -1) return undefined;\n\tconst inner = text.slice(open + \"<goal_objective>\".length, close);\n\tconst trimmed = inner.trim();\n\t// Unescape the minimal XML escaping the goal prompt builder applies.\n\treturn trimmed.replace(/&amp;/g, \"&\").replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").trim() || undefined;\n}\n\nexport function detectInjectedPrompt(text: string): InjectedPromptInfo | undefined {\n\tif (!text) return undefined;\n\n\tif (text.startsWith(\"[DEEP RESEARCH]\")) {\n\t\tconst summary =\n\t\t\textractAfterLabel(text, \"Question:\") ?? firstNonEmptyLine(text.slice(\"[DEEP RESEARCH]\".length).trim());\n\t\treturn { kind: \"research\", summary: summary ?? \"research question\" };\n\t}\n\n\tif (text.includes(\"pi-goal-prompt:\")) {\n\t\tconst objective = extractGoalObjective(text);\n\t\treturn { kind: \"goal\", summary: objective ?? \"goal\" };\n\t}\n\n\treturn undefined;\n}\n\nexport const INJECTED_PROMPT_LABELS: Record<InjectedPromptKind, string> = {\n\tresearch: \"research\",\n\tgoal: \"goal\",\n};\n"]}
@@ -0,0 +1,13 @@
1
+ /** Number of children above which aggregate launch confirmation engages. */
2
+ export declare const LARGE_SUBAGENT_LAUNCH_THRESHOLD = 2;
3
+ export declare function effectiveLargeSubagentLaunchCount(args: Record<string, unknown>): number;
4
+ interface AssistantMessageLike {
5
+ content?: ReadonlyArray<{
6
+ type?: string;
7
+ name?: string;
8
+ arguments?: unknown;
9
+ }>;
10
+ }
11
+ export declare function effectiveLargeSubagentLaunchCountForTurn(args: Record<string, unknown>, assistantMessage?: AssistantMessageLike): number;
12
+ export {};
13
+ //# sourceMappingURL=large-subagent-launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"large-subagent-launch.d.ts","sourceRoot":"","sources":["../../src/core/large-subagent-launch.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAejD,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAcvF;AAaD,UAAU,oBAAoB;IAC7B,OAAO,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC/E;AAED,wBAAgB,wCAAwC,CACvD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,gBAAgB,CAAC,EAAE,oBAAoB,GACrC,MAAM,CAWR","sourcesContent":["/** Number of children above which aggregate launch confirmation engages. */\nexport const LARGE_SUBAGENT_LAUNCH_THRESHOLD = 2;\n\nfunction countEntry(item: unknown): number {\n\tconst count = item && typeof item === \"object\" ? (item as { count?: unknown }).count : undefined;\n\treturn typeof count === \"number\" && Number.isInteger(count) && count >= 1 ? count : 1;\n}\n\nfunction isManagementCall(args: Record<string, unknown>): boolean {\n\treturn typeof args.action === \"string\" && args.action.length > 0;\n}\n\nfunction isAsyncDetached(args: Record<string, unknown>): boolean {\n\treturn args.async === true && args.clarify !== true;\n}\n\nexport function effectiveLargeSubagentLaunchCount(args: Record<string, unknown>): number {\n\tlet total = 0;\n\tif (Array.isArray(args.tasks)) {\n\t\tfor (const task of args.tasks) total += countEntry(task);\n\t}\n\tif (Array.isArray(args.chain)) {\n\t\tfor (const step of args.chain) {\n\t\t\tconst parallel = step && typeof step === \"object\" ? (step as { parallel?: unknown }).parallel : undefined;\n\t\t\tif (Array.isArray(parallel)) {\n\t\t\t\tfor (const child of parallel) total += countEntry(child);\n\t\t\t}\n\t\t}\n\t}\n\treturn total;\n}\n\nfunction siblingSingleCount(args: Record<string, unknown>): number {\n\tif (isManagementCall(args) || isAsyncDetached(args)) return 0;\n\tif (Array.isArray(args.tasks) || Array.isArray(args.chain)) return 0;\n\treturn typeof args.task === \"string\" &&\n\t\targs.task.length > 0 &&\n\t\ttypeof args.description === \"string\" &&\n\t\targs.description.length > 0\n\t\t? 1\n\t\t: 0;\n}\n\ninterface AssistantMessageLike {\n\tcontent?: ReadonlyArray<{ type?: string; name?: string; arguments?: unknown }>;\n}\n\nexport function effectiveLargeSubagentLaunchCountForTurn(\n\targs: Record<string, unknown>,\n\tassistantMessage?: AssistantMessageLike,\n): number {\n\tconst own = effectiveLargeSubagentLaunchCount(args);\n\tconst siblingSingles = (assistantMessage?.content ?? []).reduce((total, block) => {\n\t\tif (block.type !== \"toolCall\" || block.name !== \"subagent\") return total;\n\t\tconst siblingArgs =\n\t\t\tblock.arguments && typeof block.arguments === \"object\" && !Array.isArray(block.arguments)\n\t\t\t\t? (block.arguments as Record<string, unknown>)\n\t\t\t\t: undefined;\n\t\treturn siblingArgs ? total + siblingSingleCount(siblingArgs) : total;\n\t}, 0);\n\treturn Math.max(own, siblingSingles);\n}\n"]}