@camstack/server 0.2.2 → 1.0.1

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 (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -1,171 +0,0 @@
1
- /**
2
- * `AddonCallGateway` — the SINGLE hub-side router for addon-LEVEL calls (the
3
- * surfaces the removed per-addon Moleculer broker used to carry: routes,
4
- * custom-actions, settings — see `AddonCallTarget`).
5
- *
6
- * Why this exists: that routing decision (is the addon running in-process on
7
- * the hub, as a forked hub-local CHILD reachable over UDS, or on a REMOTE
8
- * agent over Moleculer?) used to be duplicated per surface — routes/custom in
9
- * `addon-registry.service.ts`, settings in `addon-settings-provider.ts`. When
10
- * the UDS migration ported routes+custom to `callAddonOnChild`, settings was
11
- * left on the dead `<addonId>.settings.<method>` Moleculer path because nothing
12
- * centralised "dispatch an addon-level call to wherever the addon runs". Every
13
- * forked hub-local addon's settings panel silently went empty for months.
14
- *
15
- * Now every addon-level surface routes through `callForked` here. Combined with
16
- * the exhaustive `AddonCallTarget` union (a `never`-checked dispatch in
17
- * `createChildAddonCallDispatch`), a future surface can't be half-wired without
18
- * a compile error — the class of "missed link" is closed.
19
- */
20
- import type { AddonCallInput, LocalChildRegistry } from '@camstack/kernel'
21
-
22
- /** An addon-level call minus its `addonId` — the gateway merges that in. */
23
- export type AddonCallSurface = Omit<AddonCallInput, 'addonId'>
24
-
25
- /**
26
- * Minimal structural view of the Moleculer broker (remote-agent leg) — typed
27
- * locally because the lint type-checker can't resolve Moleculer's
28
- * `ServiceBroker` (mirrors the pattern in `addon-settings-provider.ts`).
29
- */
30
- export interface AddonCallBroker {
31
- readonly registry: unknown
32
- call(
33
- action: string,
34
- params: Record<string, unknown>,
35
- opts?: { readonly nodeID?: string; readonly timeout?: number },
36
- ): Promise<unknown>
37
- }
38
-
39
- /** Where an addon physically runs — the routing decision, made in one place. */
40
- export type AddonCallDestination =
41
- | { readonly kind: 'in-process' }
42
- | { readonly kind: 'hub-local-child' }
43
- | { readonly kind: 'remote-agent'; readonly baseNodeId: string }
44
-
45
- export interface AddonCallGatewayDeps {
46
- /** This hub's node id (for the local short-circuit). */
47
- readonly hubNodeId: string
48
- /** Which node hosts a given addon ('hub' / hubNodeId = on this hub). */
49
- readonly resolveNode: (addonId: string) => string
50
- /** UDS registry of forked hub-local children (null before it's wired). */
51
- readonly getChildRegistry: () => LocalChildRegistry | null
52
- /** Moleculer broker for the remote-agent leg. */
53
- readonly broker: AddonCallBroker
54
- }
55
-
56
- const REMOTE_TIMEOUT_MS = 10_000
57
-
58
- export class AddonCallGateway {
59
- constructor(private readonly deps: AddonCallGatewayDeps) {}
60
-
61
- /**
62
- * Classify where an addon runs. `nodeId: 'hub'` from a caller means "the hub
63
- * cluster", NOT "force in-process" — a forked hub-local addon is still a
64
- * `hub-local-child` (UDS), never the in-process path (which is only the
65
- * `@camstack/core` builtins that have no forked runner).
66
- */
67
- classify(addonId: string, explicitNodeId?: string): AddonCallDestination {
68
- const resolved = this.deps.resolveNode(addonId)
69
- const onHub = resolved === 'hub' || resolved === this.deps.hubNodeId
70
- if (onHub) {
71
- const childRegistry = this.deps.getChildRegistry()
72
- if (childRegistry !== null && childRegistry.isChildKnown(addonId)) {
73
- return { kind: 'hub-local-child' }
74
- }
75
- return { kind: 'in-process' }
76
- }
77
- const onThisHub = explicitNodeId === 'hub' || explicitNodeId === this.deps.hubNodeId
78
- const baseNodeId = explicitNodeId && !onThisHub ? explicitNodeId : resolved
79
- return { kind: 'remote-agent', baseNodeId }
80
- }
81
-
82
- /** True when the addon is an in-process hub builtin (caller invokes directly). */
83
- isInProcess(addonId: string, explicitNodeId?: string): boolean {
84
- return this.classify(addonId, explicitNodeId).kind === 'in-process'
85
- }
86
-
87
- /**
88
- * Dispatch a forked addon-level call to wherever the addon runs:
89
- * - `hub-local-child` → UDS `LocalChildRegistry.callAddonOnChild`
90
- * - `remote-agent` → Moleculer `broker.call`
91
- * Throws for `in-process` — that addon has no forked surface, so the caller
92
- * must invoke the in-process instance directly (the invocation is
93
- * surface-specific; only the ROUTING is centralised here).
94
- */
95
- async callForked(
96
- addonId: string,
97
- input: AddonCallSurface,
98
- explicitNodeId?: string,
99
- ): Promise<unknown> {
100
- const dest = this.classify(addonId, explicitNodeId)
101
- const fullInput: AddonCallInput = { ...input, addonId }
102
- switch (dest.kind) {
103
- case 'hub-local-child': {
104
- const childRegistry = this.deps.getChildRegistry()
105
- if (childRegistry === null) {
106
- throw new Error(`AddonCallGateway: child registry unavailable for "${addonId}"`)
107
- }
108
- return childRegistry.callAddonOnChild(addonId, fullInput)
109
- }
110
- case 'remote-agent':
111
- return this.callRemoteAgent(addonId, dest.baseNodeId, fullInput)
112
- case 'in-process':
113
- throw new Error(`AddonCallGateway: addon "${addonId}" runs in-process — invoke it directly`)
114
- default: {
115
- const _exhaustive: never = dest
116
- throw new Error(`AddonCallGateway: unhandled destination ${JSON.stringify(_exhaustive)}`)
117
- }
118
- }
119
- }
120
-
121
- /** Map an addon-level call to the remote agent's Moleculer action. */
122
- private async callRemoteAgent(
123
- addonId: string,
124
- baseNodeId: string,
125
- input: AddonCallInput,
126
- ): Promise<unknown> {
127
- const workerNodeId = this.resolveWorkerNodeId(addonId, baseNodeId)
128
- const opts = workerNodeId
129
- ? { nodeID: workerNodeId, timeout: REMOTE_TIMEOUT_MS }
130
- : { timeout: REMOTE_TIMEOUT_MS }
131
- if (input.target === 'settings') {
132
- if (input.method == null) {
133
- throw new Error(`AddonCallGateway: settings call to "${addonId}" missing method`)
134
- }
135
- return this.deps.broker.call(
136
- `${addonId}.settings.${input.method}`,
137
- (input.args ?? {}) as Record<string, unknown>,
138
- opts,
139
- )
140
- }
141
- // routes/custom are hub-local-child surfaces (mounted / invoked on the
142
- // owning node); they are not proxied to a remote agent through this gateway.
143
- throw new Error(
144
- `AddonCallGateway: target "${input.target}" not supported for remote agent "${baseNodeId}"`,
145
- )
146
- }
147
-
148
- /**
149
- * Resolve the Moleculer nodeID that actually hosts an addon's service.
150
- * Forkable addons register under `${baseNodeId}/${addonId}`; in-process
151
- * addons under the base nodeId. The registry is the ground truth — baseNodeId
152
- * is a hint. (Moved verbatim from `addon-settings-provider.ts`.)
153
- */
154
- private resolveWorkerNodeId(addonId: string, baseNodeId: string): string | null {
155
- const registry = this.deps.broker.registry
156
- const services = (
157
- registry as unknown as {
158
- getServiceList: (opts: {
159
- onlyAvailable: boolean
160
- }) => readonly { name: string; nodeID: string }[]
161
- }
162
- ).getServiceList({ onlyAvailable: true })
163
- const exactNode = `${baseNodeId}/${addonId}`
164
- const preferred = services.find((s) => s.name === addonId && s.nodeID === exactNode)
165
- if (preferred) return preferred.nodeID
166
- const anyForBase = services.find((s) => s.name === addonId && s.nodeID === baseNodeId)
167
- if (anyForBase) return anyForBase.nodeID
168
- const anyWithName = services.find((s) => s.name === addonId)
169
- return anyWithName?.nodeID ?? null
170
- }
171
- }