@the-open-engine/zeroshot 6.28.0 → 6.29.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 (283) hide show
  1. package/README.md +3 -3
  2. package/cli/index.js +20 -224
  3. package/docker/zeroshot-cluster/Dockerfile +2 -3
  4. package/lib/cluster/client.cjs +30 -7
  5. package/lib/cluster/client.d.cts +52 -0
  6. package/lib/cluster/client.d.mts +52 -0
  7. package/lib/cluster/client.d.ts +4 -6
  8. package/lib/cluster/client.mjs +32 -9
  9. package/lib/cluster/connection-state.cjs +25 -0
  10. package/lib/cluster/connection-state.d.cts +13 -0
  11. package/lib/cluster/connection-state.d.mts +13 -0
  12. package/lib/cluster/connection-state.d.ts +13 -0
  13. package/lib/cluster/connection-state.mjs +21 -0
  14. package/lib/cluster/connection-types.cjs +2 -0
  15. package/lib/cluster/connection-types.d.cts +33 -0
  16. package/lib/cluster/connection-types.d.mts +33 -0
  17. package/lib/cluster/connection-types.d.ts +33 -0
  18. package/lib/cluster/connection-types.mjs +1 -0
  19. package/lib/cluster/connection.cjs +18 -46
  20. package/lib/cluster/connection.d.cts +26 -0
  21. package/lib/cluster/connection.d.mts +26 -0
  22. package/lib/cluster/connection.d.ts +7 -25
  23. package/lib/cluster/connection.mjs +12 -42
  24. package/lib/cluster/errors.cjs +9 -1
  25. package/lib/cluster/errors.d.cts +29 -0
  26. package/lib/cluster/errors.d.mts +29 -0
  27. package/lib/cluster/errors.d.ts +4 -0
  28. package/lib/cluster/errors.mjs +7 -0
  29. package/lib/cluster/frames.d.cts +12 -0
  30. package/lib/cluster/frames.d.mts +12 -0
  31. package/lib/cluster/generated/protocol-schema.d.cts +1 -0
  32. package/lib/cluster/generated/protocol-schema.d.mts +1 -0
  33. package/lib/cluster/generated/protocol.d.cts +781 -0
  34. package/lib/cluster/generated/protocol.d.mts +781 -0
  35. package/lib/cluster/index.cjs +2 -2
  36. package/lib/cluster/index.d.cts +13 -0
  37. package/lib/cluster/index.d.mts +13 -0
  38. package/lib/cluster/index.d.ts +3 -3
  39. package/lib/cluster/index.mjs +2 -2
  40. package/lib/cluster/json-source.cjs +1 -0
  41. package/lib/cluster/json-source.d.cts +5 -0
  42. package/lib/cluster/json-source.d.mts +5 -0
  43. package/lib/cluster/json-source.d.ts +1 -0
  44. package/lib/cluster/json-source.mjs +1 -0
  45. package/lib/cluster/payload-value.cjs +1 -0
  46. package/lib/cluster/payload-value.d.cts +8 -0
  47. package/lib/cluster/payload-value.d.mts +8 -0
  48. package/lib/cluster/payload-value.d.ts +1 -0
  49. package/lib/cluster/payload-value.mjs +1 -0
  50. package/lib/cluster/queue.d.cts +15 -0
  51. package/lib/cluster/queue.d.mts +15 -0
  52. package/lib/cluster/socket.cjs +8 -0
  53. package/lib/cluster/socket.d.cts +13 -0
  54. package/lib/cluster/socket.d.mts +13 -0
  55. package/lib/cluster/socket.d.ts +2 -0
  56. package/lib/cluster/socket.mjs +7 -0
  57. package/lib/cluster/subscriptions.d.cts +73 -0
  58. package/lib/cluster/subscriptions.d.mts +73 -0
  59. package/lib/cluster/subscriptions.d.ts +5 -1
  60. package/lib/cluster/validators.cjs +1 -0
  61. package/lib/cluster/validators.d.cts +7 -0
  62. package/lib/cluster/validators.d.mts +7 -0
  63. package/lib/cluster/validators.d.ts +1 -0
  64. package/lib/cluster/validators.mjs +1 -0
  65. package/lib/cluster-worker/engine-adapter.js +1 -11
  66. package/lib/cluster-worker/engine-input.js +14 -0
  67. package/lib/hosted-session/authority.cjs +61 -0
  68. package/lib/hosted-session/authority.d.cts +3 -0
  69. package/lib/hosted-session/authority.d.mts +3 -0
  70. package/lib/hosted-session/authority.d.ts +3 -0
  71. package/lib/hosted-session/authority.mjs +57 -0
  72. package/lib/hosted-session/coordinator.cjs +78 -51
  73. package/lib/hosted-session/coordinator.d.cts +11 -0
  74. package/lib/hosted-session/coordinator.d.mts +11 -0
  75. package/lib/hosted-session/coordinator.d.ts +4 -2
  76. package/lib/hosted-session/coordinator.mjs +75 -51
  77. package/lib/hosted-session/errors.cjs +25 -0
  78. package/lib/hosted-session/errors.d.cts +10 -0
  79. package/lib/hosted-session/errors.d.mts +10 -0
  80. package/lib/hosted-session/errors.d.ts +10 -0
  81. package/lib/hosted-session/errors.mjs +19 -0
  82. package/lib/hosted-session/index.cjs +4 -1
  83. package/lib/hosted-session/index.d.cts +2 -0
  84. package/lib/hosted-session/index.d.mts +2 -0
  85. package/lib/hosted-session/index.d.ts +2 -2
  86. package/lib/hosted-session/index.mjs +1 -1
  87. package/lib/hosted-session/reconnecting-watch.cjs +131 -0
  88. package/lib/hosted-session/reconnecting-watch.d.cts +13 -0
  89. package/lib/hosted-session/reconnecting-watch.d.mts +13 -0
  90. package/lib/hosted-session/reconnecting-watch.d.ts +13 -0
  91. package/lib/hosted-session/reconnecting-watch.mjs +127 -0
  92. package/lib/hosted-session/types.d.cts +32 -0
  93. package/lib/hosted-session/types.d.mts +32 -0
  94. package/lib/hosted-session/types.d.ts +19 -7
  95. package/lib/hosted-target/access-url.cjs +44 -0
  96. package/lib/hosted-target/access-url.d.cts +2 -0
  97. package/lib/hosted-target/access-url.d.mts +2 -0
  98. package/lib/hosted-target/access-url.d.ts +2 -0
  99. package/lib/hosted-target/access-url.mjs +41 -0
  100. package/lib/hosted-target/adapter-request.cjs +12 -0
  101. package/lib/hosted-target/adapter-request.d.cts +17 -0
  102. package/lib/hosted-target/adapter-request.d.mts +17 -0
  103. package/lib/hosted-target/adapter-request.d.ts +17 -0
  104. package/lib/hosted-target/adapter-request.mjs +9 -0
  105. package/lib/hosted-target/adapter-types.cjs +2 -0
  106. package/lib/hosted-target/adapter-types.d.cts +27 -0
  107. package/lib/hosted-target/adapter-types.d.mts +27 -0
  108. package/lib/hosted-target/adapter-types.d.ts +27 -0
  109. package/lib/hosted-target/adapter-types.mjs +1 -0
  110. package/lib/hosted-target/bounds.cjs +10 -0
  111. package/lib/hosted-target/bounds.d.cts +7 -0
  112. package/lib/hosted-target/bounds.d.mts +7 -0
  113. package/lib/hosted-target/bounds.d.ts +7 -0
  114. package/lib/hosted-target/bounds.mjs +7 -0
  115. package/lib/hosted-target/capsule-error-response.cjs +76 -0
  116. package/lib/hosted-target/capsule-error-response.d.cts +2 -0
  117. package/lib/hosted-target/capsule-error-response.d.mts +2 -0
  118. package/lib/hosted-target/capsule-error-response.d.ts +2 -0
  119. package/lib/hosted-target/capsule-error-response.mjs +73 -0
  120. package/lib/hosted-target/errors.cjs +88 -0
  121. package/lib/hosted-target/errors.d.cts +36 -0
  122. package/lib/hosted-target/errors.d.mts +36 -0
  123. package/lib/hosted-target/errors.d.ts +36 -0
  124. package/lib/hosted-target/errors.mjs +75 -0
  125. package/lib/hosted-target/index.cjs +16 -0
  126. package/lib/hosted-target/index.d.cts +3 -0
  127. package/lib/hosted-target/index.d.mts +3 -0
  128. package/lib/hosted-target/index.d.ts +3 -0
  129. package/lib/hosted-target/index.mjs +2 -0
  130. package/lib/hosted-target/response-validation.cjs +169 -0
  131. package/lib/hosted-target/response-validation.d.cts +5 -0
  132. package/lib/hosted-target/response-validation.d.mts +5 -0
  133. package/lib/hosted-target/response-validation.d.ts +5 -0
  134. package/lib/hosted-target/response-validation.mjs +163 -0
  135. package/lib/hosted-target/retry-executor.cjs +51 -0
  136. package/lib/hosted-target/retry-executor.d.cts +8 -0
  137. package/lib/hosted-target/retry-executor.d.mts +8 -0
  138. package/lib/hosted-target/retry-executor.d.ts +8 -0
  139. package/lib/hosted-target/retry-executor.mjs +48 -0
  140. package/lib/hosted-target/retry.cjs +78 -0
  141. package/lib/hosted-target/retry.d.cts +9 -0
  142. package/lib/hosted-target/retry.d.mts +9 -0
  143. package/lib/hosted-target/retry.d.ts +9 -0
  144. package/lib/hosted-target/retry.mjs +73 -0
  145. package/lib/hosted-target/target-adapter.cjs +10 -0
  146. package/lib/hosted-target/target-adapter.d.cts +3 -0
  147. package/lib/hosted-target/target-adapter.d.mts +3 -0
  148. package/lib/hosted-target/target-adapter.d.ts +3 -0
  149. package/lib/hosted-target/target-adapter.mjs +7 -0
  150. package/lib/hosted-target/types.cjs +4 -0
  151. package/lib/hosted-target/types.d.cts +50 -0
  152. package/lib/hosted-target/types.d.mts +50 -0
  153. package/lib/hosted-target/types.d.ts +50 -0
  154. package/lib/hosted-target/types.mjs +1 -0
  155. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +185 -0
  156. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +13 -0
  157. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +13 -0
  158. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +13 -0
  159. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +181 -0
  160. package/lib/target/bounded-response.cjs +51 -0
  161. package/lib/target/bounded-response.d.cts +1 -0
  162. package/lib/target/bounded-response.d.mts +1 -0
  163. package/lib/target/bounded-response.d.ts +1 -0
  164. package/lib/target/bounded-response.js +51 -0
  165. package/lib/target/bounded-response.mjs +48 -0
  166. package/lib/target/credential-lock.js +1 -3
  167. package/lib/target/credential-store.js +9 -4
  168. package/lib/target/device-flow.cjs +213 -0
  169. package/lib/target/device-flow.d.cts +54 -0
  170. package/lib/target/device-flow.d.mts +54 -0
  171. package/lib/target/device-flow.d.ts +26 -10
  172. package/lib/target/device-flow.js +162 -53
  173. package/lib/target/device-flow.mjs +203 -0
  174. package/lib/target/discovery-errors.cjs +10 -0
  175. package/lib/target/discovery-errors.d.cts +3 -0
  176. package/lib/target/discovery-errors.d.mts +3 -0
  177. package/lib/target/discovery-errors.d.ts +3 -0
  178. package/lib/target/discovery-errors.js +10 -0
  179. package/lib/target/discovery-errors.mjs +6 -0
  180. package/lib/target/discovery-sections.cjs +191 -0
  181. package/lib/target/discovery-sections.d.cts +52 -0
  182. package/lib/target/discovery-sections.d.mts +52 -0
  183. package/lib/target/discovery-sections.d.ts +52 -0
  184. package/lib/target/discovery-sections.js +191 -0
  185. package/lib/target/discovery-sections.mjs +179 -0
  186. package/lib/target/discovery-validation.cjs +109 -0
  187. package/lib/target/discovery-validation.d.cts +28 -0
  188. package/lib/target/discovery-validation.d.mts +28 -0
  189. package/lib/target/discovery-validation.d.ts +28 -0
  190. package/lib/target/discovery-validation.js +109 -0
  191. package/lib/target/discovery-validation.mjs +99 -0
  192. package/lib/target/discovery.cjs +112 -0
  193. package/lib/target/discovery.d.cts +74 -0
  194. package/lib/target/discovery.d.mts +74 -0
  195. package/lib/target/discovery.d.ts +68 -5
  196. package/lib/target/discovery.js +93 -101
  197. package/lib/target/discovery.mjs +105 -0
  198. package/lib/target/index.d.ts +6 -6
  199. package/lib/target/index.js +36 -36
  200. package/lib/target/oauth-http.d.ts +2 -0
  201. package/lib/target/oauth-http.js +25 -0
  202. package/lib/target/refresh-exchange.d.ts +11 -0
  203. package/lib/target/refresh-exchange.js +24 -0
  204. package/lib/target/refresh-revocation.d.ts +8 -0
  205. package/lib/target/refresh-revocation.js +19 -0
  206. package/lib/target/route-template.cjs +85 -0
  207. package/lib/target/route-template.d.cts +7 -0
  208. package/lib/target/route-template.d.mts +7 -0
  209. package/lib/target/route-template.d.ts +7 -0
  210. package/lib/target/route-template.js +85 -0
  211. package/lib/target/route-template.mjs +81 -0
  212. package/lib/target/session-errors.d.ts +4 -0
  213. package/lib/target/session-errors.js +12 -0
  214. package/lib/target/session-verification.d.ts +10 -0
  215. package/lib/target/session-verification.js +42 -0
  216. package/lib/target/target-registry.d.ts +7 -3
  217. package/lib/target/target-registry.js +41 -10
  218. package/lib/target/target-session-manager.d.ts +48 -0
  219. package/lib/target/target-session-manager.js +226 -0
  220. package/lib/target/target-session.d.ts +39 -31
  221. package/lib/target/target-session.js +63 -150
  222. package/lib/target/token-response.cjs +51 -0
  223. package/lib/target/token-response.d.cts +7 -0
  224. package/lib/target/token-response.d.mts +7 -0
  225. package/lib/target/token-response.d.ts +7 -0
  226. package/lib/target/token-response.js +51 -0
  227. package/lib/target/token-response.mjs +48 -0
  228. package/package.json +16 -6
  229. package/scripts/audit-production-dependencies.js +150 -0
  230. package/scripts/build-cluster.js +23 -3
  231. package/scripts/opcore-agent-gate.js +159 -0
  232. package/scripts/opcore-agent-tool-overlays.js +154 -0
  233. package/scripts/opcore-introduced-check.js +288 -0
  234. package/src/cluster/client.ts +42 -15
  235. package/src/cluster/connection-state.ts +33 -0
  236. package/src/cluster/connection-types.ts +28 -0
  237. package/src/cluster/connection.ts +37 -68
  238. package/src/cluster/errors.ts +5 -0
  239. package/src/cluster/index.ts +3 -1
  240. package/src/cluster/json-source.ts +1 -0
  241. package/src/cluster/payload-value.ts +1 -0
  242. package/src/cluster/socket.ts +13 -0
  243. package/src/cluster/subscriptions.ts +7 -2
  244. package/src/cluster/validators.ts +1 -0
  245. package/src/cluster/ws.d.ts +1 -1
  246. package/src/hosted-session/authority.ts +77 -0
  247. package/src/hosted-session/coordinator.ts +113 -56
  248. package/src/hosted-session/errors.ts +21 -0
  249. package/src/hosted-session/index.ts +14 -2
  250. package/src/hosted-session/reconnecting-watch.ts +148 -0
  251. package/src/hosted-session/types.ts +22 -8
  252. package/src/hosted-target/access-url.ts +46 -0
  253. package/src/hosted-target/adapter-request.ts +28 -0
  254. package/src/hosted-target/adapter-types.ts +39 -0
  255. package/src/hosted-target/bounds.ts +1 -0
  256. package/src/hosted-target/capsule-error-response.ts +107 -0
  257. package/src/hosted-target/errors.ts +36 -35
  258. package/src/hosted-target/index.ts +10 -23
  259. package/src/hosted-target/response-validation.ts +170 -62
  260. package/src/hosted-target/retry-executor.ts +62 -0
  261. package/src/hosted-target/retry.ts +47 -11
  262. package/src/hosted-target/target-adapter.ts +12 -8
  263. package/src/hosted-target/types.ts +19 -18
  264. package/src/hosted-target/zero-cloud-v1-adapter.ts +229 -316
  265. package/src/target/bounded-response.ts +68 -0
  266. package/src/target/credential-lock.ts +1 -3
  267. package/src/target/credential-store.ts +14 -10
  268. package/src/target/device-flow.ts +206 -71
  269. package/src/target/discovery-errors.ts +6 -0
  270. package/src/target/discovery-sections.ts +251 -0
  271. package/src/target/discovery-validation.ts +144 -0
  272. package/src/target/discovery.ts +163 -115
  273. package/src/target/index.ts +15 -11
  274. package/src/target/oauth-http.ts +27 -0
  275. package/src/target/refresh-exchange.ts +36 -0
  276. package/src/target/refresh-revocation.ts +29 -0
  277. package/src/target/route-template.ts +105 -0
  278. package/src/target/session-errors.ts +9 -0
  279. package/src/target/session-verification.ts +51 -0
  280. package/src/target/target-registry.ts +60 -27
  281. package/src/target/target-session-manager.ts +285 -0
  282. package/src/target/target-session.ts +105 -222
  283. package/src/target/token-response.ts +62 -0
@@ -1,20 +1,23 @@
1
+ /// <reference path="./ws.d.ts" />
1
2
  import { PROTOCOL_VERSION } from './generated/protocol.js';
2
3
  import type {
3
4
  AgentAttachParams, AgentAttachResult, ApplyParams, ApplyResult, DeleteParams, DeleteResult,
4
5
  GetParams, GetResult, InitializeParams, InitializeResult, LogsParams, LogsResult,
5
6
  PlanParams, PlanResult, ResubmitParams, ResubmitResult, RetryParams, RetryResult,
6
- StopParams, StopResult, UpdateParams, UpdateResult, WatchParams, WatchResult,
7
+ StopParams, StopResult, UpdateParams, UpdateResult, WatchParams,
7
8
  } from './generated/protocol.js';
8
9
  import { Connection } from './connection.js';
9
10
  import type { CallOptions } from './connection.js';
10
- import { addSocketListener } from './socket.js';
11
+ import { addSocketEmitterListener, addSocketListener } from './socket.js';
11
12
  import type { WebSocketLike } from './socket.js';
12
- import { ClusterConfigError, ClusterProtocolError, ClusterTransportError } from './errors.js';
13
+ import { ClusterConfigError, ClusterProtocolError, ClusterTransportError, ClusterUpgradeError } from './errors.js';
13
14
  import {
14
15
  AgentAttachSubscriptionStream,
15
16
  LogsSubscriptionStream,
16
17
  WatchSubscriptionStream,
17
18
  } from './subscriptions.js';
19
+ import type { WatchSubscription } from './subscriptions.js';
20
+ export type { WatchSubscription } from './subscriptions.js';
18
21
 
19
22
  export interface WebSocketFactoryOptions {
20
23
  readonly headers?: Readonly<Record<string, string>>;
@@ -26,7 +29,6 @@ export interface ConnectOptions {
26
29
  readonly initialize?: InitializeParams;
27
30
  readonly headers?: Readonly<Record<string, string>>;
28
31
  }
29
- export interface WatchSubscription { readonly result: WatchResult; readonly stream: WatchSubscriptionStream; }
30
32
  export interface LogsSubscription { readonly result: LogsResult; readonly stream: LogsSubscriptionStream; }
31
33
  export interface AgentAttachSubscription { readonly result: AgentAttachResult; readonly stream: AgentAttachSubscriptionStream; }
32
34
  export interface CoherentWatchSubscription extends WatchSubscription { readonly snapshot: GetResult; }
@@ -131,13 +133,7 @@ async function defaultWebSocketFactory(
131
133
  protocols?: string | readonly string[],
132
134
  ) => WebSocketLike;
133
135
  }).WebSocket;
134
- if (globalWebSocket) {
135
- if (options?.headers && Object.keys(options.headers).length > 0) {
136
- throw new ClusterConfigError(
137
- 'WebSocket upgrade headers require the ws library; the browser WebSocket API cannot carry request headers',
138
- 'HEADERS_UNSUPPORTED',
139
- );
140
- }
136
+ if (globalWebSocket && (!options?.headers || Object.keys(options.headers).length === 0)) {
141
137
  return new globalWebSocket(url, protocols);
142
138
  }
143
139
  try {
@@ -175,6 +171,7 @@ async function waitForOpen(socket: WebSocketLike, signal?: AbortSignal): Promise
175
171
  await new Promise<void>((resolve, reject) => {
176
172
  let settled = false;
177
173
  const removers: Array<() => void> = [];
174
+ let upgradeFailure: ClusterUpgradeError | ClusterTransportError | undefined;
178
175
  const settle = (fn: () => void) => {
179
176
  if (settled) return;
180
177
  settled = true;
@@ -186,15 +183,45 @@ async function waitForOpen(socket: WebSocketLike, signal?: AbortSignal): Promise
186
183
  'connect aborted locally; the server may still have committed this request',
187
184
  'AbortError',
188
185
  )));
186
+ const onUnexpectedResponse = (...args: unknown[]) => {
187
+ const response = args.find(
188
+ (value) => value !== null && typeof value === 'object' && 'statusCode' in value,
189
+ ) as { statusCode?: unknown; resume?: () => void } | undefined;
190
+ const status = response?.statusCode;
191
+ upgradeFailure = typeof status === 'number'
192
+ ? new ClusterUpgradeError(status)
193
+ : new ClusterTransportError('WebSocket upgrade rejected', 'UPGRADE_REJECTED');
194
+ response?.resume?.();
195
+ if (socket.terminate) {
196
+ socket.terminate();
197
+ } else {
198
+ settle(() => reject(upgradeFailure));
199
+ }
200
+ };
189
201
  removers.push(
190
202
  addSocketListener(socket, 'open', () => settle(resolve)),
191
- addSocketListener(socket, 'error', (error) => settle(() => reject(new ClusterTransportError('WebSocket failed to open', 'OPEN_FAILED', { cause: error })))),
192
- addSocketListener(socket, 'close', () => settle(() => reject(new ClusterTransportError('WebSocket closed before opening', 'OPEN_FAILED')))),
203
+ addSocketListener(socket, 'error', (error) => settle(() => reject(
204
+ upgradeFailure ??
205
+ new ClusterTransportError('WebSocket failed to open', 'OPEN_FAILED', { cause: error }),
206
+ ))),
207
+ addSocketListener(socket, 'close', () => settle(() => reject(
208
+ upgradeFailure ??
209
+ new ClusterTransportError('WebSocket closed before opening', 'OPEN_FAILED'),
210
+ ))),
211
+ addSocketEmitterListener(socket, 'unexpected-response', onUnexpectedResponse),
193
212
  );
194
213
  signal?.addEventListener('abort', onAbort, { once: true });
195
214
  });
196
215
  }
197
216
 
217
+ async function disposeFailedSocket(socket: WebSocketLike): Promise<void> {
218
+ if (socket.readyState === 0 && socket.terminate) {
219
+ socket.terminate();
220
+ } else {
221
+ await socket.close();
222
+ }
223
+ }
224
+
198
225
  export async function connect(url: string, options: ConnectOptions = {}): Promise<Connection> {
199
226
  const factory = options.webSocketFactory ?? defaultWebSocketFactory;
200
227
  let socket: WebSocketLike | undefined;
@@ -209,7 +236,7 @@ export async function connect(url: string, options: ConnectOptions = {}): Promis
209
236
  return connection;
210
237
  } catch (error) {
211
238
  if (socket) {
212
- try { await socket.close(); }
239
+ try { await disposeFailedSocket(socket); }
213
240
  catch { /* preserve the construction error */ }
214
241
  }
215
242
  throw error;
@@ -231,7 +258,7 @@ export async function connectInitialized(url: string, options: ConnectOptions =
231
258
  return { connection, client, initializeResult };
232
259
  } catch (error) {
233
260
  if (socket) {
234
- try { await socket.close(); }
261
+ try { await disposeFailedSocket(socket); }
235
262
  catch { /* preserve the construction error */ }
236
263
  }
237
264
  throw error;
@@ -0,0 +1,33 @@
1
+ if (!Object.prototype.hasOwnProperty.call(Symbol, 'asyncDispose')) {
2
+ Object.defineProperty(Symbol, 'asyncDispose', {
3
+ configurable: false, enumerable: false,
4
+ value: Symbol.for('Symbol.asyncDispose'), writable: false,
5
+ });
6
+ }
7
+
8
+ export type ConnectionState = 'OPEN' | 'CLOSING' | 'CLOSED';
9
+ export const CONNECTION_TRANSITIONS: Readonly<Record<ConnectionState, readonly ConnectionState[]>> = Object.freeze({
10
+ OPEN: Object.freeze(['CLOSING'] as const),
11
+ CLOSING: Object.freeze(['CLOSED'] as const),
12
+ CLOSED: Object.freeze([] as const),
13
+ });
14
+ export const PROTOCOL_DIAGNOSTIC_CAPACITY = 128;
15
+ export interface ConnectionCloseSnapshot {
16
+ readonly code: number | null;
17
+ readonly reason: null;
18
+ }
19
+
20
+ export type Deferred<T> = {
21
+ readonly promise: Promise<T>;
22
+ readonly resolve: (value: T) => void;
23
+ readonly reject: (reason: unknown) => void;
24
+ };
25
+ export function deferred<T>(): Deferred<T> {
26
+ let resolve!: (value: T) => void;
27
+ let reject!: (reason: unknown) => void;
28
+ const promise = new Promise<T>((onResolve, onReject) => {
29
+ resolve = onResolve;
30
+ reject = onReject;
31
+ });
32
+ return { promise, resolve, reject };
33
+ }
@@ -0,0 +1,28 @@
1
+ import type { FrameRecord } from './frames.js';
2
+ import type { BoundedQueue } from './queue.js';
3
+ import type { ClusterMethod } from './generated/protocol.js';
4
+
5
+ export interface CallOptions {
6
+ readonly signal?: AbortSignal;
7
+ readonly requestTimeoutMs?: number;
8
+ }
9
+ export type SubscriptionKind = 'watch' | 'logs' | 'agent/attach';
10
+ export type SubscriptionRegistration = {
11
+ readonly id: string;
12
+ readonly kind: SubscriptionKind;
13
+ readonly queue: BoundedQueue<FrameRecord>;
14
+ overflowed: boolean;
15
+ cancelSent: boolean;
16
+ abortHandler?: () => void;
17
+ abortSignal?: AbortSignal;
18
+ };
19
+ export type EstablishedSubscription<R> = {
20
+ readonly result: R;
21
+ readonly registration: SubscriptionRegistration;
22
+ };
23
+ export type PendingEntry = {
24
+ readonly id: string; readonly method: ClusterMethod; readonly expectedId: string;
25
+ readonly resolve: (value: unknown) => void; readonly reject: (reason: unknown) => void;
26
+ readonly subscriptionKind?: SubscriptionKind; settled: boolean;
27
+ abortHandler?: () => void; signal?: AbortSignal; timeout?: ReturnType<typeof setTimeout>;
28
+ };
@@ -13,68 +13,32 @@ import { BoundedQueue } from './queue.js';
13
13
  import { addSocketListener } from './socket.js';
14
14
  import type { WebSocketLike } from './socket.js';
15
15
  import { assertDefinition, assertMethodResult } from './validators.js';
16
- if (!Object.prototype.hasOwnProperty.call(Symbol, 'asyncDispose')) {
17
- Object.defineProperty(Symbol, 'asyncDispose', {
18
- configurable: false, enumerable: false,
19
- value: Symbol.for('Symbol.asyncDispose'), writable: false,
20
- });
21
- }
22
-
23
- export type ConnectionState = 'OPEN' | 'CLOSING' | 'CLOSED';
24
- export const CONNECTION_TRANSITIONS: Readonly<Record<ConnectionState, readonly ConnectionState[]>> = Object.freeze({
25
- OPEN: Object.freeze(['CLOSING'] as const),
26
- CLOSING: Object.freeze(['CLOSED'] as const),
27
- CLOSED: Object.freeze([] as const),
28
- });
29
- export const PROTOCOL_DIAGNOSTIC_CAPACITY = 128;
30
- export const CLOSE_REASON_MAX_BYTES = 123;
31
- const CLOSE_REASON_ENCODER = new TextEncoder();
32
- function boundedCloseReason(reason: string): string {
33
- const retained: string[] = [];
34
- const scratch = new Uint8Array(4);
35
- let bytes = 0;
36
- for (const codePoint of reason) {
37
- const { written } = CLOSE_REASON_ENCODER.encodeInto(codePoint, scratch);
38
- if (bytes + written > CLOSE_REASON_MAX_BYTES) break;
39
- retained.push(codePoint);
40
- bytes += written;
41
- }
42
- return retained.join('');
43
- }
44
- export interface CallOptions { readonly signal?: AbortSignal; readonly requestTimeoutMs?: number; }
45
-
46
- type Deferred<T> = {
47
- readonly promise: Promise<T>;
48
- readonly resolve: (value: T) => void;
49
- readonly reject: (reason: unknown) => void;
50
- };
51
- function deferred<T>(): Deferred<T> {
52
- let resolve!: (value: T) => void; let reject!: (reason: unknown) => void;
53
- const promise = new Promise<T>((onResolve, onReject) => {
54
- resolve = onResolve; reject = onReject;
55
- });
56
- return { promise, resolve, reject };
57
- }
58
- export type SubscriptionKind = 'watch' | 'logs' | 'agent/attach';
59
- export type SubscriptionRegistration = {
60
- readonly id: string;
61
- readonly kind: SubscriptionKind;
62
- readonly queue: BoundedQueue<FrameRecord>;
63
- overflowed: boolean;
64
- cancelSent: boolean;
65
- abortHandler?: () => void;
66
- abortSignal?: AbortSignal;
67
- };
68
- export type EstablishedSubscription<R> = {
69
- readonly result: R;
70
- readonly registration: SubscriptionRegistration;
71
- };
72
- type PendingEntry = {
73
- readonly id: string; readonly method: ClusterMethod; readonly expectedId: string;
74
- readonly resolve: (value: unknown) => void; readonly reject: (reason: unknown) => void;
75
- readonly subscriptionKind?: SubscriptionKind; settled: boolean;
76
- abortHandler?: () => void; signal?: AbortSignal; timeout?: ReturnType<typeof setTimeout>;
77
- };
16
+ import {
17
+ CONNECTION_TRANSITIONS,
18
+ PROTOCOL_DIAGNOSTIC_CAPACITY,
19
+ deferred,
20
+ type ConnectionCloseSnapshot,
21
+ type ConnectionState,
22
+ } from './connection-state.js';
23
+ export {
24
+ CONNECTION_TRANSITIONS,
25
+ PROTOCOL_DIAGNOSTIC_CAPACITY,
26
+ type ConnectionCloseSnapshot,
27
+ type ConnectionState,
28
+ } from './connection-state.js';
29
+ import type {
30
+ CallOptions,
31
+ EstablishedSubscription,
32
+ PendingEntry,
33
+ SubscriptionKind,
34
+ SubscriptionRegistration,
35
+ } from './connection-types.js';
36
+ export type {
37
+ CallOptions,
38
+ EstablishedSubscription,
39
+ SubscriptionKind,
40
+ SubscriptionRegistration,
41
+ } from './connection-types.js';
78
42
 
79
43
  export class Connection {
80
44
  #state: ConnectionState = 'OPEN'; #sequence = 1;
@@ -85,7 +49,9 @@ export class Connection {
85
49
  readonly #ownedSubscriptions = new WeakSet<SubscriptionRegistration>();
86
50
  #closePromise?: Promise<void>;
87
51
  #closeCode: number | undefined;
88
- #closeReason: string | undefined;
52
+ readonly #closedCompletion = deferred<ConnectionCloseSnapshot>();
53
+ readonly closed: Promise<ConnectionCloseSnapshot> = this.#closedCompletion.promise;
54
+ #closeSnapshot: ConnectionCloseSnapshot | undefined;
89
55
  readonly closeDiagnostics: unknown[] = [];
90
56
  readonly protocolDiagnostics: ClusterProtocolError[] = [];
91
57
 
@@ -103,7 +69,8 @@ export class Connection {
103
69
  get pendingSize(): number { return this.#pending.size; }
104
70
  get subscriptionCount(): number { return this.#subscriptions.size; }
105
71
  get closeCode(): number | undefined { return this.#closeCode; }
106
- get closeReason(): string | undefined { return this.#closeReason; }
72
+ get closeReason(): undefined { return undefined; }
73
+ get closeSnapshot(): ConnectionCloseSnapshot | undefined { return this.#closeSnapshot; }
107
74
  call<M extends UnaryClusterMethod>(method: M, params: ClusterMethodParams[M], options: CallOptions = {}): Promise<ClusterMethodResults[M]> {
108
75
  if (!(UNARY_METHODS as readonly string[]).includes(method)) {
109
76
  throw new ClusterConfigError(`${method} is a subscription method`, 'INVALID_METHOD');
@@ -289,12 +256,9 @@ export class Connection {
289
256
  const first = args[0];
290
257
  if (typeof first === 'number') {
291
258
  this.#closeCode = first;
292
- const raw = args.length > 1 ? String(args[1]) : undefined;
293
- this.#closeReason = raw === undefined ? undefined : boundedCloseReason(raw);
294
259
  } else if (first !== null && typeof first === 'object') {
295
- const event = first as { code?: unknown; reason?: unknown };
260
+ const event = first as { code?: unknown };
296
261
  if (typeof event.code === 'number') this.#closeCode = event.code;
297
- if (typeof event.reason === 'string') this.#closeReason = boundedCloseReason(event.reason);
298
262
  }
299
263
  }
300
264
  #startClose(sendCancels: boolean): Promise<void> {
@@ -319,6 +283,11 @@ export class Connection {
319
283
  try { remove(); } catch (error) { this.recordDiagnostic(error); }
320
284
  }
321
285
  this.#transition('CLOSED');
286
+ this.#closeSnapshot = Object.freeze({
287
+ code: this.#closeCode ?? null,
288
+ reason: null,
289
+ });
290
+ this.#closedCompletion.resolve(this.#closeSnapshot);
322
291
  }
323
292
  #transition(to: ConnectionState): void {
324
293
  if (!CONNECTION_TRANSITIONS[this.#state].includes(to)) throw new ClusterInternalError(`illegal connection transition ${this.#state} -> ${to}`, 'ILLEGAL_STATE_TRANSITION');
@@ -15,6 +15,11 @@ export class ClusterTransportError extends ClusterError {}
15
15
  export class ClusterTimeoutError extends ClusterError {}
16
16
  export class ClusterRequestError extends ClusterError {}
17
17
 
18
+ export class ClusterUpgradeError extends ClusterTransportError {
19
+ constructor(readonly status: number) {
20
+ super(`WebSocket upgrade rejected with HTTP ${status}`, 'UPGRADE_REJECTED');
21
+ }
22
+ }
18
23
  export function requestAbortError(method: string): DOMException {
19
24
  return new DOMException(
20
25
  `${method} aborted locally; the server may still have committed this request`,
@@ -10,14 +10,16 @@ export {
10
10
  ClusterStateError,
11
11
  ClusterTimeoutError,
12
12
  ClusterTransportError,
13
+ ClusterUpgradeError,
13
14
  } from './errors.js';
14
15
  export { assertGraphProfile, assertGraphProfileSupported, assertGraphSpec } from './validators.js';
15
16
  export * from './payload-value.js';
16
17
  export * from './json-source.js';
17
- export { CLOSE_REASON_MAX_BYTES, CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.js';
18
+ export { CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.js';
18
19
  export type {
19
20
  CallOptions,
20
21
  ConnectionState,
22
+ ConnectionCloseSnapshot,
21
23
  } from './connection.js';
22
24
  export type { WebSocketLike } from './socket.js';
23
25
  export {
@@ -3,6 +3,7 @@ import { MAX_FRAME_BYTES } from './generated/protocol.js';
3
3
 
4
4
  export const MAX_REQUEST_BYTES = MAX_FRAME_BYTES;
5
5
 
6
+ /** Decode one already-bounded request body with strict UTF-8 and JSON handling. */
6
7
  export function decodeBoundedJson(bytes: Uint8Array): unknown {
7
8
  if (bytes.length > MAX_REQUEST_BYTES) {
8
9
  throw new ClusterRequestError(
@@ -65,6 +65,7 @@ function evaluate(type: PayloadType, value: unknown, path: string): InternalIssu
65
65
  }
66
66
  }
67
67
 
68
+ /** Return the first deterministic schema issue without retaining the input value. */
68
69
  export function firstInputValidationIssue(
69
70
  type: PayloadType,
70
71
  value: unknown,
@@ -4,6 +4,7 @@ export interface WebSocketLike {
4
4
  readonly readyState: number;
5
5
  send(data: string, callback?: (error?: Error) => void): void | Promise<void>;
6
6
  close(code?: number, reason?: string): void | Promise<void>;
7
+ terminate?(): void;
7
8
  addEventListener?(type: string, listener: (...args: unknown[]) => void): void;
8
9
  removeEventListener?(type: string, listener: (...args: unknown[]) => void): void;
9
10
  on?(type: string, listener: (...args: unknown[]) => void): void;
@@ -29,3 +30,15 @@ export function addSocketListener(
29
30
  'INVALID_WEBSOCKET',
30
31
  );
31
32
  }
33
+
34
+ export function addSocketEmitterListener(
35
+ socket: WebSocketLike,
36
+ type: string,
37
+ listener: (...args: unknown[]) => void,
38
+ ): () => void {
39
+ if (socket.on) {
40
+ socket.on(type, listener);
41
+ return () => (socket.off ?? socket.removeListener)?.call(socket, type, listener);
42
+ }
43
+ return addSocketListener(socket, type, listener);
44
+ }
@@ -22,6 +22,11 @@ export interface Subscription<T> extends AsyncIterator<SubscriptionItem<T>>, Asy
22
22
  readonly retainedCount: number;
23
23
  cancel(): Promise<void>;
24
24
  }
25
+ export interface WatchSubscription {
26
+ readonly result: WatchResult;
27
+ readonly stream: WatchSubscriptionStream;
28
+ }
29
+
25
30
 
26
31
  type FrameParser<T> = (frame: FrameRecord) => SubscriptionItem<T>;
27
32
  class SubscriptionStream<T> implements Subscription<T> {
@@ -198,13 +203,13 @@ export class WatchSubscriptionStream implements AsyncIterator<WatchSubscriptionI
198
203
  await this.#terminate(true); throw error;
199
204
  }
200
205
  cancel(): Promise<void> { return this.#terminate(true); }
201
- reconnect(freshConnection: Connection): Promise<import('./client.js').WatchSubscription> {
206
+ reconnect(freshConnection: Connection): Promise<WatchSubscription> {
202
207
  if (this.#reconnectConsumed) {
203
208
  throw new ClusterStateError('watch stream reconnect is one-shot', 'RECONNECT_CONSUMED');
204
209
  }
205
210
  this.#reconnectConsumed = true; return this.#reconnectOn(freshConnection);
206
211
  }
207
- async #reconnectOn(freshConnection: Connection): Promise<import('./client.js').WatchSubscription> {
212
+ async #reconnectOn(freshConnection: Connection): Promise<WatchSubscription> {
208
213
  await this.#terminate(true);
209
214
  try {
210
215
  const runId = this.#runId; const fromCursor = this.#lastDelivered;
@@ -64,6 +64,7 @@ function assertRequestDefinition(definition: string, value: unknown, code: strin
64
64
  throw new ClusterRequestError(`${definition} validation failed: ${details}`, code);
65
65
  }
66
66
 
67
+ /** Validate an execution graph against the frozen cluster protocol schema. */
67
68
  export function assertGraphSpec(value: unknown): asserts value is GraphSpec {
68
69
  assertRequestDefinition('GraphSpec', value, 'INVALID_GRAPH');
69
70
  }
@@ -3,6 +3,6 @@ declare module 'ws' {
3
3
  url: string,
4
4
  protocols?: string | readonly string[],
5
5
  options?: { readonly headers?: Readonly<Record<string, string>> },
6
- ) => import('./index.js').WebSocketLike;
6
+ ) => import('./socket.js').WebSocketLike;
7
7
  export default WebSocket;
8
8
  }
@@ -0,0 +1,77 @@
1
+ import { ClusterConfigError } from '../cluster/index.js';
2
+ import type { HostedAccess } from './types.js';
3
+
4
+ const LOOPBACK_HOSTS: Readonly<Record<string, true>> = Object.freeze({
5
+ '127.0.0.1': true,
6
+ '::1': true,
7
+ '[::1]': true,
8
+ });
9
+
10
+ function parseCanonicalUrl(value: string, message: string, code: string): URL {
11
+ try {
12
+ if (/[\u0000-\u0020\u007f]/.test(value)) throw new TypeError('unsafe URL code point');
13
+ const url = new URL(value);
14
+ if (url.href !== value && url.origin !== value) throw new TypeError('non-canonical URL');
15
+ return url;
16
+ } catch {
17
+ throw new ClusterConfigError(message, code);
18
+ }
19
+ }
20
+
21
+ function hasOriginSuffix(url: URL): boolean {
22
+ return Boolean(
23
+ url.username ||
24
+ url.password ||
25
+ url.pathname !== '/' ||
26
+ url.search ||
27
+ url.hash,
28
+ );
29
+ }
30
+
31
+ export function normalizedAuthority(value: string): string {
32
+ const message = 'targetAuthority must be an HTTPS or literal-loopback HTTP origin';
33
+ const url = parseCanonicalUrl(value, message, 'INVALID_TARGET_AUTHORITY');
34
+ const loopbackHttp = url.protocol === 'http:' && LOOPBACK_HOSTS[url.hostname] === true;
35
+ if ((url.protocol !== 'https:' && !loopbackHttp) || hasOriginSuffix(url) ||
36
+ value !== url.origin) {
37
+ throw new ClusterConfigError(message, 'INVALID_TARGET_AUTHORITY');
38
+ }
39
+ return url.origin;
40
+ }
41
+
42
+ function accessAuthorityMatches(endpoint: URL, target: URL): boolean {
43
+ const expectedProtocol = target.protocol === 'http:' ? 'ws:' : 'wss:';
44
+ return (
45
+ endpoint.protocol === expectedProtocol &&
46
+ endpoint.host === target.host &&
47
+ !endpoint.username &&
48
+ !endpoint.password &&
49
+ !endpoint.search &&
50
+ !endpoint.hash
51
+ );
52
+ }
53
+
54
+ export function validateHostedAccess(access: HostedAccess, targetAuthority: string): void {
55
+ const endpoint = parseCanonicalUrl(
56
+ access.websocketUrl,
57
+ 'access endpoint must be an absolute target WebSocket URL',
58
+ 'INVALID_ACCESS_ENDPOINT',
59
+ );
60
+ if (endpoint.href !== access.websocketUrl ||
61
+ !accessAuthorityMatches(endpoint, new URL(targetAuthority))) {
62
+ throw new ClusterConfigError(
63
+ 'access endpoint must remain on the exact target authority',
64
+ 'INVALID_ACCESS_ENDPOINT',
65
+ );
66
+ }
67
+ if (
68
+ access.protocol !== 'openengine.cluster/v1' ||
69
+ access.tokenType !== 'Bearer' ||
70
+ access.accessToken.length === 0
71
+ ) {
72
+ throw new ClusterConfigError(
73
+ 'access grant does not match the hosted session contract',
74
+ 'INVALID_ACCESS_GRANT',
75
+ );
76
+ }
77
+ }