gloomberb 0.16.3 → 0.16.4

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 (451) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -0,0 +1,137 @@
1
+ import type { PluginPersistence } from "../../../../types/plugin";
2
+
3
+ /**
4
+ * Which remote assistants may control this terminal. Asked once per
5
+ * assistant (a Gloom Cloud key or a signed-in client): "Always allow" and
6
+ * "Deny" are kept with the profile, "Allow for this session" lasts until the
7
+ * app quits. Revoking in Account Management, revoking the key or
8
+ * disconnecting the client in Cloud settings forgets the decision.
9
+ */
10
+ export type AssistantDecision = "always" | "session" | "deny";
11
+
12
+ export interface AssistantGrant {
13
+ clientId: string;
14
+ name: string;
15
+ decision: AssistantDecision;
16
+ decidedAt: number;
17
+ lastUsedAt: number | null;
18
+ }
19
+
20
+ const GRANTS_STATE_KEY = "terminal-relay:grants";
21
+ const DEVICE_STATE_KEY = "terminal-relay:device";
22
+ const MAX_GRANTS = 50;
23
+
24
+ function isGrant(value: unknown): value is AssistantGrant {
25
+ if (!value || typeof value !== "object") return false;
26
+ const record = value as Record<string, unknown>;
27
+ return typeof record.clientId === "string"
28
+ && typeof record.name === "string"
29
+ && (record.decision === "always" || record.decision === "deny")
30
+ && typeof record.decidedAt === "number";
31
+ }
32
+
33
+ function randomDeviceId(): string {
34
+ const bytes = new Uint8Array(16);
35
+ globalThis.crypto.getRandomValues(bytes);
36
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
37
+ }
38
+
39
+ export class TerminalRelayGrants {
40
+ private persistence: PluginPersistence | null = null;
41
+ private stored = new Map<string, AssistantGrant>();
42
+ private readonly session = new Map<string, AssistantGrant>();
43
+ private readonly listeners = new Set<() => void>();
44
+ private listed: AssistantGrant[] = [];
45
+
46
+ attach(persistence: PluginPersistence): void {
47
+ this.persistence = persistence;
48
+ const raw = persistence.getState<unknown>(GRANTS_STATE_KEY);
49
+ this.stored = new Map(
50
+ (Array.isArray(raw) ? raw : [])
51
+ .filter(isGrant)
52
+ .map((grant) => [grant.clientId, { ...grant, lastUsedAt: grant.lastUsedAt ?? null }]),
53
+ );
54
+ this.emit();
55
+ }
56
+
57
+ detach(): void {
58
+ this.persistence = null;
59
+ this.stored.clear();
60
+ this.session.clear();
61
+ this.emit();
62
+ }
63
+
64
+ get attached(): boolean {
65
+ return this.persistence !== null;
66
+ }
67
+
68
+ /** A random id for this profile, created once; nothing about the machine is in it. */
69
+ deviceId(): string | null {
70
+ if (!this.persistence) return null;
71
+ const stored = this.persistence.getState<{ id?: unknown }>(DEVICE_STATE_KEY);
72
+ if (stored && typeof stored.id === "string" && /^[a-f0-9]{32}$/.test(stored.id)) return stored.id;
73
+ const id = randomDeviceId();
74
+ this.persistence.setState(DEVICE_STATE_KEY, { id });
75
+ return id;
76
+ }
77
+
78
+ decision(clientId: string): AssistantDecision | null {
79
+ return this.session.get(clientId)?.decision ?? this.stored.get(clientId)?.decision ?? null;
80
+ }
81
+
82
+ decide(clientId: string, name: string, decision: AssistantDecision, now = Date.now()): void {
83
+ const grant: AssistantGrant = { clientId, name, decision, decidedAt: now, lastUsedAt: null };
84
+ if (decision === "session") {
85
+ this.session.set(clientId, grant);
86
+ this.stored.delete(clientId);
87
+ } else {
88
+ this.session.delete(clientId);
89
+ this.stored.set(clientId, grant);
90
+ }
91
+ this.persist();
92
+ }
93
+
94
+ markUsed(clientId: string, now = Date.now()): void {
95
+ const grant = this.session.get(clientId) ?? this.stored.get(clientId);
96
+ if (!grant) return;
97
+ grant.lastUsedAt = now;
98
+ this.emit();
99
+ }
100
+
101
+ /** Forgets an assistant, so its next call asks again. */
102
+ forget(clientId: string): void {
103
+ const had = this.session.delete(clientId) || this.stored.has(clientId);
104
+ this.stored.delete(clientId);
105
+ if (had) this.persist();
106
+ }
107
+
108
+ /** The same array until something changes, so React can subscribe to it. */
109
+ list(): AssistantGrant[] {
110
+ return this.listed;
111
+ }
112
+
113
+ subscribe(listener: () => void): () => void {
114
+ this.listeners.add(listener);
115
+ return () => {
116
+ this.listeners.delete(listener);
117
+ };
118
+ }
119
+
120
+ private persist(): void {
121
+ const kept = [...this.stored.values()]
122
+ .sort((left, right) => right.decidedAt - left.decidedAt)
123
+ .slice(0, MAX_GRANTS);
124
+ this.stored = new Map(kept.map((grant) => [grant.clientId, grant]));
125
+ this.persistence?.setState(GRANTS_STATE_KEY, kept);
126
+ this.emit();
127
+ }
128
+
129
+ private emit(): void {
130
+ this.listed = [...this.session.values(), ...this.stored.values()]
131
+ .map((grant) => ({ ...grant }))
132
+ .sort((left, right) => (right.lastUsedAt ?? right.decidedAt) - (left.lastUsedAt ?? left.decidedAt));
133
+ for (const listener of this.listeners) listener();
134
+ }
135
+ }
136
+
137
+ export const terminalRelayGrants = new TerminalRelayGrants();
@@ -0,0 +1,81 @@
1
+ import { useEffect, useRef, useSyncExternalStore } from "react";
2
+ import { apiClient } from "../../../../api-client";
3
+ import { useRemoteControlHandler } from "../../../../remote/app-host";
4
+ import { useAppActive } from "../../../../state/app/activity";
5
+ import { useOptionalDialog } from "../../../../ui/dialog";
6
+ import { VERSION } from "../../../../version";
7
+ import { TerminalRelayClient, type TerminalRelaySocket } from "./client";
8
+ import { TerminalRelayEngine } from "./engine";
9
+ import { terminalRelayGrants } from "./grants";
10
+ import { recordRelayActivity } from "./indicator";
11
+ import { dialogRelayPrompts } from "./prompts";
12
+
13
+ /** What kind of app this is and what to call it in Cloud settings and tool results. */
14
+ export interface TerminalRelayDevice {
15
+ kind: "desktop" | "tui";
16
+ name: string;
17
+ }
18
+
19
+ const ACTIVE_PING_MS = 30_000;
20
+
21
+ const cloudSocket: TerminalRelaySocket = {
22
+ subscribeConnection: (listener) => apiClient.subscribeSocketConnection(listener),
23
+ subscribeCloudEvent: (type, listener) => apiClient.subscribeCloudEvent(type, listener),
24
+ sendFrame: (frame) => apiClient.sendSocketFrame(frame),
25
+ serverOffers: (feature) => apiClient.socketServerOffers(feature),
26
+ };
27
+
28
+ /**
29
+ * Lets a remote assistant connected to the Gloom Cloud MCP drive this
30
+ * terminal. Mounted once per app window that owns the layout; renders
31
+ * nothing itself (the prompts open as dialogs, the chip is a status widget).
32
+ * Off while the Gloom Cloud plugin is off.
33
+ */
34
+ export function TerminalRelayHost({ device }: { device: TerminalRelayDevice | undefined }) {
35
+ const handle = useRemoteControlHandler();
36
+ const dialog = useOptionalDialog();
37
+ const appActive = useAppActive();
38
+ const attached = useSyncExternalStore(
39
+ (listener) => terminalRelayGrants.subscribe(listener),
40
+ () => terminalRelayGrants.attached,
41
+ () => false,
42
+ );
43
+ const clientRef = useRef<TerminalRelayClient | null>(null);
44
+ const kind = device?.kind;
45
+ const name = device?.name;
46
+
47
+ useEffect(() => {
48
+ if (!handle || !dialog || !kind || !name || !attached) return;
49
+ const engine = new TerminalRelayEngine({
50
+ handle,
51
+ send: (frame) => apiClient.sendSocketFrame(frame),
52
+ grants: terminalRelayGrants,
53
+ prompts: dialogRelayPrompts(dialog),
54
+ onActivity: recordRelayActivity,
55
+ });
56
+ const client = new TerminalRelayClient(cloudSocket, engine, () => {
57
+ // Signed out, or not verified yet: nothing to announce.
58
+ if (!apiClient.isVerified()) return null;
59
+ const id = terminalRelayGrants.deviceId();
60
+ return id ? { id, kind, name, appVersion: VERSION } : null;
61
+ });
62
+ clientRef.current = client;
63
+ const stop = client.start();
64
+ const unsubscribeUser = apiClient.subscribeCurrentUser(() => client.refresh());
65
+ return () => {
66
+ clientRef.current = null;
67
+ unsubscribeUser();
68
+ stop();
69
+ engine.dispose();
70
+ };
71
+ }, [attached, dialog, handle, kind, name]);
72
+
73
+ useEffect(() => {
74
+ if (!appActive) return;
75
+ clientRef.current?.activity();
76
+ const timer = setInterval(() => clientRef.current?.activity(), ACTIVE_PING_MS);
77
+ return () => clearInterval(timer);
78
+ }, [appActive, attached, kind]);
79
+
80
+ return null;
81
+ }
@@ -0,0 +1,82 @@
1
+ import { useEffect, useState, useSyncExternalStore } from "react";
2
+ import { Button } from "../../../../components/ui/button";
3
+ import { Box } from "../../../../ui";
4
+ import { t, tf } from "../../../../i18n";
5
+ import { getSharedRegistry } from "../../../registry";
6
+ import { requestAccountManagementTab } from "../../account-management/navigation";
7
+ import type { RelayActivity } from "./engine";
8
+
9
+ /** An assistant counts as connected for this long after its last call. */
10
+ const CONNECTED_FOR_MS = 5 * 60_000;
11
+ /** The chip brightens this long after each call. */
12
+ const ACTING_FOR_MS = 2_500;
13
+
14
+ interface ActivityState {
15
+ name: string;
16
+ clientId: string;
17
+ at: number;
18
+ }
19
+
20
+ let current: ActivityState | null = null;
21
+ const listeners = new Set<() => void>();
22
+
23
+ function emit(): void {
24
+ for (const listener of listeners) listener();
25
+ }
26
+
27
+ /** Fed by the relay engine. */
28
+ export function recordRelayActivity(activity: RelayActivity): void {
29
+ if (activity.type === "acted") {
30
+ current = { name: activity.caller.name, clientId: activity.caller.id, at: activity.at };
31
+ } else if (current?.clientId === activity.clientId) {
32
+ current = null;
33
+ }
34
+ emit();
35
+ }
36
+
37
+ function subscribe(listener: () => void): () => void {
38
+ listeners.add(listener);
39
+ return () => {
40
+ listeners.delete(listener);
41
+ };
42
+ }
43
+
44
+ /**
45
+ * A quiet chip in the status bar while a remote assistant drives this
46
+ * terminal: dim while connected, bright for a moment when it acts. It opens
47
+ * Account Management on the Agents tab, where the assistant can be revoked.
48
+ */
49
+ export function TerminalRelayStatusWidget() {
50
+ const activity = useSyncExternalStore(subscribe, () => current, () => current);
51
+ const [now, setNow] = useState(() => Date.now());
52
+
53
+ useEffect(() => {
54
+ if (!activity) return;
55
+ setNow(Date.now());
56
+ const acting = setTimeout(() => setNow(Date.now()), ACTING_FOR_MS + 50);
57
+ const connected = setTimeout(() => setNow(Date.now()), CONNECTED_FOR_MS + 50);
58
+ return () => {
59
+ clearTimeout(acting);
60
+ clearTimeout(connected);
61
+ };
62
+ }, [activity]);
63
+
64
+ if (!activity || now - activity.at > CONNECTED_FOR_MS) return null;
65
+ const acting = now - activity.at < ACTING_FOR_MS;
66
+ return (
67
+ <Box paddingRight={1} flexShrink={0}>
68
+ <Button
69
+ label={acting ? tf("{name} is acting", { name: activity.name }) : tf("{name} connected", { name: activity.name })}
70
+ title={t("A remote assistant controls this terminal. Open Agents to revoke it.")}
71
+ variant="plain"
72
+ active={acting}
73
+ compact
74
+ stopPropagation
75
+ onPress={() => {
76
+ requestAccountManagementTab("agents");
77
+ getSharedRegistry()?.showPane("account-management");
78
+ }}
79
+ />
80
+ </Box>
81
+ );
82
+ }
@@ -0,0 +1,234 @@
1
+ import {
2
+ REMOTE_OPERATIONS,
3
+ REMOTE_RESOURCES,
4
+ remoteOperationToolName,
5
+ } from "../../../../remote/schema";
6
+ import type {
7
+ RemoteJsonSchema,
8
+ RemoteSideEffectLevel,
9
+ RemoteWriteTier,
10
+ } from "../../../../remote/types";
11
+ import {
12
+ operationPolicy,
13
+ patchPolicy,
14
+ policySentence,
15
+ readPolicy,
16
+ tierLabel,
17
+ type TerminalRelayPolicy,
18
+ } from "./policy";
19
+
20
+ /**
21
+ * The tools this app runs for a remote assistant, generated from the remote
22
+ * API schema: every operation is a tool, so a new operation reaches the
23
+ * assistant without anyone listing it. Gloom Cloud advertises these as
24
+ * `terminal.<name>` and adds its own `terminal.devices` and
25
+ * `terminal.call_result`.
26
+ */
27
+ export const TERMINAL_RELAY_PROTOCOL = 1;
28
+
29
+ export type TerminalRelayToolKind =
30
+ | { kind: "operation"; operation: string }
31
+ | { kind: "resource" }
32
+ | { kind: "patch" }
33
+ | { kind: "data" }
34
+ | { kind: "snapshot" }
35
+ | { kind: "pane-content" };
36
+
37
+ export interface TerminalRelayToolDescriptor {
38
+ name: string;
39
+ title: string;
40
+ description: string;
41
+ inputSchema: RemoteJsonSchema;
42
+ tier: RemoteSideEffectLevel;
43
+ writeTier: RemoteWriteTier;
44
+ policy: TerminalRelayPolicy;
45
+ annotations: {
46
+ readOnly: boolean;
47
+ destructive: boolean;
48
+ idempotent: boolean;
49
+ openWorld: boolean;
50
+ };
51
+ }
52
+
53
+ interface TerminalRelayTool extends TerminalRelayToolDescriptor {
54
+ binding: TerminalRelayToolKind;
55
+ }
56
+
57
+ const DESTRUCTIVE_OPERATIONS = new Set(["layout.delete", "pane.close", "layout.closeFloating", "desktop.closeDetachedPane"]);
58
+ const IDEMPOTENT_OPERATIONS = new Set([
59
+ "pane.show",
60
+ "pane.focus",
61
+ "ticker.select",
62
+ "ticker.switchTab",
63
+ "layout.switch",
64
+ "layout.focusRegion",
65
+ "layout.setGrid",
66
+ "app.closeCommandBar",
67
+ ]);
68
+
69
+ function describe(text: string, tier: RemoteSideEffectLevel, policy: TerminalRelayPolicy): string {
70
+ return `${text} Tier: ${tierLabel(tier)}. ${policySentence(policy)}`;
71
+ }
72
+
73
+ function readTool(
74
+ name: string,
75
+ title: string,
76
+ text: string,
77
+ inputSchema: RemoteJsonSchema,
78
+ binding: TerminalRelayToolKind,
79
+ ): TerminalRelayTool {
80
+ const { policy } = readPolicy();
81
+ return {
82
+ name,
83
+ title,
84
+ description: describe(text, "none", policy),
85
+ inputSchema,
86
+ tier: "none",
87
+ writeTier: "read",
88
+ policy,
89
+ annotations: { readOnly: true, destructive: false, idempotent: true, openWorld: false },
90
+ binding,
91
+ };
92
+ }
93
+
94
+ const RESOURCE_LIST = REMOTE_RESOURCES.map(({ uri }) => uri).join(", ");
95
+
96
+ function buildTools(): TerminalRelayTool[] {
97
+ const operations = REMOTE_OPERATIONS.map((operation): TerminalRelayTool => {
98
+ const { policy } = operationPolicy(operation.id);
99
+ const external = operation.sideEffectLevel === "external-side-effect"
100
+ || operation.sideEffectLevel === "external-trade"
101
+ || operation.sideEffectLevel === "network-write";
102
+ return {
103
+ name: remoteOperationToolName(operation.id),
104
+ title: operation.title,
105
+ description: describe(operation.description, operation.sideEffectLevel, policy),
106
+ inputSchema: operation.inputSchema,
107
+ tier: operation.sideEffectLevel,
108
+ writeTier: operation.writeTier,
109
+ policy,
110
+ annotations: {
111
+ readOnly: operation.sideEffectLevel === "none",
112
+ destructive: DESTRUCTIVE_OPERATIONS.has(operation.id) || operation.writeTier === "user-data" || external,
113
+ idempotent: IDEMPOTENT_OPERATIONS.has(operation.id),
114
+ openWorld: external,
115
+ },
116
+ binding: { kind: "operation", operation: operation.id },
117
+ };
118
+ });
119
+ return [
120
+ readTool(
121
+ "snapshot",
122
+ "Terminal: Snapshot",
123
+ "What is on screen: the active layout and the saved layouts, every pane with its placement, the focused pane and its ticker, the command bar and any open form.",
124
+ { type: "object", properties: {}, additionalProperties: false },
125
+ { kind: "snapshot" },
126
+ ),
127
+ readTool(
128
+ "pane_content",
129
+ "Terminal: Pane content",
130
+ "What one pane shows: its settings and runtime state, and the semantic UI nodes drawn in it (table rows, tabs, lists, buttons, chart data). paneId is an instance id from terminal.snapshot.",
131
+ {
132
+ type: "object",
133
+ properties: { paneId: { type: "string", minLength: 1 } },
134
+ required: ["paneId"],
135
+ additionalProperties: false,
136
+ },
137
+ { kind: "pane-content" },
138
+ ),
139
+ readTool(
140
+ "get_resource",
141
+ "Terminal: Get resource",
142
+ `Read one remote API resource. Resources: ${RESOURCE_LIST}. Configuration arrives with credentials removed.`,
143
+ {
144
+ type: "object",
145
+ properties: { resource: { type: "string", minLength: 1, description: "A resource URI, such as app://panes or app://pane-state/{paneId}." } },
146
+ required: ["resource"],
147
+ additionalProperties: false,
148
+ },
149
+ { kind: "resource" },
150
+ ),
151
+ readTool(
152
+ "market_data",
153
+ "Terminal: Market data",
154
+ "Market data through the terminal's own sources: search, quote, financials, secFilings, holders, analystResearch, corporateActions, earningsCalendar.",
155
+ {
156
+ type: "object",
157
+ properties: {
158
+ operation: {
159
+ type: "string",
160
+ enum: ["search", "quote", "financials", "secFilings", "holders", "analystResearch", "corporateActions", "earningsCalendar"],
161
+ },
162
+ query: { type: "string" },
163
+ symbol: { type: "string" },
164
+ exchange: { type: "string" },
165
+ count: { type: "integer", minimum: 1 },
166
+ symbols: { type: "array", items: { type: "string", minLength: 1 } },
167
+ },
168
+ required: ["operation"],
169
+ additionalProperties: false,
170
+ },
171
+ { kind: "data" },
172
+ ),
173
+ {
174
+ name: "patch_resource",
175
+ title: "Terminal: Patch resource",
176
+ description: `Apply a JSON Patch to a mutable resource: app://layout/current, app://pane-state/{paneId}, app://pane-settings/{paneId} or app://config. Pass expectRev from a read so a stale edit fails. Tier: local-write. ${policySentence(patchPolicy("app://layout/current").policy)} Patching app://config is confirmed by the person on each call.`,
177
+ inputSchema: {
178
+ type: "object",
179
+ properties: {
180
+ resource: { type: "string", minLength: 1 },
181
+ patch: {
182
+ type: "array",
183
+ minItems: 1,
184
+ items: {
185
+ type: "object",
186
+ properties: {
187
+ op: { type: "string", enum: ["add", "replace", "remove"] },
188
+ path: { type: "string" },
189
+ value: {},
190
+ },
191
+ required: ["op", "path"],
192
+ additionalProperties: false,
193
+ },
194
+ },
195
+ expectRev: { type: "string" },
196
+ },
197
+ required: ["resource", "patch"],
198
+ additionalProperties: false,
199
+ },
200
+ tier: "local-write",
201
+ writeTier: "ui-write",
202
+ policy: "allow",
203
+ annotations: { readOnly: false, destructive: true, idempotent: false, openWorld: false },
204
+ binding: { kind: "patch" },
205
+ },
206
+ ...operations,
207
+ ];
208
+ }
209
+
210
+ const TOOLS = buildTools();
211
+ const TOOLS_BY_NAME = new Map(TOOLS.map((tool) => [tool.name, tool]));
212
+
213
+ /** What the app announces to Gloom Cloud. */
214
+ export function terminalRelayToolDescriptors(): TerminalRelayToolDescriptor[] {
215
+ return TOOLS.map(({ binding: _binding, ...descriptor }) => descriptor);
216
+ }
217
+
218
+ /** The app's own table: a name it did not generate is never run. */
219
+ export function resolveTerminalRelayTool(name: string): TerminalRelayToolKind | null {
220
+ return TOOLS_BY_NAME.get(name)?.binding ?? null;
221
+ }
222
+
223
+ export function terminalRelayToolTitle(name: string): string {
224
+ return TOOLS_BY_NAME.get(name)?.title ?? name;
225
+ }
226
+
227
+ function resourcePatternMatches(pattern: string, value: string): boolean {
228
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
229
+ return new RegExp(`^${escaped.replace(/\\\{[^}]+\\\}/g, "[^/]+")}$`).test(value);
230
+ }
231
+
232
+ export function isKnownRemoteResource(resource: string): boolean {
233
+ return REMOTE_RESOURCES.some(({ uri }) => resourcePatternMatches(uri, resource));
234
+ }
@@ -0,0 +1,153 @@
1
+ import type { RemoteSideEffectLevel } from "../../../../remote/types";
2
+
3
+ /**
4
+ * What a remote assistant connected through Gloom Cloud may do in this app.
5
+ *
6
+ * - `allow`: runs once the person approved the assistant for this terminal
7
+ * (one prompt per assistant, see grants.ts).
8
+ * - `confirm`: the person confirms every single call in the app, after
9
+ * reading what it will do. A remote assistant reads untrusted text and can
10
+ * be steered by it, so anything that reaches an outside service (a broker
11
+ * order, a transfer, a message, a plugin capability) asks each time.
12
+ * - `local-ui`: `allow` for semantic UI actions that only move, select or
13
+ * type inside the app; `confirm` for every other action (press, submit,
14
+ * activate), since a button can send an order or a message.
15
+ * - `navigation`: `allow` when the command-bar row opens a ticker or a pane;
16
+ * `confirm` for rows that run a command.
17
+ *
18
+ * Every operation of the local remote API has a row, so the policy can be
19
+ * read in one place. An operation added to the schema without a row is
20
+ * still relayed, under `confirm`, never `allow`.
21
+ */
22
+ export type TerminalRelayPolicy = "allow" | "confirm" | "local-ui" | "navigation";
23
+
24
+ export interface TerminalRelayPolicyEntry {
25
+ policy: TerminalRelayPolicy;
26
+ /** Why, in one line, for reviewers. */
27
+ reason: string;
28
+ }
29
+
30
+ const ARRANGE = "Rearranges the person's own layout; layout.undo reverts it.";
31
+ const OPEN = "Opens, focuses or navigates panes in this app only.";
32
+
33
+ export const TERMINAL_RELAY_OPERATION_POLICY: Readonly<Record<string, TerminalRelayPolicyEntry>> = {
34
+ "app.openCommandBar": { policy: "allow", reason: "Opens the command bar; running a row is commandBar.activateResult." },
35
+ "app.closeCommandBar": { policy: "allow", reason: "Closes the command bar." },
36
+ "app.closeDialog": { policy: "allow", reason: "Closes the dialog on top as Esc would; a relay prompt answers Deny when closed." },
37
+ "app.setCommandBarQuery": { policy: "allow", reason: "Types into the command bar." },
38
+ "app.search": { policy: "allow", reason: "Opens command-bar search." },
39
+ "app.switchPanel": { policy: "allow", reason: OPEN },
40
+ "app.notify": { policy: "allow", reason: "Shows a local notification." },
41
+ "commandBar.activateResult": { policy: "navigation", reason: "A row can open a ticker or a pane, or run any command." },
42
+ "pane.show": { policy: "allow", reason: OPEN },
43
+ "pane.focus": { policy: "allow", reason: OPEN },
44
+ "pane.close": { policy: "allow", reason: "Closes a pane; layout.undo brings it back." },
45
+ "pane.createFromTemplate": { policy: "allow", reason: OPEN },
46
+ "view.create": { policy: "allow", reason: "Creates a custom view pane from data the app already reads." },
47
+ "view.update": { policy: "allow", reason: "Replaces a custom view's spec; layout.undo reverts it." },
48
+ "pane.setState": { policy: "allow", reason: "Patches a pane's runtime state." },
49
+ "pane.setSetting": { policy: "allow", reason: "Changes one pane setting." },
50
+ "ticker.navigate": { policy: "allow", reason: OPEN },
51
+ "ticker.pin": { policy: "allow", reason: OPEN },
52
+ "ticker.select": { policy: "allow", reason: OPEN },
53
+ "ticker.switchTab": { policy: "allow", reason: OPEN },
54
+ "layout.switch": { policy: "allow", reason: ARRANGE },
55
+ "layout.new": { policy: "allow", reason: "Adds a blank layout." },
56
+ "layout.rename": { policy: "allow", reason: "Renames a layout; renaming back restores it." },
57
+ "layout.duplicate": { policy: "allow", reason: "Copies a layout." },
58
+ "layout.delete": {
59
+ policy: "confirm",
60
+ reason: "Deleting a layout drops its undo history with it, so it cannot be reverted; asks each time.",
61
+ },
62
+ "layout.undo": { policy: "allow", reason: ARRANGE },
63
+ "layout.redo": { policy: "allow", reason: ARRANGE },
64
+ "layout.gridlock": { policy: "allow", reason: ARRANGE },
65
+ "layout.closeFloating": { policy: "allow", reason: ARRANGE },
66
+ "layout.placePane": { policy: "allow", reason: ARRANGE },
67
+ "layout.focusRegion": { policy: "allow", reason: OPEN },
68
+ "layout.setGrid": { policy: "allow", reason: ARRANGE },
69
+ "desktop.popOutPane": { policy: "allow", reason: "Moves a pane into its own desktop window." },
70
+ "desktop.dockPane": { policy: "allow", reason: "Docks a detached desktop pane back." },
71
+ "desktop.closeDetachedPane": { policy: "allow", reason: "Closes a detached desktop window." },
72
+ "desktop.focusDetachedPane": { policy: "allow", reason: "Focuses a detached desktop window." },
73
+ "capability.invoke": {
74
+ policy: "confirm",
75
+ reason: "Plugin capabilities reach outside services: broker orders, transfers, paid data, messages.",
76
+ },
77
+ "ui.invoke": { policy: "local-ui", reason: "A semantic action can scroll a list or press a Send or Submit button." },
78
+ "ui.invokeMatching": { policy: "local-ui", reason: "Same as ui.invoke, with the node picked by role and label." },
79
+ };
80
+
81
+ /** Reads never change anything; app://config arrives with credentials removed (redact.ts). */
82
+ const TERMINAL_RELAY_READ_POLICY: TerminalRelayPolicyEntry = {
83
+ policy: "allow",
84
+ reason: "Reads what the app shows; credentials are removed from configuration.",
85
+ };
86
+
87
+ /** JSON Patch on a mutable resource. Configuration holds broker connections. */
88
+ export function patchPolicy(resource: string): TerminalRelayPolicyEntry {
89
+ return resource === "app://config"
90
+ ? { policy: "confirm", reason: "Configuration holds broker connections and plugin settings." }
91
+ : { policy: "allow", reason: "Patches layout, pane state or pane settings; layout.undo reverts layout changes." };
92
+ }
93
+
94
+ /** Every read: snapshots, pane content, resources, market data. */
95
+ export function readPolicy(): TerminalRelayPolicyEntry {
96
+ return TERMINAL_RELAY_READ_POLICY;
97
+ }
98
+
99
+ export function operationPolicy(operation: string): TerminalRelayPolicyEntry {
100
+ return TERMINAL_RELAY_OPERATION_POLICY[operation]
101
+ ?? { policy: "confirm", reason: "Not reviewed for remote control yet." };
102
+ }
103
+
104
+ /**
105
+ * UI actions that only scroll, move a cursor, select or type inside the app.
106
+ * Everything else a node exposes (press, submit, activate, toggle, release)
107
+ * may act outside it and asks each time.
108
+ */
109
+ export const LOCAL_UI_ACTIONS: ReadonlySet<string> = new Set([
110
+ "scroll",
111
+ "scrollBy",
112
+ "scrollTo",
113
+ "moveCursor",
114
+ "drag",
115
+ "selectRow",
116
+ "sort",
117
+ "select",
118
+ "setValue",
119
+ "focus",
120
+ "blur",
121
+ "hover",
122
+ "open",
123
+ "close",
124
+ ]);
125
+
126
+ /** Command-bar rows that only open a ticker or a pane. */
127
+ export const NAVIGATION_RESULT_KINDS: ReadonlySet<string> = new Set([
128
+ "ticker",
129
+ "ticker-search",
130
+ "pane",
131
+ "pane-template",
132
+ "route",
133
+ "search",
134
+ "mode",
135
+ ]);
136
+
137
+ /** What the policy carries into a tool's description. */
138
+ export function policySentence(policy: TerminalRelayPolicy): string {
139
+ switch (policy) {
140
+ case "allow":
141
+ return "Runs once the person approved this assistant in Gloom.";
142
+ case "confirm":
143
+ return "The person confirms each call in Gloom; unanswered after 60 s it is denied.";
144
+ case "local-ui":
145
+ return "Scroll, select, setValue and focus run once the assistant is approved; press, submit and other actions are confirmed by the person on each call.";
146
+ case "navigation":
147
+ return "Rows that open a ticker or a pane run once the assistant is approved; rows that run a command are confirmed by the person on each call.";
148
+ }
149
+ }
150
+
151
+ export function tierLabel(level: RemoteSideEffectLevel): string {
152
+ return level === "none" ? "read" : level;
153
+ }