gloomberb 0.12.0 → 0.13.2

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 (219) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -2
  3. package/src/api-client/auth.ts +2 -1
  4. package/src/api-client/index.test.ts +34 -0
  5. package/src/api-client/index.ts +10 -2
  6. package/src/api-client/request.ts +4 -1
  7. package/src/api-client/research-activity.ts +61 -0
  8. package/src/api-client/types.ts +2 -1
  9. package/src/cli/commands/plugins.ts +4 -3
  10. package/src/cli/desktop-pane-shot.ts +79 -10
  11. package/src/cli/pane-functions/capabilities.ts +76 -23
  12. package/src/cli/pane-functions/cloud-session.test.ts +54 -0
  13. package/src/cli/pane-functions/cloud-session.ts +38 -0
  14. package/src/cli/pane-functions/dom.test.ts +41 -0
  15. package/src/cli/pane-functions/dom.ts +199 -0
  16. package/src/cli/pane-functions/index.test.ts +13 -2
  17. package/src/cli/pane-functions/index.ts +9 -5
  18. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  19. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  20. package/src/cli/pane-functions/report.test.ts +29 -1
  21. package/src/cli/pane-functions/report.ts +68 -7
  22. package/src/cli/pane-functions/screenshot.ts +195 -42
  23. package/src/cli/restore-plugins.ts +10 -3
  24. package/src/components/chart/composite/composite-chart.test.tsx +68 -1
  25. package/src/components/chart/composite/composite-chart.tsx +87 -21
  26. package/src/components/chart/composite/rasterizer.ts +33 -0
  27. package/src/components/chart/composite/renderers.test.ts +51 -4
  28. package/src/components/chart/composite/scene.test.ts +128 -2
  29. package/src/components/chart/composite/scene.ts +92 -16
  30. package/src/components/chart/composite/text-renderer.ts +13 -0
  31. package/src/components/chart/composite/time-scale.ts +30 -10
  32. package/src/components/chart/composite/types.ts +26 -0
  33. package/src/components/command-bar/assist/runtime.ts +38 -30
  34. package/src/components/command-bar/surface/assist.test.tsx +63 -9
  35. package/src/components/command-bar/surface/test-harness.tsx +23 -4
  36. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  37. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  38. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  39. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  40. package/src/data/config/store/index.test.ts +23 -0
  41. package/src/data/config/store/node.ts +7 -1
  42. package/src/data/config/store/normalize.ts +4 -0
  43. package/src/news/types.ts +1 -0
  44. package/src/plugins/builtin/cds/client.test.ts +20 -0
  45. package/src/plugins/builtin/cds/client.ts +13 -3
  46. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  47. package/src/plugins/builtin/cds/headless.ts +113 -0
  48. package/src/plugins/builtin/cds/index.tsx +4 -0
  49. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  50. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  51. package/src/plugins/builtin/chat/controller/channels.ts +2 -1
  52. package/src/plugins/builtin/chat/controller/index.ts +13 -5
  53. package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
  54. package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
  55. package/src/plugins/builtin/chat/controller/state.ts +1 -0
  56. package/src/plugins/builtin/chat/controller.test.ts +260 -4
  57. package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
  58. package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
  59. package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
  60. package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
  61. package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
  62. package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
  63. package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
  64. package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
  65. package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
  66. package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
  67. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  68. package/src/plugins/builtin/cloud/plugin.tsx +2 -0
  69. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  70. package/src/plugins/builtin/congress-trades/client.ts +11 -0
  71. package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
  72. package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
  73. package/src/plugins/builtin/congress-trades/headless.ts +145 -0
  74. package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
  75. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  76. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  77. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  78. package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
  79. package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
  80. package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
  81. package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
  82. package/src/plugins/builtin/dividend-yield/model.ts +4 -22
  83. package/src/plugins/builtin/dividend-yield/view.ts +17 -0
  84. package/src/plugins/builtin/earnings/headless.test.ts +82 -0
  85. package/src/plugins/builtin/earnings/headless.ts +162 -0
  86. package/src/plugins/builtin/earnings/index.tsx +2 -0
  87. package/src/plugins/builtin/earnings-calls/data.ts +29 -8
  88. package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
  89. package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
  90. package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
  91. package/src/plugins/builtin/earnings-calls/model.ts +238 -0
  92. package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
  93. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
  94. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  95. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  96. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  97. package/src/plugins/builtin/holders/client.ts +29 -0
  98. package/src/plugins/builtin/holders/headless.test.ts +58 -0
  99. package/src/plugins/builtin/holders/headless.ts +162 -0
  100. package/src/plugins/builtin/holders/index.tsx +14 -8
  101. package/src/plugins/builtin/holders/pane.tsx +7 -1
  102. package/src/plugins/builtin/insider/client.ts +50 -0
  103. package/src/plugins/builtin/insider/headless.test.ts +122 -0
  104. package/src/plugins/builtin/insider/headless.ts +117 -0
  105. package/src/plugins/builtin/insider/index.tsx +21 -50
  106. package/src/plugins/builtin/insider/insider-data.ts +1 -1
  107. package/src/plugins/builtin/insider/model.ts +72 -0
  108. package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
  109. package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
  110. package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
  111. package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
  112. package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
  113. package/src/plugins/builtin/market-movers/client.ts +76 -0
  114. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  115. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  116. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  117. package/src/plugins/builtin/news/headless.test.ts +138 -0
  118. package/src/plugins/builtin/news/headless.ts +190 -0
  119. package/src/plugins/builtin/news/index.tsx +13 -9
  120. package/src/plugins/builtin/news/wire/index.tsx +2 -1
  121. package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
  122. package/src/plugins/builtin/research/analyst-headless.ts +172 -0
  123. package/src/plugins/builtin/research/analyst-model.ts +219 -0
  124. package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
  125. package/src/plugins/builtin/research/client.ts +12 -0
  126. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  127. package/src/plugins/builtin/research/event-model.ts +245 -0
  128. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  129. package/src/plugins/builtin/research/events-headless.ts +87 -0
  130. package/src/plugins/builtin/research/headless.test.ts +93 -0
  131. package/src/plugins/builtin/research/headless.ts +161 -0
  132. package/src/plugins/builtin/research/index.tsx +32 -18
  133. package/src/plugins/builtin/research-search/pane.tsx +4 -0
  134. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  135. package/src/plugins/builtin/scanner/headless.ts +125 -0
  136. package/src/plugins/builtin/scanner/index.ts +4 -0
  137. package/src/plugins/builtin/sec/client.ts +16 -0
  138. package/src/plugins/builtin/sec/headless.test.ts +72 -0
  139. package/src/plugins/builtin/sec/headless.ts +69 -0
  140. package/src/plugins/builtin/sec/index.tsx +20 -75
  141. package/src/plugins/builtin/sec/model.ts +89 -0
  142. package/src/plugins/builtin/sectors/client.ts +54 -0
  143. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  144. package/src/plugins/builtin/sectors/headless.ts +80 -0
  145. package/src/plugins/builtin/sectors/index.tsx +6 -51
  146. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  147. package/src/plugins/builtin/short-interest/client.ts +5 -2
  148. package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
  149. package/src/plugins/builtin/short-interest/headless.ts +111 -0
  150. package/src/plugins/builtin/short-interest/index.tsx +15 -9
  151. package/src/plugins/builtin/thirteenf/data.ts +13 -11
  152. package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
  153. package/src/plugins/builtin/thirteenf/headless.ts +286 -0
  154. package/src/plugins/builtin/thirteenf/index.tsx +4 -0
  155. package/src/plugins/builtin/thirteenf/model.ts +13 -0
  156. package/src/plugins/builtin/thirteenf/table.ts +3 -15
  157. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  158. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  159. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  160. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  161. package/src/plugins/builtin/volatility/client.ts +16 -3
  162. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  163. package/src/plugins/builtin/volatility/headless.ts +75 -0
  164. package/src/plugins/builtin/volatility/index.tsx +4 -0
  165. package/src/plugins/builtin/world-indices/client.ts +43 -0
  166. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  167. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  168. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  169. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  170. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  171. package/src/plugins/builtin/yield-curve/index.tsx +6 -8
  172. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  173. package/src/plugins/bundle.test.ts +58 -1
  174. package/src/plugins/bundle.ts +4 -2
  175. package/src/plugins/catalog-backend.test.ts +26 -0
  176. package/src/plugins/catalog-backend.ts +9 -2
  177. package/src/plugins/catalog-browser.test.ts +0 -13
  178. package/src/plugins/catalog-browser.ts +4 -0
  179. package/src/plugins/catalog-manifest.test.ts +15 -0
  180. package/src/plugins/catalog-manifest.ts +12 -5
  181. package/src/plugins/catalog-ui.ts +0 -6
  182. package/src/plugins/host-contract.ts +9 -0
  183. package/src/plugins/host-modules.ts +12 -0
  184. package/src/plugins/loader.ts +63 -15
  185. package/src/plugins/prediction-markets/headless.test.ts +119 -0
  186. package/src/plugins/prediction-markets/headless.ts +255 -0
  187. package/src/plugins/prediction-markets/index.tsx +2 -0
  188. package/src/plugins/seed.ts +3 -0
  189. package/src/public/broker.ts +6 -0
  190. package/src/remote/schema.test.ts +65 -0
  191. package/src/remote/schema.ts +322 -39
  192. package/src/remote/types.ts +49 -2
  193. package/src/renderers/browser/app-services.ts +3 -0
  194. package/src/renderers/browser/config-host.ts +24 -8
  195. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  196. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  197. package/src/renderers/browser/main.tsx +3 -1
  198. package/src/renderers/browser/research-entry.test.ts +8 -0
  199. package/src/renderers/browser/research-entry.ts +7 -0
  200. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  201. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  202. package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
  203. package/src/renderers/electrobun/view/build-assets.ts +0 -2
  204. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
  205. package/src/sources/gloomberb-cloud/news.ts +1 -0
  206. package/src/sync/core-contributors.test.ts +111 -0
  207. package/src/sync/core-contributors.ts +155 -1
  208. package/src/time-series/resolve.test.ts +118 -1
  209. package/src/time-series/resolve.ts +14 -16
  210. package/src/types/config.ts +2 -0
  211. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  212. package/src/plugins/broker-sync/native-loader.ts +0 -10
  213. package/src/plugins/broker-sync/normalize.ts +0 -308
  214. package/src/plugins/broker-sync/public.ts +0 -122
  215. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  216. package/src/plugins/broker-sync/robinhood.ts +0 -114
  217. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  218. package/src/plugins/broker-sync/simplefin.ts +0 -70
  219. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -1,4 +1,12 @@
1
- import type { RemoteControlSchema, RemoteOperationSchema, RemoteResourceSchema } from "./types";
1
+ import type {
2
+ RemoteControlSchema,
3
+ RemoteJsonSchema,
4
+ RemoteOperationDescriptor,
5
+ RemoteOperationSchema,
6
+ RemoteResourceSchema,
7
+ RemoteSideEffectLevel,
8
+ RemoteWriteTier,
9
+ } from "./types";
2
10
 
3
11
  export const REMOTE_RESOURCES: RemoteResourceSchema[] = [
4
12
  { uri: "app://snapshot", description: "Current app snapshot including layout, focus, panes, command bar, and plugin/capability catalogs." },
@@ -19,43 +27,262 @@ export const REMOTE_RESOURCES: RemoteResourceSchema[] = [
19
27
  { uri: "ui://tree", description: "Live semantic UI node tree populated by shared controls and interactive primitives." },
20
28
  ];
21
29
 
30
+ const stringSchema = { type: "string" } satisfies RemoteJsonSchema;
31
+ const requiredStringSchema = { type: "string", minLength: 1 } satisfies RemoteJsonSchema;
32
+ const booleanSchema = { type: "boolean" } satisfies RemoteJsonSchema;
33
+ const indexSchema = { type: "integer", minimum: 0 } satisfies RemoteJsonSchema;
34
+ const openObjectSchema = { type: "object", additionalProperties: true } satisfies RemoteJsonSchema;
35
+ const anySchema = {} satisfies RemoteJsonSchema;
36
+
37
+ function objectSchema(
38
+ properties: Record<string, RemoteJsonSchema> = {},
39
+ required: string[] = [],
40
+ ): RemoteJsonSchema {
41
+ return {
42
+ type: "object",
43
+ properties,
44
+ ...(required.length > 0 ? { required } : {}),
45
+ additionalProperties: false,
46
+ };
47
+ }
48
+
49
+ const paneIdInput = objectSchema({ paneId: requiredStringSchema }, ["paneId"]);
50
+
22
51
  export const REMOTE_OPERATIONS: RemoteOperationSchema[] = [
23
- op("app.openCommandBar", "Open the command bar with an optional query and optional mode.", "{ query?: string, mode?: 'command' | 'ticker' }", "local-write"),
24
- op("app.closeCommandBar", "Close the command bar.", "{}", "local-write"),
25
- op("app.setCommandBarQuery", "Set the command bar query.", "{ query: string }", "local-write"),
26
- op("app.search", "Open command-bar search without requiring UI prefix syntax.", "{ mode: 'command' | 'ticker', query?: string }", "local-write"),
27
- op("app.switchPanel", "Switch the active panel.", "{ panel: 'left' | 'right' }", "local-write"),
28
- op("app.notify", "Show an in-app notification.", "{ body: string, type?: string }", "local-write"),
29
- op("commandBar.activateResult", "Activate a visible command-bar result by zero-based index, node id, item id, or label.", "{ index?: number, nodeId?: string, itemId?: string, label?: string }", "local-write"),
30
- op("pane.show", "Show or focus a pane type.", "{ paneId: string }", "local-write"),
31
- op("pane.focus", "Focus a pane instance or pane type.", "{ paneId: string }", "local-write"),
32
- op("pane.close", "Close a pane instance or pane type.", "{ paneId: string }", "local-write"),
33
- op("pane.createFromTemplate", "Create a pane from a registered template.", "{ templateId: string, options?: object }", "local-write"),
34
- op("pane.setState", "Patch pane runtime state.", "{ paneId: string, patch: object }", "local-write"),
35
- op("pane.setSetting", "Set one pane setting using the pane's registered setting field when available.", "{ paneId: string, key: string, value: any }", "local-write"),
36
- op("ticker.navigate", "Navigate a ticker into the best available ticker research target.", "{ symbol: string, sourcePaneId?: string }", "local-write"),
37
- op("ticker.pin", "Open or focus a fixed ticker research pane.", "{ symbol: string, floating?: boolean, forceNewPane?: boolean, paneType?: string }", "local-write"),
38
- op("ticker.select", "Select a ticker in a target pane.", "{ symbol: string, paneId?: string }", "local-write"),
39
- op("ticker.switchTab", "Switch ticker research tab.", "{ tabId: string, paneId?: string }", "local-write"),
40
- op("layout.switch", "Switch active layout by index.", "{ index: number }", "local-write"),
41
- op("layout.new", "Create a new blank layout.", "{ name: string }", "local-write"),
42
- op("layout.rename", "Rename a layout.", "{ index: number, name: string }", "local-write"),
43
- op("layout.duplicate", "Duplicate a layout.", "{ index: number }", "local-write"),
44
- op("layout.delete", "Delete a layout.", "{ index: number }", "local-write"),
45
- op("layout.undo", "Undo last layout change.", "{}", "local-write"),
46
- op("layout.redo", "Redo last layout change.", "{}", "local-write"),
47
- op("layout.gridlock", "Tidy all panes into a dense layout.", "{}", "local-write"),
48
- op("layout.closeFloating", "Close all floating panes in the active layout.", "{}", "local-write"),
49
- op("layout.placePane", "Move a pane to a layout region.", "{ paneId: string, region: 'left' | 'right' | 'top' | 'bottom' | 'floating', relativeTo?: string }", "local-write"),
50
- op("layout.focusRegion", "Focus a pane by visual layout region.", "{ region: 'left' | 'right' | 'top' | 'bottom' | 'center' }", "local-write"),
51
- op("layout.setGrid", "Dock visible or specified panes into a simple grid.", "{ paneIds?: string[], columns?: number }", "local-write"),
52
- op("desktop.popOutPane", "Pop a pane into a detached desktop window.", "{ paneId: string }", "local-write"),
53
- op("desktop.dockPane", "Dock a detached desktop pane.", "{ paneId: string }", "local-write"),
54
- op("desktop.closeDetachedPane", "Close a detached desktop pane.", "{ paneId: string }", "local-write"),
55
- op("desktop.focusDetachedPane", "Focus a detached desktop pane.", "{ paneId: string }", "local-write"),
56
- op("capability.invoke", "Invoke a registered plugin capability operation.", "{ capabilityId: string, operationId: string, payload?: object }", "local-write"),
57
- op("ui.invoke", "Invoke an action on a live semantic UI node.", "{ nodeId: string, action?: string, input?: any }", "local-write"),
58
- op("ui.invokeMatching", "Invoke an action on the first semantic UI node matching role, label, index, or metadata.", "{ role?: string, label?: string, contains?: string, index?: number, action?: string, input?: any, metadata?: object }", "local-write"),
52
+ op(
53
+ "app.openCommandBar",
54
+ "Open the command bar with an optional query and optional mode.",
55
+ "{ query?: string, mode?: 'command' | 'ticker' | 'default' }",
56
+ "local-write",
57
+ objectSchema({
58
+ query: stringSchema,
59
+ mode: { type: "string", enum: ["command", "ticker", "default"] },
60
+ }),
61
+ ),
62
+ op("app.closeCommandBar", "Close the command bar.", "{}", "local-write", objectSchema()),
63
+ op(
64
+ "app.setCommandBarQuery",
65
+ "Set the command bar query.",
66
+ "{ query: string }",
67
+ "local-write",
68
+ objectSchema({ query: requiredStringSchema }, ["query"]),
69
+ ),
70
+ op(
71
+ "app.search",
72
+ "Open command-bar search without requiring UI prefix syntax.",
73
+ "{ mode?: 'command' | 'ticker' | 'default', query?: string }",
74
+ "local-write",
75
+ objectSchema({
76
+ mode: { type: "string", enum: ["command", "ticker", "default"] },
77
+ query: stringSchema,
78
+ }),
79
+ ),
80
+ op(
81
+ "app.switchPanel",
82
+ "Switch the active panel.",
83
+ "{ panel: 'left' | 'right' }",
84
+ "local-write",
85
+ objectSchema({ panel: { type: "string", enum: ["left", "right"] } }, ["panel"]),
86
+ ),
87
+ op(
88
+ "app.notify",
89
+ "Show an in-app notification.",
90
+ "{ body: string, type?: 'info' | 'success' | 'error' }",
91
+ "local-write",
92
+ objectSchema({
93
+ body: requiredStringSchema,
94
+ type: { type: "string", enum: ["info", "success", "error"] },
95
+ }, ["body"]),
96
+ ),
97
+ op(
98
+ "commandBar.activateResult",
99
+ "Activate a visible command-bar result by zero-based index, node id, item id, or label.",
100
+ "{ index?: number, nodeId?: string, itemId?: string, label?: string }",
101
+ "local-write",
102
+ objectSchema({ index: indexSchema, nodeId: stringSchema, itemId: stringSchema, label: stringSchema }),
103
+ ),
104
+ op("pane.show", "Show or focus a pane type.", "{ paneId: string }", "local-write", paneIdInput),
105
+ op("pane.focus", "Focus a pane instance or pane type.", "{ paneId: string }", "local-write", paneIdInput),
106
+ op("pane.close", "Close a pane instance or pane type.", "{ paneId: string }", "local-write", paneIdInput),
107
+ op(
108
+ "pane.createFromTemplate",
109
+ "Create a pane from a registered template.",
110
+ "{ templateId: string, options?: object }",
111
+ "local-write",
112
+ objectSchema({ templateId: requiredStringSchema, options: openObjectSchema }, ["templateId"]),
113
+ ),
114
+ op(
115
+ "pane.setState",
116
+ "Patch pane runtime state.",
117
+ "{ paneId: string, patch: object }",
118
+ "local-write",
119
+ objectSchema({ paneId: requiredStringSchema, patch: openObjectSchema }, ["paneId", "patch"]),
120
+ ),
121
+ op(
122
+ "pane.setSetting",
123
+ "Set one pane setting using the pane's registered setting field when available.",
124
+ "{ paneId: string, key: string, value: any }",
125
+ "local-write",
126
+ objectSchema({ paneId: requiredStringSchema, key: requiredStringSchema, value: anySchema }, ["paneId", "key", "value"]),
127
+ ),
128
+ op(
129
+ "ticker.navigate",
130
+ "Navigate a ticker into the best available ticker research target.",
131
+ "{ symbol: string, sourcePaneId?: string }",
132
+ "local-write",
133
+ objectSchema({ symbol: requiredStringSchema, sourcePaneId: stringSchema }, ["symbol"]),
134
+ ),
135
+ op(
136
+ "ticker.pin",
137
+ "Open or focus a fixed ticker research pane.",
138
+ "{ symbol: string, floating?: boolean, forceNewPane?: boolean, paneType?: string }",
139
+ "local-write",
140
+ objectSchema({
141
+ symbol: requiredStringSchema,
142
+ floating: booleanSchema,
143
+ forceNewPane: booleanSchema,
144
+ paneType: stringSchema,
145
+ }, ["symbol"]),
146
+ ),
147
+ op(
148
+ "ticker.select",
149
+ "Select a ticker in a target pane.",
150
+ "{ symbol: string, paneId?: string }",
151
+ "local-write",
152
+ objectSchema({ symbol: requiredStringSchema, paneId: stringSchema }, ["symbol"]),
153
+ ),
154
+ op(
155
+ "ticker.switchTab",
156
+ "Switch ticker research tab.",
157
+ "{ tabId: string, paneId?: string }",
158
+ "local-write",
159
+ objectSchema({ tabId: requiredStringSchema, paneId: stringSchema }, ["tabId"]),
160
+ ),
161
+ op(
162
+ "layout.switch",
163
+ "Switch active layout by index.",
164
+ "{ index: number }",
165
+ "local-write",
166
+ objectSchema({ index: indexSchema }, ["index"]),
167
+ ),
168
+ op(
169
+ "layout.new",
170
+ "Create a new blank layout.",
171
+ "{ name: string }",
172
+ "local-write",
173
+ objectSchema({ name: requiredStringSchema }, ["name"]),
174
+ ),
175
+ op(
176
+ "layout.rename",
177
+ "Rename a layout.",
178
+ "{ index: number, name: string }",
179
+ "local-write",
180
+ objectSchema({ index: indexSchema, name: requiredStringSchema }, ["index", "name"]),
181
+ ),
182
+ op(
183
+ "layout.duplicate",
184
+ "Duplicate a layout.",
185
+ "{ index: number }",
186
+ "local-write",
187
+ objectSchema({ index: indexSchema }, ["index"]),
188
+ ),
189
+ op(
190
+ "layout.delete",
191
+ "Delete a layout.",
192
+ "{ index: number }",
193
+ "local-write",
194
+ objectSchema({ index: indexSchema }, ["index"]),
195
+ "user-data",
196
+ ),
197
+ op("layout.undo", "Undo last layout change.", "{}", "local-write", objectSchema()),
198
+ op("layout.redo", "Redo last layout change.", "{}", "local-write", objectSchema()),
199
+ op("layout.gridlock", "Tidy all panes into a dense layout.", "{}", "local-write", objectSchema()),
200
+ op(
201
+ "layout.closeFloating",
202
+ "Close all floating panes in the active layout.",
203
+ "{}",
204
+ "local-write",
205
+ objectSchema(),
206
+ ),
207
+ op(
208
+ "layout.placePane",
209
+ "Move a pane to a layout region.",
210
+ "{ paneId: string, region: 'left' | 'right' | 'top' | 'bottom' | 'floating', relativeTo?: string }",
211
+ "local-write",
212
+ objectSchema({
213
+ paneId: requiredStringSchema,
214
+ region: { type: "string", enum: ["left", "right", "top", "bottom", "floating"] },
215
+ relativeTo: stringSchema,
216
+ }, ["paneId", "region"]),
217
+ ),
218
+ op(
219
+ "layout.focusRegion",
220
+ "Focus a pane by visual layout region.",
221
+ "{ region: 'left' | 'right' | 'top' | 'bottom' | 'center' }",
222
+ "local-write",
223
+ objectSchema({
224
+ region: { type: "string", enum: ["left", "right", "top", "bottom", "center"] },
225
+ }, ["region"]),
226
+ ),
227
+ op(
228
+ "layout.setGrid",
229
+ "Dock visible or specified panes into a simple grid.",
230
+ "{ paneIds?: string[], columns?: number }",
231
+ "local-write",
232
+ objectSchema({
233
+ paneIds: { type: "array", items: requiredStringSchema },
234
+ columns: { type: "integer", minimum: 1 },
235
+ }),
236
+ ),
237
+ op("desktop.popOutPane", "Pop a pane into a detached desktop window.", "{ paneId: string }", "local-write", paneIdInput),
238
+ op("desktop.dockPane", "Dock a detached desktop pane.", "{ paneId: string }", "local-write", paneIdInput),
239
+ op(
240
+ "desktop.closeDetachedPane",
241
+ "Close a detached desktop pane.",
242
+ "{ paneId: string }",
243
+ "local-write",
244
+ paneIdInput,
245
+ ),
246
+ op(
247
+ "desktop.focusDetachedPane",
248
+ "Focus a detached desktop pane.",
249
+ "{ paneId: string }",
250
+ "local-write",
251
+ paneIdInput,
252
+ ),
253
+ op(
254
+ "capability.invoke",
255
+ "Invoke a registered plugin capability operation.",
256
+ "{ capabilityId: string, operationId: string, payload?: object }",
257
+ "external-side-effect",
258
+ objectSchema({
259
+ capabilityId: requiredStringSchema,
260
+ operationId: requiredStringSchema,
261
+ payload: openObjectSchema,
262
+ }, ["capabilityId", "operationId"]),
263
+ ),
264
+ op(
265
+ "ui.invoke",
266
+ "Invoke an action on a live semantic UI node.",
267
+ "{ nodeId: string, action?: string, input?: any }",
268
+ "local-write",
269
+ objectSchema({ nodeId: requiredStringSchema, action: stringSchema, input: anySchema }, ["nodeId"]),
270
+ ),
271
+ op(
272
+ "ui.invokeMatching",
273
+ "Invoke an action on the first semantic UI node matching role, label, index, or metadata.",
274
+ "{ role?: string, label?: string, contains?: string, index?: number, action?: string, input?: any, metadata?: object }",
275
+ "local-write",
276
+ objectSchema({
277
+ role: stringSchema,
278
+ label: stringSchema,
279
+ contains: stringSchema,
280
+ index: indexSchema,
281
+ action: stringSchema,
282
+ input: anySchema,
283
+ metadata: openObjectSchema,
284
+ }),
285
+ ),
59
286
  ];
60
287
 
61
288
  export const REMOTE_AGENT_HELP = {
@@ -134,11 +361,67 @@ export function remoteControlSchema(): RemoteControlSchema {
134
361
  };
135
362
  }
136
363
 
364
+ export function writeTierForSideEffectLevel(
365
+ sideEffectLevel: RemoteSideEffectLevel,
366
+ ): RemoteWriteTier {
367
+ switch (sideEffectLevel) {
368
+ case "none":
369
+ return "read";
370
+ case "local-write":
371
+ return "ui-write";
372
+ case "network-write":
373
+ return "user-data";
374
+ case "external-side-effect":
375
+ case "external-trade":
376
+ return "broker";
377
+ }
378
+ }
379
+
380
+ export function remoteOperationDescriptors(): RemoteOperationDescriptor[] {
381
+ return REMOTE_OPERATIONS.map(({ id, title, description, writeTier, inputSchema }) => ({
382
+ name: remoteOperationToolName(id),
383
+ title,
384
+ description,
385
+ writeTier,
386
+ inputSchema,
387
+ }));
388
+ }
389
+
390
+ export function remoteOperationToolName(id: string): string {
391
+ return id
392
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
393
+ .toLowerCase();
394
+ }
395
+
396
+ function operationTitle(id: string): string {
397
+ const [scope = "", action = ""] = id.split(".");
398
+ return `${humanizeIdentifier(scope)}: ${humanizeIdentifier(action)}`;
399
+ }
400
+
401
+ function humanizeIdentifier(value: string): string {
402
+ if (value === "ui") return "UI";
403
+ return value
404
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
405
+ .replace(/^./, (character) => character.toUpperCase());
406
+ }
407
+
137
408
  function op(
138
409
  id: string,
139
410
  description: string,
140
411
  inputShape: string,
141
- sideEffectLevel: RemoteOperationSchema["sideEffectLevel"],
412
+ sideEffectLevel: RemoteSideEffectLevel,
413
+ inputSchema: RemoteJsonSchema,
414
+ writeTier = writeTierForSideEffectLevel(sideEffectLevel),
142
415
  ): RemoteOperationSchema {
143
- return { id, description, inputShape, sideEffectLevel, dryRun: sideEffectLevel !== "none" };
416
+ return {
417
+ id,
418
+ title: operationTitle(id),
419
+ description,
420
+ inputShape,
421
+ inputSchema,
422
+ sideEffectLevel,
423
+ writeTier,
424
+ requiresConfirmation: writeTier === "user-data" || writeTier === "broker",
425
+ dryRun: sideEffectLevel !== "none",
426
+ };
144
427
  }
@@ -100,19 +100,66 @@ export interface RemoteResourceSchema {
100
100
  patchable?: boolean;
101
101
  }
102
102
 
103
+ export type RemoteSideEffectLevel =
104
+ | "none"
105
+ | "local-write"
106
+ | "network-write"
107
+ | "external-side-effect"
108
+ | "external-trade";
109
+
110
+ export type RemoteWriteTier = "read" | "ui-write" | "user-data" | "broker";
111
+
112
+ export type RemoteJsonSchemaType =
113
+ | "object"
114
+ | "array"
115
+ | "string"
116
+ | "number"
117
+ | "integer"
118
+ | "boolean"
119
+ | "null";
120
+
121
+ /** Serializable subset of JSON Schema used for remote operation inputs. */
122
+ export interface RemoteJsonSchema {
123
+ type?: RemoteJsonSchemaType | RemoteJsonSchemaType[];
124
+ description?: string;
125
+ properties?: Record<string, RemoteJsonSchema>;
126
+ required?: string[];
127
+ additionalProperties?: boolean;
128
+ items?: RemoteJsonSchema;
129
+ enum?: Array<string | number | boolean | null>;
130
+ minimum?: number;
131
+ maximum?: number;
132
+ minItems?: number;
133
+ maxItems?: number;
134
+ minLength?: number;
135
+ maxLength?: number;
136
+ }
137
+
103
138
  export interface RemoteOperationSchema {
104
139
  id: string;
140
+ title: string;
105
141
  description: string;
106
- inputShape?: string;
142
+ inputShape: string;
143
+ inputSchema: RemoteJsonSchema;
107
144
  outputShape?: string;
108
145
  returns?: string;
109
146
  recommendedNextReads?: string[];
110
147
  examples?: unknown[];
111
- sideEffectLevel: "none" | "local-write" | "network-write" | "external-side-effect" | "external-trade";
148
+ sideEffectLevel: RemoteSideEffectLevel;
149
+ writeTier: RemoteWriteTier;
112
150
  requiresConfirmation?: boolean;
113
151
  dryRun?: boolean;
114
152
  }
115
153
 
154
+ /** Stable operation metadata suitable for advertising outside the process. */
155
+ export interface RemoteOperationDescriptor {
156
+ name: string;
157
+ title: string;
158
+ description: string;
159
+ writeTier: RemoteWriteTier;
160
+ inputSchema: RemoteJsonSchema;
161
+ }
162
+
116
163
  export interface RemoteControlSchema {
117
164
  protocolVersion: number;
118
165
  resources: RemoteResourceSchema[];
@@ -1,3 +1,4 @@
1
+ import { recordResearchActivity } from "../../api-client/research-activity";
1
2
  import { newsProvider, type NewsCapability } from "../../capabilities";
2
3
  import type { AppRuntimeServices, AppServicesFactoryOptions } from "../../core/app-service-ports";
3
4
  import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../market-data/coordinator";
@@ -23,6 +24,7 @@ export function createBrowserAppServices({
23
24
  );
24
25
  const pluginRegistry = new PluginRegistry(dataProvider, tickerRepository, persistence);
25
26
  dataProvider.attachRegistry(pluginRegistry);
27
+ const stopSavedActivity = pluginRegistry.events.on("command-bar:portfolio-membership-persisted", () => recordResearchActivity("ticker_saved"));
26
28
  const newsService = new NewsService({
27
29
  pollIntervalMs: () => Math.max(1, config.refreshIntervalMinutes) * 60_000,
28
30
  });
@@ -51,6 +53,7 @@ export function createBrowserAppServices({
51
53
  pluginRegistry,
52
54
  ready,
53
55
  destroy() {
56
+ stopSavedActivity();
54
57
  setSharedMarketDataCoordinator(null);
55
58
  setSharedNewsService(null);
56
59
  newsService.stop();
@@ -6,7 +6,8 @@ import {
6
6
  normalizeConfigForSave,
7
7
  normalizeLoadedConfig,
8
8
  } from "../../data/config/store/normalize";
9
- import { createDefaultConfig, type AppConfig } from "../../types/config";
9
+ import { createDefaultConfig, TICKER_RESEARCH_PANE_ID, type AppConfig } from "../../types/config";
10
+ import { researchEntryFromSearch } from "./research-entry";
10
11
  import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./storage";
11
12
 
12
13
  export const BROWSER_DATA_DIR = "browser://local";
@@ -15,18 +16,33 @@ function browserReady(config: AppConfig): AppConfig {
15
16
  return { ...config, onboardingComplete: true, onboardingProgress: undefined };
16
17
  }
17
18
 
18
- function createBrowserDefaultConfig(dataDir: string): AppConfig {
19
- return browserReady(createDefaultConfig(dataDir));
19
+ function createBrowserDefaultConfig(dataDir: string, search = ""): AppConfig {
20
+ const config = createDefaultConfig(dataDir);
21
+ const entry = researchEntryFromSearch(search) ?? { symbol: "NVDA", tab: "overview" };
22
+ // A first visit starts with one useful research pane. Saved layouts retain
23
+ // their existing panes and bindings when a visitor returns.
24
+ config.layouts[0] = {
25
+ name: "Research",
26
+ layout: {
27
+ dockRoot: { kind: "pane", instanceId: "ticker-detail:main" },
28
+ instances: [{ instanceId: "ticker-detail:main", paneId: TICKER_RESEARCH_PANE_ID,
29
+ binding: { kind: "fixed", symbol: entry.symbol }, settings: { hideTabs: false } }],
30
+ floating: [], detached: [],
31
+ },
32
+ paneState: { "ticker-detail:main": { activeTabId: entry.tab } },
33
+ };
34
+ config.layout = config.layouts[0].layout;
35
+ return browserReady(config);
20
36
  }
21
37
 
22
- export function createBrowserConfigStore(storage: StorageLike): ConfigStoreHost {
38
+ export function createBrowserConfigStore(storage: StorageLike, search = ""): ConfigStoreHost {
23
39
  const data = new SafeJsonStorage<unknown>(storage, BROWSER_STORAGE_KEYS.config, null);
24
40
  return {
25
41
  async getDataDir() { return BROWSER_DATA_DIR; },
26
42
  async loadConfig(dataDir) {
27
43
  const saved = data.get();
28
44
  if (!saved || typeof saved !== "object" || Array.isArray(saved)) {
29
- return createBrowserDefaultConfig(dataDir);
45
+ return createBrowserDefaultConfig(dataDir, search);
30
46
  }
31
47
  return browserReady(normalizeLoadedConfig(saved as Record<string, unknown>, dataDir).config);
32
48
  },
@@ -34,7 +50,7 @@ export function createBrowserConfigStore(storage: StorageLike): ConfigStoreHost
34
50
  data.set(normalizeConfigForSave(browserReady({ ...config, dataDir: BROWSER_DATA_DIR })));
35
51
  },
36
52
  async initDataDir(dataDir) {
37
- const config = createBrowserDefaultConfig(dataDir);
53
+ const config = createBrowserDefaultConfig(dataDir, search);
38
54
  data.set(config);
39
55
  return config;
40
56
  },
@@ -42,7 +58,7 @@ export function createBrowserConfigStore(storage: StorageLike): ConfigStoreHost
42
58
  for (const key of Object.values(BROWSER_STORAGE_KEYS)) {
43
59
  try { storage.removeItem(key); } catch {}
44
60
  }
45
- data.set(createBrowserDefaultConfig(dataDir));
61
+ data.set(createBrowserDefaultConfig(dataDir, search));
46
62
  },
47
63
  async exportConfig() {
48
64
  throw new Error("Config file export is unavailable in the browser.");
@@ -54,5 +70,5 @@ export function createBrowserConfigStore(storage: StorageLike): ConfigStoreHost
54
70
  }
55
71
 
56
72
  export function installBrowserConfigStore(storage: StorageLike = localStorage): void {
57
- setConfigStoreHost(createBrowserConfigStore(storage));
73
+ setConfigStoreHost(createBrowserConfigStore(storage, typeof location !== "undefined" ? location.search : ""));
58
74
  }
@@ -9,6 +9,17 @@ afterEach(() => {
9
9
  });
10
10
 
11
11
  describe("browser social share handoff", () => {
12
+ test("preserves the incoming ticker while the previous workspace restores", () => {
13
+ const location = { search: "?ticker=NVDA&tab=earnings-calls" };
14
+ Object.defineProperty(globalThis, "window", { configurable: true, value: {
15
+ location, addEventListener() {}, removeEventListener() {},
16
+ } });
17
+ const bridge = createBrowserDeepLinkBridge();
18
+ location.search = "?ticker=AAPL&tab=overview";
19
+ const seen: string[] = [];
20
+ bridge.subscribe(({ url }) => seen.push(url));
21
+ expect(seen).toEqual(["gloomberb://ticker/NVDA?tab=earnings-calls"]);
22
+ });
12
23
  test("maps a valid pane share query to the common deep-link runtime", () => {
13
24
  const id = "0123456789abcdef0123456789abcdef";
14
25
  Object.defineProperty(globalThis, "window", {
@@ -1,18 +1,30 @@
1
1
  import { marketplaceLayoutIdFromSearch } from "../../layout-marketplace/api";
2
2
  import { paneShareIdFromSearch } from "../../shares/location";
3
3
  import type { DesktopDeepLinkBridge } from "../../types/desktop-deeplink";
4
+ import { researchEntryFromSearch } from "./research-entry";
4
5
 
5
6
  export function createBrowserDeepLinkBridge(): DesktopDeepLinkBridge {
7
+ // Restoring a saved pane can update the address before App subscribes.
8
+ // Keep the incoming link intact until it has been delivered once.
9
+ const initialSearch = window.location.search;
6
10
  return {
7
11
  subscribe(listener) {
12
+ let initial = true;
8
13
  const emit = () => {
9
- const layoutId = marketplaceLayoutIdFromSearch(window.location.search);
14
+ const search = initial ? initialSearch : window.location.search;
15
+ initial = false;
16
+ const layoutId = marketplaceLayoutIdFromSearch(search);
10
17
  if (layoutId) {
11
18
  listener({ url: `gloomberb://layout/${layoutId}` });
12
19
  return;
13
20
  }
14
- const shareId = paneShareIdFromSearch(window.location.search);
15
- if (shareId) listener({ url: `gloomberb://share/${shareId}` });
21
+ const shareId = paneShareIdFromSearch(search);
22
+ if (shareId) {
23
+ listener({ url: `gloomberb://share/${shareId}` });
24
+ return;
25
+ }
26
+ const entry = researchEntryFromSearch(search);
27
+ if (entry) listener({ url: `gloomberb://ticker/${encodeURIComponent(entry.symbol)}?tab=${entry.tab}` });
16
28
  };
17
29
  emit();
18
30
  window.addEventListener("popstate", emit);
@@ -21,6 +21,7 @@ import {
21
21
  import { BROWSER_DATA_DIR, installBrowserConfigStore } from "./config-host";
22
22
  import { browserRendererHost, browserUiHost } from "./ui-host";
23
23
  import { createBrowserDeepLinkBridge } from "./deeplink-bridge";
24
+ import { initializeBrowserResearchActivity, recordResearchActivity } from "../../api-client/research-activity";
24
25
 
25
26
  // Declared here rather than sniffed: the desktop view and the hosted browser
26
27
  // app are both browser contexts but differ in what plugins may do.
@@ -36,8 +37,10 @@ root.render(<div className="gloom-loading">Starting Gloomberb...</div>);
36
37
  async function boot(): Promise<void> {
37
38
  installBrowserConfigStore();
38
39
  installBrowserFetchTransports();
40
+ initializeBrowserResearchActivity();
39
41
  installFocusScopeRelease();
40
42
  await restoreBrowserCloudSession();
43
+ recordResearchActivity("workspace_opened");
41
44
  const config = await loadConfig(BROWSER_DATA_DIR);
42
45
  applyLanguageFromConfig(config);
43
46
  const deepLinkBridge = createBrowserDeepLinkBridge();
@@ -53,7 +56,6 @@ async function boot(): Promise<void> {
53
56
  plugins={getBrowserBuiltinPlugins()}
54
57
  desktopDeepLinkBridge={deepLinkBridge}
55
58
  updatesEnabled={false}
56
- requireSignIn
57
59
  />
58
60
  </WebDialogHostProvider>
59
61
  </WebToastHostProvider>
@@ -0,0 +1,8 @@
1
+ import { expect, test } from "bun:test";
2
+ import { researchEntryFromSearch } from "./research-entry";
3
+
4
+ test("normalizes ticker links while rejecting commands and invalid tab paths", () => {
5
+ expect(researchEntryFromSearch("?ticker=brk.b&tab=earnings-calls")).toEqual({ symbol: "BRK.B", tab: "earnings-calls" });
6
+ expect(researchEntryFromSearch("?ticker=NVDA&tab=../../bad")).toEqual({ symbol: "NVDA", tab: "overview" });
7
+ for (const search of ["", "?ticker=", "?ticker=NVDA%20%3B%20rm", "?ticker=%3Cscript%3E"]) expect(researchEntryFromSearch(search)).toBeNull();
8
+ });
@@ -0,0 +1,7 @@
1
+ export function researchEntryFromSearch(search: string): { symbol: string; tab: string } | null {
2
+ const params = new URLSearchParams(search);
3
+ const symbol = params.get("ticker")?.trim().toUpperCase();
4
+ if (!symbol || !/^[A-Z0-9.^=:_-]{1,24}$/.test(symbol)) return null;
5
+ const tab = params.get("tab") ?? "overview";
6
+ return { symbol, tab: /^[a-z-]{1,40}$/.test(tab) ? tab : "overview" };
7
+ }
@@ -1,5 +1,5 @@
1
1
  import { createAppServices, type AppServices } from "../../../../core/app-services";
2
- import { getDesktopBackendPlugins } from "../../../../plugins/catalog-backend";
2
+ import { loadDesktopBackendPlugins } from "../../../../plugins/catalog-backend";
3
3
  import type { AppSessionSnapshot } from "../../../../core/state/session-persistence";
4
4
  import {
5
5
  exportConfig,
@@ -63,7 +63,7 @@ async function importDesktopConfig({
63
63
  setCurrentConfig(await importConfig(payload.dataDir, payload.srcPath));
64
64
  setServices(createAppServices({
65
65
  config: getConfig(),
66
- plugins: getDesktopBackendPlugins(),
66
+ plugins: await loadDesktopBackendPlugins(),
67
67
  }));
68
68
  syncConfigAccessors();
69
69
  registerCoreCapabilities();