@the-open-engine/zeroshot 6.29.0 → 6.30.0

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 (347) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/cli/index.js +2 -4
  3. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  4. package/lib/agent-cli-provider/adapters/codex.js +0 -1
  5. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  6. package/lib/agent-cli-provider/contract-actions.d.ts +1 -1
  7. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
  8. package/lib/agent-cli-provider/contract-actions.js +5 -5
  9. package/lib/agent-cli-provider/contract-actions.js.map +1 -1
  10. package/lib/agent-cli-provider/contract-invoke.d.ts +1 -1
  11. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/contract-invoke.js +2 -2
  13. package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
  14. package/lib/agent-cli-provider/contract-support.d.ts +1 -1
  15. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -1
  16. package/lib/agent-cli-provider/contract-support.js +2 -2
  17. package/lib/agent-cli-provider/contract-support.js.map +1 -1
  18. package/lib/agent-cli-provider/contract.d.ts +1 -0
  19. package/lib/agent-cli-provider/contract.d.ts.map +1 -1
  20. package/lib/agent-cli-provider/contract.js +1 -1
  21. package/lib/agent-cli-provider/contract.js.map +1 -1
  22. package/lib/agent-cli-provider/index.d.ts +1 -1
  23. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  24. package/lib/agent-cli-provider/index.js +2 -1
  25. package/lib/agent-cli-provider/index.js.map +1 -1
  26. package/lib/agent-cli-provider/provider-registry.d.ts +1 -0
  27. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  28. package/lib/agent-cli-provider/provider-registry.js +4 -0
  29. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  30. package/lib/agent-cli-provider/single-agent-runtime.d.ts +3 -3
  31. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/single-agent-runtime.js +22 -48
  33. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  34. package/lib/cluster/client.cjs +30 -7
  35. package/lib/cluster/client.d.cts +52 -0
  36. package/lib/cluster/client.d.mts +52 -0
  37. package/lib/cluster/client.d.ts +4 -6
  38. package/lib/cluster/client.mjs +32 -9
  39. package/lib/cluster/connection-state.cjs +25 -0
  40. package/lib/cluster/connection-state.d.cts +13 -0
  41. package/lib/cluster/connection-state.d.mts +13 -0
  42. package/lib/cluster/connection-state.d.ts +13 -0
  43. package/lib/cluster/connection-state.mjs +21 -0
  44. package/lib/{target/hosted-run/contracts.js → cluster/connection-types.cjs} +0 -1
  45. package/lib/cluster/connection-types.d.cts +33 -0
  46. package/lib/cluster/connection-types.d.mts +33 -0
  47. package/lib/cluster/connection-types.d.ts +33 -0
  48. package/lib/cluster/connection-types.mjs +1 -0
  49. package/lib/cluster/connection.cjs +18 -46
  50. package/lib/cluster/connection.d.cts +26 -0
  51. package/lib/cluster/connection.d.mts +26 -0
  52. package/lib/cluster/connection.d.ts +7 -25
  53. package/lib/cluster/connection.mjs +12 -42
  54. package/lib/cluster/errors.cjs +9 -1
  55. package/lib/cluster/errors.d.cts +29 -0
  56. package/lib/cluster/errors.d.mts +29 -0
  57. package/lib/cluster/errors.d.ts +4 -0
  58. package/lib/cluster/errors.mjs +7 -0
  59. package/lib/cluster/frames.d.cts +12 -0
  60. package/lib/cluster/frames.d.mts +12 -0
  61. package/lib/cluster/generated/protocol-schema.d.cts +1 -0
  62. package/lib/cluster/generated/protocol-schema.d.mts +1 -0
  63. package/lib/cluster/generated/protocol.d.cts +781 -0
  64. package/lib/cluster/generated/protocol.d.mts +781 -0
  65. package/lib/cluster/index.cjs +2 -2
  66. package/lib/cluster/index.d.cts +13 -0
  67. package/lib/cluster/index.d.mts +13 -0
  68. package/lib/cluster/index.d.ts +3 -3
  69. package/lib/cluster/index.mjs +2 -2
  70. package/lib/cluster/json-source.cjs +1 -0
  71. package/lib/cluster/json-source.d.cts +5 -0
  72. package/lib/cluster/json-source.d.mts +5 -0
  73. package/lib/cluster/json-source.d.ts +1 -0
  74. package/lib/cluster/json-source.mjs +1 -0
  75. package/lib/cluster/payload-value.cjs +1 -0
  76. package/lib/cluster/payload-value.d.cts +8 -0
  77. package/lib/cluster/payload-value.d.mts +8 -0
  78. package/lib/cluster/payload-value.d.ts +1 -0
  79. package/lib/cluster/payload-value.mjs +1 -0
  80. package/lib/cluster/queue.d.cts +15 -0
  81. package/lib/cluster/queue.d.mts +15 -0
  82. package/lib/cluster/socket.cjs +8 -0
  83. package/lib/cluster/socket.d.cts +13 -0
  84. package/lib/cluster/socket.d.mts +13 -0
  85. package/lib/cluster/socket.d.ts +2 -0
  86. package/lib/cluster/socket.mjs +7 -0
  87. package/lib/cluster/subscriptions.d.cts +73 -0
  88. package/lib/cluster/subscriptions.d.mts +73 -0
  89. package/lib/cluster/subscriptions.d.ts +5 -1
  90. package/lib/cluster/validators.cjs +1 -0
  91. package/lib/cluster/validators.d.cts +7 -0
  92. package/lib/cluster/validators.d.mts +7 -0
  93. package/lib/cluster/validators.d.ts +1 -0
  94. package/lib/cluster/validators.mjs +1 -0
  95. package/lib/cluster-worker/engine-adapter-common.js +37 -0
  96. package/lib/cluster-worker/engine-adapter.js +24 -30
  97. package/lib/cluster-worker/engine-start.js +59 -0
  98. package/lib/cluster-worker/index.js +49 -67
  99. package/lib/cluster-worker/profiles.js +1 -42
  100. package/lib/cluster-worker/runtime-dependencies.js +49 -0
  101. package/lib/cluster-worker/runtime-support.js +2 -34
  102. package/lib/cluster-worker/terminal-normalizer.js +9 -10
  103. package/lib/hosted-session/authority.cjs +61 -0
  104. package/lib/hosted-session/authority.d.cts +3 -0
  105. package/lib/hosted-session/authority.d.mts +3 -0
  106. package/lib/hosted-session/authority.d.ts +3 -0
  107. package/lib/hosted-session/authority.mjs +57 -0
  108. package/lib/hosted-session/coordinator.cjs +78 -51
  109. package/lib/hosted-session/coordinator.d.cts +11 -0
  110. package/lib/hosted-session/coordinator.d.mts +11 -0
  111. package/lib/hosted-session/coordinator.d.ts +4 -2
  112. package/lib/hosted-session/coordinator.mjs +75 -51
  113. package/lib/hosted-session/errors.cjs +25 -0
  114. package/lib/hosted-session/errors.d.cts +10 -0
  115. package/lib/hosted-session/errors.d.mts +10 -0
  116. package/lib/hosted-session/errors.d.ts +10 -0
  117. package/lib/hosted-session/errors.mjs +19 -0
  118. package/lib/hosted-session/index.cjs +4 -1
  119. package/lib/hosted-session/index.d.cts +2 -0
  120. package/lib/hosted-session/index.d.mts +2 -0
  121. package/lib/hosted-session/index.d.ts +2 -2
  122. package/lib/hosted-session/index.mjs +1 -1
  123. package/lib/hosted-session/reconnecting-watch.cjs +131 -0
  124. package/lib/hosted-session/reconnecting-watch.d.cts +13 -0
  125. package/lib/hosted-session/reconnecting-watch.d.mts +13 -0
  126. package/lib/hosted-session/reconnecting-watch.d.ts +13 -0
  127. package/lib/hosted-session/reconnecting-watch.mjs +127 -0
  128. package/lib/hosted-session/types.d.cts +32 -0
  129. package/lib/hosted-session/types.d.mts +32 -0
  130. package/lib/hosted-session/types.d.ts +19 -7
  131. package/lib/hosted-target/access-url.cjs +44 -0
  132. package/lib/hosted-target/access-url.d.cts +2 -0
  133. package/lib/hosted-target/access-url.d.mts +2 -0
  134. package/lib/hosted-target/access-url.d.ts +2 -0
  135. package/lib/hosted-target/access-url.mjs +41 -0
  136. package/lib/hosted-target/adapter-request.cjs +12 -0
  137. package/lib/hosted-target/adapter-request.d.cts +17 -0
  138. package/lib/hosted-target/adapter-request.d.mts +17 -0
  139. package/lib/hosted-target/adapter-request.d.ts +17 -0
  140. package/lib/hosted-target/adapter-request.mjs +9 -0
  141. package/lib/hosted-target/adapter-types.cjs +2 -0
  142. package/lib/hosted-target/adapter-types.d.cts +27 -0
  143. package/lib/hosted-target/adapter-types.d.mts +27 -0
  144. package/lib/hosted-target/adapter-types.d.ts +27 -0
  145. package/lib/hosted-target/adapter-types.mjs +1 -0
  146. package/lib/hosted-target/bounds.cjs +10 -0
  147. package/lib/hosted-target/bounds.d.cts +7 -0
  148. package/lib/hosted-target/bounds.d.mts +7 -0
  149. package/lib/hosted-target/bounds.d.ts +7 -0
  150. package/lib/hosted-target/bounds.mjs +7 -0
  151. package/lib/hosted-target/capsule-error-response.cjs +76 -0
  152. package/lib/hosted-target/capsule-error-response.d.cts +2 -0
  153. package/lib/hosted-target/capsule-error-response.d.mts +2 -0
  154. package/lib/hosted-target/capsule-error-response.d.ts +2 -0
  155. package/lib/hosted-target/capsule-error-response.mjs +73 -0
  156. package/lib/hosted-target/errors.cjs +88 -0
  157. package/lib/hosted-target/errors.d.cts +36 -0
  158. package/lib/hosted-target/errors.d.mts +36 -0
  159. package/lib/hosted-target/errors.d.ts +36 -0
  160. package/lib/hosted-target/errors.mjs +75 -0
  161. package/lib/hosted-target/index.cjs +16 -0
  162. package/lib/hosted-target/index.d.cts +3 -0
  163. package/lib/hosted-target/index.d.mts +3 -0
  164. package/lib/hosted-target/index.d.ts +3 -0
  165. package/lib/hosted-target/index.mjs +2 -0
  166. package/lib/hosted-target/response-validation.cjs +169 -0
  167. package/lib/hosted-target/response-validation.d.cts +5 -0
  168. package/lib/hosted-target/response-validation.d.mts +5 -0
  169. package/lib/hosted-target/response-validation.d.ts +5 -0
  170. package/lib/hosted-target/response-validation.mjs +163 -0
  171. package/lib/hosted-target/retry-executor.cjs +51 -0
  172. package/lib/hosted-target/retry-executor.d.cts +8 -0
  173. package/lib/hosted-target/retry-executor.d.mts +8 -0
  174. package/lib/hosted-target/retry-executor.d.ts +8 -0
  175. package/lib/hosted-target/retry-executor.mjs +48 -0
  176. package/lib/hosted-target/retry.cjs +78 -0
  177. package/lib/hosted-target/retry.d.cts +9 -0
  178. package/lib/hosted-target/retry.d.mts +9 -0
  179. package/lib/hosted-target/retry.d.ts +9 -0
  180. package/lib/hosted-target/retry.mjs +73 -0
  181. package/lib/hosted-target/target-adapter.cjs +10 -0
  182. package/lib/hosted-target/target-adapter.d.cts +3 -0
  183. package/lib/hosted-target/target-adapter.d.mts +3 -0
  184. package/lib/hosted-target/target-adapter.d.ts +3 -0
  185. package/lib/hosted-target/target-adapter.mjs +7 -0
  186. package/lib/hosted-target/types.cjs +4 -0
  187. package/lib/hosted-target/types.d.cts +50 -0
  188. package/lib/hosted-target/types.d.mts +50 -0
  189. package/lib/hosted-target/types.d.ts +50 -0
  190. package/lib/hosted-target/types.mjs +1 -0
  191. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +185 -0
  192. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +13 -0
  193. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +13 -0
  194. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +13 -0
  195. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +181 -0
  196. package/lib/start-cluster.js +3 -3
  197. package/lib/target/bounded-response.cjs +51 -0
  198. package/lib/target/bounded-response.d.cts +1 -0
  199. package/lib/target/bounded-response.d.mts +1 -0
  200. package/lib/target/bounded-response.d.ts +1 -0
  201. package/lib/target/bounded-response.js +51 -0
  202. package/lib/target/bounded-response.mjs +48 -0
  203. package/lib/target/credential-lock.js +1 -3
  204. package/lib/target/credential-store.js +9 -4
  205. package/lib/target/device-flow.cjs +213 -0
  206. package/lib/target/device-flow.d.cts +54 -0
  207. package/lib/target/device-flow.d.mts +54 -0
  208. package/lib/target/device-flow.d.ts +26 -14
  209. package/lib/target/device-flow.js +162 -58
  210. package/lib/target/device-flow.mjs +203 -0
  211. package/lib/target/discovery-errors.cjs +10 -0
  212. package/lib/target/discovery-errors.d.cts +3 -0
  213. package/lib/target/discovery-errors.d.mts +3 -0
  214. package/lib/target/discovery-errors.d.ts +3 -0
  215. package/lib/target/discovery-errors.js +10 -0
  216. package/lib/target/discovery-errors.mjs +6 -0
  217. package/lib/target/discovery-sections.cjs +191 -0
  218. package/lib/target/discovery-sections.d.cts +52 -0
  219. package/lib/target/discovery-sections.d.mts +52 -0
  220. package/lib/target/discovery-sections.d.ts +52 -0
  221. package/lib/target/discovery-sections.js +191 -0
  222. package/lib/target/discovery-sections.mjs +179 -0
  223. package/lib/target/discovery-validation.cjs +109 -0
  224. package/lib/target/discovery-validation.d.cts +28 -0
  225. package/lib/target/discovery-validation.d.mts +28 -0
  226. package/lib/target/discovery-validation.d.ts +28 -0
  227. package/lib/target/discovery-validation.js +109 -0
  228. package/lib/target/discovery-validation.mjs +99 -0
  229. package/lib/target/discovery.cjs +112 -0
  230. package/lib/target/discovery.d.cts +74 -0
  231. package/lib/target/discovery.d.mts +74 -0
  232. package/lib/target/discovery.d.ts +67 -5
  233. package/lib/target/discovery.js +94 -79
  234. package/lib/target/discovery.mjs +105 -0
  235. package/lib/target/index.d.ts +6 -6
  236. package/lib/target/index.js +36 -36
  237. package/lib/target/oauth-http.d.ts +2 -0
  238. package/lib/target/oauth-http.js +25 -0
  239. package/lib/target/refresh-exchange.d.ts +11 -0
  240. package/lib/target/refresh-exchange.js +24 -0
  241. package/lib/target/refresh-revocation.d.ts +8 -0
  242. package/lib/target/refresh-revocation.js +19 -0
  243. package/lib/target/route-template.cjs +85 -0
  244. package/lib/target/route-template.d.cts +7 -0
  245. package/lib/target/route-template.d.mts +7 -0
  246. package/lib/target/route-template.d.ts +7 -0
  247. package/lib/target/route-template.js +85 -0
  248. package/lib/target/route-template.mjs +81 -0
  249. package/lib/target/session-errors.d.ts +4 -0
  250. package/lib/target/session-errors.js +12 -0
  251. package/lib/target/session-verification.d.ts +10 -0
  252. package/lib/target/session-verification.js +42 -0
  253. package/lib/target/target-registry.d.ts +7 -3
  254. package/lib/target/target-registry.js +41 -10
  255. package/lib/target/target-session-manager.d.ts +48 -0
  256. package/lib/target/target-session-manager.js +226 -0
  257. package/lib/target/target-session.d.ts +39 -32
  258. package/lib/target/target-session.js +63 -151
  259. package/lib/target/token-response.cjs +51 -0
  260. package/lib/target/token-response.d.cts +7 -0
  261. package/lib/target/token-response.d.mts +7 -0
  262. package/lib/target/token-response.d.ts +7 -0
  263. package/lib/target/token-response.js +51 -0
  264. package/lib/target/token-response.mjs +48 -0
  265. package/package.json +14 -7
  266. package/protocol/openengine-cluster/v1/worker.schema.json +171 -9
  267. package/scripts/build-cluster.js +23 -3
  268. package/scripts/opcore-introduced-check.js +0 -2
  269. package/src/agent-cli-provider/adapters/codex.ts +0 -1
  270. package/src/agent-cli-provider/contract-actions.ts +9 -5
  271. package/src/agent-cli-provider/contract-invoke.ts +3 -2
  272. package/src/agent-cli-provider/contract-support.ts +12 -6
  273. package/src/agent-cli-provider/contract.ts +6 -1
  274. package/src/agent-cli-provider/index.ts +1 -0
  275. package/src/agent-cli-provider/provider-registry.ts +4 -0
  276. package/src/agent-cli-provider/single-agent-runtime.ts +32 -45
  277. package/src/cluster/client.ts +42 -15
  278. package/src/cluster/connection-state.ts +33 -0
  279. package/src/cluster/connection-types.ts +28 -0
  280. package/src/cluster/connection.ts +37 -68
  281. package/src/cluster/errors.ts +5 -0
  282. package/src/cluster/index.ts +3 -1
  283. package/src/cluster/json-source.ts +1 -0
  284. package/src/cluster/payload-value.ts +1 -0
  285. package/src/cluster/socket.ts +13 -0
  286. package/src/cluster/subscriptions.ts +7 -2
  287. package/src/cluster/validators.ts +1 -0
  288. package/src/cluster/ws.d.ts +1 -1
  289. package/src/hosted-session/authority.ts +77 -0
  290. package/src/hosted-session/coordinator.ts +113 -56
  291. package/src/hosted-session/errors.ts +21 -0
  292. package/src/hosted-session/index.ts +14 -2
  293. package/src/hosted-session/reconnecting-watch.ts +148 -0
  294. package/src/hosted-session/types.ts +22 -8
  295. package/src/hosted-target/access-url.ts +46 -0
  296. package/src/hosted-target/adapter-request.ts +28 -0
  297. package/src/hosted-target/adapter-types.ts +39 -0
  298. package/src/hosted-target/bounds.ts +1 -0
  299. package/src/hosted-target/capsule-error-response.ts +107 -0
  300. package/src/hosted-target/errors.ts +36 -35
  301. package/src/hosted-target/index.ts +10 -23
  302. package/src/hosted-target/response-validation.ts +170 -62
  303. package/src/hosted-target/retry-executor.ts +62 -0
  304. package/src/hosted-target/retry.ts +47 -11
  305. package/src/hosted-target/target-adapter.ts +12 -8
  306. package/src/hosted-target/types.ts +19 -18
  307. package/src/hosted-target/zero-cloud-v1-adapter.ts +229 -316
  308. package/src/isolation-manager.js +1 -16
  309. package/src/target/bounded-response.ts +68 -0
  310. package/src/target/credential-lock.ts +1 -3
  311. package/src/target/credential-store.ts +14 -10
  312. package/src/target/device-flow.ts +209 -85
  313. package/src/target/discovery-errors.ts +6 -0
  314. package/src/target/discovery-sections.ts +251 -0
  315. package/src/target/discovery-validation.ts +144 -0
  316. package/src/target/discovery.ts +163 -97
  317. package/src/target/index.ts +15 -11
  318. package/src/target/oauth-http.ts +27 -0
  319. package/src/target/refresh-exchange.ts +36 -0
  320. package/src/target/refresh-revocation.ts +29 -0
  321. package/src/target/route-template.ts +105 -0
  322. package/src/target/session-errors.ts +9 -0
  323. package/src/target/session-verification.ts +51 -0
  324. package/src/target/target-registry.ts +60 -27
  325. package/src/target/target-session-manager.ts +285 -0
  326. package/src/target/target-session.ts +105 -226
  327. package/src/target/token-response.ts +62 -0
  328. package/docker/zeroshot-oecp/Cargo.toml +0 -12
  329. package/docker/zeroshot-oecp/Dockerfile +0 -65
  330. package/docker/zeroshot-oecp/src/main.rs +0 -32
  331. package/lib/target/bounded-json.d.ts +0 -6
  332. package/lib/target/bounded-json.js +0 -43
  333. package/lib/target/hosted-run/client.d.ts +0 -26
  334. package/lib/target/hosted-run/client.js +0 -158
  335. package/lib/target/hosted-run/commands.d.ts +0 -4
  336. package/lib/target/hosted-run/commands.js +0 -113
  337. package/lib/target/hosted-run/contracts.d.ts +0 -49
  338. package/lib/target/hosted-run/input.d.ts +0 -5
  339. package/lib/target/hosted-run/input.js +0 -200
  340. package/lib/target/hosted-run.d.ts +0 -4
  341. package/lib/target/hosted-run.js +0 -12
  342. package/src/target/bounded-json.ts +0 -48
  343. package/src/target/hosted-run/client.ts +0 -198
  344. package/src/target/hosted-run/commands.ts +0 -140
  345. package/src/target/hosted-run/contracts.ts +0 -53
  346. package/src/target/hosted-run/input.ts +0 -199
  347. package/src/target/hosted-run.ts +0 -8
@@ -1,19 +1,19 @@
1
1
  import crypto from 'node:crypto';
2
+ import type { TargetDiscoveryDescriptor } from './discovery.js';
2
3
 
3
4
  export interface TargetRecord {
4
5
  readonly id: string;
5
6
  readonly url: string;
6
7
  readonly adapterVersion: string;
7
8
  readonly deviceToken: string;
8
- readonly organization?: { readonly id: string; readonly name: string };
9
+ readonly organization?: { readonly id: string; readonly name?: string };
10
+ readonly refreshInvalidated?: true;
9
11
  readonly createdAt: string;
10
12
  }
11
13
 
12
14
  export class TargetNameInvalidError extends Error {
13
15
  constructor(name: string) {
14
- super(
15
- `Invalid target name "${name}". Must be 1-64 characters, alphanumeric and hyphens only.`,
16
- );
16
+ super(`Invalid target name "${name}". Must be 1-64 characters, alphanumeric and hyphens only.`);
17
17
  this.name = 'TargetNameInvalidError';
18
18
  }
19
19
  }
@@ -40,7 +40,11 @@ export class TargetUrlInvalidError extends Error {
40
40
  }
41
41
 
42
42
  const TARGET_NAME_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$/;
43
- const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
43
+ const LOOPBACK_HOSTS: Readonly<Record<string, true>> = Object.freeze({
44
+ '127.0.0.1': true,
45
+ '::1': true,
46
+ '[::1]': true,
47
+ });
44
48
 
45
49
  export function validateTargetName(name: string): void {
46
50
  if (!TARGET_NAME_PATTERN.test(name) || name.length > 64) {
@@ -49,6 +53,9 @@ export function validateTargetName(name: string): void {
49
53
  }
50
54
 
51
55
  export function normalizeAndValidateUrl(rawUrl: string): string {
56
+ if (/[\u0000-\u0020\u007f]|\s/u.test(rawUrl)) {
57
+ throw new TargetUrlInvalidError(rawUrl, 'URL contains forbidden whitespace or controls');
58
+ }
52
59
  let parsed: URL;
53
60
  try {
54
61
  parsed = new URL(rawUrl);
@@ -64,16 +71,20 @@ export function normalizeAndValidateUrl(rawUrl: string): string {
64
71
  throw new TargetUrlInvalidError(rawUrl, 'URL must not contain query or fragment');
65
72
  }
66
73
 
67
- const isLoopback = LOOPBACK_HOSTS.has(parsed.hostname);
68
- if (parsed.protocol !== 'https:' && !isLoopback) {
69
- throw new TargetUrlInvalidError(rawUrl, 'HTTPS required for non-loopback targets');
74
+ const isLoopback = LOOPBACK_HOSTS[parsed.hostname] === true;
75
+ const protocolAllowed =
76
+ parsed.protocol === 'https:' || (parsed.protocol === 'http:' && isLoopback);
77
+ if (!protocolAllowed) {
78
+ throw new TargetUrlInvalidError(
79
+ rawUrl,
80
+ 'HTTPS required except for literal loopback HTTP targets'
81
+ );
70
82
  }
71
-
72
- let normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
73
- if (normalized.endsWith('/') && normalized.length > 1) {
74
- normalized = normalized.slice(0, -1);
83
+ if (parsed.pathname !== '/') {
84
+ throw new TargetUrlInvalidError(rawUrl, 'URL must contain only an origin');
75
85
  }
76
- return normalized;
86
+
87
+ return parsed.origin;
77
88
  }
78
89
 
79
90
  interface SettingsWithTargets {
@@ -90,6 +101,7 @@ export function addTarget(
90
101
  name: string,
91
102
  rawUrl: string,
92
103
  settings: SettingsPort,
104
+ descriptor: TargetDiscoveryDescriptor
93
105
  ): TargetRecord {
94
106
  validateTargetName(name);
95
107
  const url = normalizeAndValidateUrl(rawUrl);
@@ -98,11 +110,14 @@ export function addTarget(
98
110
  if (existing._targets?.[name]) {
99
111
  throw new TargetNameExistsError(name);
100
112
  }
113
+ if (!descriptor || descriptor.origin !== url || descriptor.adapter.majorVersion !== 1) {
114
+ throw new TargetUrlInvalidError(rawUrl, 'validated discovery does not match the target origin');
115
+ }
101
116
 
102
117
  const record: TargetRecord = {
103
118
  id: crypto.randomUUID(),
104
119
  url,
105
- adapterVersion: 'v1',
120
+ adapterVersion: `v${descriptor.adapter.majorVersion}`,
106
121
  deviceToken: crypto.randomUUID(),
107
122
  createdAt: new Date().toISOString(),
108
123
  };
@@ -117,10 +132,7 @@ export function addTarget(
117
132
  return record;
118
133
  }
119
134
 
120
- export function removeTarget(
121
- name: string,
122
- settings: SettingsPort,
123
- ): TargetRecord {
135
+ export function removeTarget(name: string, settings: SettingsPort): TargetRecord {
124
136
  const existing = settings.load();
125
137
  const record = existing._targets?.[name];
126
138
  if (!record) {
@@ -136,17 +148,12 @@ export function removeTarget(
136
148
  return record;
137
149
  }
138
150
 
139
- export function getTarget(
140
- name: string,
141
- settings: SettingsPort,
142
- ): TargetRecord | null {
151
+ export function getTarget(name: string, settings: SettingsPort): TargetRecord | null {
143
152
  const existing = settings.load();
144
153
  return existing._targets?.[name] ?? null;
145
154
  }
146
155
 
147
- export function listTargets(
148
- settings: SettingsPort,
149
- ): Array<{ name: string; record: TargetRecord }> {
156
+ export function listTargets(settings: SettingsPort): Array<{ name: string; record: TargetRecord }> {
150
157
  const existing = settings.load();
151
158
  const targets = existing._targets ?? {};
152
159
  return Object.entries(targets).map(([name, record]) => ({ name, record }));
@@ -154,8 +161,8 @@ export function listTargets(
154
161
 
155
162
  export function updateTargetOrganization(
156
163
  name: string,
157
- organization: { id: string; name: string },
158
- settings: SettingsPort,
164
+ organization: { id: string; name?: string },
165
+ settings: SettingsPort
159
166
  ): void {
160
167
  const existing = settings.load();
161
168
  if (!existing._targets?.[name]) {
@@ -172,3 +179,29 @@ export function updateTargetOrganization(
172
179
  }
173
180
  });
174
181
  }
182
+
183
+ export function targetRefreshIsInvalidated(
184
+ name: string,
185
+ settings: SettingsPort,
186
+ ): boolean {
187
+ return settings.load()._targets?.[name]?.refreshInvalidated === true;
188
+ }
189
+
190
+ export function setTargetRefreshInvalidated(
191
+ name: string,
192
+ invalidated: boolean,
193
+ settings: SettingsPort,
194
+ ): void {
195
+ settings.mutate((state) => {
196
+ const targets = state._targets;
197
+ const target = targets?.[name];
198
+ if (targets === undefined || target === undefined) throw new TargetNotFoundError(name);
199
+ if (invalidated) {
200
+ targets[name] = { ...target, refreshInvalidated: true };
201
+ } else {
202
+ const active = { ...target };
203
+ Reflect.deleteProperty(active, 'refreshInvalidated');
204
+ targets[name] = active;
205
+ }
206
+ });
207
+ }
@@ -0,0 +1,285 @@
1
+ import type { TargetCredentialStore } from './credential-store.js';
2
+ import { exchangeRefreshToken } from './refresh-exchange.js';
3
+ import type { TargetRecord, SettingsPort } from './target-registry.js';
4
+ import {
5
+ requestDeviceCode,
6
+ pollForToken,
7
+ type Clock,
8
+ type HttpTransport,
9
+ } from './device-flow.js';
10
+ import { targetServiceKey, TARGET_ACCOUNT } from './credential-store.js';
11
+ import {
12
+ setTargetRefreshInvalidated,
13
+ targetRefreshIsInvalidated,
14
+ updateTargetOrganization,
15
+ } from './target-registry.js';
16
+ import type { TargetSessionEndpoints } from './discovery.js';
17
+ import { LoginRequiredError } from './session-errors.js';
18
+ export { LoginRequiredError } from './session-errors.js';
19
+ import { readVerifiedOrganization } from './session-verification.js';
20
+ import { revokeRefreshToken } from './refresh-revocation.js';
21
+
22
+ const DEVICE_LABEL = 'zeroshot-cli';
23
+ const TOKEN_EXPIRY_SKEW_MS = 30_000;
24
+ const AUDIENCE_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
25
+ const INVALID_REFRESH_FAMILY = 'zeroshot.invalidated-refresh-family/v1';
26
+
27
+
28
+ export interface BrowserOpener {
29
+ open(url: string): Promise<void>;
30
+ }
31
+
32
+ export interface TargetSessionDeps {
33
+ readonly http: HttpTransport;
34
+ readonly clock: Clock;
35
+ readonly browserOpener: BrowserOpener;
36
+ readonly stderr: { write(s: string): void };
37
+ readonly discoveryEndpoints: TargetSessionEndpoints;
38
+ }
39
+
40
+ export interface TargetSessionManagerInit {
41
+ readonly targetName: string;
42
+ readonly target: TargetRecord;
43
+ readonly credentialStore: TargetCredentialStore;
44
+ readonly acquireLock: () => Promise<() => Promise<void>>;
45
+ readonly settings: SettingsPort;
46
+ readonly deps: TargetSessionDeps;
47
+ }
48
+
49
+ interface CachedAccess {
50
+ readonly token: string;
51
+ readonly expiresAt: number;
52
+ }
53
+
54
+ function abortReason(signal?: AbortSignal): unknown {
55
+ return signal?.reason ?? new DOMException('Aborted', 'AbortError');
56
+ }
57
+
58
+ function requireAudience(audience: string): void {
59
+ if (!AUDIENCE_PATTERN.test(audience)) throw new Error('Requested target audience is invalid');
60
+ }
61
+
62
+ /**
63
+ * Sole owner of one target's rotating refresh family and audience-scoped access cache.
64
+ * The cross-process lock covers durable reread, exchange, replacement, and invalidation.
65
+ */
66
+ export class TargetSessionManager {
67
+ readonly #targetName: string;
68
+ readonly #target: TargetRecord;
69
+ readonly #store: TargetCredentialStore;
70
+ readonly #acquireLock: () => Promise<() => Promise<void>>;
71
+ readonly #settings: SettingsPort;
72
+ readonly #deps: TargetSessionDeps;
73
+ readonly #access = new Map<string, CachedAccess>();
74
+
75
+ constructor(init: TargetSessionManagerInit) {
76
+ this.#targetName = init.targetName;
77
+ this.#target = init.target;
78
+ this.#store = init.credentialStore;
79
+ this.#acquireLock = init.acquireLock;
80
+ this.#settings = init.settings;
81
+ this.#deps = init.deps;
82
+ }
83
+
84
+ #cachedAccess(audience: string): { accessToken: string; expiresIn: number } | null {
85
+ const cached = this.#access.get(audience);
86
+ if (
87
+ cached === undefined ||
88
+ this.#deps.clock.now() >= cached.expiresAt - TOKEN_EXPIRY_SKEW_MS
89
+ ) {
90
+ return null;
91
+ }
92
+ return {
93
+ accessToken: cached.token,
94
+ expiresIn: Math.max(
95
+ 0,
96
+ Math.floor((cached.expiresAt - this.#deps.clock.now()) / 1_000),
97
+ ),
98
+ };
99
+ }
100
+
101
+ async login(signal?: AbortSignal): Promise<{ organization: { id: string } }> {
102
+ const endpoints = this.#deps.discoveryEndpoints;
103
+ const code = await requestDeviceCode(
104
+ endpoints.deviceAuthorizationEndpoint,
105
+ endpoints.clientId,
106
+ this.#deps.http,
107
+ signal
108
+ );
109
+ this.#deps.stderr.write(
110
+ `\nOpen this URL to authorize:\n ${code.verification_uri}\n\nEnter code: ${code.user_code}\n\n`
111
+ );
112
+ if (code.verification_uri_complete) {
113
+ try {
114
+ await this.#deps.browserOpener.open(code.verification_uri_complete);
115
+ } catch {
116
+ // Opening a browser is explicitly optional; polling remains authoritative.
117
+ }
118
+ }
119
+
120
+ const release = await this.#acquireLock();
121
+ try {
122
+ const storedPrevious = await this.#store.get(this.#serviceKey(), TARGET_ACCOUNT);
123
+ const previous = storedPrevious === INVALID_REFRESH_FAMILY ? null : storedPrevious;
124
+
125
+ const token = await pollForToken({
126
+ tokenEndpoint: endpoints.tokenEndpoint,
127
+ clientId: endpoints.clientId,
128
+ deviceCode: code.device_code,
129
+ interval: code.interval,
130
+ expiresIn: code.expires_in,
131
+ http: this.#deps.http,
132
+ clock: this.#deps.clock,
133
+ ...(signal === undefined ? {} : { signal }),
134
+ exchange: {
135
+ grantType: endpoints.deviceGrantType,
136
+ deviceToken: this.#target.deviceToken,
137
+ deviceLabel: DEVICE_LABEL,
138
+ audience: endpoints.audience,
139
+ },
140
+ });
141
+ const organization = await readVerifiedOrganization(
142
+ token.access_token,
143
+ signal,
144
+ this.#deps,
145
+ ).catch(
146
+ async () => {
147
+ await this.#bestEffortRevoke(token.refresh_token);
148
+ throw new LoginRequiredError(this.#targetName);
149
+ }
150
+ );
151
+ try {
152
+ await this.#store.set(this.#serviceKey(), TARGET_ACCOUNT, token.refresh_token);
153
+ setTargetRefreshInvalidated(this.#targetName, false, this.#settings);
154
+ this.#access.set(endpoints.audience, {
155
+ token: token.access_token,
156
+ expiresAt: this.#deps.clock.now() + token.expires_in * 1_000,
157
+ });
158
+ updateTargetOrganization(this.#targetName, organization, this.#settings);
159
+ if (previous && previous !== token.refresh_token) {
160
+ const revoked = await revokeRefreshToken(previous, this.#deps);
161
+ if (!revoked.ok) throw new Error('Prior refresh family revocation failed');
162
+ }
163
+ } catch {
164
+ await this.#invalidateFamily(token.refresh_token);
165
+ throw new LoginRequiredError(this.#targetName);
166
+ }
167
+ return { organization };
168
+ } finally {
169
+ await release();
170
+ }
171
+ }
172
+
173
+ async getAccessTokenWithLifetime(
174
+ audience: string,
175
+ signal?: AbortSignal,
176
+ ): Promise<{ accessToken: string; expiresIn: number }> {
177
+ requireAudience(audience);
178
+ const cached = this.#cachedAccess(audience);
179
+ if (cached !== null) return cached;
180
+ if (signal?.aborted) throw abortReason(signal);
181
+
182
+ const release = await this.#acquireLock();
183
+ try {
184
+ const afterLock = this.#cachedAccess(audience);
185
+ if (afterLock !== null) return afterLock;
186
+ if (targetRefreshIsInvalidated(this.#targetName, this.#settings)) {
187
+ throw new LoginRequiredError(this.#targetName);
188
+ }
189
+ const refreshToken = await this.#store.get(this.#serviceKey(), TARGET_ACCOUNT);
190
+ if (!refreshToken || refreshToken === INVALID_REFRESH_FAMILY) {
191
+ throw new LoginRequiredError(this.#targetName);
192
+ }
193
+ if (signal?.aborted) throw abortReason(signal);
194
+ setTargetRefreshInvalidated(this.#targetName, true, this.#settings);
195
+ await this.#store.set(this.#serviceKey(), TARGET_ACCOUNT, INVALID_REFRESH_FAMILY);
196
+
197
+ let replacement: Awaited<ReturnType<typeof exchangeRefreshToken>> | undefined;
198
+ try {
199
+ replacement = await exchangeRefreshToken({
200
+ tokenEndpoint: this.#deps.discoveryEndpoints.tokenEndpoint,
201
+ clientId: this.#deps.discoveryEndpoints.clientId,
202
+ refreshToken,
203
+ audience,
204
+ http: this.#deps.http,
205
+ ...(signal === undefined ? {} : { signal }),
206
+ });
207
+ await this.#store.set(this.#serviceKey(), TARGET_ACCOUNT, replacement.refresh_token);
208
+ setTargetRefreshInvalidated(this.#targetName, false, this.#settings);
209
+ this.#access.clear();
210
+ this.#access.set(audience, {
211
+ token: replacement.access_token,
212
+ expiresAt: this.#deps.clock.now() + replacement.expires_in * 1_000,
213
+ });
214
+ return { accessToken: replacement.access_token, expiresIn: replacement.expires_in };
215
+ } catch {
216
+ await this.#invalidateFamily(replacement?.refresh_token);
217
+ throw new LoginRequiredError(this.#targetName);
218
+ }
219
+ } finally {
220
+ await release();
221
+ }
222
+ }
223
+
224
+ async getAccessToken(audience: string, signal?: AbortSignal): Promise<string> {
225
+ return (await this.getAccessTokenWithLifetime(audience, signal)).accessToken;
226
+ }
227
+
228
+ tokenProvider(audience: string): { getAccessToken(signal?: AbortSignal): Promise<string> } {
229
+ requireAudience(audience);
230
+ return Object.freeze({
231
+ getAccessToken: (signal?: AbortSignal) => this.getAccessToken(audience, signal),
232
+ });
233
+ }
234
+
235
+ async revoke(force: boolean): Promise<void> {
236
+ const release = await this.#acquireLock();
237
+ try {
238
+ const token = await this.#store.get(this.#serviceKey(), TARGET_ACCOUNT);
239
+ if (token && token !== INVALID_REFRESH_FAMILY) {
240
+ try {
241
+ const response = await revokeRefreshToken(token, this.#deps);
242
+ if (!response.ok && !force)
243
+ throw new Error('Remote revocation failed. Use --force to remove anyway.');
244
+ } catch (error) {
245
+ if (!force) throw error;
246
+ }
247
+ }
248
+ this.#access.clear();
249
+ await this.#store.delete(this.#serviceKey(), TARGET_ACCOUNT);
250
+ setTargetRefreshInvalidated(this.#targetName, false, this.#settings);
251
+ } finally {
252
+ await release();
253
+ }
254
+ }
255
+
256
+ clearMemory(): void {
257
+ this.#access.clear();
258
+ }
259
+
260
+
261
+ async #invalidateFamily(replacement?: string): Promise<void> {
262
+ this.#access.clear();
263
+ setTargetRefreshInvalidated(this.#targetName, true, this.#settings);
264
+ try {
265
+ await this.#store.set(this.#serviceKey(), TARGET_ACCOUNT, INVALID_REFRESH_FAMILY);
266
+ } catch {
267
+ // A direct delete remains safe if overwriting the stale family was unavailable.
268
+ }
269
+ if (replacement) await this.#bestEffortRevoke(replacement);
270
+ await this.#store.delete(this.#serviceKey(), TARGET_ACCOUNT);
271
+ }
272
+
273
+ async #bestEffortRevoke(token: string): Promise<void> {
274
+ try {
275
+ await revokeRefreshToken(token, this.#deps);
276
+ } catch {
277
+ // Revocation is compensating cleanup; callers still receive login-required.
278
+ }
279
+ }
280
+
281
+
282
+ #serviceKey(): string {
283
+ return targetServiceKey(this.#target.id);
284
+ }
285
+ }