@agimon-ai/doompi-ui 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (327) hide show
  1. package/README.md +21 -18
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  3. package/dist/adapters/pi/extension.cjs +2 -0
  4. package/dist/adapters/pi/extension.cjs.map +1 -0
  5. package/dist/adapters/pi/extension.d.cts +7 -0
  6. package/dist/adapters/pi/extension.d.cts.map +1 -0
  7. package/dist/adapters/pi/extension.d.mts +7 -0
  8. package/dist/adapters/pi/extension.d.mts.map +1 -0
  9. package/dist/adapters/pi/extension.mjs +2 -0
  10. package/dist/adapters/pi/extension.mjs.map +1 -0
  11. package/dist/adapters/pi/extensionName.cjs +2 -0
  12. package/dist/adapters/pi/extensionName.cjs.map +1 -0
  13. package/dist/adapters/pi/extensionName.d.cts +16 -0
  14. package/dist/adapters/pi/extensionName.d.cts.map +1 -0
  15. package/dist/adapters/pi/extensionName.d.mts +16 -0
  16. package/dist/adapters/pi/extensionName.d.mts.map +1 -0
  17. package/dist/adapters/pi/extensionName.mjs +2 -0
  18. package/dist/adapters/pi/extensionName.mjs.map +1 -0
  19. package/dist/adapters/pi/toolOverrides.cjs +2 -0
  20. package/dist/adapters/pi/toolOverrides.cjs.map +1 -0
  21. package/dist/adapters/pi/toolOverrides.mjs +2 -0
  22. package/dist/adapters/pi/toolOverrides.mjs.map +1 -0
  23. package/dist/adapters/telemetry/logSinkTelemetry.cjs +2 -0
  24. package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +1 -0
  25. package/dist/adapters/telemetry/logSinkTelemetry.d.cts +29 -0
  26. package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +1 -0
  27. package/dist/adapters/telemetry/logSinkTelemetry.d.mts +29 -0
  28. package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +1 -0
  29. package/dist/adapters/telemetry/logSinkTelemetry.mjs +2 -0
  30. package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +1 -0
  31. package/dist/components/configOverlay.cjs +0 -0
  32. package/dist/components/configOverlay.d.cts +0 -0
  33. package/dist/components/configOverlay.d.mts +0 -0
  34. package/dist/components/configOverlay.mjs +0 -0
  35. package/dist/components/doomEditor.cjs +1 -2
  36. package/dist/components/doomEditor.d.cts +2 -59
  37. package/dist/components/doomEditor.d.mts +2 -59
  38. package/dist/components/doomEditor.mjs +1 -2
  39. package/dist/components/doomFooter.cjs +1 -2
  40. package/dist/components/doomFooter.d.cts +2 -32
  41. package/dist/components/doomFooter.d.mts +2 -32
  42. package/dist/components/doomFooter.mjs +1 -2
  43. package/dist/components/doomHeader.cjs +1 -2
  44. package/dist/components/doomHeader.d.cts +2 -34
  45. package/dist/components/doomHeader.d.mts +2 -34
  46. package/dist/components/doomHeader.mjs +1 -2
  47. package/dist/components/doomOverlay.cjs +1 -2
  48. package/dist/components/doomOverlay.d.cts +2 -79
  49. package/dist/components/doomOverlay.d.mts +2 -79
  50. package/dist/components/doomOverlay.mjs +1 -2
  51. package/dist/components/leaderHints.cjs +1 -2
  52. package/dist/components/leaderHints.d.cts +2 -37
  53. package/dist/components/leaderHints.d.mts +2 -37
  54. package/dist/components/leaderHints.mjs +1 -2
  55. package/dist/components/toolsOverlay.cjs +1 -2
  56. package/dist/components/toolsOverlay.d.cts +2 -29
  57. package/dist/components/toolsOverlay.d.mts +2 -29
  58. package/dist/components/toolsOverlay.mjs +1 -2
  59. package/dist/config.cjs +1 -2
  60. package/dist/config.d.cts +2 -5
  61. package/dist/config.d.mts +2 -5
  62. package/dist/config.mjs +1 -2
  63. package/dist/extension.cjs +1 -2
  64. package/dist/extension.d.cts +2 -7
  65. package/dist/extension.d.mts +2 -7
  66. package/dist/extension.mjs +1 -2
  67. package/dist/extensionName.cjs +1 -2
  68. package/dist/extensionName.d.cts +2 -5
  69. package/dist/extensionName.d.mts +2 -5
  70. package/dist/extensionName.mjs +1 -2
  71. package/dist/extensions/pi.cjs +1 -1
  72. package/dist/extensions/pi.cjs.map +1 -1
  73. package/dist/extensions/pi.d.cts +1 -1
  74. package/dist/extensions/pi.d.mts +1 -1
  75. package/dist/extensions/pi.mjs +1 -1
  76. package/dist/extensions/pi.mjs.map +1 -1
  77. package/dist/footer.cjs +1 -2
  78. package/dist/footer.d.cts +2 -5
  79. package/dist/footer.d.mts +2 -5
  80. package/dist/footer.mjs +1 -2
  81. package/dist/harnessMetadata.cjs +1 -2
  82. package/dist/harnessMetadata.d.cts +2 -21
  83. package/dist/harnessMetadata.d.mts +2 -21
  84. package/dist/harnessMetadata.mjs +1 -2
  85. package/dist/index.cjs +1 -1
  86. package/dist/index.d.cts +14 -25
  87. package/dist/index.d.mts +14 -25
  88. package/dist/index.mjs +1 -1
  89. package/dist/leaderRegistry.cjs +1 -2
  90. package/dist/leaderRegistry.d.cts +2 -58
  91. package/dist/leaderRegistry.d.mts +2 -58
  92. package/dist/leaderRegistry.mjs +1 -2
  93. package/dist/logSinkTelemetry.cjs +1 -2
  94. package/dist/logSinkTelemetry.d.cts +2 -29
  95. package/dist/logSinkTelemetry.d.mts +2 -29
  96. package/dist/logSinkTelemetry.mjs +1 -2
  97. package/dist/rendering.cjs +1 -2
  98. package/dist/rendering.d.cts +2 -11
  99. package/dist/rendering.d.mts +2 -11
  100. package/dist/rendering.mjs +1 -2
  101. package/dist/services/leader/leaderRegistry.cjs +2 -0
  102. package/dist/services/leader/leaderRegistry.cjs.map +1 -0
  103. package/dist/services/leader/leaderRegistry.d.cts +58 -0
  104. package/dist/services/leader/leaderRegistry.d.cts.map +1 -0
  105. package/dist/services/leader/leaderRegistry.d.mts +58 -0
  106. package/dist/services/leader/leaderRegistry.d.mts.map +1 -0
  107. package/dist/services/leader/leaderRegistry.mjs +2 -0
  108. package/dist/services/leader/leaderRegistry.mjs.map +1 -0
  109. package/dist/services/state/uiState.cjs +2 -0
  110. package/dist/services/state/uiState.cjs.map +1 -0
  111. package/dist/services/state/uiState.d.cts +38 -0
  112. package/dist/services/state/uiState.d.cts.map +1 -0
  113. package/dist/services/state/uiState.d.mts +38 -0
  114. package/dist/services/state/uiState.d.mts.map +1 -0
  115. package/dist/services/state/uiState.mjs +2 -0
  116. package/dist/services/state/uiState.mjs.map +1 -0
  117. package/dist/services/tools/toolInventory.cjs +2 -0
  118. package/dist/services/tools/toolInventory.cjs.map +1 -0
  119. package/dist/services/tools/toolInventory.d.cts +51 -0
  120. package/dist/services/tools/toolInventory.d.cts.map +1 -0
  121. package/dist/services/tools/toolInventory.d.mts +51 -0
  122. package/dist/services/tools/toolInventory.d.mts.map +1 -0
  123. package/dist/services/tools/toolInventory.mjs +2 -0
  124. package/dist/services/tools/toolInventory.mjs.map +1 -0
  125. package/dist/skills.cjs +1 -1
  126. package/dist/skills.d.cts +2 -1
  127. package/dist/skills.d.mts +2 -1
  128. package/dist/skills.mjs +1 -1
  129. package/dist/theme.cjs +1 -2
  130. package/dist/theme.d.cts +2 -95
  131. package/dist/theme.d.mts +2 -95
  132. package/dist/theme.mjs +1 -2
  133. package/dist/toolChrome.cjs +1 -0
  134. package/dist/toolChrome.d.cts +2 -0
  135. package/dist/toolChrome.d.mts +2 -0
  136. package/dist/toolChrome.mjs +1 -0
  137. package/dist/toolInventory.cjs +1 -2
  138. package/dist/toolInventory.d.cts +2 -40
  139. package/dist/toolInventory.d.mts +2 -40
  140. package/dist/toolInventory.mjs +1 -2
  141. package/dist/tui/builtinToolRender.cjs +14 -0
  142. package/dist/tui/builtinToolRender.cjs.map +1 -0
  143. package/dist/tui/builtinToolRender.mjs +14 -0
  144. package/dist/tui/builtinToolRender.mjs.map +1 -0
  145. package/dist/tui/codeGutter.cjs +2 -0
  146. package/dist/tui/codeGutter.cjs.map +1 -0
  147. package/dist/tui/codeGutter.mjs +2 -0
  148. package/dist/tui/codeGutter.mjs.map +1 -0
  149. package/dist/tui/configOverlay.cjs +0 -0
  150. package/dist/tui/configOverlay.cjs.map +1 -0
  151. package/dist/tui/configOverlay.d.cts +0 -0
  152. package/dist/tui/configOverlay.d.cts.map +1 -0
  153. package/dist/tui/configOverlay.d.mts +0 -0
  154. package/dist/tui/configOverlay.d.mts.map +1 -0
  155. package/dist/tui/configOverlay.mjs +0 -0
  156. package/dist/tui/configOverlay.mjs.map +1 -0
  157. package/dist/tui/diffRender.cjs +4 -0
  158. package/dist/tui/diffRender.cjs.map +1 -0
  159. package/dist/tui/diffRender.mjs +4 -0
  160. package/dist/tui/diffRender.mjs.map +1 -0
  161. package/dist/tui/doomEditor.cjs +2 -0
  162. package/dist/tui/doomEditor.cjs.map +1 -0
  163. package/dist/tui/doomEditor.d.cts +63 -0
  164. package/dist/tui/doomEditor.d.cts.map +1 -0
  165. package/dist/tui/doomEditor.d.mts +63 -0
  166. package/dist/tui/doomEditor.d.mts.map +1 -0
  167. package/dist/tui/doomEditor.mjs +2 -0
  168. package/dist/tui/doomEditor.mjs.map +1 -0
  169. package/dist/tui/doomFooter.cjs +2 -0
  170. package/dist/tui/doomFooter.cjs.map +1 -0
  171. package/dist/tui/doomFooter.d.cts +32 -0
  172. package/dist/tui/doomFooter.d.cts.map +1 -0
  173. package/dist/tui/doomFooter.d.mts +32 -0
  174. package/dist/tui/doomFooter.d.mts.map +1 -0
  175. package/dist/tui/doomFooter.mjs +2 -0
  176. package/dist/tui/doomFooter.mjs.map +1 -0
  177. package/dist/tui/doomHeader.cjs +2 -0
  178. package/dist/tui/doomHeader.cjs.map +1 -0
  179. package/dist/tui/doomHeader.d.cts +34 -0
  180. package/dist/tui/doomHeader.d.cts.map +1 -0
  181. package/dist/tui/doomHeader.d.mts +34 -0
  182. package/dist/tui/doomHeader.d.mts.map +1 -0
  183. package/dist/tui/doomHeader.mjs +2 -0
  184. package/dist/tui/doomHeader.mjs.map +1 -0
  185. package/dist/tui/doomOverlay.cjs +2 -0
  186. package/dist/tui/doomOverlay.cjs.map +1 -0
  187. package/dist/tui/doomOverlay.d.cts +84 -0
  188. package/dist/tui/doomOverlay.d.cts.map +1 -0
  189. package/dist/tui/doomOverlay.d.mts +84 -0
  190. package/dist/tui/doomOverlay.d.mts.map +1 -0
  191. package/dist/tui/doomOverlay.mjs +2 -0
  192. package/dist/tui/doomOverlay.mjs.map +1 -0
  193. package/dist/tui/leaderHints.cjs +2 -0
  194. package/dist/tui/leaderHints.cjs.map +1 -0
  195. package/dist/tui/leaderHints.d.cts +36 -0
  196. package/dist/tui/leaderHints.d.cts.map +1 -0
  197. package/dist/tui/leaderHints.d.mts +36 -0
  198. package/dist/tui/leaderHints.d.mts.map +1 -0
  199. package/dist/tui/leaderHints.mjs +2 -0
  200. package/dist/tui/leaderHints.mjs.map +1 -0
  201. package/dist/tui/rendering.cjs +2 -0
  202. package/dist/tui/rendering.cjs.map +1 -0
  203. package/dist/tui/rendering.d.cts +16 -0
  204. package/dist/tui/rendering.d.cts.map +1 -0
  205. package/dist/tui/rendering.d.mts +16 -0
  206. package/dist/tui/rendering.d.mts.map +1 -0
  207. package/dist/tui/rendering.mjs +2 -0
  208. package/dist/tui/rendering.mjs.map +1 -0
  209. package/dist/tui/theme.cjs +2 -0
  210. package/dist/tui/theme.cjs.map +1 -0
  211. package/dist/tui/theme.d.cts +103 -0
  212. package/dist/tui/theme.d.cts.map +1 -0
  213. package/dist/tui/theme.d.mts +103 -0
  214. package/dist/tui/theme.d.mts.map +1 -0
  215. package/dist/tui/theme.mjs +2 -0
  216. package/dist/tui/theme.mjs.map +1 -0
  217. package/dist/tui/toolChrome.cjs +2 -0
  218. package/dist/tui/toolChrome.cjs.map +1 -0
  219. package/dist/tui/toolChrome.d.cts +30 -0
  220. package/dist/tui/toolChrome.d.cts.map +1 -0
  221. package/dist/tui/toolChrome.d.mts +30 -0
  222. package/dist/tui/toolChrome.d.mts.map +1 -0
  223. package/dist/tui/toolChrome.mjs +2 -0
  224. package/dist/tui/toolChrome.mjs.map +1 -0
  225. package/dist/tui/toolsOverlay.cjs +2 -0
  226. package/dist/tui/toolsOverlay.cjs.map +1 -0
  227. package/dist/tui/toolsOverlay.d.cts +29 -0
  228. package/dist/tui/toolsOverlay.d.cts.map +1 -0
  229. package/dist/tui/toolsOverlay.d.mts +29 -0
  230. package/dist/tui/toolsOverlay.d.mts.map +1 -0
  231. package/dist/tui/toolsOverlay.mjs +2 -0
  232. package/dist/tui/toolsOverlay.mjs.map +1 -0
  233. package/dist/types/harnessMetadata.cjs +2 -0
  234. package/dist/types/harnessMetadata.cjs.map +1 -0
  235. package/dist/types/harnessMetadata.d.cts +21 -0
  236. package/dist/types/harnessMetadata.d.cts.map +1 -0
  237. package/dist/types/harnessMetadata.d.mts +21 -0
  238. package/dist/types/harnessMetadata.d.mts.map +1 -0
  239. package/dist/types/harnessMetadata.mjs +2 -0
  240. package/dist/types/harnessMetadata.mjs.map +1 -0
  241. package/dist/uiState.cjs +1 -2
  242. package/dist/uiState.d.cts +2 -37
  243. package/dist/uiState.d.mts +2 -37
  244. package/dist/uiState.mjs +1 -2
  245. package/package.json +19 -12
  246. package/themes/doom-pi-dark.json +32 -30
  247. package/dist/_virtual/_rolldown/runtime.mjs +0 -1
  248. package/dist/components/configOverlay.cjs.map +0 -1
  249. package/dist/components/configOverlay.d.cts.map +0 -1
  250. package/dist/components/configOverlay.d.mts.map +0 -1
  251. package/dist/components/configOverlay.mjs.map +0 -1
  252. package/dist/components/doomEditor.cjs.map +0 -1
  253. package/dist/components/doomEditor.d.cts.map +0 -1
  254. package/dist/components/doomEditor.d.mts.map +0 -1
  255. package/dist/components/doomEditor.mjs.map +0 -1
  256. package/dist/components/doomFooter.cjs.map +0 -1
  257. package/dist/components/doomFooter.d.cts.map +0 -1
  258. package/dist/components/doomFooter.d.mts.map +0 -1
  259. package/dist/components/doomFooter.mjs.map +0 -1
  260. package/dist/components/doomHeader.cjs.map +0 -1
  261. package/dist/components/doomHeader.d.cts.map +0 -1
  262. package/dist/components/doomHeader.d.mts.map +0 -1
  263. package/dist/components/doomHeader.mjs.map +0 -1
  264. package/dist/components/doomOverlay.cjs.map +0 -1
  265. package/dist/components/doomOverlay.d.cts.map +0 -1
  266. package/dist/components/doomOverlay.d.mts.map +0 -1
  267. package/dist/components/doomOverlay.mjs.map +0 -1
  268. package/dist/components/leaderHints.cjs.map +0 -1
  269. package/dist/components/leaderHints.d.cts.map +0 -1
  270. package/dist/components/leaderHints.d.mts.map +0 -1
  271. package/dist/components/leaderHints.mjs.map +0 -1
  272. package/dist/components/toolsOverlay.cjs.map +0 -1
  273. package/dist/components/toolsOverlay.d.cts.map +0 -1
  274. package/dist/components/toolsOverlay.d.mts.map +0 -1
  275. package/dist/components/toolsOverlay.mjs.map +0 -1
  276. package/dist/config.cjs.map +0 -1
  277. package/dist/config.d.cts.map +0 -1
  278. package/dist/config.d.mts.map +0 -1
  279. package/dist/config.mjs.map +0 -1
  280. package/dist/extension.cjs.map +0 -1
  281. package/dist/extension.d.cts.map +0 -1
  282. package/dist/extension.d.mts.map +0 -1
  283. package/dist/extension.mjs.map +0 -1
  284. package/dist/extensionName.cjs.map +0 -1
  285. package/dist/extensionName.d.cts.map +0 -1
  286. package/dist/extensionName.d.mts.map +0 -1
  287. package/dist/extensionName.mjs.map +0 -1
  288. package/dist/footer.cjs.map +0 -1
  289. package/dist/footer.d.cts.map +0 -1
  290. package/dist/footer.d.mts.map +0 -1
  291. package/dist/footer.mjs.map +0 -1
  292. package/dist/harnessMetadata.cjs.map +0 -1
  293. package/dist/harnessMetadata.d.cts.map +0 -1
  294. package/dist/harnessMetadata.d.mts.map +0 -1
  295. package/dist/harnessMetadata.mjs.map +0 -1
  296. package/dist/leaderRegistry.cjs.map +0 -1
  297. package/dist/leaderRegistry.d.cts.map +0 -1
  298. package/dist/leaderRegistry.d.mts.map +0 -1
  299. package/dist/leaderRegistry.mjs.map +0 -1
  300. package/dist/logSinkTelemetry.cjs.map +0 -1
  301. package/dist/logSinkTelemetry.d.cts.map +0 -1
  302. package/dist/logSinkTelemetry.d.mts.map +0 -1
  303. package/dist/logSinkTelemetry.mjs.map +0 -1
  304. package/dist/mode.cjs +0 -2
  305. package/dist/mode.cjs.map +0 -1
  306. package/dist/mode.d.cts +0 -5
  307. package/dist/mode.d.cts.map +0 -1
  308. package/dist/mode.d.mts +0 -5
  309. package/dist/mode.d.mts.map +0 -1
  310. package/dist/mode.mjs +0 -2
  311. package/dist/mode.mjs.map +0 -1
  312. package/dist/rendering.cjs.map +0 -1
  313. package/dist/rendering.d.cts.map +0 -1
  314. package/dist/rendering.d.mts.map +0 -1
  315. package/dist/rendering.mjs.map +0 -1
  316. package/dist/theme.cjs.map +0 -1
  317. package/dist/theme.d.cts.map +0 -1
  318. package/dist/theme.d.mts.map +0 -1
  319. package/dist/theme.mjs.map +0 -1
  320. package/dist/toolInventory.cjs.map +0 -1
  321. package/dist/toolInventory.d.cts.map +0 -1
  322. package/dist/toolInventory.d.mts.map +0 -1
  323. package/dist/toolInventory.mjs.map +0 -1
  324. package/dist/uiState.cjs.map +0 -1
  325. package/dist/uiState.d.cts.map +0 -1
  326. package/dist/uiState.d.mts.map +0 -1
  327. package/dist/uiState.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"leaderHints.mjs","names":[],"sources":["../../src/components/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport { visibleWidth, type Component } from '@earendil-works/pi-tui';\nimport { alignLine, fitLine, formatTokens, packSegments, padLine } from '../rendering.ts';\nimport type { DoomModeStatus } from '../mode.ts';\nimport type { LeaderSnapshot } from '../uiState.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n */\nconst MAX_WIDGET_LINES = 16;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/** The inverse key badge is ` k `, three columns wide. */\nconst KEY_BADGE_WIDTH = 3;\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Raised surface tone: one step above the terminal background, no frame needed. */\nconst PANEL_BACKGROUND: ThemeBg = 'userMessageBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly DoomModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const optionLines = this.renderOptionGrid(contentWidth).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n const diagnostics = this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade);\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n */\n private renderOptionGrid(width: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const key = this.theme.inverse(this.theme.bold(this.theme.fg(ACCENT_COLOR, ` ${option.key} `)));\n return {\n heading: `${key} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n headingWidth: KEY_BADGE_WIDTH + 1 + visibleWidth(option.label),\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n detailWidth: option.detail ? visibleWidth(DETAIL_INDENT) + visibleWidth(option.detail) : 0,\n };\n });\n const widest = Math.max(...cells.map((cell) => Math.max(cell.headingWidth, cell.detailWidth)));\n const cellWidth = Math.min(width, widest);\n const columns = Math.max(1, Math.floor((width + GRID_GAP) / (cellWidth + GRID_GAP)));\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n rows.push(fitLine(group.map((cell) => padLine(cell.heading, cellWidth)).join(gap), width));\n // Always two rows per card, even where no option carries a description:\n // dropping the empty one lets the next row's badges close up against the\n // ones above, which reads as a vertical block of keys rather than cards.\n rows.push(fitLine(group.map((cell) => padLine(cell.detail, cellWidth)).join(gap), width));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"toolsOverlay.cjs","names":["truncateToWidth","visibleWidth","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/components/toolsOverlay.ts"],"sourcesContent":["/**\n * Tools browser: the `SPC e t` overlay.\n *\n * A grouped list on the left (Pi core, one group per MCP server, one per\n * extension) and a detail pane on the right for the tool under the cursor.\n * Read-only: nothing here enables or disables a tool, so it cannot fight plan\n * mode or the workflow dispatcher over the active tool set.\n *\n * Group headings are labels, not controls: everything is always expanded and\n * the cursor steps over them, so there is no expansion state to track and the\n * whole inventory is legible without navigating it. The tree stays a flat row\n * list, which keeps selection a single index and every row assertable as text.\n */\n\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { ToolEntry, ToolSource } from '../toolInventory.ts';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\n\ntype Row = { kind: 'source'; source: ToolSource } | { kind: 'tool'; source: ToolSource; tool: ToolEntry };\n\nconst TITLE = 'TOOLS';\nconst BREADCRUMB = 'SPC › e / t · tools';\nconst HEADER_RIGHT = 'esc close';\nconst HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'move'],\n ['esc', 'close'],\n];\n\nconst MIN_WIDTH = 48;\n/** A third for the tree, two thirds for the tool being read. */\nconst TREE_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst TOOL_INDENT = ' ';\nconst LABEL_COLUMN = 12;\nconst REQUIRED_MARKER = ' *';\nconst EMPTY_MESSAGE = 'No tools are registered in this session.';\nconst ACTIVE_LABEL = 'active';\nconst INACTIVE_LABEL = 'inactive';\nconst SOURCE_HEADING = 'SOURCES';\n\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Top-level properties of a JSON-schema-shaped parameter object, if it has any. */\nfunction parameterRows(parameters: unknown): readonly { name: string; type: string; required: boolean }[] {\n if (typeof parameters !== 'object' || parameters === null) return [];\n const schema = parameters as { properties?: unknown; required?: unknown };\n if (typeof schema.properties !== 'object' || schema.properties === null) return [];\n const required = new Set(\n Array.isArray(schema.required) ? schema.required.filter((name) => typeof name === 'string') : [],\n );\n return Object.entries(schema.properties as Record<string, unknown>).map(([name, value]) => {\n const type = typeof value === 'object' && value !== null ? (value as { type?: unknown }).type : undefined;\n return {\n name,\n type: typeof type === 'string' ? type : '',\n required: required.has(name),\n };\n });\n}\n\nfunction activeCount(source: ToolSource): number {\n return source.tools.reduce((total, tool) => total + (tool.active ? 1 : 0), 0);\n}\n\nexport class ToolsOverlayComponent extends DoomOverlay {\n private readonly sources: readonly ToolSource[];\n private readonly done: (result: undefined) => void;\n private selected: number;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, sources: readonly ToolSource[], done: (result: undefined) => void) {\n super(tui, theme);\n this.sources = sources;\n this.done = done;\n this.selected = this.rows().findIndex((row) => row.kind === 'tool');\n }\n\n /** Every source is always expanded: the source row is a heading, not a control. */\n private rows(): readonly Row[] {\n const rows: Row[] = [];\n for (const source of this.sources) {\n rows.push({ kind: 'source', source });\n for (const tool of source.tools) rows.push({ kind: 'tool', source, tool });\n }\n return rows;\n }\n\n private currentTool(): Extract<Row, { kind: 'tool' }> | undefined {\n const row = this.rows()[this.selected];\n return row?.kind === 'tool' ? row : undefined;\n }\n\n /** Source rows are labels, so the cursor steps over them to the next tool. */\n private move(delta: number): void {\n const rows = this.rows();\n const step = delta < 0 ? -1 : 1;\n let index = this.selected;\n for (let remaining = Math.abs(delta); remaining > 0; remaining -= 1) {\n let next = index + step;\n while (next >= 0 && next < rows.length && rows[next]?.kind !== 'tool') next += step;\n if (next < 0 || next >= rows.length) break;\n index = next;\n }\n if (index === this.selected) return;\n this.selected = index;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (matchesKey(data, 'pageUp')) return this.move(-Math.max(1, this.bodyHeight - 1));\n if (matchesKey(data, 'pageDown')) return this.move(Math.max(1, this.bodyHeight - 1));\n }\n\n private treeLines(width: number): string[] {\n const rows = this.rows();\n const lines = [this.theme.bold(SOURCE_HEADING), ''];\n if (rows.length === 0) {\n lines.push(this.theme.fg('dim', EMPTY_MESSAGE));\n return lines;\n }\n\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.selected, rows.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, rows.length - budget)));\n for (const [offset, row] of rows.slice(start, start + budget).entries()) {\n const line =\n row.kind === 'source'\n ? rightAligned(\n this.theme.bold(row.source.label),\n this.theme.fg('dim', row.source.status ?? String(activeCount(row.source))),\n width,\n )\n : // Colour separates the active tool set from the merely registered one,\n // so the cursor never has to visit a row to learn its state.\n this.theme.fg(row.tool.active ? 'text' : 'dim', fit(`${TOOL_INDENT}${row.tool.name}`, width));\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fit(line, width)) : line);\n }\n return lines;\n }\n\n private field(label: string, value: string, width: number): string[] {\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n return wrap(value, valueWidth).map(\n (line, index) => `${this.theme.fg('dim', fit(index === 0 ? label : '', LABEL_COLUMN))}${fit(line, valueWidth)}`,\n );\n }\n\n private toolLines(source: ToolSource, tool: ToolEntry, width: number): string[] {\n const lines = [this.theme.bold(tool.name), ''];\n lines.push(...this.field('source', source.label, width));\n // Source rows no longer open a detail pane, so the entry path is shown here.\n if (source.detail) lines.push(...this.field('registered', source.detail, width));\n lines.push(\n ...this.field(\n 'status',\n tool.active ? this.theme.fg('success', ACTIVE_LABEL) : this.theme.fg('dim', INACTIVE_LABEL),\n width,\n ),\n );\n if (tool.description) {\n lines.push('');\n lines.push(...wrap(tool.description, width));\n }\n const parameters = parameterRows(tool.parameters);\n if (parameters.length > 0) {\n lines.push('');\n lines.push(this.theme.fg('dim', 'PARAMETERS'));\n for (const parameter of parameters) {\n // The name takes the whole line so long ones stop truncating into the\n // type, which sits flush right where the eye can run down the column.\n const name = parameter.required\n ? `${parameter.name}${this.theme.fg('warning', REQUIRED_MARKER)}`\n : parameter.name;\n lines.push(rightAligned(name, this.theme.fg('dim', parameter.type), width));\n }\n }\n for (const guideline of tool.promptGuidelines ?? []) {\n lines.push('');\n lines.push(...wrap(guideline, width));\n }\n return lines;\n }\n\n private detailLines(width: number): string[] {\n const row = this.currentTool();\n if (!row) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.toolLines(row.source, row.tool, width);\n }\n\n protected getChrome(): DoomOverlayChrome {\n // Active over registered: the first number is what the model can call, the\n // second is what a different mode or profile could turn back on.\n const active = this.sources.reduce((total, source) => total + activeCount(source), 0);\n const tools = `${active}/${this.sources.reduce((total, source) => total + source.tools.length, 0)}`;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${tools} tools · ${this.sources.length} sources · ${HEADER_RIGHT}`,\n footer: this.hintLine(),\n };\n }\n\n private hintLine(): string {\n return HINTS.map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n ).join(this.theme.fg('dim', ' '));\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * TREE_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.treeLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.treeLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openToolsOverlay(ctx: ExtensionContext, sources: readonly ToolSource[]): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ToolsOverlayComponent(tui, theme, sources, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"toolsOverlay.d.cts","names":[],"sources":["../../src/components/toolsOverlay.ts"],"mappings":";;;;cAmGa,8BAA8B;mBACxB;mBACA;UACT;UACA;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,kBAAkB,cAAc,OAAO;;UAQ9E;UASA;;UAMA;EAeR,YAAY;UAWJ;UA2BA;UAOA;UAoCA;YAME,aAAa;UAcf;YAOE,WAAW,eAAe;UAkB5B;;iBAeY,iBAAiB,KAAK,kBAAkB,kBAAkB,eAAe"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"toolsOverlay.d.mts","names":[],"sources":["../../src/components/toolsOverlay.ts"],"mappings":";;;;cAmGa,8BAA8B;mBACxB;mBACA;UACT;UACA;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,kBAAkB,cAAc,OAAO;;UAQ9E;UASA;;UAMA;EAeR,YAAY;UAWJ;UA2BA;UAOA;UAoCA;YAME,aAAa;UAcf;YAOE,WAAW,eAAe;UAkB5B;;iBAeY,iBAAiB,KAAK,kBAAkB,kBAAkB,eAAe"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"toolsOverlay.mjs","names":[],"sources":["../../src/components/toolsOverlay.ts"],"sourcesContent":["/**\n * Tools browser: the `SPC e t` overlay.\n *\n * A grouped list on the left (Pi core, one group per MCP server, one per\n * extension) and a detail pane on the right for the tool under the cursor.\n * Read-only: nothing here enables or disables a tool, so it cannot fight plan\n * mode or the workflow dispatcher over the active tool set.\n *\n * Group headings are labels, not controls: everything is always expanded and\n * the cursor steps over them, so there is no expansion state to track and the\n * whole inventory is legible without navigating it. The tree stays a flat row\n * list, which keeps selection a single index and every row assertable as text.\n */\n\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { ToolEntry, ToolSource } from '../toolInventory.ts';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\n\ntype Row = { kind: 'source'; source: ToolSource } | { kind: 'tool'; source: ToolSource; tool: ToolEntry };\n\nconst TITLE = 'TOOLS';\nconst BREADCRUMB = 'SPC › e / t · tools';\nconst HEADER_RIGHT = 'esc close';\nconst HINTS: readonly (readonly [string, string])[] = [\n ['↑↓', 'move'],\n ['esc', 'close'],\n];\n\nconst MIN_WIDTH = 48;\n/** A third for the tree, two thirds for the tool being read. */\nconst TREE_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst TOOL_INDENT = ' ';\nconst LABEL_COLUMN = 12;\nconst REQUIRED_MARKER = ' *';\nconst EMPTY_MESSAGE = 'No tools are registered in this session.';\nconst ACTIVE_LABEL = 'active';\nconst INACTIVE_LABEL = 'inactive';\nconst SOURCE_HEADING = 'SOURCES';\n\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Top-level properties of a JSON-schema-shaped parameter object, if it has any. */\nfunction parameterRows(parameters: unknown): readonly { name: string; type: string; required: boolean }[] {\n if (typeof parameters !== 'object' || parameters === null) return [];\n const schema = parameters as { properties?: unknown; required?: unknown };\n if (typeof schema.properties !== 'object' || schema.properties === null) return [];\n const required = new Set(\n Array.isArray(schema.required) ? schema.required.filter((name) => typeof name === 'string') : [],\n );\n return Object.entries(schema.properties as Record<string, unknown>).map(([name, value]) => {\n const type = typeof value === 'object' && value !== null ? (value as { type?: unknown }).type : undefined;\n return {\n name,\n type: typeof type === 'string' ? type : '',\n required: required.has(name),\n };\n });\n}\n\nfunction activeCount(source: ToolSource): number {\n return source.tools.reduce((total, tool) => total + (tool.active ? 1 : 0), 0);\n}\n\nexport class ToolsOverlayComponent extends DoomOverlay {\n private readonly sources: readonly ToolSource[];\n private readonly done: (result: undefined) => void;\n private selected: number;\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, sources: readonly ToolSource[], done: (result: undefined) => void) {\n super(tui, theme);\n this.sources = sources;\n this.done = done;\n this.selected = this.rows().findIndex((row) => row.kind === 'tool');\n }\n\n /** Every source is always expanded: the source row is a heading, not a control. */\n private rows(): readonly Row[] {\n const rows: Row[] = [];\n for (const source of this.sources) {\n rows.push({ kind: 'source', source });\n for (const tool of source.tools) rows.push({ kind: 'tool', source, tool });\n }\n return rows;\n }\n\n private currentTool(): Extract<Row, { kind: 'tool' }> | undefined {\n const row = this.rows()[this.selected];\n return row?.kind === 'tool' ? row : undefined;\n }\n\n /** Source rows are labels, so the cursor steps over them to the next tool. */\n private move(delta: number): void {\n const rows = this.rows();\n const step = delta < 0 ? -1 : 1;\n let index = this.selected;\n for (let remaining = Math.abs(delta); remaining > 0; remaining -= 1) {\n let next = index + step;\n while (next >= 0 && next < rows.length && rows[next]?.kind !== 'tool') next += step;\n if (next < 0 || next >= rows.length) break;\n index = next;\n }\n if (index === this.selected) return;\n this.selected = index;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (matchesKey(data, 'pageUp')) return this.move(-Math.max(1, this.bodyHeight - 1));\n if (matchesKey(data, 'pageDown')) return this.move(Math.max(1, this.bodyHeight - 1));\n }\n\n private treeLines(width: number): string[] {\n const rows = this.rows();\n const lines = [this.theme.bold(SOURCE_HEADING), ''];\n if (rows.length === 0) {\n lines.push(this.theme.fg('dim', EMPTY_MESSAGE));\n return lines;\n }\n\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.selected, rows.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, rows.length - budget)));\n for (const [offset, row] of rows.slice(start, start + budget).entries()) {\n const line =\n row.kind === 'source'\n ? rightAligned(\n this.theme.bold(row.source.label),\n this.theme.fg('dim', row.source.status ?? String(activeCount(row.source))),\n width,\n )\n : // Colour separates the active tool set from the merely registered one,\n // so the cursor never has to visit a row to learn its state.\n this.theme.fg(row.tool.active ? 'text' : 'dim', fit(`${TOOL_INDENT}${row.tool.name}`, width));\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fit(line, width)) : line);\n }\n return lines;\n }\n\n private field(label: string, value: string, width: number): string[] {\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n return wrap(value, valueWidth).map(\n (line, index) => `${this.theme.fg('dim', fit(index === 0 ? label : '', LABEL_COLUMN))}${fit(line, valueWidth)}`,\n );\n }\n\n private toolLines(source: ToolSource, tool: ToolEntry, width: number): string[] {\n const lines = [this.theme.bold(tool.name), ''];\n lines.push(...this.field('source', source.label, width));\n // Source rows no longer open a detail pane, so the entry path is shown here.\n if (source.detail) lines.push(...this.field('registered', source.detail, width));\n lines.push(\n ...this.field(\n 'status',\n tool.active ? this.theme.fg('success', ACTIVE_LABEL) : this.theme.fg('dim', INACTIVE_LABEL),\n width,\n ),\n );\n if (tool.description) {\n lines.push('');\n lines.push(...wrap(tool.description, width));\n }\n const parameters = parameterRows(tool.parameters);\n if (parameters.length > 0) {\n lines.push('');\n lines.push(this.theme.fg('dim', 'PARAMETERS'));\n for (const parameter of parameters) {\n // The name takes the whole line so long ones stop truncating into the\n // type, which sits flush right where the eye can run down the column.\n const name = parameter.required\n ? `${parameter.name}${this.theme.fg('warning', REQUIRED_MARKER)}`\n : parameter.name;\n lines.push(rightAligned(name, this.theme.fg('dim', parameter.type), width));\n }\n }\n for (const guideline of tool.promptGuidelines ?? []) {\n lines.push('');\n lines.push(...wrap(guideline, width));\n }\n return lines;\n }\n\n private detailLines(width: number): string[] {\n const row = this.currentTool();\n if (!row) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.toolLines(row.source, row.tool, width);\n }\n\n protected getChrome(): DoomOverlayChrome {\n // Active over registered: the first number is what the model can call, the\n // second is what a different mode or profile could turn back on.\n const active = this.sources.reduce((total, source) => total + activeCount(source), 0);\n const tools = `${active}/${this.sources.reduce((total, source) => total + source.tools.length, 0)}`;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: `${tools} tools · ${this.sources.length} sources · ${HEADER_RIGHT}`,\n footer: this.hintLine(),\n };\n }\n\n private hintLine(): string {\n return HINTS.map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n ).join(this.theme.fg('dim', ' '));\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * TREE_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.treeLines(leftContent);\n const right = this.detailLines(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(right[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.treeLines(width);\n const bottom = this.detailLines(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openToolsOverlay(ctx: ExtensionContext, sources: readonly ToolSource[]): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ToolsOverlayComponent(tui, theme, sources, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/config';\n"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/config';\n"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.cjs","names":["path","createUiTelemetry","DoomUiState","DoomLeaderRegistry","McpStatusQueryProtocol","buildToolSources","openToolsOverlay","openConfigOverlay","UI_EVENT","installDoomLeaderBootstrapHost","createProtocolRuntime","LeaderRegistrationProtocol","LeaderUnregistrationProtocol","DoomHeader","DoomFooter","DoomEditor","LeaderHints","LeaderActionInvoked"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n installDoomLeaderBootstrapHost,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\n let disposeLeaderBootstrap: (() => void) | undefined;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, configRegistry);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const registerLeaderContribution = (contribution: LeaderContribution, deferred: boolean) => {\n const generation = Symbol(contribution.source);\n const register = deferred\n ? (value: LeaderContribution) => leaderRegistry.registerDeferred(value)\n : (value: LeaderContribution) => leaderRegistry.register(value);\n const result = register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return { update: () => undefined, dispose: () => undefined };\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return {\n update(bindings: LeaderContribution['bindings']) {\n if (disposed || contributionGenerations.get(contribution.source) !== generation) return;\n reportDiagnostics(register({ source: contribution.source, bindings }).diagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n register({ source: contribution.source, bindings: [] });\n },\n };\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const handle = registerLeaderContribution(contribution, false);\n return () => handle.dispose();\n },\n });\n // Installed during the UI factory, before the remaining Doom factories run.\n // Their registrations are validated immediately and rebuilt as one batch at\n // session_start, so the first editor always receives the complete keyspace.\n disposeLeaderBootstrap = installDoomLeaderBootstrapHost({\n register: (contribution) => registerLeaderContribution(contribution, true),\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n reportDiagnostics(leaderRegistry.flush());\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) ctx.ui.notify(message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n ctx.ui.notify(`Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, footerStatusRegistry, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { source, action });\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n ctx.ui.notify(`Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n disposeLeaderBootstrap?.();\n disposeLeaderBootstrap = undefined;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAoDgB,kBAAkB,IAAI,cAAc,YAAW"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAoDgB,kBAAkB,IAAI,cAAc,YAAW"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.mjs","names":["DoomFooterStatusRegistry","DoomModeStatusRegistry","DoomConfigRegistry"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n installDoomLeaderBootstrapHost,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\n let disposeLeaderBootstrap: (() => void) | undefined;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, configRegistry);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const registerLeaderContribution = (contribution: LeaderContribution, deferred: boolean) => {\n const generation = Symbol(contribution.source);\n const register = deferred\n ? (value: LeaderContribution) => leaderRegistry.registerDeferred(value)\n : (value: LeaderContribution) => leaderRegistry.register(value);\n const result = register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return { update: () => undefined, dispose: () => undefined };\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return {\n update(bindings: LeaderContribution['bindings']) {\n if (disposed || contributionGenerations.get(contribution.source) !== generation) return;\n reportDiagnostics(register({ source: contribution.source, bindings }).diagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n register({ source: contribution.source, bindings: [] });\n },\n };\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const handle = registerLeaderContribution(contribution, false);\n return () => handle.dispose();\n },\n });\n // Installed during the UI factory, before the remaining Doom factories run.\n // Their registrations are validated immediately and rebuilt as one batch at\n // session_start, so the first editor always receives the complete keyspace.\n disposeLeaderBootstrap = installDoomLeaderBootstrapHost({\n register: (contribution) => registerLeaderContribution(contribution, true),\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n reportDiagnostics(leaderRegistry.flush());\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) ctx.ui.notify(message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n ctx.ui.notify(`Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, footerStatusRegistry, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { source, action });\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n ctx.ui.notify(`Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n disposeLeaderBootstrap?.();\n disposeLeaderBootstrap = undefined;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extensionName.cjs","names":["fs","path"],"sources":["../src/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n"],"mappings":"6MAaA,MAAM,EAAQ,IAAI,IAElB,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAMA,EAAAA,QAAG,aAAaC,EAAAA,QAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAYA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQA,EAAAA,QAAK,SAAS,EAAWA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extensionName.d.cts","names":[],"sources":["../src/extensionName.ts"],"mappings":";iBA8BgB,cAAc"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extensionName.d.mts","names":[],"sources":["../src/extensionName.ts"],"mappings":";iBA8BgB,cAAc"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extensionName.mjs","names":[],"sources":["../src/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n"],"mappings":"gDAaA,MAAM,EAAQ,IAAI,IAElB,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAAS,EAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAY,EAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQ,EAAK,SAAS,EAAW,EAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"footer.cjs","names":[],"sources":["../src/footer.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/footer';\n"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"footer.d.cts","names":[],"sources":["../src/footer.ts"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"footer.d.mts","names":[],"sources":["../src/footer.ts"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"footer.mjs","names":[],"sources":["../src/footer.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/footer';\n"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"harnessMetadata.cjs","names":[],"sources":["../src/harnessMetadata.ts"],"sourcesContent":["/**\n * The slice of harness state the header renders.\n *\n * A projection of the store rather than its own reader. This used to\n * re-implement five of the state keys with its own list parser and its own\n * default mode, which is two definitions of one protocol to keep in step.\n */\n\nconst DEFAULT_MAJOR_MODE = 'copilot';\n\nexport interface DoomHarnessMetadata {\n root?: string;\n /** The one named major mode this session runs under. */\n majorMode: string;\n domains: string[];\n /** The layer components the major mode resolved to. */\n layers: string[];\n profile?: string;\n}\n\nfunction parseList(value: string | undefined): string[] {\n return value\n ? value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean)\n : [];\n}\n\nexport function readDoomHarnessMetadata(environment: NodeJS.ProcessEnv = process.env): DoomHarnessMetadata {\n return {\n root: environment.DOOMPI_ROOT,\n majorMode: environment.DOOMPI_MAJOR_MODE?.trim() || DEFAULT_MAJOR_MODE,\n domains: parseList(environment.DOOMPI_DOMAINS),\n layers: parseList(environment.DOOMPI_LAYERS),\n profile: environment.DOOMPI_PROFILE?.trim() || undefined,\n };\n}\n"],"mappings":"mEAoBA,SAAS,EAAU,EAAqC,CACtD,OAAO,EACH,EACG,MAAM,GAAG,CAAC,CACV,IAAK,GAAS,EAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO,EACjB,CAAC,CACP,CAEA,SAAgB,EAAwB,EAAiC,QAAQ,IAA0B,CACzG,MAAO,CACL,KAAM,EAAY,YAClB,UAAW,EAAY,mBAAmB,KAAK,GAAK,UACpD,QAAS,EAAU,EAAY,cAAc,EAC7C,OAAQ,EAAU,EAAY,aAAa,EAC3C,QAAS,EAAY,gBAAgB,KAAK,GAAK,IAAA,EACjD,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"harnessMetadata.d.cts","names":[],"sources":["../src/harnessMetadata.ts"],"mappings":";;;;;;;;UAUiB;EACf;;EAEA;EACA;;EAEA;EACA;;iBAYc,wBAAwB,cAAa,OAAO,aAA2B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"harnessMetadata.d.mts","names":[],"sources":["../src/harnessMetadata.ts"],"mappings":";;;;;;;;UAUiB;EACf;;EAEA;EACA;;EAEA;EACA;;iBAYc,wBAAwB,cAAa,OAAO,aAA2B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"harnessMetadata.mjs","names":[],"sources":["../src/harnessMetadata.ts"],"sourcesContent":["/**\n * The slice of harness state the header renders.\n *\n * A projection of the store rather than its own reader. This used to\n * re-implement five of the state keys with its own list parser and its own\n * default mode, which is two definitions of one protocol to keep in step.\n */\n\nconst DEFAULT_MAJOR_MODE = 'copilot';\n\nexport interface DoomHarnessMetadata {\n root?: string;\n /** The one named major mode this session runs under. */\n majorMode: string;\n domains: string[];\n /** The layer components the major mode resolved to. */\n layers: string[];\n profile?: string;\n}\n\nfunction parseList(value: string | undefined): string[] {\n return value\n ? value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean)\n : [];\n}\n\nexport function readDoomHarnessMetadata(environment: NodeJS.ProcessEnv = process.env): DoomHarnessMetadata {\n return {\n root: environment.DOOMPI_ROOT,\n majorMode: environment.DOOMPI_MAJOR_MODE?.trim() || DEFAULT_MAJOR_MODE,\n domains: parseList(environment.DOOMPI_DOMAINS),\n layers: parseList(environment.DOOMPI_LAYERS),\n profile: environment.DOOMPI_PROFILE?.trim() || undefined,\n };\n}\n"],"mappings":"AAoBA,SAAS,EAAU,EAAqC,CACtD,OAAO,EACH,EACG,MAAM,GAAG,CAAC,CACV,IAAK,GAAS,EAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO,EACjB,CAAC,CACP,CAEA,SAAgB,EAAwB,EAAiC,QAAQ,IAA0B,CACzG,MAAO,CACL,KAAM,EAAY,YAClB,UAAW,EAAY,mBAAmB,KAAK,GAAK,UACpD,QAAS,EAAU,EAAY,cAAc,EAC7C,OAAQ,EAAU,EAAY,aAAa,EAC3C,QAAS,EAAY,gBAAgB,KAAK,GAAK,IAAA,EACjD,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;UACA;;EAMR,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA4BnC,kBAAkB;EAIlB,UAAU;UAKF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;UACA;;EAMR,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA4BnC,kBAAkB;EAIlB,UAAU;UAKF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logSinkTelemetry.cjs","names":["createDoomTelemetry"],"sources":["../src/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-pi-ui';\nconst PACKAGE_NAME = '@agimon-ai/doompi-ui';\n\nexport const UI_EVENT = {\n leaderContributionRejected: 'doom_pi_ui.leader_contribution_rejected',\n leaderCommandUnavailable: 'doom_pi_ui.leader_command_unavailable',\n leaderActionUnavailable: 'doom_pi_ui.leader_action_unavailable',\n themeApplyFailed: 'doom_pi_ui.theme_apply_failed',\n shellInstalled: 'doom_pi_ui.shell_installed',\n} as const;\n\nexport type UiEventName = (typeof UI_EVENT)[keyof typeof UI_EVENT];\nexport type UiEventAttributes = Record<string, string | number | boolean>;\n\nexport interface UiTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface UiTelemetry {\n recordError(event: UiEventName, error: unknown, attributes?: UiEventAttributes): Promise<void>;\n recordWarning(event: UiEventName, error: unknown, attributes?: UiEventAttributes): Promise<void>;\n recordEvent(event: UiEventName, attributes?: UiEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createUiTelemetry(options: UiTelemetryOptions = {}): UiTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"gHAEA,MAGa,EAAW,CACtB,2BAA4B,0CAC5B,yBAA0B,wCAC1B,wBAAyB,uCACzB,iBAAkB,gCAClB,eAAgB,4BAClB,EAqBA,SAAgB,EAAkB,EAA8B,CAAC,EAAgB,CAC/E,IAAM,GAAA,EAA2BA,EAAAA,oBAAAA,CAAoB,CACnD,YAAa,aACb,YAAa,uBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logSinkTelemetry.d.cts","names":[],"sources":["../src/logSinkTelemetry.ts"],"mappings":";;cAKa;;;;;;;KAQD,sBAAsB,uBAAuB;KAC7C,oBAAoB;UAEf;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,aAAa,gBAAgB,aAAa,oBAAoB;EACjF,cAAc,OAAO,aAAa,gBAAgB,aAAa,oBAAoB;EACnF,YAAY,OAAO,aAAa,aAAa,oBAAoB;EACjE,SAAS;EACT,YAAY;;iBAGE,kBAAkB,UAAS,qBAA0B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logSinkTelemetry.d.mts","names":[],"sources":["../src/logSinkTelemetry.ts"],"mappings":";;cAKa;;;;;;;KAQD,sBAAsB,uBAAuB;KAC7C,oBAAoB;UAEf;EACf;EACA;EACA,MAAM,OAAO;EACb,mBAAmB,YAAY;EAC/B,QAAQ;;UAGO;EACf,YAAY,OAAO,aAAa,gBAAgB,aAAa,oBAAoB;EACjF,cAAc,OAAO,aAAa,gBAAgB,aAAa,oBAAoB;EACnF,YAAY,OAAO,aAAa,aAAa,oBAAoB;EACjE,SAAS;EACT,YAAY;;iBAGE,kBAAkB,UAAS,qBAA0B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"logSinkTelemetry.mjs","names":[],"sources":["../src/logSinkTelemetry.ts"],"sourcesContent":["import { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\n\nconst SERVICE_NAME = 'doom-pi-ui';\nconst PACKAGE_NAME = '@agimon-ai/doompi-ui';\n\nexport const UI_EVENT = {\n leaderContributionRejected: 'doom_pi_ui.leader_contribution_rejected',\n leaderCommandUnavailable: 'doom_pi_ui.leader_command_unavailable',\n leaderActionUnavailable: 'doom_pi_ui.leader_action_unavailable',\n themeApplyFailed: 'doom_pi_ui.theme_apply_failed',\n shellInstalled: 'doom_pi_ui.shell_installed',\n} as const;\n\nexport type UiEventName = (typeof UI_EVENT)[keyof typeof UI_EVENT];\nexport type UiEventAttributes = Record<string, string | number | boolean>;\n\nexport interface UiTelemetryOptions {\n cwd?: string;\n workspaceRoot?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: NonNullable<DoomTelemetryOptions['telemetryFactory']>;\n warn?: (message: string) => void;\n}\n\nexport interface UiTelemetry {\n recordError(event: UiEventName, error: unknown, attributes?: UiEventAttributes): Promise<void>;\n recordWarning(event: UiEventName, error: unknown, attributes?: UiEventAttributes): Promise<void>;\n recordEvent(event: UiEventName, attributes?: UiEventAttributes): Promise<void>;\n flush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport function createUiTelemetry(options: UiTelemetryOptions = {}): UiTelemetry {\n const telemetry: DoomTelemetry = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: options.cwd,\n workspaceRoot: options.workspaceRoot,\n env: options.env,\n telemetryFactory: options.telemetryFactory,\n warn: options.warn,\n enableLogs: true,\n enableTraces: true,\n });\n return {\n recordError: (event, error, attributes) => telemetry.recordError(event, error, attributes),\n recordWarning: (event, error, attributes) => telemetry.recordWarning(event, error, attributes),\n recordEvent: (event, attributes) => telemetry.recordEvent(event, attributes),\n flush: () => telemetry.flush(),\n shutdown: () => telemetry.shutdown(),\n };\n}\n"],"mappings":"kEAEA,MAGa,EAAW,CACtB,2BAA4B,0CAC5B,yBAA0B,wCAC1B,wBAAyB,uCACzB,iBAAkB,gCAClB,eAAgB,4BAClB,EAqBA,SAAgB,EAAkB,EAA8B,CAAC,EAAgB,CAC/E,IAAM,EAA2B,EAAoB,CACnD,YAAa,aACb,YAAa,uBACb,IAAK,EAAQ,IACb,cAAe,EAAQ,cACvB,IAAK,EAAQ,IACb,iBAAkB,EAAQ,iBAC1B,KAAM,EAAQ,KACd,WAAY,GACZ,aAAc,EAChB,CAAC,EACD,MAAO,CACL,aAAc,EAAO,EAAO,IAAe,EAAU,YAAY,EAAO,EAAO,CAAU,EACzF,eAAgB,EAAO,EAAO,IAAe,EAAU,cAAc,EAAO,EAAO,CAAU,EAC7F,aAAc,EAAO,IAAe,EAAU,YAAY,EAAO,CAAU,EAC3E,UAAa,EAAU,MAAM,EAC7B,aAAgB,EAAU,SAAS,CACrC,CACF"}
package/dist/mode.cjs DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs");var t=e.__exportAll({});e.__reExport(t,require("@agimon-ai/doompi-extension-contracts/mode")),Object.defineProperty(exports,"mode_exports",{enumerable:!0,get:function(){return t}});var n=require("@agimon-ai/doompi-extension-contracts/mode");Object.keys(n).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return n[e]}})});
2
- //# sourceMappingURL=mode.cjs.map
package/dist/mode.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mode.cjs","names":[],"sources":["../src/mode.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/mode';\n"],"mappings":""}
package/dist/mode.d.cts DELETED
@@ -1,5 +0,0 @@
1
- export * from "@agimon-ai/doompi-extension-contracts/mode";
2
- import * as import__agimon_ai_doompi_extension_contracts_mode from "@agimon-ai/doompi-extension-contracts/mode";
3
- //#endregion
4
- export { import__agimon_ai_doompi_extension_contracts_mode as mode_d_exports };
5
- //# sourceMappingURL=mode.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mode.d.cts","names":[],"sources":["../src/mode.ts"],"mappings":""}
package/dist/mode.d.mts DELETED
@@ -1,5 +0,0 @@
1
- export * from "@agimon-ai/doompi-extension-contracts/mode";
2
- import * as import__agimon_ai_doompi_extension_contracts_mode from "@agimon-ai/doompi-extension-contracts/mode";
3
- //#endregion
4
- export { import__agimon_ai_doompi_extension_contracts_mode as mode_d_exports };
5
- //# sourceMappingURL=mode.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mode.d.mts","names":[],"sources":["../src/mode.ts"],"mappings":""}
package/dist/mode.mjs DELETED
@@ -1,2 +0,0 @@
1
- import{__exportAll as e,__reExport as t}from"./_virtual/_rolldown/runtime.mjs";export*from"@agimon-ai/doompi-extension-contracts/mode";var n=e({});import*as r from"@agimon-ai/doompi-extension-contracts/mode";t(n,r);export{n as mode_exports};
2
- //# sourceMappingURL=mode.mjs.map
package/dist/mode.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mode.mjs","names":[],"sources":["../src/mode.ts"],"sourcesContent":["export * from '@agimon-ai/doompi-extension-contracts/mode';\n"],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rendering.cjs","names":["truncateToWidth","visibleWidth"],"sources":["../src/rendering.ts"],"sourcesContent":["import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nconst THOUSAND = 1000;\nconst MILLION = 1_000_000;\n/** Below this many tokens the \"k\" form keeps a decimal, above it rounds. */\nconst K_DECIMAL_LIMIT = 10_000;\n\n/** Compact token count, e.g. `812`, `4.2k`, `186k`, `1.3M`. */\nexport function formatTokens(value: number): string {\n if (value < THOUSAND) return String(value);\n if (value < MILLION) return `${(value / THOUSAND).toFixed(value < K_DECIMAL_LIMIT ? 1 : 0)}k`;\n return `${(value / MILLION).toFixed(1)}M`;\n}\n\nexport function fitLine(text: string, width: number): string {\n return truncateToWidth(text, Math.max(0, width), '');\n}\n\nexport function padLine(text: string, width: number): string {\n const safeWidth = Math.max(0, width);\n const fitted = fitLine(text, safeWidth);\n return `${fitted}${' '.repeat(Math.max(0, safeWidth - visibleWidth(fitted)))}`;\n}\n\nexport function alignLine(left: string, right: string, width: number): string {\n const safeWidth = Math.max(0, width);\n const rightWidth = visibleWidth(right);\n if (!right || rightWidth >= safeWidth) return fitLine(right || left, safeWidth);\n\n const maxLeftWidth = Math.max(0, safeWidth - rightWidth - 1);\n const fittedLeft = fitLine(left, maxLeftWidth);\n const gap = ' '.repeat(Math.max(1, safeWidth - visibleWidth(fittedLeft) - rightWidth));\n return fitLine(`${fittedLeft}${gap}${right}`, safeWidth);\n}\n\nexport function frameLine(content: string, width: number, left = '│', right = '│'): string {\n const safeWidth = Math.max(0, width);\n const frameWidth = visibleWidth(left) + visibleWidth(right);\n if (safeWidth <= frameWidth) return fitLine(`${left}${right}`, safeWidth);\n return `${left}${padLine(content, safeWidth - frameWidth)}${right}`;\n}\n\nexport function packSegments(segments: string[], width: number, separator = ' '): string[] {\n const safeWidth = Math.max(0, width);\n if (safeWidth === 0) return [];\n\n const lines: string[] = [];\n let current = '';\n for (const segment of segments) {\n const candidate = current ? `${current}${separator}${segment}` : segment;\n if (visibleWidth(candidate) <= safeWidth) {\n current = candidate;\n continue;\n }\n if (current) lines.push(fitLine(current, safeWidth));\n current = fitLine(segment, safeWidth);\n }\n if (current) lines.push(current);\n return lines;\n}\n"],"mappings":"2GAEA,MAAM,EAAW,IACX,EAAU,IAKhB,SAAgB,EAAa,EAAuB,CAGlD,OAFI,EAAQ,EAAiB,OAAO,CAAK,EACrC,EAAQ,EAAgB,IAAI,EAAQ,EAAA,CAAU,QAAQ,IAAQ,IAAuB,EAAE,GACpF,IAAI,EAAQ,EAAA,CAAS,QAAQ,CAAC,EAAE,EACzC,CAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAM,KAAK,IAAI,EAAG,CAAK,EAAG,EAAE,CACrD,CAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,EAAS,EAAQ,EAAM,CAAS,EACtC,MAAO,GAAG,IAAS,IAAI,OAAO,KAAK,IAAI,EAAG,GAAA,EAAYC,EAAAA,aAAAA,CAAa,CAAM,CAAC,CAAC,GAC7E,CAEA,SAAgB,EAAU,EAAc,EAAe,EAAuB,CAC5E,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,GAAA,EAAaA,EAAAA,aAAAA,CAAa,CAAK,EACrC,GAAI,CAAC,GAAS,GAAc,EAAW,OAAO,EAAQ,GAAS,EAAM,CAAS,EAG9E,IAAM,EAAa,EAAQ,EADN,KAAK,IAAI,EAAG,EAAY,EAAa,CACd,CAAC,EAE7C,OAAO,EAAQ,GAAG,IADN,IAAI,OAAO,KAAK,IAAI,EAAG,GAAA,EAAYA,EAAAA,aAAAA,CAAa,CAAU,EAAI,CAAU,CACnD,IAAI,IAAS,CAAS,CACzD,CAEA,SAAgB,EAAU,EAAiB,EAAe,EAAO,IAAK,EAAQ,IAAa,CACzF,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,GAAA,EAAaA,EAAAA,aAAAA,CAAa,CAAI,GAAA,EAAIA,EAAAA,aAAAA,CAAa,CAAK,EAE1D,OADI,GAAa,EAAmB,EAAQ,GAAG,IAAO,IAAS,CAAS,EACjE,GAAG,IAAO,EAAQ,EAAS,EAAY,CAAU,IAAI,GAC9D,CAEA,SAAgB,EAAa,EAAoB,EAAe,EAAY,KAAgB,CAC1F,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EACnC,GAAI,IAAc,EAAG,MAAO,CAAC,EAE7B,IAAM,EAAkB,CAAC,EACrB,EAAU,GACd,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAM,EAAY,EAAU,GAAG,IAAU,IAAY,IAAY,EACjE,IAAA,EAAIA,EAAAA,aAAAA,CAAa,CAAS,GAAK,EAAW,CACxC,EAAU,EACV,QACF,CACI,GAAS,EAAM,KAAK,EAAQ,EAAS,CAAS,CAAC,EACnD,EAAU,EAAQ,EAAS,CAAS,CACtC,CAEA,OADI,GAAS,EAAM,KAAK,CAAO,EACxB,CACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rendering.d.cts","names":[],"sources":["../src/rendering.ts"],"mappings":";;iBAQgB,aAAa;iBAMb,QAAQ,cAAc;iBAItB,QAAQ,cAAc;iBAMtB,UAAU,cAAc,eAAe;iBAWvC,UAAU,iBAAiB,eAAe,eAAY;iBAOtD,aAAa,oBAAoB,eAAe"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rendering.d.mts","names":[],"sources":["../src/rendering.ts"],"mappings":";;iBAQgB,aAAa;iBAMb,QAAQ,cAAc;iBAItB,QAAQ,cAAc;iBAMtB,UAAU,cAAc,eAAe;iBAWvC,UAAU,iBAAiB,eAAe,eAAY;iBAOtD,aAAa,oBAAoB,eAAe"}