gloomberb 0.16.4 → 0.16.5

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 (554) hide show
  1. package/README.md +14 -91
  2. package/package.json +4 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/data.ts +24 -2
  15. package/src/api-client/earnings.ts +6 -0
  16. package/src/api-client/futures-curve.ts +56 -0
  17. package/src/api-client/index.ts +4 -1
  18. package/src/api-client/market-discovery.ts +10 -1
  19. package/src/api-client/paths.ts +7 -0
  20. package/src/api-client/research-activity.ts +15 -10
  21. package/src/api-client/types.ts +17 -1
  22. package/src/api-client/yield-curves.ts +8 -0
  23. package/src/app/global-shortcuts.ts +15 -1
  24. package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
  25. package/src/architecture/core-builtin-allowlist.json +4 -13
  26. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  27. package/src/capabilities/chart-series.ts +11 -1
  28. package/src/cli/commands/crypto-hints.ts +7 -0
  29. package/src/cli/commands/fred-series.ts +61 -0
  30. package/src/cli/commands/market.ts +407 -104
  31. package/src/cli/commands/news-hints.ts +62 -0
  32. package/src/cli/commands/news-search.ts +53 -0
  33. package/src/cli/commands/options-leaps.ts +5 -2
  34. package/src/cli/commands/overview.ts +93 -20
  35. package/src/cli/commands/remote.ts +2 -1
  36. package/src/cli/commands/search.ts +82 -9
  37. package/src/cli/commands/system.ts +55 -8
  38. package/src/cli/commands/ticker.ts +88 -11
  39. package/src/cli/context.ts +45 -14
  40. package/src/cli/desktop-pane-shot.ts +59 -7
  41. package/src/cli/earnings-calendar.ts +71 -0
  42. package/src/cli/freshness.ts +18 -7
  43. package/src/cli/glossary.ts +429 -0
  44. package/src/cli/history-rows.ts +48 -7
  45. package/src/cli/index-roots.ts +7 -0
  46. package/src/cli/index.ts +21 -7
  47. package/src/cli/known-gaps.ts +27 -0
  48. package/src/cli/listing-arg.ts +417 -56
  49. package/src/cli/not-a-ticker.ts +12 -5
  50. package/src/cli/pane-functions/access-gate.ts +5 -0
  51. package/src/cli/pane-functions/capabilities.ts +3 -2
  52. package/src/cli/pane-functions/catalog.ts +35 -5
  53. package/src/cli/pane-functions/data.ts +12 -8
  54. package/src/cli/pane-functions/discovery.ts +1 -0
  55. package/src/cli/pane-functions/dom.ts +20 -6
  56. package/src/cli/pane-functions/freshness.ts +363 -43
  57. package/src/cli/pane-functions/function-help.ts +12 -5
  58. package/src/cli/pane-functions/headless.ts +45 -17
  59. package/src/cli/pane-functions/index.ts +63 -10
  60. package/src/cli/pane-functions/market-session.ts +22 -8
  61. package/src/cli/pane-functions/options.ts +63 -22
  62. package/src/cli/pane-functions/report-notices.ts +25 -7
  63. package/src/cli/pane-functions/report.ts +15 -8
  64. package/src/cli/pane-functions/resolver.ts +49 -12
  65. package/src/cli/pane-functions/screenshot.ts +110 -23
  66. package/src/cli/report-tables.ts +3 -0
  67. package/src/cli/result.ts +4 -1
  68. package/src/cli/staged-tickers.ts +52 -0
  69. package/src/cli/symbol-args.ts +9 -0
  70. package/src/cli/symbol-list.ts +172 -0
  71. package/src/cli/ticker-resolution.ts +223 -30
  72. package/src/cli/types.ts +5 -0
  73. package/src/components/chart/composite/axis-overlays.tsx +6 -2
  74. package/src/components/chart/composite/composite-chart.tsx +40 -6
  75. package/src/components/chart/composite/legend.tsx +49 -2
  76. package/src/components/chart/composite/panel-surface.tsx +44 -17
  77. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  78. package/src/components/command-bar/panel/index.tsx +11 -3
  79. package/src/components/command-bar/panel/runtime.ts +3 -0
  80. package/src/components/command-bar/panel/types.ts +2 -0
  81. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  82. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  83. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  84. package/src/components/command-bar/workflow/tickers.ts +23 -4
  85. package/src/components/layout/detached-pane-shell.tsx +9 -0
  86. package/src/components/layout/floating-pane.tsx +3 -0
  87. package/src/components/layout/header.tsx +20 -5
  88. package/src/components/layout/pane/content.tsx +7 -13
  89. package/src/components/layout/pane/footer/index.tsx +6 -2
  90. package/src/components/layout/pane/footer/model.ts +35 -1
  91. package/src/components/layout/pane/header.tsx +24 -1
  92. package/src/components/layout/pane/index.tsx +12 -2
  93. package/src/components/layout/pane/sidebar.tsx +78 -10
  94. package/src/components/layout/shell/active-drag.ts +21 -5
  95. package/src/components/layout/shell/drag/index.ts +39 -9
  96. package/src/components/layout/shell/drag/live.ts +61 -51
  97. package/src/components/layout/shell/drag/runtime.ts +54 -12
  98. package/src/components/layout/shell/index.tsx +25 -7
  99. package/src/components/layout/shell/menu.ts +5 -0
  100. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  101. package/src/components/layout/shell/pane/layers.tsx +192 -45
  102. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  103. package/src/components/markdown-text.tsx +62 -20
  104. package/src/components/metric-treemap/heat-surface.tsx +10 -1
  105. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  106. package/src/components/onboarding/account-step/index.tsx +1 -1
  107. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  108. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  109. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  110. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  111. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  112. package/src/components/onboarding/wizard-account.ts +2 -2
  113. package/src/components/pane-template-wizard.tsx +9 -1
  114. package/src/components/price-selector-dialog.tsx +11 -2
  115. package/src/components/sign-in-gate.tsx +3 -3
  116. package/src/components/ticker/list-table-view.tsx +24 -1
  117. package/src/components/ui/action-row.tsx +2 -1
  118. package/src/components/ui/button.tsx +7 -1
  119. package/src/components/ui/choice-dialog.tsx +6 -4
  120. package/src/components/ui/confirm-dialog.tsx +28 -18
  121. package/src/components/ui/data-table/index.tsx +17 -6
  122. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  123. package/src/components/ui/data-table/types.ts +12 -0
  124. package/src/components/ui/dialog-actions.tsx +64 -0
  125. package/src/components/ui/display.tsx +17 -3
  126. package/src/components/ui/frame.tsx +35 -2
  127. package/src/components/ui/icon.tsx +2 -0
  128. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  129. package/src/data/cached-query.ts +2 -0
  130. package/src/data/config/store/normalize.ts +7 -2
  131. package/src/data/ticker-repository.ts +5 -0
  132. package/src/data/ticker-store.ts +15 -0
  133. package/src/docs/content.ts +215 -0
  134. package/src/docs/load.ts +139 -0
  135. package/src/docs/requests.ts +39 -0
  136. package/src/docs/snapshot.chunk.ts +8 -0
  137. package/src/docs/snapshot.json +7416 -0
  138. package/src/docs/topics.ts +216 -0
  139. package/src/i18n/es.ts +61 -4
  140. package/src/i18n/ja.ts +61 -4
  141. package/src/i18n/ko.ts +61 -4
  142. package/src/i18n/zh-cn.ts +61 -4
  143. package/src/i18n/zh-tw.ts +61 -4
  144. package/src/layout/desks.ts +1 -1
  145. package/src/layout/pane-manager/dock-tree.ts +18 -0
  146. package/src/layout/pane-manager/index.ts +1 -0
  147. package/src/market-data/coordinator/chart.ts +10 -4
  148. package/src/market-data/coordinator/index.ts +6 -3
  149. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  150. package/src/market-data/futures-contract.ts +151 -0
  151. package/src/market-data/hooks.tsx +10 -2
  152. package/src/market-data/market/format.ts +39 -21
  153. package/src/market-data/market/freshness.ts +81 -18
  154. package/src/market-data/market/status.ts +19 -23
  155. package/src/market-data/published-apac-sessions.ts +23 -1
  156. package/src/market-data/quotes/contributions.ts +3 -0
  157. package/src/market-data/quotes/metadata.ts +7 -24
  158. package/src/market-data/quotes/resolution.ts +2 -0
  159. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  160. package/src/market-data/result-types.ts +2 -0
  161. package/src/plugins/builtin/account-management/model.ts +1 -56
  162. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  164. package/src/plugins/builtin/alerts/cli.ts +313 -47
  165. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  166. package/src/plugins/builtin/alerts/events.ts +23 -2
  167. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  168. package/src/plugins/builtin/alerts/history.ts +2 -0
  169. package/src/plugins/builtin/alerts/index.tsx +41 -9
  170. package/src/plugins/builtin/alerts/pane.tsx +8 -0
  171. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  172. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  173. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  174. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  175. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  176. package/src/plugins/builtin/alerts/targets.ts +92 -0
  177. package/src/plugins/builtin/analytics/index.tsx +4 -2
  178. package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
  179. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  180. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  181. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  182. package/src/plugins/builtin/attention/index.tsx +2 -0
  183. package/src/plugins/builtin/attention/pane.tsx +3 -1
  184. package/src/plugins/builtin/attention/series.ts +26 -0
  185. package/src/plugins/builtin/awards/index.tsx +2 -0
  186. package/src/plugins/builtin/awards/pane.tsx +3 -1
  187. package/src/plugins/builtin/awards/series.ts +29 -0
  188. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  189. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  190. package/src/plugins/builtin/cds/client.ts +70 -13
  191. package/src/plugins/builtin/cds/headless.ts +116 -19
  192. package/src/plugins/builtin/cds/index.tsx +3 -1
  193. package/src/plugins/builtin/cds/model.ts +193 -31
  194. package/src/plugins/builtin/cds/pane.tsx +56 -14
  195. package/src/plugins/builtin/cds/series.ts +19 -0
  196. package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
  197. package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
  198. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
  199. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  200. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  201. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  202. package/src/plugins/builtin/chart-composer/headless.ts +8 -3
  203. package/src/plugins/builtin/chart-composer/index.tsx +9 -2
  204. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  205. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  206. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  207. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  208. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  209. package/src/plugins/builtin/chat/controller/index.ts +2 -0
  210. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  211. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  212. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  213. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  214. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  215. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  216. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  217. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  218. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  219. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  220. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  221. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  222. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  223. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  224. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  225. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  226. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  227. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  228. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  229. package/src/plugins/builtin/company-attention/headless.ts +19 -5
  230. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  231. package/src/plugins/builtin/company-attention/model.ts +26 -7
  232. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  233. package/src/plugins/builtin/company-attention/series.ts +52 -0
  234. package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
  235. package/src/plugins/builtin/composite-plugins.ts +19 -13
  236. package/src/plugins/builtin/cot/index.tsx +3 -1
  237. package/src/plugins/builtin/cot/model.ts +40 -11
  238. package/src/plugins/builtin/cot/series.ts +27 -0
  239. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  240. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  241. package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
  242. package/src/plugins/builtin/credit-conditions/series.ts +26 -0
  243. package/src/plugins/builtin/credit-documents/headless.ts +31 -1
  244. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  245. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  246. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  247. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  248. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  249. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  250. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  251. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  252. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  253. package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
  254. package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
  255. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  256. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  257. package/src/plugins/builtin/docs/index.tsx +68 -0
  258. package/src/plugins/builtin/docs/pane.tsx +426 -0
  259. package/src/plugins/builtin/docs/targets.ts +72 -0
  260. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  261. package/src/plugins/builtin/doe/index.tsx +2 -0
  262. package/src/plugins/builtin/doe/series.ts +62 -0
  263. package/src/plugins/builtin/earnings/client.ts +3 -2
  264. package/src/plugins/builtin/earnings/format.ts +1 -5
  265. package/src/plugins/builtin/earnings/headless.ts +58 -6
  266. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  267. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  268. package/src/plugins/builtin/earnings/history.tsx +7 -3
  269. package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
  270. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  271. package/src/plugins/builtin/econ/index.tsx +2 -0
  272. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  273. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  274. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  276. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  277. package/src/plugins/builtin/exposure/pane.tsx +3 -1
  278. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  279. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  280. package/src/plugins/builtin/futures/index.tsx +43 -3
  281. package/src/plugins/builtin/futures/model.ts +1 -1
  282. package/src/plugins/builtin/futures/returns.ts +1 -1
  283. package/src/plugins/builtin/futures/series.ts +13 -0
  284. package/src/plugins/builtin/futures/table.tsx +1 -1
  285. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  286. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  287. package/src/plugins/builtin/futures-curve/headless.ts +70 -9
  288. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  289. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  290. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  291. package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
  292. package/src/plugins/builtin/gpu/board.tsx +13 -10
  293. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  294. package/src/plugins/builtin/gpu/client.ts +19 -1
  295. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  296. package/src/plugins/builtin/gpu/headless.ts +56 -25
  297. package/src/plugins/builtin/gpu/history.tsx +3 -3
  298. package/src/plugins/builtin/gpu/index.tsx +2 -0
  299. package/src/plugins/builtin/gpu/model.ts +101 -16
  300. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  301. package/src/plugins/builtin/gpu/series.ts +38 -0
  302. package/src/plugins/builtin/help/function-card.tsx +25 -6
  303. package/src/plugins/builtin/help/index.tsx +17 -8
  304. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  305. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  306. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  307. package/src/plugins/builtin/holders/client.ts +22 -3
  308. package/src/plugins/builtin/holders/format.ts +18 -3
  309. package/src/plugins/builtin/holders/headless.ts +36 -133
  310. package/src/plugins/builtin/holders/pane.tsx +11 -4
  311. package/src/plugins/builtin/holders/report-header.ts +40 -5
  312. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  313. package/src/plugins/builtin/insider/client.ts +35 -20
  314. package/src/plugins/builtin/insider/headless.ts +41 -11
  315. package/src/plugins/builtin/insider/index.tsx +26 -9
  316. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  317. package/src/plugins/builtin/insider/issuer.ts +49 -0
  318. package/src/plugins/builtin/insider/model.ts +3 -0
  319. package/src/plugins/builtin/iv-history/client.ts +14 -4
  320. package/src/plugins/builtin/iv-history/headless.ts +7 -4
  321. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  322. package/src/plugins/builtin/iv-history/model.ts +111 -7
  323. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  324. package/src/plugins/builtin/iv-history/series.ts +29 -0
  325. package/src/plugins/builtin/jobs/headless.ts +28 -3
  326. package/src/plugins/builtin/jobs/index.tsx +3 -1
  327. package/src/plugins/builtin/jobs/series.ts +19 -0
  328. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  329. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  330. package/src/plugins/builtin/macro-day/client.ts +44 -3
  331. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  332. package/src/plugins/builtin/macro-day/model.ts +56 -10
  333. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  334. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  335. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  336. package/src/plugins/builtin/market-movers/client.ts +7 -5
  337. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  338. package/src/plugins/builtin/market-movers/model.ts +30 -0
  339. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  340. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  341. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  342. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  343. package/src/plugins/builtin/members/headless.ts +7 -2
  344. package/src/plugins/builtin/members/model.ts +31 -3
  345. package/src/plugins/builtin/members/pane.tsx +3 -6
  346. package/src/plugins/builtin/mna/headless.ts +15 -3
  347. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  348. package/src/plugins/builtin/money-markets/series.ts +41 -0
  349. package/src/plugins/builtin/notes/cli.ts +12 -7
  350. package/src/plugins/builtin/options/view.tsx +1 -1
  351. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  352. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  353. package/src/plugins/builtin/options-positioning/headless.ts +1 -0
  354. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  355. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  356. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  357. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  358. package/src/plugins/builtin/perps/client.ts +40 -6
  359. package/src/plugins/builtin/perps/equity-row.tsx +33 -11
  360. package/src/plugins/builtin/perps/freshness.ts +66 -0
  361. package/src/plugins/builtin/perps/headless.ts +45 -30
  362. package/src/plugins/builtin/perps/index.tsx +18 -1
  363. package/src/plugins/builtin/perps/model.ts +20 -5
  364. package/src/plugins/builtin/perps/pane.tsx +76 -20
  365. package/src/plugins/builtin/perps/series.ts +76 -0
  366. package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
  367. package/src/plugins/builtin/plugin-module.ts +3 -0
  368. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  369. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
  370. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  371. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
  372. package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
  373. package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
  374. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
  375. package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
  376. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  377. package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
  378. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  379. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  380. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
  381. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
  382. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  383. package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
  384. package/src/plugins/builtin/power/client.ts +16 -0
  385. package/src/plugins/builtin/power/evidence.ts +6 -2
  386. package/src/plugins/builtin/power/headless.ts +71 -7
  387. package/src/plugins/builtin/power/index.tsx +4 -1
  388. package/src/plugins/builtin/power/model.ts +72 -1
  389. package/src/plugins/builtin/power/pane.tsx +32 -9
  390. package/src/plugins/builtin/rate-path/headless.ts +11 -3
  391. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  392. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  393. package/src/plugins/builtin/research/event-model.ts +68 -24
  394. package/src/plugins/builtin/research/events-headless.ts +2 -1
  395. package/src/plugins/builtin/research/headless.ts +2 -1
  396. package/src/plugins/builtin/seasonality/client.ts +15 -7
  397. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  398. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  399. package/src/plugins/builtin/seasonality/model.ts +119 -0
  400. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  401. package/src/plugins/builtin/series-inventory.ts +91 -0
  402. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  403. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  404. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  405. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  406. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  407. package/src/plugins/builtin/shared/open-description.ts +29 -0
  408. package/src/plugins/builtin/shared/report-freshness.ts +9 -0
  409. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  410. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  411. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  412. package/src/plugins/builtin/short-interest/series.ts +36 -0
  413. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  414. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  415. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  416. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  417. package/src/plugins/builtin/short-volume/series.ts +23 -0
  418. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  419. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  420. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  421. package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
  422. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  423. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  424. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  425. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  426. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  427. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  428. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  429. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  430. package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
  431. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  432. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  433. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  434. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  435. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
  436. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  437. package/src/plugins/builtin/time-sales/headless.ts +1 -0
  438. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  439. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  440. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  441. package/src/plugins/builtin/vol-surface/headless.ts +1 -0
  442. package/src/plugins/builtin/volatility/index.tsx +2 -0
  443. package/src/plugins/builtin/volatility/series.ts +84 -0
  444. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  445. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  446. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  447. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
  448. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  449. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  450. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  451. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  452. package/src/plugins/builtin/yield-curve/index.tsx +3 -1
  453. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  454. package/src/plugins/catalog-browser.ts +2 -1
  455. package/src/plugins/registry/index.ts +89 -4
  456. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  457. package/src/portfolio/position-value.ts +40 -0
  458. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  459. package/src/public/time-series.ts +17 -0
  460. package/src/react/auto-refresh.ts +13 -3
  461. package/src/remote/app-host.tsx +20 -4
  462. package/src/remote/confirm-change.tsx +85 -0
  463. package/src/remote/controller-utils.ts +8 -1
  464. package/src/remote/controller.ts +123 -6
  465. package/src/remote/pane-screenshot.ts +47 -0
  466. package/src/remote/schema.ts +62 -0
  467. package/src/remote/types.ts +30 -0
  468. package/src/remote/watchlist.ts +306 -0
  469. package/src/renderers/browser/main.tsx +3 -2
  470. package/src/renderers/cloudflare/worker.ts +1 -1
  471. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  472. package/src/renderers/dom/data-table/index.tsx +33 -6
  473. package/src/renderers/dom/data-table/row.tsx +27 -1
  474. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  475. package/src/renderers/dom/desktop/controls.tsx +30 -23
  476. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  477. package/src/renderers/dom/desktop/icons.tsx +7 -0
  478. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  479. package/src/renderers/dom/host/box.tsx +3 -0
  480. package/src/renderers/dom/host/chart-surface.tsx +6 -2
  481. package/src/renderers/dom/host/live-frame.ts +6 -2
  482. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  483. package/src/renderers/dom/host/size-observer.ts +26 -0
  484. package/src/renderers/dom/host/surface-3d.tsx +7 -2
  485. package/src/renderers/dom/host/text.tsx +1 -1
  486. package/src/renderers/dom/styles.css +61 -0
  487. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
  488. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
  489. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  490. package/src/renderers/electrobun/view/main.tsx +2 -0
  491. package/src/series/capability.ts +75 -0
  492. package/src/series/csv.ts +160 -0
  493. package/src/series/files.ts +107 -0
  494. package/src/series/registry.ts +198 -0
  495. package/src/series/resolve.ts +107 -0
  496. package/src/series/standalone.ts +48 -0
  497. package/src/series/types.ts +145 -0
  498. package/src/series/validate.ts +171 -0
  499. package/src/sources/gloomberb-cloud/index.ts +7 -7
  500. package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
  501. package/src/sources/history-result.ts +1 -0
  502. package/src/sources/listing-symbols.ts +4 -1
  503. package/src/sources/provider-router/batches.ts +4 -1
  504. package/src/sources/provider-router/cache.ts +4 -2
  505. package/src/sources/provider-router/cached-routes.ts +1 -1
  506. package/src/sources/provider-router/financial-routes.ts +1 -0
  507. package/src/sources/provider-router/financials.ts +13 -0
  508. package/src/sources/provider-router/history.ts +43 -25
  509. package/src/sources/provider-router/search.ts +1 -0
  510. package/src/sync/core-contributors.ts +1 -0
  511. package/src/tickers/company-name.ts +75 -0
  512. package/src/tickers/instrument-kind.ts +25 -0
  513. package/src/tickers/open-target.ts +3 -2
  514. package/src/tickers/search/index.ts +45 -7
  515. package/src/tickers/search/ranking.ts +33 -17
  516. package/src/tickers/search/types.ts +7 -1
  517. package/src/tickers/us-listing.ts +34 -0
  518. package/src/time-series/hooks.ts +9 -0
  519. package/src/time-series/resolve.ts +95 -16
  520. package/src/time-series/types.ts +4 -2
  521. package/src/time-series/use-chart-resolution.ts +32 -0
  522. package/src/types/config.ts +6 -0
  523. package/src/types/data-provider.ts +5 -0
  524. package/src/types/financials.ts +52 -1
  525. package/src/types/headless.ts +61 -0
  526. package/src/types/plugin.ts +19 -0
  527. package/src/types/price-history.ts +7 -0
  528. package/src/types/ticker.ts +14 -0
  529. package/src/ui/host.tsx +18 -0
  530. package/src/ui/live-resize.ts +81 -0
  531. package/src/ui/live-stretch.ts +121 -0
  532. package/src/ui/measured-cells.ts +58 -0
  533. package/src/ui/pane-fill.ts +11 -0
  534. package/src/utils/basis-points.ts +17 -0
  535. package/src/utils/cli-output.ts +7 -1
  536. package/src/utils/concurrency.ts +25 -0
  537. package/src/utils/crypto-pair.ts +20 -0
  538. package/src/utils/currency-pair.ts +19 -0
  539. package/src/utils/decimal-comma.ts +19 -0
  540. package/src/utils/dom-screenshot-css.ts +265 -0
  541. package/src/utils/dom-screenshot-rasters.ts +276 -0
  542. package/src/utils/dom-screenshot.ts +437 -52
  543. package/src/utils/exchange-rate-snapshot.ts +6 -2
  544. package/src/utils/exchanges.ts +107 -1
  545. package/src/utils/format.ts +16 -3
  546. package/src/utils/fundamentals.ts +66 -0
  547. package/src/utils/futures-generic.ts +6 -12
  548. package/src/utils/fx-market-hours.ts +15 -5
  549. package/src/utils/price-history.ts +99 -42
  550. package/src/utils/sec-eps-basis.ts +24 -11
  551. package/src/utils/sec.ts +61 -7
  552. package/src/utils/utc-time.ts +51 -4
  553. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  554. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from "react";
1
+ import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
2
2
  import {
3
3
  buildTickerSearchCandidates,
4
4
  searchTickerCandidates,
@@ -6,6 +6,8 @@ import {
6
6
  import { useOptionalAppSelector } from "../../../state/app/context";
7
7
  import type { TickerRecord } from "../../../types/ticker";
8
8
  import { searchChartSeriesCapabilities } from "../../../capabilities";
9
+ import { SERIES_CATALOG_CAPABILITY_ID } from "../../../series/capability";
10
+ import type { SeriesCatalogEntry } from "../../../series/types";
9
11
  import { getSharedRegistry } from "../../registry";
10
12
  import {
11
13
  analyzeSeriesSearchQuery,
@@ -17,6 +19,9 @@ import {
17
19
 
18
20
  const EMPTY_TICKERS: ReadonlyMap<string, TickerRecord> = new Map();
19
21
  const EMPTY_RECENT: readonly string[] = [];
22
+ const EMPTY_PLUGINS: readonly string[] = [];
23
+ const EMPTY_ENTRIES: readonly SeriesCatalogEntry[] = [];
24
+ const NO_SUBSCRIPTION = () => () => {};
20
25
  const MAX_SERIES_SUGGESTIONS = 8;
21
26
  const DEFAULT_CATALOG_INSTRUMENTS: readonly SeriesCatalogInstrument[] = [
22
27
  { symbol: "AAPL", exchange: "NASDAQ", name: "Apple Inc." },
@@ -72,6 +77,56 @@ function candidateToInstrument(candidate: {
72
77
  };
73
78
  }
74
79
 
80
+ /**
81
+ * Every registered series of the plugins that are switched on. On the
82
+ * desktop, series only the Bun process has (files in the profile's series
83
+ * folder) are asked for once and listed after the rest; elsewhere, sources
84
+ * that change while the app runs catch up when the catalog opens.
85
+ */
86
+ export function useRegisteredSeries(): readonly SeriesCatalogEntry[] {
87
+ const registry = getSharedRegistry();
88
+ const series = registry?.series;
89
+ const version = useSyncExternalStore(
90
+ series ? series.subscribe.bind(series) : NO_SUBSCRIPTION,
91
+ () => series?.getVersion() ?? 0,
92
+ () => 0,
93
+ );
94
+ const disabledPlugins = useOptionalAppSelector((state) => state.config.disabledPlugins, EMPTY_PLUGINS);
95
+ const [remote, setRemote] = useState<readonly SeriesCatalogEntry[] | null>(null);
96
+
97
+ useEffect(() => {
98
+ if (!registry || !series) return;
99
+ if (!registry.usesRemoteCapabilities) {
100
+ void series.refresh();
101
+ return;
102
+ }
103
+ let cancelled = false;
104
+ void registry.invokeCapability<{ entries: SeriesCatalogEntry[] }>(SERIES_CATALOG_CAPABILITY_ID, "list", {})
105
+ .then((result) => {
106
+ if (!cancelled && Array.isArray(result?.entries)) setRemote(result.entries);
107
+ })
108
+ .catch(() => {
109
+ // The view's own series still list; only the Bun process's extras are missing.
110
+ });
111
+ return () => { cancelled = true; };
112
+ }, [registry, series]);
113
+
114
+ return useMemo(() => {
115
+ const local = series?.catalog() ?? EMPTY_ENTRIES;
116
+ let entries = local;
117
+ if (remote?.length) {
118
+ const known = new Set(local.map((entry) => entry.id));
119
+ const extra = remote.filter((entry) => !known.has(entry.id));
120
+ if (extra.length) entries = [...local, ...extra];
121
+ }
122
+ if (disabledPlugins.length === 0) return entries;
123
+ const disabled = new Set(disabledPlugins);
124
+ return entries.some((entry) => disabled.has(entry.pluginId))
125
+ ? entries.filter((entry) => !disabled.has(entry.pluginId))
126
+ : entries;
127
+ }, [disabledPlugins, remote, series, version]);
128
+ }
129
+
75
130
  export function useCatalogUniverse(query: string): {
76
131
  instruments: SeriesCatalogInstrument[];
77
132
  loading: boolean;
@@ -193,6 +248,7 @@ export function useSeriesCatalogSuggestions({
193
248
  enabled: boolean;
194
249
  }): SeriesCatalogSearchResult {
195
250
  const tickers = useOptionalAppSelector((state) => state.tickers, EMPTY_TICKERS);
251
+ const registered = useRegisteredSeries();
196
252
  const analysis = useMemo(() => analyzeSeriesSearchQuery(query), [query]);
197
253
  const [providerSearch, setProviderSearch] = useState<{
198
254
  query: string;
@@ -298,11 +354,11 @@ export function useSeriesCatalogSuggestions({
298
354
  // and they arrive well before plugin catalogs (which can take seconds), so
299
355
  // late plugin results append below instead of pushing the list around.
300
356
  const suggestions = useMemo(() => {
301
- const builtIn = buildSeriesCatalogSuggestions(query, defaultInstrument, instruments);
357
+ const builtIn = buildSeriesCatalogSuggestions(query, defaultInstrument, instruments, MAX_SERIES_SUGGESTIONS, registered);
302
358
  const provider = providerSearch.query === query.trim() ? providerSearch.suggestions : [];
303
359
  return [...builtIn, ...provider.filter((entry) => !builtIn.some((candidate) => candidate.id === entry.id))]
304
360
  .slice(0, MAX_SERIES_SUGGESTIONS);
305
- }, [defaultInstrument, instruments, providerSearch, query]);
361
+ }, [defaultInstrument, instruments, providerSearch, query, registered]);
306
362
 
307
363
  return {
308
364
  suggestions,
@@ -0,0 +1,84 @@
1
+ import { getCurrentPluginTarget } from "../../current-target";
2
+ import { getSharedRegistry } from "../../registry";
3
+ import { listSeriesFiles, seriesFromFile } from "../../../series/files";
4
+ import type { SeriesDefinition } from "../../../series/types";
5
+ import type { GloomPluginContext } from "../../../types/plugin";
6
+ import type { PluginModule } from "../plugin-module";
7
+
8
+ interface RegisteredFile {
9
+ mtimeMs: number;
10
+ dispose: () => void;
11
+ }
12
+
13
+ /**
14
+ * Keeps the catalog in step with the profile's series folder: a file added,
15
+ * changed or removed shows up the next time the catalog opens.
16
+ */
17
+ function watchUserSeriesFolder(ctx: GloomPluginContext): () => void {
18
+ const registered = new Map<string, RegisteredFile>();
19
+ let running: Promise<void> = Promise.resolve();
20
+
21
+ const sync = async () => {
22
+ const files = await listSeriesFiles(ctx.getConfig().dataDir);
23
+ const present = new Set(files.map((file) => file.name));
24
+ for (const [name, entry] of registered) {
25
+ if (present.has(name)) continue;
26
+ entry.dispose();
27
+ registered.delete(name);
28
+ }
29
+ for (const file of files) {
30
+ const existing = registered.get(file.name);
31
+ if (existing?.mtimeMs === file.mtimeMs) continue;
32
+ existing?.dispose();
33
+ registered.delete(file.name);
34
+ let definitions: SeriesDefinition[];
35
+ try {
36
+ definitions = seriesFromFile(file.name, await file.readText(), file.readText);
37
+ } catch (error) {
38
+ ctx.log.warn("Series file skipped", { file: file.name, error: error instanceof Error ? error.message : String(error) });
39
+ continue;
40
+ }
41
+ const disposers = definitions.flatMap((definition) => {
42
+ try {
43
+ return [ctx.registerSeries(definition)];
44
+ } catch (error) {
45
+ ctx.log.warn("Series skipped", { file: file.name, error: error instanceof Error ? error.message : String(error) });
46
+ return [];
47
+ }
48
+ });
49
+ registered.set(file.name, { mtimeMs: file.mtimeMs, dispose: () => { for (const dispose of disposers) dispose(); } });
50
+ }
51
+ };
52
+ const refresh = () => {
53
+ running = running.then(sync, sync);
54
+ return running;
55
+ };
56
+
57
+ void refresh();
58
+ const removeRefresher = getSharedRegistry()?.series.addRefresher(refresh);
59
+ return () => {
60
+ removeRefresher?.();
61
+ for (const entry of registered.values()) entry.dispose();
62
+ registered.clear();
63
+ };
64
+ }
65
+
66
+ let stopWatching: (() => void) | null = null;
67
+
68
+ /**
69
+ * CSV files in `<data dir>/series/` as catalog series, chartable against
70
+ * anything else. Files are read where the app can reach the disk: the
71
+ * terminal, and the desktop's Bun process, which lists them for its view.
72
+ */
73
+ export const userSeriesModule: PluginModule = {
74
+ setup(ctx) {
75
+ const target = getCurrentPluginTarget();
76
+ if (target !== "cli" && target !== "tui") return;
77
+ stopWatching?.();
78
+ stopWatching = watchUserSeriesFolder(ctx);
79
+ },
80
+ dispose() {
81
+ stopWatching?.();
82
+ stopWatching = null;
83
+ },
84
+ };
@@ -267,9 +267,11 @@ export function NewDmDialog({
267
267
  const startButton = (
268
268
  <Button
269
269
  label={submitting ? t("Starting") : t("Start")}
270
- width={submitting ? 10 : START_BUTTON_WIDTH}
270
+ // The terminal keeps its column fixed; the desktop sizes to the label and its key.
271
+ width={nativePaneChrome ? undefined : submitting ? 10 : START_BUTTON_WIDTH}
271
272
  variant="primary"
272
273
  disabled={!canSubmit}
274
+ desktopShortcut="Enter"
273
275
  stopPropagation
274
276
  onPress={() => { void submit(); }}
275
277
  />
@@ -362,7 +364,13 @@ export function NewDmDialog({
362
364
  pointerEvents: "auto",
363
365
  }}
364
366
  >
365
- <DialogFrame title="New DM" onClose={onCancel} footer={footer} shrinkable>
367
+ <DialogFrame
368
+ title="New DM"
369
+ onClose={onCancel}
370
+ footer={footer}
371
+ desktopFooter={items.length > 0 ? { keys: [{ key: "Tab", label: "add" }] } : null}
372
+ shrinkable
373
+ >
366
374
  <Box flexDirection="column" flexShrink={1} minHeight={0} style={{ gap: 6 }}>
367
375
  {field}
368
376
  <Text fg={selectedUsernames.length > 0 ? colors.textMuted : colors.textDim} style={ELLIPSIS_STYLE}>
@@ -1,3 +1,4 @@
1
+ import { registerAccountSession } from "../../../../account/session";
1
2
  import type {
2
3
  AppNotificationDelivery,
3
4
  AppNotificationRequest,
@@ -757,3 +758,4 @@ export class ChatController {
757
758
  }
758
759
 
759
760
  export const chatController = new ChatController();
761
+ registerAccountSession(chatController);
@@ -4,13 +4,14 @@ import {
4
4
  type ASKGToolResultOutcome,
5
5
  type ASKGTransport,
6
6
  } from "../../../../api-client/askg";
7
- import type { ASKGToolExecutor } from "./executor";
7
+ import { toolCallRemainingMs, type ASKGToolExecutor, type ASKGToolPreview } from "./executor";
8
8
  import {
9
9
  activeTurn,
10
10
  askgReducer,
11
11
  canRateTurn,
12
12
  EMPTY_ASKG_CONVERSATION,
13
13
  isTurnRunning,
14
+ NOT_APPROVED_IN_TIME_NOTE,
14
15
  requiresLocalConfirmation,
15
16
  type ASKGAction,
16
17
  type ASKGConversationState,
@@ -42,6 +43,18 @@ const MAX_HISTORY_TURNS = 8;
42
43
  const DEFAULT_TURN_WALL_CLOCK_MS = 120_000;
43
44
  /** Sessions are renegotiated a little before they expire. */
44
45
  const SESSION_EXPIRY_GRACE_MS = 5_000;
46
+ /**
47
+ * A confirmation prompt closes this long before the server stops waiting for
48
+ * its call, so an approval still leaves time for the write and its result.
49
+ */
50
+ export const CONFIRMATION_CLOSE_MARGIN_MS = 2_000;
51
+
52
+ /**
53
+ * How a confirmation prompt ended. `expired` is this app's own deadline for
54
+ * the call; `abandoned` is the turn giving up on it (the call timed out on the
55
+ * server, the turn ended, was cancelled or lost its stream).
56
+ */
57
+ type ConfirmationAnswer = "approved" | "declined" | "expired" | "abandoned";
45
58
 
46
59
  export interface ASKGControllerManifest {
47
60
  tools: ClientToolManifest[];
@@ -146,9 +159,7 @@ export class ASKGSessionController {
146
159
  private session: ASKGSessionStartResponse | null = null;
147
160
  private sessionRequest: Promise<ASKGSessionStartResponse> | null = null;
148
161
  private manifest: ASKGControllerManifest | null = null;
149
- private readonly confirmations = new Map<string, (approved: boolean) => void>();
150
- /** Calls whose turn ended while they were still waiting to be approved. */
151
- private readonly abandonedCalls = new Set<string>();
162
+ private readonly confirmations = new Map<string, (answer: ConfirmationAnswer) => void>();
152
163
  private readonly toolTasks = new Set<Promise<void>>();
153
164
  private turnAbort: AbortController | null = null;
154
165
  /** Ratings of one answer go out in the order they were made. */
@@ -286,6 +297,8 @@ export class ASKGSessionController {
286
297
  signal: abort.signal,
287
298
  onEvent: (event) => this.handleEvent(event),
288
299
  });
300
+ // The stream is over, so no approval can reach this turn any more.
301
+ this.abandonPendingConfirmations();
289
302
  // Tool calls delivered late in the stream may still be running.
290
303
  await Promise.allSettled([...this.toolTasks]);
291
304
  } catch (error) {
@@ -350,6 +363,13 @@ export class ASKGSessionController {
350
363
  this.abandonPendingConfirmations();
351
364
  return;
352
365
  }
366
+ // The server reports a delegated call it gave up on (timed out, or its
367
+ // script ended) like one it got a result for. A prompt still open for it
368
+ // closes: an approval could no longer reach the turn.
369
+ if (event.type === "tool-executed") {
370
+ this.settleConfirmation(event.toolCallId, "abandoned");
371
+ return;
372
+ }
353
373
  if (event.type !== "tool-call") return;
354
374
  const task = this.runToolCall(event).finally(() => {
355
375
  this.toolTasks.delete(task);
@@ -384,12 +404,43 @@ export class ASKGSessionController {
384
404
 
385
405
  try {
386
406
  let confirmed = false;
407
+ let confirmedChange: string | undefined;
387
408
  if (requiresLocalConfirmation(call)) {
388
- this.dispatch({ type: "tool-awaiting-confirmation", toolCallId: call.toolCallId });
389
- confirmed = await new Promise<boolean>((resolve) => {
390
- this.confirmations.set(call.toolCallId, resolve);
391
- });
392
- if (this.abandonedCalls.delete(call.toolCallId)) return;
409
+ const preview = await this.previewCall(call, signal);
410
+ // The turn ended or moved on while the app looked.
411
+ if (signal?.aborted || this.toolRowStatus(call.toolCallId) !== "pending") return;
412
+ if (preview?.kind === "refusal") {
413
+ await this.deliver(failure(preview.message));
414
+ return;
415
+ }
416
+ if (preview?.kind === "change") confirmedChange = preview.confirmKey;
417
+ // Nothing to approve: the operation re-checks and asks in the app itself if that changed.
418
+ if (preview?.kind === "no-change") {
419
+ confirmed = true;
420
+ } else {
421
+ this.dispatch({
422
+ type: "tool-awaiting-confirmation",
423
+ toolCallId: call.toolCallId,
424
+ ...(preview?.kind === "change" ? { preview: preview.summary } : {}),
425
+ });
426
+ const answer = await this.awaitConfirmation(
427
+ call.toolCallId,
428
+ startedAt + toolCallRemainingMs(call, startedAt),
429
+ );
430
+ if (answer === "abandoned") return;
431
+ if (answer === "expired") {
432
+ await this.deliver({
433
+ turnId: call.turnId,
434
+ toolCallId: call.toolCallId,
435
+ status: "timeout",
436
+ truncated: false,
437
+ elapsedMs: Math.max(0, this.now() - startedAt),
438
+ note: NOT_APPROVED_IN_TIME_NOTE,
439
+ });
440
+ return;
441
+ }
442
+ confirmed = answer === "approved";
443
+ }
393
444
  if (!confirmed) {
394
445
  await this.deliver({
395
446
  turnId: call.turnId,
@@ -413,6 +464,7 @@ export class ASKGSessionController {
413
464
  }
414
465
  const payload = await executor.execute(call, {
415
466
  confirmed: confirmed || !requiresLocalConfirmation(call),
467
+ ...(confirmedChange ? { confirmedChange } : {}),
416
468
  ...(signal ? { signal } : {}),
417
469
  });
418
470
  await this.deliver(payload);
@@ -425,6 +477,28 @@ export class ASKGSessionController {
425
477
  }
426
478
  }
427
479
 
480
+ /**
481
+ * The operation's own account of a call that needs approval, when it has
482
+ * one. Null keeps the server's description.
483
+ */
484
+ private async previewCall(call: ASKGToolCallEvent, signal?: AbortSignal): Promise<ASKGToolPreview | null> {
485
+ try {
486
+ const manifest = this.manifest ?? await this.options.loadManifest();
487
+ this.manifest = manifest;
488
+ return await this.options.getExecutor(manifest)?.preview?.(call, signal) ?? null;
489
+ } catch {
490
+ return null;
491
+ }
492
+ }
493
+
494
+ private toolRowStatus(toolCallId: string): string | undefined {
495
+ for (const turn of this.state.turns) {
496
+ const row = turn.tools.find((entry) => entry.toolCallId === toolCallId);
497
+ if (row) return row.status;
498
+ }
499
+ return undefined;
500
+ }
501
+
428
502
  /** Records a tool result locally, then posts it to the turn loop. */
429
503
  private async deliver(payload: ToolResultPayload): Promise<void> {
430
504
  this.dispatch({ type: "tool-result", payload });
@@ -444,25 +518,43 @@ export class ASKGSessionController {
444
518
  }
445
519
  }
446
520
 
447
- /** Answers a `user-data` or `broker` confirmation prompt. */
448
- resolveConfirmation(toolCallId: string, approved: boolean): void {
521
+ /**
522
+ * Waits for the person's answer to one call. The prompt closes on its own
523
+ * a little before the server stops waiting (`deadlineAt`, by this clock),
524
+ * so an approval always has time to land; an older server that waits less
525
+ * than the call declared says so in the frame, and is honoured the same way.
526
+ */
527
+ private awaitConfirmation(toolCallId: string, deadlineAt: number): Promise<ConfirmationAnswer> {
528
+ return new Promise<ConfirmationAnswer>((resolve) => {
529
+ const timer = setTimeout(
530
+ () => this.settleConfirmation(toolCallId, "expired"),
531
+ Math.max(0, deadlineAt - CONFIRMATION_CLOSE_MARGIN_MS - this.now()),
532
+ );
533
+ this.confirmations.set(toolCallId, (answer) => {
534
+ clearTimeout(timer);
535
+ resolve(answer);
536
+ });
537
+ });
538
+ }
539
+
540
+ /** Ends one prompt. Only the first answer counts; a later one does nothing. */
541
+ private settleConfirmation(toolCallId: string, answer: ConfirmationAnswer): void {
449
542
  const resolve = this.confirmations.get(toolCallId);
450
543
  if (!resolve) return;
451
544
  this.confirmations.delete(toolCallId);
452
- resolve(approved);
545
+ resolve(answer);
453
546
  }
454
547
 
455
- private rejectPendingConfirmations(): void {
456
- for (const [toolCallId, resolve] of [...this.confirmations]) {
457
- this.confirmations.delete(toolCallId);
458
- resolve(false);
459
- }
548
+ /** Answers a `user-data` or `broker` confirmation prompt. */
549
+ resolveConfirmation(toolCallId: string, approved: boolean): void {
550
+ this.settleConfirmation(toolCallId, approved ? "approved" : "declined");
460
551
  }
461
552
 
462
553
  /** Drops confirmations whose answer can no longer reach the turn. */
463
554
  private abandonPendingConfirmations(): void {
464
- for (const toolCallId of this.confirmations.keys()) this.abandonedCalls.add(toolCallId);
465
- this.rejectPendingConfirmations();
555
+ for (const toolCallId of [...this.confirmations.keys()]) {
556
+ this.settleConfirmation(toolCallId, "abandoned");
557
+ }
466
558
  }
467
559
 
468
560
  /**
@@ -32,6 +32,7 @@ import { describeTrimmedLists, fitResultToBytes } from "./result-budget";
32
32
  import {
33
33
  remoteRequestForTool,
34
34
  resolveRemoteToolBinding,
35
+ toolExecutionTimeoutMs,
35
36
  } from "./manifest";
36
37
  import {
37
38
  ASKGUndoManager,
@@ -67,16 +68,38 @@ export interface ASKGToolExecutorDependencies {
67
68
  interface ASKGToolExecutionOptions {
68
69
  signal?: AbortSignal;
69
70
  confirmed?: boolean;
71
+ /** The confirmKey of the preview the person approved, so the app does not ask again. */
72
+ confirmedChange?: string;
70
73
  }
71
74
 
75
+ /**
76
+ * What a call needing approval will change, from the operation's own dry run:
77
+ * the list and listing it resolved rather than the arguments as sent.
78
+ */
79
+ export type ASKGToolPreview =
80
+ | { kind: "change"; summary: string; confirmKey: string }
81
+ /**
82
+ * Nothing would change (already on the list), so there is nothing to
83
+ * approve. The operation checks again when it runs and asks in the app
84
+ * itself if that is no longer so.
85
+ */
86
+ | { kind: "no-change" }
87
+ /** The app refuses the call as asked. */
88
+ | { kind: "refusal"; message: string };
89
+
72
90
  export interface ASKGToolExecutor {
73
91
  execute(
74
92
  call: ASKGToolCallEvent,
75
93
  options?: ASKGToolExecutionOptions,
76
94
  ): Promise<ToolResultPayload>;
95
+ /** Null when the call has no dry run that says more than its arguments. */
96
+ preview?(call: ASKGToolCallEvent, signal?: AbortSignal): Promise<ASKGToolPreview | null>;
77
97
  undo(token: string, signal?: AbortSignal): Promise<AppliedUndo>;
78
98
  }
79
99
 
100
+ /** A dry run answers from local state and at most one search. */
101
+ const PREVIEW_TIMEOUT_MS = 3_000;
102
+
80
103
  interface ExecutionValue {
81
104
  status: ToolResultStatus;
82
105
  result?: unknown;
@@ -214,7 +237,11 @@ function inferRemoteRowCount(value: unknown): number | undefined {
214
237
  return 1;
215
238
  }
216
239
 
217
- function effectiveTimeoutMs(call: ASKGToolCallEvent, now: number): number {
240
+ /**
241
+ * How much longer the server waits for this call, by its frame: its timeout,
242
+ * and never past the time it expires.
243
+ */
244
+ export function toolCallRemainingMs(call: ASKGToolCallEvent, now: number): number {
218
245
  const declared = Math.max(0, call.timeoutMs);
219
246
  const expiresAt = Date.parse(call.expiresAt);
220
247
  if (!Number.isFinite(expiresAt)) return declared;
@@ -346,7 +373,7 @@ export function createASKGToolExecutor(
346
373
  return base({ status: "denied", note: "This tool call requires local confirmation." });
347
374
  }
348
375
 
349
- const timeoutMs = Math.min(manifest.timeoutMs, effectiveTimeoutMs(call, now()));
376
+ const timeoutMs = Math.min(toolExecutionTimeoutMs(manifest), toolCallRemainingMs(call, now()));
350
377
  try {
351
378
  const value = await runWithDeadline(async (signal): Promise<ExecutionValue> => {
352
379
  if (manifest.source === "headless") {
@@ -375,7 +402,10 @@ export function createASKGToolExecutor(
375
402
  }
376
403
  }
377
404
 
378
- const response = await dependencies.remoteHandler(remoteRequestForTool(binding, call.args));
405
+ const response = await dependencies.remoteHandler(remoteRequestForTool(binding, call.args), {
406
+ signal,
407
+ ...(options.confirmed && options.confirmedChange ? { confirmed: options.confirmedChange } : {}),
408
+ });
379
409
  if (!response.ok) {
380
410
  return {
381
411
  status: "error",
@@ -413,8 +443,32 @@ export function createASKGToolExecutor(
413
443
  }
414
444
  };
415
445
 
446
+ const preview = async (call: ASKGToolCallEvent, signal?: AbortSignal): Promise<ASKGToolPreview | null> => {
447
+ const manifest = manifests.get(call.name);
448
+ if (!manifest || manifest.source !== "remote-op" || manifest.confirm !== "always") return null;
449
+ const binding = resolveRemoteToolBinding(call.name);
450
+ if (binding?.kind !== "operation") return null;
451
+ try {
452
+ const response = await runWithDeadline(
453
+ () => dependencies.remoteHandler({ type: "call", operation: binding.operation, input: call.args, dryRun: true, include: [] }),
454
+ PREVIEW_TIMEOUT_MS,
455
+ signal,
456
+ );
457
+ if (!response.ok) return { kind: "refusal", message: response.error.message };
458
+ const data = response.data as { summary?: unknown; confirmKey?: unknown; changes?: unknown } | null;
459
+ if (typeof data?.summary !== "string" || typeof data.confirmKey !== "string") return null;
460
+ return data.changes === false
461
+ ? { kind: "no-change" }
462
+ : { kind: "change", summary: data.summary, confirmKey: data.confirmKey };
463
+ } catch {
464
+ // The person still approves the call as the server described it.
465
+ return null;
466
+ }
467
+ };
468
+
416
469
  return {
417
470
  execute,
471
+ preview,
418
472
  undo: (token, signal) => undoManager.undo(token, signal),
419
473
  };
420
474
  }
@@ -25,6 +25,12 @@ import {
25
25
 
26
26
  const HEADLESS_TOOL_TIMEOUT_MS = 30_000;
27
27
  const REMOTE_TOOL_TIMEOUT_MS = 10_000;
28
+ /**
29
+ * A tool that asks first waits for a person to read the prompt and click, so
30
+ * the server holds the call this long: the minute the terminal relay gives a
31
+ * confirmation. A server from before the window holds it 10 s.
32
+ */
33
+ const CONFIRM_TOOL_TIMEOUT_MS = 60_000;
28
34
  const REMOTE_RESOURCE_TOOL_NAME = "app.get_resource";
29
35
 
30
36
  const TOOL_NAME_REGEX = new RegExp(TOOL_NAME_PATTERN);
@@ -166,11 +172,14 @@ function headlessCandidates(registry: PaneFunctionCatalog): ManifestCandidate[]
166
172
  return candidates;
167
173
  }
168
174
 
175
+ /** Ask Gloom's tool results are text, so an operation that answers with a PNG is not offered there. */
176
+ const ASKG_REMOTE_OPERATIONS = REMOTE_OPERATIONS.filter((operation) => !operation.returnsImage);
177
+
169
178
  function remoteCandidates(): ManifestCandidate[] {
170
- const descriptors = remoteOperationDescriptors();
171
179
  const operationsByName = new Map(
172
- REMOTE_OPERATIONS.map((operation) => [remoteOperationToolName(operation.id), operation]),
180
+ ASKG_REMOTE_OPERATIONS.map((operation) => [remoteOperationToolName(operation.id), operation]),
173
181
  );
182
+ const descriptors = remoteOperationDescriptors().filter((descriptor) => operationsByName.has(descriptor.name));
174
183
  const candidates = descriptors.map((descriptor): ManifestCandidate => ({
175
184
  token: descriptor.name,
176
185
  identity: `operation:${operationsByName.get(descriptor.name)?.id ?? descriptor.name}`,
@@ -181,10 +190,7 @@ function remoteCandidates(): ManifestCandidate[] {
181
190
  description: descriptor.description,
182
191
  writeTier: descriptor.writeTier,
183
192
  inputSchema: descriptor.inputSchema,
184
- confirm: descriptor.writeTier === "user-data" || descriptor.writeTier === "broker"
185
- ? "always"
186
- : "never",
187
- timeoutMs: REMOTE_TOOL_TIMEOUT_MS,
193
+ ...remoteConfirmation(descriptor.writeTier),
188
194
  },
189
195
  }));
190
196
 
@@ -217,6 +223,25 @@ function remoteCandidates(): ManifestCandidate[] {
217
223
  return candidates;
218
224
  }
219
225
 
226
+ /** Account and broker writes ask first, and get the minute that takes. */
227
+ function remoteConfirmation(writeTier: ClientToolManifest["writeTier"]): Pick<ClientToolManifest, "confirm" | "timeoutMs"> {
228
+ return writeTier === "user-data" || writeTier === "broker"
229
+ ? { confirm: "always", timeoutMs: CONFIRM_TOOL_TIMEOUT_MS }
230
+ : { confirm: "never", timeoutMs: REMOTE_TOOL_TIMEOUT_MS };
231
+ }
232
+
233
+ /**
234
+ * How long a call may run once it starts. A tool that asks first declares the
235
+ * window for the person and the write together; the write itself keeps the
236
+ * budget of every app action, counted from the approval rather than from the
237
+ * call. The server's deadline for the call still bounds it.
238
+ */
239
+ export function toolExecutionTimeoutMs(manifest: Pick<ClientToolManifest, "confirm" | "timeoutMs">): number {
240
+ return manifest.confirm === "always"
241
+ ? Math.min(manifest.timeoutMs, REMOTE_TOOL_TIMEOUT_MS)
242
+ : manifest.timeoutMs;
243
+ }
244
+
220
245
  function compareManifests(left: ClientToolManifest, right: ClientToolManifest): number {
221
246
  return compareText(left.name, right.name) || compareText(left.source, right.source);
222
247
  }
@@ -314,7 +339,7 @@ export async function buildASKGClientManifest(
314
339
 
315
340
  export function resolveRemoteToolBinding(name: string): RemoteToolBinding | null {
316
341
  if (name === REMOTE_RESOURCE_TOOL_NAME) return { kind: "resource" };
317
- const operation = REMOTE_OPERATIONS.find((entry) => remoteOperationToolName(entry.id) === name);
342
+ const operation = ASKG_REMOTE_OPERATIONS.find((entry) => remoteOperationToolName(entry.id) === name);
318
343
  return operation ? { kind: "operation", operation: operation.id } : null;
319
344
  }
320
345