gloomberb 0.16.2 → 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 (527) 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/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -0,0 +1,168 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { Box, Text, useUiHost } from "../../../../ui";
3
+ import { Button } from "../../../../components/ui/button";
4
+ import { DialogFrame } from "../../../../components/ui/frame";
5
+ import { t, tf } from "../../../../i18n";
6
+ import { RemoteUiScope } from "../../../../remote/semantic-tree";
7
+ import { useThemeColors } from "../../../../theme/theme-context";
8
+ import { type DialogApi, type PromptContext, useDialogKeyboard } from "../../../../ui/dialog";
9
+ import { isPlainKey } from "../../../../utils/keyboard";
10
+ import type { AssistantDecision } from "./grants";
11
+ import { CONFIRM_WINDOW_MS, RELAY_PROMPT_SCOPE, type ApprovalAnswer, type ConfirmationAnswer, type RelayCaller, type RelayPrompts } from "./engine";
12
+ import type { CallSummary } from "./summary";
13
+
14
+ const WIDTH = 56;
15
+ /** Keys typed just before a prompt appeared do not answer it. */
16
+ const KEY_GRACE_MS = 600;
17
+
18
+ /** A fixed width in the terminal; on the desktop and the web, up to it, so a phone fits. */
19
+ function usePromptWidth(): { width: number | string; maxWidth?: number } {
20
+ return useUiHost().kind === "opentui" ? { width: WIDTH } : { width: "100%", maxWidth: WIDTH };
21
+ }
22
+
23
+ function useKeyGrace(graceMs: number): () => boolean {
24
+ const openedAt = useRef(Date.now());
25
+ return useCallback(() => Date.now() - openedAt.current >= graceMs, [graceMs]);
26
+ }
27
+
28
+ function useCloseOnAbort(signal: AbortSignal, close: () => void) {
29
+ useEffect(() => {
30
+ if (signal.aborted) {
31
+ close();
32
+ return;
33
+ }
34
+ signal.addEventListener("abort", close, { once: true });
35
+ return () => signal.removeEventListener("abort", close);
36
+ }, [close, signal]);
37
+ }
38
+
39
+ interface PromptProps {
40
+ caller: RelayCaller;
41
+ signal: AbortSignal;
42
+ /** How long after opening keys are ignored; tests pass 0. */
43
+ keyGraceMs?: number;
44
+ }
45
+
46
+ /** First time an assistant asks to drive this terminal. */
47
+ export function AssistantApprovalDialog({
48
+ resolve,
49
+ caller,
50
+ signal,
51
+ keyGraceMs = KEY_GRACE_MS,
52
+ }: PromptContext<AssistantDecision> & PromptProps) {
53
+ const colors = useThemeColors();
54
+ const size = usePromptWidth();
55
+ const keysReady = useKeyGrace(keyGraceMs);
56
+ const deny = useCallback(() => resolve("deny"), [resolve]);
57
+ useCloseOnAbort(signal, deny);
58
+ useDialogKeyboard((event) => {
59
+ event.stopPropagation();
60
+ event.preventDefault();
61
+ if (event.name === "escape" || isPlainKey(event, "n", "d")) {
62
+ deny();
63
+ return;
64
+ }
65
+ if (!keysReady()) return;
66
+ if (isPlainKey(event, "s")) resolve("session");
67
+ else if (isPlainKey(event, "a")) resolve("always");
68
+ }, { allowEditable: true });
69
+
70
+ return (
71
+ <RemoteUiScope scope={RELAY_PROMPT_SCOPE}>
72
+ <DialogFrame title={tf("{name} wants to control this terminal", { name: caller.name })} footer={t("S this session · A always · N or Esc deny")}>
73
+ <Box flexDirection="column" {...size}>
74
+ <Text fg={colors.text} wrapText>
75
+ {t("It connected through Gloom Cloud with Terminal control. It can read what is on screen, open and arrange panes and switch layouts.")}
76
+ </Text>
77
+ <Box height={1} />
78
+ <Text fg={colors.textDim} wrapText>
79
+ {t("Orders, transfers, messages and plugin actions still ask you each time. Revoke it any time in Account Management, Agents.")}
80
+ </Text>
81
+ <Box height={1} />
82
+ <Box flexDirection="row" flexWrap="wrap" gap={1}>
83
+ <Button label={t("Allow for this session")} variant="primary" onPress={() => resolve("session")} />
84
+ <Button label={t("Always allow")} variant="secondary" onPress={() => resolve("always")} />
85
+ <Button label={t("Deny")} variant="secondary" onPress={deny} />
86
+ </Box>
87
+ </Box>
88
+ </DialogFrame>
89
+ </RemoteUiScope>
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Every call that can reach an outside service. There is no "always" here,
95
+ * and only `y` allows: `a` means "always" in the approval prompt.
96
+ */
97
+ export function CallConfirmationDialog({
98
+ resolve,
99
+ caller,
100
+ summary,
101
+ signal,
102
+ keyGraceMs = KEY_GRACE_MS,
103
+ }: PromptContext<ConfirmationAnswer> & PromptProps & { summary: CallSummary }) {
104
+ const colors = useThemeColors();
105
+ const size = usePromptWidth();
106
+ const keysReady = useKeyGrace(keyGraceMs);
107
+ const deny = useCallback(() => resolve("deny"), [resolve]);
108
+ useCloseOnAbort(signal, deny);
109
+ useDialogKeyboard((event) => {
110
+ event.stopPropagation();
111
+ event.preventDefault();
112
+ if (event.name === "escape" || isPlainKey(event, "n", "d")) {
113
+ deny();
114
+ return;
115
+ }
116
+ if (keysReady() && isPlainKey(event, "y")) resolve("allow");
117
+ }, { allowEditable: true });
118
+ const labelWidth = Math.max(0, ...summary.lines.map((line) => line.label.length)) + 2;
119
+
120
+ return (
121
+ <RemoteUiScope scope={RELAY_PROMPT_SCOPE}>
122
+ <DialogFrame
123
+ title={tf("{name} asks to run an action", { name: caller.name })}
124
+ footer={tf("Y allow · N or Esc deny · denied after {seconds} s", { seconds: Math.round(CONFIRM_WINDOW_MS / 1_000) })}
125
+ >
126
+ <Box flexDirection="column" {...size}>
127
+ <Text fg={colors.textBright} wrapText>{summary.title}</Text>
128
+ <Box height={1} />
129
+ {summary.lines.map((line, index) => (
130
+ <Box key={`${line.label}:${index}`} flexDirection="row">
131
+ <Box width={labelWidth} flexShrink={0}>
132
+ <Text fg={colors.textDim}>{line.label}</Text>
133
+ </Box>
134
+ <Text fg={colors.text} wrapText>{line.value}</Text>
135
+ </Box>
136
+ ))}
137
+ <Box height={1} />
138
+ <Box flexDirection="row" flexWrap="wrap" gap={1}>
139
+ <Button label={t("Allow")} variant="primary" onPress={() => resolve("allow")} />
140
+ <Button label={t("Deny")} variant="secondary" onPress={deny} />
141
+ </Box>
142
+ </Box>
143
+ </DialogFrame>
144
+ </RemoteUiScope>
145
+ );
146
+ }
147
+
148
+ /** The engine's prompts, drawn with the app's dialog host. */
149
+ export function dialogRelayPrompts(dialog: DialogApi): RelayPrompts {
150
+ return {
151
+ async askApproval(caller, signal): Promise<ApprovalAnswer> {
152
+ const answer = await dialog.prompt<AssistantDecision>({
153
+ closeOnClickOutside: false,
154
+ closeOnEscape: true,
155
+ content: (context: PromptContext<AssistantDecision>) => <AssistantApprovalDialog {...context} caller={caller} signal={signal} />,
156
+ }).catch(() => undefined);
157
+ return answer ?? "deny";
158
+ },
159
+ async askConfirmation(caller, summary, signal): Promise<ConfirmationAnswer> {
160
+ const answer = await dialog.prompt<ConfirmationAnswer>({
161
+ closeOnClickOutside: false,
162
+ closeOnEscape: true,
163
+ content: (context: PromptContext<ConfirmationAnswer>) => <CallConfirmationDialog {...context} caller={caller} summary={summary} signal={signal} />,
164
+ }).catch(() => undefined);
165
+ return answer ?? "deny";
166
+ },
167
+ };
168
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Configuration holds broker passwords, API secrets and plugin tokens, and
3
+ * pane settings, pane state, layouts and the UI tree are free-form maps that
4
+ * can carry the same (a token a plugin keeps in its settings, a password
5
+ * typed into a form). Everything the relay sends back passes through here,
6
+ * with every such value replaced, so a prompt-injected assistant cannot copy
7
+ * a credential out of the terminal.
8
+ */
9
+ const SECRET_KEY = /(pass(word|phrase)?|secret|token|api[-_]?key|private[-_]?key|credential|cookie)/i;
10
+ /** Fields of a form field or input that hold what was typed. */
11
+ const TYPED_VALUE_KEYS = ["value", "summary", "defaultValue", "initialValue"];
12
+ export const REDACTED = "[redacted]";
13
+
14
+ function isPasswordField(record: Record<string, unknown>): boolean {
15
+ return record.fieldType === "password" || record.inputType === "password" || record.type === "password";
16
+ }
17
+
18
+ function redactSecrets(value: unknown, depth = 0): unknown {
19
+ if (depth > 32 || value === null || typeof value !== "object") return value;
20
+ if (Array.isArray(value)) return value.map((entry) => redactSecrets(entry, depth + 1));
21
+ const record = value as Record<string, unknown>;
22
+ const password = isPasswordField(record);
23
+ const result: Record<string, unknown> = {};
24
+ for (const [key, entry] of Object.entries(record)) {
25
+ const filled = entry !== null && entry !== undefined && entry !== "" && typeof entry !== "boolean";
26
+ if (filled && (SECRET_KEY.test(key) || (password && TYPED_VALUE_KEYS.includes(key)))) {
27
+ result[key] = REDACTED;
28
+ } else {
29
+ result[key] = redactSecrets(entry, depth + 1);
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+
35
+ /** Broker profile settings are stored as free-form maps; every value there is withheld. */
36
+ function redactBrokerInstances(config: Record<string, unknown>): Record<string, unknown> {
37
+ const instances = config.brokerInstances;
38
+ if (!Array.isArray(instances)) return config;
39
+ return {
40
+ ...config,
41
+ brokerInstances: instances.map((instance) => {
42
+ if (!instance || typeof instance !== "object") return instance;
43
+ const record = instance as Record<string, unknown>;
44
+ const settings = record.config && typeof record.config === "object" && !Array.isArray(record.config)
45
+ ? Object.fromEntries(Object.keys(record.config as Record<string, unknown>).map((key) => [key, REDACTED]))
46
+ : record.config;
47
+ return { ...record, config: settings };
48
+ }),
49
+ };
50
+ }
51
+
52
+ /** A config object as a remote assistant may see it. */
53
+ export function redactConfig(config: unknown): unknown {
54
+ const redacted = redactSecrets(config);
55
+ return redacted && typeof redacted === "object" && !Array.isArray(redacted)
56
+ ? redactBrokerInstances(redacted as Record<string, unknown>)
57
+ : redacted;
58
+ }
59
+
60
+ /** Resources that carry the whole configuration, before the general pass. */
61
+ export function redactResource(resource: string, value: unknown): unknown {
62
+ if (resource === "app://config") return redactConfig(value);
63
+ if (resource === "app://snapshot" && value && typeof value === "object") {
64
+ const snapshot = value as Record<string, unknown>;
65
+ return { ...snapshot, config: redactConfig(snapshot.config) };
66
+ }
67
+ return value;
68
+ }
69
+
70
+ /** The last step before any answer leaves the app: every free-form map, at any depth. */
71
+ export function redactRelayResult<T>(result: T): T {
72
+ return redactSecrets(result) as T;
73
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * What the per-call confirmation says: the exact action in plain words, with
3
+ * the arguments that matter (plugin, operation, symbol, side, size), so the
4
+ * person can tell an order from a lookup before allowing it.
5
+ */
6
+ export interface CallSummaryLine {
7
+ label: string;
8
+ value: string;
9
+ }
10
+
11
+ export interface CallSummary {
12
+ title: string;
13
+ lines: CallSummaryLine[];
14
+ }
15
+
16
+ const KEY_ARGUMENTS: Array<{ keys: string[]; label: string }> = [
17
+ { keys: ["symbol", "ticker", "instrument", "contract", "conid"], label: "Symbol" },
18
+ { keys: ["side", "action", "direction"], label: "Side" },
19
+ { keys: ["quantity", "qty", "size", "shares", "units", "contracts"], label: "Size" },
20
+ { keys: ["amount", "notional", "value", "cash"], label: "Amount" },
21
+ { keys: ["orderType", "type", "kind"], label: "Order type" },
22
+ { keys: ["price", "limitPrice", "limit", "stopPrice", "stop"], label: "Price" },
23
+ { keys: ["timeInForce", "tif"], label: "Time in force" },
24
+ { keys: ["account", "accountId", "portfolioId"], label: "Account" },
25
+ { keys: ["to", "recipient", "destination", "channelId", "email"], label: "To" },
26
+ { keys: ["message", "text", "content", "body"], label: "Message" },
27
+ ];
28
+
29
+ const MAX_VALUE = 80;
30
+
31
+ export function shortValue(value: unknown): string {
32
+ const text = typeof value === "string"
33
+ ? value
34
+ : value === undefined
35
+ ? ""
36
+ : JSON.stringify(value) ?? String(value);
37
+ const single = text.replace(/\s+/g, " ").trim();
38
+ return single.length > MAX_VALUE ? `${single.slice(0, MAX_VALUE - 1)}…` : single;
39
+ }
40
+
41
+ /** Lines for the order-like fields of a payload, then a count of the rest. */
42
+ export function keyArgumentLines(payload: unknown): CallSummaryLine[] {
43
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
44
+ return payload === undefined || payload === null ? [] : [{ label: "Input", value: shortValue(payload) }];
45
+ }
46
+ const record = payload as Record<string, unknown>;
47
+ const used = new Set<string>();
48
+ const lines: CallSummaryLine[] = [];
49
+ for (const { keys, label } of KEY_ARGUMENTS) {
50
+ const key = keys.find((candidate) => record[candidate] !== undefined && record[candidate] !== null && record[candidate] !== "");
51
+ if (!key) continue;
52
+ used.add(key);
53
+ lines.push({ label, value: shortValue(record[key]) });
54
+ }
55
+ const rest = Object.keys(record).filter((key) => !used.has(key));
56
+ if (rest.length > 0 && rest.length <= 3) {
57
+ for (const key of rest) lines.push({ label: key, value: shortValue(record[key]) });
58
+ } else if (rest.length > 3) {
59
+ lines.push({ label: "Other fields", value: rest.join(", ") });
60
+ }
61
+ return lines;
62
+ }
@@ -21,6 +21,8 @@ import {
21
21
  monthsFreeYearly,
22
22
  trialDaysOf,
23
23
  } from "../account-management/model";
24
+ import { useUpgradePersonalization } from "./upgrade-personalization";
25
+ import { upgradeTickerList } from "./upgrade-tickers";
24
26
 
25
27
  const CONTENT_WIDTH = 64;
26
28
  /** Border plus padding the terminal dialog host draws around the content. */
@@ -113,6 +115,13 @@ export function proStepCopy(offer: UpgradeOffer | null): ProStepCopy {
113
115
  };
114
116
  }
115
117
 
118
+ /** The Pro step's first feature; with tickers it names the account's own holdings. */
119
+ export function proStepRealtimeTitle(tickers: readonly string[] | null): string {
120
+ return tickers?.length
121
+ ? tf("Real-time quotes for {tickers}", { tickers: upgradeTickerList(tickers) })
122
+ : t("Real-time market data");
123
+ }
124
+
116
125
  /** Ranked like the onboarding Pro step: the data first, then what reads it. */
117
126
  const PRO_FEATURES: Array<{ label: string; value: string }> = [
118
127
  { label: "Real-time data", value: "Free runs 15 min behind on quotes, 12 h on news" },
@@ -121,6 +130,20 @@ const PRO_FEATURES: Array<{ label: string; value: string }> = [
121
130
  { label: "Flow and X", value: "Options flow, the X feed and sentiment" },
122
131
  ];
123
132
 
133
+ /**
134
+ * The feature list, translated. With tickers (the `upgrade_personalized`
135
+ * arm) the real-time row names the account's own tickers; without
136
+ * them it is exactly the generic list.
137
+ */
138
+ export function proFeatureRows(tickers: readonly string[] | null): Array<{ label: string; value: string }> {
139
+ return PRO_FEATURES.map((feature, index) => ({
140
+ label: feature.label,
141
+ value: index === 0 && tickers?.length
142
+ ? tf("{tickers} quotes, not 15 min behind", { tickers: upgradeTickerList(tickers) })
143
+ : t(feature.value),
144
+ }));
145
+ }
146
+
124
147
  /** Pricing and this account's trial and billing state; never rejects, unknowns come back null. */
125
148
  export async function loadUpgradeOffer(): Promise<UpgradeOffer> {
126
149
  const [pricing, account] = await Promise.allSettled([
@@ -150,6 +173,7 @@ function UpgradeDialog({
150
173
  useAppLanguage();
151
174
  const [interval, setBillingInterval] = useState<CloudBillingInterval>(initialInterval);
152
175
  const [offer, setOffer] = useState<UpgradeOffer | null>(null);
176
+ const personalTickers = useUpgradePersonalization("upgrade-sheet", offer !== null && !offer.paymentFailed);
153
177
 
154
178
  useEffect(() => {
155
179
  let live = true;
@@ -204,11 +228,11 @@ function UpgradeDialog({
204
228
  shortcutScope={dialogId}
205
229
  />
206
230
  <Box height={1} />
207
- {PRO_FEATURES.map((feature) => (
231
+ {proFeatureRows(personalTickers).map((feature) => (
208
232
  <KeyValueRow
209
233
  key={feature.label}
210
234
  label={feature.label}
211
- value={t(feature.value)}
235
+ value={feature.value}
212
236
  labelWidth={FEATURE_LABEL_WIDTH}
213
237
  width={width}
214
238
  emphasis={false}
@@ -0,0 +1,57 @@
1
+ import { useEffect, useMemo, useReducer } from "react";
2
+ import { apiClient } from "../../../api-client";
3
+ import { usePlanAccess } from "../../../api-client/plan-access";
4
+ import { exposeUpgradePersonalized, knownUpgradePersonalized } from "../../../api-client/research-activity";
5
+ import { useOptionalAppSelector } from "../../../state/app/context";
6
+ import { useAppVisible } from "../../../state/app/activity";
7
+ import type { TickerFinancials } from "../../../types/financials";
8
+ import type { TickerRecord, Watchlist } from "../../../types/ticker";
9
+ import { selectUpgradeTickers } from "./upgrade-tickers";
10
+
11
+ const NO_TICKERS = new Map<string, TickerRecord>();
12
+ const NO_FINANCIALS = new Map<string, TickerFinancials>();
13
+ const NO_WATCHLISTS: Watchlist[] = [];
14
+
15
+ /** This device's top tickers for an upgrade surface; stable while the selection is. */
16
+ function useUpgradeTickers(): readonly string[] {
17
+ const tickers = useOptionalAppSelector((state) => state.tickers, NO_TICKERS);
18
+ const financials = useOptionalAppSelector((state) => state.financials, NO_FINANCIALS);
19
+ const watchlists = useOptionalAppSelector((state) => state.config.watchlists, NO_WATCHLISTS);
20
+ const key = useMemo(
21
+ () => selectUpgradeTickers(tickers.values(), financials, watchlists).join(","),
22
+ [financials, tickers, watchlists],
23
+ );
24
+ return useMemo(() => (key ? key.split(",") : []), [key]);
25
+ }
26
+
27
+ /**
28
+ * The tickers to name on an upgrade surface, or null for the generic copy.
29
+ * Asks for the `upgrade_personalized` arm once the surface is `shown` to a
30
+ * signed-in free account with at least one usable ticker; everyone else, the
31
+ * control arm, and anyone the API leaves out keep the generic copy. Until the
32
+ * first answer of the session arrives the surface shows the generic copy.
33
+ */
34
+ export function useUpgradePersonalization(placement: string, shown: boolean): readonly string[] | null {
35
+ const tickers = useUpgradeTickers();
36
+ const access = usePlanAccess();
37
+ const visible = useAppVisible();
38
+ const accountId = access.signedIn ? apiClient.getCurrentUser()?.id ?? null : null;
39
+ const eligible = shown && visible && !!accountId && !access.hasProAccess && tickers.length > 0;
40
+ const [, refresh] = useReducer((revision: number) => revision + 1, 0);
41
+
42
+ useEffect(() => {
43
+ if (!eligible || !accountId) return;
44
+ let live = true;
45
+ void exposeUpgradePersonalized(placement).then(() => {
46
+ if (live) refresh();
47
+ });
48
+ return () => {
49
+ live = false;
50
+ };
51
+ }, [accountId, eligible, placement]);
52
+
53
+ if (!eligible) return null;
54
+ // Recheck the current account and privacy flags even after an earlier answer.
55
+ const arm = knownUpgradePersonalized();
56
+ return arm === "personalized" ? tickers : null;
57
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The tickers an upgrade surface may name for the `upgrade_personalized`
3
+ * experiment: the account's own holdings, or with none, the tickers it chose
4
+ * to watch, read from this device's portfolios and watchlists. They stay on
5
+ * the device; nothing here is sent anywhere.
6
+ */
7
+ import { tf } from "../../../i18n";
8
+ import type { TickerFinancials } from "../../../types/financials";
9
+ import type { TickerRecord, Watchlist } from "../../../types/ticker";
10
+ import { isUsListingExchange } from "../../../utils/exchanges";
11
+ import { getPortfolioPositionValue } from "../kelly-sizer/portfolio";
12
+ import { STARTER_SYMBOLS } from "./starter-symbols";
13
+
14
+ const UPGRADE_TICKER_LIMIT = 3;
15
+ /** One to five letters and an optional class letter: BRK.B, never BTC-USD, ES=F, ^GSPC, SHOP.TO or 7203. */
16
+ const PLAIN_TICKER = /^[A-Z]{1,5}(?:\.[A-Z])?$/;
17
+ /** Every candidate is a US listing, so values compare in dollars without a rate. */
18
+ const DOLLARS = new Map([["USD", 1]]);
19
+ /** Stocks and exchange-traded funds; not mutual funds, which have no intraday quote. */
20
+ const LISTED_TYPES = new Set(["", "STK", "EQUITY", "COMMONSTOCK", "ADR", "DEPOSITARYRECEIPT", "ETF", "ETP", "CEF", "CLOSEDEND", "CLOSEDENDFUND"]);
21
+
22
+ /** A stock or fund on a US listing exchange (not OTC), priced in dollars. */
23
+ function isUsListedStockOrFund(ticker: TickerRecord): boolean {
24
+ const contract = ticker.metadata.broker_contracts?.[0];
25
+ const type = (contract?.secType ?? ticker.metadata.assetCategory ?? "").trim().toUpperCase().replace(/[\s_-]/g, "");
26
+ const currency = (contract?.currency ?? ticker.metadata.currency ?? "").trim().toUpperCase();
27
+ return LISTED_TYPES.has(type)
28
+ && (currency === "USD" || currency === "")
29
+ && [contract?.primaryExchange, contract?.exchange, ticker.metadata.exchange].some((exchange) => isUsListingExchange(exchange));
30
+ }
31
+
32
+ /**
33
+ * The symbol when it is a plain US stock or fund someone can get a real-time
34
+ * quote for: not cash, an option, a future, a crypto pair or a foreign listing.
35
+ */
36
+ function plainSymbol(ticker: TickerRecord): string | null {
37
+ const symbol = ticker.metadata.ticker.trim().toUpperCase();
38
+ if (!PLAIN_TICKER.test(symbol) || !isUsListedStockOrFund(ticker)) return null;
39
+ // A contract multiplier means the holding is an option or a future on it.
40
+ if (ticker.metadata.positions.some((position) => (position.multiplier ?? 1) !== 1)) return null;
41
+ return symbol;
42
+ }
43
+
44
+ interface Holding {
45
+ /** The market value is known on this device: a quote it already has, or the broker's own value. */
46
+ priced: boolean;
47
+ value: number;
48
+ }
49
+
50
+ /**
51
+ * Gross market value of the shares held, valued like the portfolio pane from
52
+ * what the device already has; null when none are held. Without a quote or a
53
+ * broker value it falls back to what was paid, and says so with `priced`.
54
+ */
55
+ function heldValue(ticker: TickerRecord, financials: TickerFinancials | undefined): Holding | null {
56
+ const held = ticker.metadata.positions.filter((position) => position.shares !== 0);
57
+ if (held.length === 0) return null;
58
+ const value = getPortfolioPositionValue({
59
+ ticker,
60
+ financials: financials ?? null,
61
+ portfolioId: null,
62
+ baseCurrency: "USD",
63
+ exchangeRates: DOLLARS,
64
+ });
65
+ if (Number.isFinite(value)) return { priced: true, value };
66
+ const cost = held.reduce((total, position) => total + Math.abs(position.shares * (position.avgCost ?? 0)), 0);
67
+ return { priced: false, value: Number.isFinite(cost) ? cost : 0 };
68
+ }
69
+
70
+ /**
71
+ * Up to three tickers, or none for the generic copy:
72
+ * - the biggest holdings across every portfolio, those with a known market
73
+ * value first by that value, then any the device cannot value yet by cost;
74
+ * - with nothing held, the first watchlist tickers in list order (lists in
75
+ * their order, each sorted by ticker as the pane shows it). Team lists and
76
+ * the names the app seeds on first run are skipped: nobody here picked them.
77
+ */
78
+ export function selectUpgradeTickers(
79
+ tickers: Iterable<TickerRecord>,
80
+ financials: ReadonlyMap<string, TickerFinancials>,
81
+ watchlists: readonly Watchlist[],
82
+ ): string[] {
83
+ const records = [...tickers];
84
+ const holdings = new Map<string, Holding>();
85
+ for (const ticker of records) {
86
+ const symbol = plainSymbol(ticker);
87
+ if (!symbol) continue;
88
+ const holding = heldValue(ticker, financials.get(ticker.metadata.ticker));
89
+ if (holding) holdings.set(symbol, holding);
90
+ }
91
+ if (holdings.size > 0) {
92
+ return [...holdings]
93
+ .sort(([leftSymbol, left], [rightSymbol, right]) => (
94
+ Number(right.priced) - Number(left.priced)
95
+ || right.value - left.value
96
+ || leftSymbol.localeCompare(rightSymbol)
97
+ ))
98
+ .slice(0, UPGRADE_TICKER_LIMIT)
99
+ .map(([symbol]) => symbol);
100
+ }
101
+
102
+ const picked: string[] = [];
103
+ for (const watchlist of watchlists) {
104
+ if (watchlist.teamId) continue;
105
+ const members = records
106
+ .filter((ticker) => ticker.metadata.watchlists.includes(watchlist.id))
107
+ .sort((left, right) => left.metadata.ticker.localeCompare(right.metadata.ticker));
108
+ for (const ticker of members) {
109
+ const symbol = plainSymbol(ticker);
110
+ if (!symbol || STARTER_SYMBOLS.has(symbol) || picked.includes(symbol)) continue;
111
+ picked.push(symbol);
112
+ if (picked.length === UPGRADE_TICKER_LIMIT) return picked;
113
+ }
114
+ }
115
+ return picked;
116
+ }
117
+
118
+ /** "NVDA", "NVDA and AAPL", "NVDA, AAPL and MSFT". */
119
+ export function upgradeTickerList(tickers: readonly string[]): string {
120
+ const [first = "", second, third] = tickers;
121
+ if (third) return tf("{first}, {second} and {third}", { first, second: second!, third });
122
+ if (second) return tf("{first} and {second}", { first, second });
123
+ return first;
124
+ }
@@ -277,9 +277,7 @@ export type IssuerFilingsLoader = (ticker: string, force: boolean) => Promise<{
277
277
 
278
278
  export class CommentLettersClient {
279
279
  private async getJson(url: string, signal?: AbortSignal): Promise<unknown> {
280
- const response = await lettersFetch.fetch(url, {
281
- signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(FETCH_TIMEOUT_MS)]) : undefined,
282
- });
280
+ const response = await lettersFetch.fetch(url, { signal });
283
281
  if (!response.ok) throw new Error(`SEC full-text search failed (${response.status})`);
284
282
  return response.json();
285
283
  }
@@ -33,7 +33,7 @@ const LABEL_COLUMNS = new Set(["chart"]);
33
33
  function Evidence({ row, width, height }: Size & { row: AttentionRow }) {
34
34
  const colors = useThemeColors();
35
35
  const desktop = !!useUiCapabilities().nativePaneChrome;
36
- return <ScrollBox width={width} height={desktop ? undefined : height} flexGrow={1} flexBasis={0} contentOptions={{ paddingX: 1 }}>
36
+ return <ScrollBox scrollY width={width} height={desktop ? undefined : height} flexGrow={1} flexBasis={0} contentOptions={{ paddingX: 1 }}>
37
37
  {(row.details ?? Object.entries(row.values).map(([label, value]) => ({ label, value: String(value ?? "--") }))).map((entry) => <KeyValueRow key={entry.label} label={entry.label} value={entry.value} labelWidth={22} />)}
38
38
  {row.url ? <Box paddingY={1}><Text fg={colors.textMuted}>{row.url}</Text></Box> : null}
39
39
  </ScrollBox>;
@@ -1,12 +1,15 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { GuidancePayload, KpiEvidence, KpiGuidance, KpiObservation, KpiPeriod, KpiQueryOptions, KpisPayload } from "../../../api-client/company-kpis";
3
+ import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
4
+ import { parsePublicTickerKey } from "../../../utils/exchanges";
3
5
  import { createPluginCache } from "../../../data/plugin-cache";
4
6
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
7
 
6
8
  export type CompanyDataset = KpisPayload | GuidancePayload;
7
9
  export type CompanyMode = "kpis" | "guidance";
8
10
  export const KPI_UNAVAILABLE = "Company disclosures are not available yet.";
9
- export const companyKpisCache = createPluginCache<CompanyDataset>({ kind: "company-kpis", source: "gloom-cloud", schemaVersion: 1,
11
+ // Version 2: keys name a listing abroad by venue and company, not by the symbol alone.
12
+ export const companyKpisCache = createPluginCache<CompanyDataset>({ kind: "company-kpis", source: "gloom-cloud", schemaVersion: 2,
10
13
  policy: { staleMs: 60 * 60_000, expireMs: 30 * 86_400_000 } });
11
14
  const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
12
15
  const text = (value: unknown): value is string => typeof value === "string";
@@ -75,18 +78,30 @@ export function validateCompanyData<T extends CompanyDataset>(data: T): T {
75
78
  return data;
76
79
  }
77
80
  type CompanyClient = Pick<typeof apiClient, "getCloudCompanyKpis" | "getCloudCompanyGuidance">;
78
- export async function fetchCompanyData(mode: CompanyMode, symbol: string, options: KpiQueryOptions = {}, client: CompanyClient = apiClient) {
81
+ /** The server answers for the symbol it was asked about; for a listing abroad, its bare symbol or its venue key. */
82
+ function answersFor(answered: string, symbol: string, abroad: ListingAbroad | null): boolean {
83
+ if (!abroad) return answered.toUpperCase() === symbol.trim().toUpperCase();
84
+ const parsed = parsePublicTickerKey(answered);
85
+ return parsed.symbol === abroad.symbol && (!parsed.exchange || parsed.exchange === abroad.exchange);
86
+ }
87
+ export async function fetchCompanyData(mode: CompanyMode, symbol: string, options: KpiQueryOptions = {}, client: CompanyClient = apiClient, listing: IssuerListingParams = {}) {
79
88
  try {
80
- const data = validateCompanyData(await (mode === "kpis" ? client.getCloudCompanyKpis(symbol, options) : client.getCloudCompanyGuidance(symbol, options)));
81
- if (("series" in data) !== (mode === "kpis") || data.symbol.toUpperCase() !== symbol.trim().toUpperCase()) throw new Error("Company disclosures do not match the requested company");
89
+ const data = validateCompanyData(await (mode === "kpis" ? client.getCloudCompanyKpis(symbol, options, listing) : client.getCloudCompanyGuidance(symbol, options, listing)));
90
+ if (("series" in data) !== (mode === "kpis") || !answersFor(data.symbol, symbol, listingAbroad(symbol, listing.exchange, listing.name))) throw new Error("Company disclosures do not match the requested company");
82
91
  return data;
83
92
  }
84
93
  catch (error) { throw unavailableOnServer(error, KPI_UNAVAILABLE, [404, 503]); }
85
94
  }
86
95
  export const companyQuery = (options: Record<string, unknown>): KpiQueryOptions => Object.fromEntries(["metric", "basis", "from", "to", "asOf"]
87
96
  .flatMap((key) => typeof options[key] === "string" && options[key] !== "all" ? [[key, options[key]]] : [])) as KpiQueryOptions;
97
+ /** A listing abroad is its venue and company, so it never shares an entry with a US company of the same symbol. */
98
+ function companyIdentity(symbol: string, listing: IssuerListingParams): string {
99
+ const abroad = listingAbroad(symbol, listing.exchange, listing.name);
100
+ return abroad ? JSON.stringify([abroad.symbol, abroad.exchange, abroad.name ?? null]) : symbol.toUpperCase();
101
+ }
88
102
  // Partition paid history by both identity and entitlement, including screenshot snapshots.
89
- const cacheKey = (mode: CompanyMode, symbol: string, access: string) => `${mode}:${symbol.toUpperCase()}:${access}`;
90
- export const cachedCompanyData = (mode: CompanyMode, symbol: string, access: string) => cachedCloudResource(companyKpisCache, cacheKey(mode, symbol, access), validateCompanyData);
91
- export const loadCompanyData = (mode: CompanyMode, symbol: string, access: string, force = false) => loadCloudResource(companyKpisCache,
92
- cacheKey(mode, symbol, access), () => fetchCompanyData(mode, symbol), { force, validate: validateCompanyData });
103
+ const cacheKey = (mode: CompanyMode, symbol: string, access: string, listing: IssuerListingParams) => `${mode}:${companyIdentity(symbol, listing)}:${access}`;
104
+ export const cachedCompanyData = (mode: CompanyMode, symbol: string, access: string, listing: IssuerListingParams = {}) =>
105
+ cachedCloudResource(companyKpisCache, cacheKey(mode, symbol, access, listing), validateCompanyData);
106
+ export const loadCompanyData = (mode: CompanyMode, symbol: string, access: string, force = false, listing: IssuerListingParams = {}) => loadCloudResource(companyKpisCache,
107
+ cacheKey(mode, symbol, access, listing), () => fetchCompanyData(mode, symbol, {}, apiClient, listing), { force, validate: validateCompanyData });
@@ -1,4 +1,5 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
+ import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
2
3
  import { companyQuery, fetchCompanyData, type CompanyMode } from "./client";
3
4
 
4
5
  export function companyHeadless(mode: CompanyMode): HeadlessPaneDefinition<"bundle"> {
@@ -19,7 +20,8 @@ export function companyHeadless(mode: CompanyMode): HeadlessPaneDefinition<"bund
19
20
  describe: (args) => `${mode === "kpis" ? "Company KPIs" : "Company guidance"} | ${args.symbols[0]}`,
20
21
  async load(args, ctx) {
21
22
  const options = companyQuery(args.options);
22
- const data = await fetchCompanyData(mode, args.symbols[0]!, options, ctx.apiClient);
23
+ const symbol = args.symbols[0]!;
24
+ const data = await fetchCompanyData(mode, symbol, options, ctx.apiClient, await resolveHeadlessIssuerListing(ctx, symbol));
23
25
  return { complete: !data.truncated, errors: data.truncated ? [data.access === "preview" ? "Full disclosures and history require Gloom Pro" : "Response limit reached; narrow the metric or date range"] : [],
24
26
  sections: "series" in data ? [
25
27
  { title: "Latest KPIs", rows: data.series.map((series) => ({ ...series.latest })) },