hyperbrane 0.1.1 → 0.2.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 (287) hide show
  1. package/README.md +81 -11
  2. package/dist/agent/agentClient.d.ts +5 -0
  3. package/dist/agent/agentClient.js +4 -0
  4. package/dist/agent/agentService.d.ts +9 -30
  5. package/dist/agent/agentService.js +62 -293
  6. package/dist/agent/agentStorage.d.ts +1 -20
  7. package/dist/agent/agentStorage.js +0 -30
  8. package/dist/agent/api.d.ts +89 -133
  9. package/dist/agent/api.js +4 -4
  10. package/dist/agent/client.d.ts +3 -1
  11. package/dist/agent/client.js +2 -3
  12. package/dist/agent/control.d.ts +23 -0
  13. package/dist/agent/control.js +479 -0
  14. package/dist/agent/hostTranscript.d.ts +0 -6
  15. package/dist/agent/hostTranscript.js +3 -14
  16. package/dist/agent/session/backend.d.ts +17 -30
  17. package/dist/agent/session/claude/home.d.ts +0 -1
  18. package/dist/agent/session/claude/home.js +0 -1
  19. package/dist/agent/session/claude/provision.d.ts +10 -0
  20. package/dist/agent/session/claude/provision.js +17 -0
  21. package/dist/agent/session/claude/transcript.d.ts +2 -7
  22. package/dist/agent/session/claude/transcript.js +68 -14
  23. package/dist/agent/session/client.d.ts +1 -10
  24. package/dist/agent/session/client.js +0 -3
  25. package/dist/agent/session/codex/provision.d.ts +14 -0
  26. package/dist/agent/session/codex/provision.js +25 -0
  27. package/dist/agent/session/fileTranscript.d.ts +6 -49
  28. package/dist/agent/session/fileTranscript.js +26 -82
  29. package/dist/agent/session/install.d.ts +5 -0
  30. package/dist/agent/session/install.js +35 -0
  31. package/dist/agent/session/sessionStorage.d.ts +4 -35
  32. package/dist/agent/session/sessionStorage.js +10 -16
  33. package/dist/agent/session/sharedConnection.d.ts +0 -8
  34. package/dist/agent/session/sharedConnection.js +0 -7
  35. package/dist/agent/session/storedTranscripts.d.ts +1 -6
  36. package/dist/agent/session/storedTranscripts.js +0 -3
  37. package/dist/agent/session/worker/jsonRpc.d.ts +2 -13
  38. package/dist/agent/session/worker/jsonRpc.js +2 -11
  39. package/dist/agent/session/worker/protocol.d.ts +27 -18
  40. package/dist/agent/session/worker/protocol.js +32 -16
  41. package/dist/agent/session/worker/workerParams.d.ts +0 -5
  42. package/dist/agent/session/worker/workerParams.js +15 -14
  43. package/dist/agent/session/workerConnection.d.ts +2 -13
  44. package/dist/agent/session/workerConnection.js +7 -12
  45. package/dist/agent/session/workerGatewayConnection.d.ts +18 -0
  46. package/dist/agent/session/workerGatewayConnection.js +86 -0
  47. package/dist/agent/vendor-types/manifest.json +87 -0
  48. package/dist/agent/workerBundle.d.ts +4 -19
  49. package/dist/agent/workerBundle.generated.js +1 -1
  50. package/dist/agent/workerBundle.js +3 -30
  51. package/dist/agent/workerReplacement.d.ts +3 -0
  52. package/dist/agent/workerReplacement.js +49 -0
  53. package/dist/cli/agent.js +44 -16
  54. package/dist/cli/command.d.ts +10 -11
  55. package/dist/cli/command.js +0 -4
  56. package/dist/cli/dimension.js +6 -6
  57. package/dist/cli/gateway.js +3 -6
  58. package/dist/cli/main.js +14 -31
  59. package/dist/cli/streamPrinter.d.ts +2 -10
  60. package/dist/cli/streamPrinter.js +16 -46
  61. package/dist/cli/table.d.ts +0 -3
  62. package/dist/cli/table.js +0 -4
  63. package/dist/dimension/api.d.ts +39 -27
  64. package/dist/dimension/api.js +28 -47
  65. package/dist/dimension/client.d.ts +5 -0
  66. package/dist/dimension/client.js +4 -0
  67. package/dist/dimension/control.d.ts +21 -0
  68. package/dist/dimension/control.js +328 -0
  69. package/dist/dimension/image.d.ts +0 -5
  70. package/dist/dimension/image.js +0 -8
  71. package/dist/dimension/imageFiles.generated.js +1 -1
  72. package/dist/dimension/paths.d.ts +5 -4
  73. package/dist/dimension/paths.js +15 -6
  74. package/dist/dimension/service.d.ts +8 -21
  75. package/dist/dimension/service.js +60 -224
  76. package/dist/dimension/transport-worker.d.ts +1 -0
  77. package/dist/dimension/transport-worker.js +151 -0
  78. package/dist/dimension/transportBundle.d.ts +2 -0
  79. package/dist/dimension/transportBundle.generated.d.ts +1 -0
  80. package/dist/dimension/transportBundle.generated.js +2 -0
  81. package/dist/dimension/transportBundle.js +4 -0
  82. package/dist/dimension/transportClient.d.ts +10 -0
  83. package/dist/dimension/transportClient.js +115 -0
  84. package/dist/dimension/transportProtocol.d.ts +53 -0
  85. package/dist/dimension/transportProtocol.js +8 -0
  86. package/dist/index.d.ts +5 -8
  87. package/dist/index.js +5 -16
  88. package/dist/lib/control.d.ts +8 -0
  89. package/dist/lib/control.js +62 -0
  90. package/dist/lib/docker.d.ts +0 -22
  91. package/dist/lib/docker.js +0 -10
  92. package/dist/lib/image.d.ts +0 -3
  93. package/dist/lib/image.js +0 -2
  94. package/dist/lib/jsonDirectory.d.ts +0 -13
  95. package/dist/lib/jsonDirectory.js +10 -15
  96. package/dist/lib/scriptBundle.d.ts +6 -0
  97. package/dist/lib/scriptBundle.js +28 -0
  98. package/dist/lib/sh.d.ts +2 -0
  99. package/dist/lib/sh.js +8 -0
  100. package/dist/lib/sleep.d.ts +0 -1
  101. package/dist/lib/sleep.js +0 -1
  102. package/dist/lib/streamFetch.d.ts +15 -0
  103. package/dist/lib/streamFetch.js +150 -0
  104. package/dist/lib/tar.d.ts +0 -1
  105. package/dist/lib/tar.js +5 -11
  106. package/dist/portal-gateway/api.d.ts +13 -44
  107. package/dist/portal-gateway/api.js +1 -3
  108. package/dist/portal-gateway/control.d.ts +42 -0
  109. package/dist/portal-gateway/control.js +657 -0
  110. package/dist/portal-gateway/gatewayClient.d.ts +5 -0
  111. package/dist/portal-gateway/gatewayClient.js +4 -0
  112. package/dist/portal-gateway/gatewayService.d.ts +8 -35
  113. package/dist/portal-gateway/gatewayService.js +79 -253
  114. package/dist/portal-gateway/image.d.ts +1 -2
  115. package/dist/portal-gateway/image.js +1 -3
  116. package/dist/portal-gateway/imageFiles.generated.js +3 -3
  117. package/dist/portal-gateway/portalClient.d.ts +5 -0
  118. package/dist/portal-gateway/portalClient.js +4 -0
  119. package/dist/portal-gateway/portalService.d.ts +5 -47
  120. package/dist/portal-gateway/portalService.js +19 -317
  121. package/dist/portal-gateway/transport-worker.d.ts +1 -0
  122. package/dist/portal-gateway/transport-worker.js +205 -0
  123. package/dist/portal-gateway/transportBundle.d.ts +2 -0
  124. package/dist/portal-gateway/transportBundle.generated.d.ts +1 -0
  125. package/dist/portal-gateway/transportBundle.generated.js +2 -0
  126. package/dist/portal-gateway/transportBundle.js +4 -0
  127. package/dist/portal-gateway/upstream.d.ts +12 -82
  128. package/dist/portal-gateway/upstream.js +0 -1
  129. package/dist/sandbox/api.d.ts +121 -178
  130. package/dist/sandbox/api.js +5 -7
  131. package/dist/sandbox/client.d.ts +2 -0
  132. package/dist/sandbox/client.js +4 -0
  133. package/dist/sandbox/data-endpoints/dataEndpoints.d.ts +13 -0
  134. package/dist/sandbox/data-endpoints/dataEndpoints.js +100 -0
  135. package/dist/sandbox/data-endpoints/firewalledPorts.d.ts +4 -0
  136. package/dist/sandbox/data-endpoints/firewalledPorts.js +28 -0
  137. package/dist/sandbox/lib/httpOverStream.d.ts +3 -0
  138. package/dist/sandbox/lib/httpOverStream.js +21 -0
  139. package/dist/sandbox/lib/remoteFileOperations.d.ts +10 -0
  140. package/dist/sandbox/lib/remoteFileOperations.js +70 -0
  141. package/dist/sandbox/lib/shellCommand.d.ts +3 -0
  142. package/dist/sandbox/lib/shellCommand.js +7 -0
  143. package/dist/sandbox/providers/boat/boatService.d.ts +2 -0
  144. package/dist/sandbox/providers/boat/boatService.js +321 -0
  145. package/dist/sandbox/providers/exedev/exeDevService.d.ts +2 -0
  146. package/dist/sandbox/providers/exedev/exeDevService.js +127 -0
  147. package/dist/sandbox/providers/exedev/exeDevToken.d.ts +1 -0
  148. package/dist/sandbox/providers/exedev/exeDevToken.js +54 -0
  149. package/dist/sandbox/providers/local/localService.d.ts +2 -0
  150. package/dist/sandbox/providers/local/localService.js +219 -0
  151. package/dist/sandbox/providers/ssh/sshService.d.ts +2 -0
  152. package/dist/sandbox/providers/ssh/sshService.js +82 -0
  153. package/dist/sandbox/providers/ssh/sshTransport.d.ts +12 -0
  154. package/dist/sandbox/providers/ssh/sshTransport.js +216 -0
  155. package/dist/sandbox/service.d.ts +2 -0
  156. package/dist/sandbox/service.js +18 -0
  157. package/dist/sandbox/storage/descriptors.d.ts +6 -0
  158. package/dist/sandbox/storage/descriptors.js +25 -0
  159. package/dist/sandbox/storage/sandboxStorage.d.ts +11 -0
  160. package/dist/sandbox/storage/sandboxStorage.js +42 -0
  161. package/dist/upstreams/anthropic.d.ts +0 -5
  162. package/dist/upstreams/anthropic.js +1 -8
  163. package/dist/upstreams/index.d.ts +2 -6
  164. package/dist/upstreams/index.js +0 -4
  165. package/dist/upstreams/openai.d.ts +0 -5
  166. package/dist/upstreams/openai.js +1 -13
  167. package/package.json +12 -4
  168. package/dist/agent/gatewaySignIns.d.ts +0 -5
  169. package/dist/agent/gatewaySignIns.js +0 -11
  170. package/dist/agent/session/codex/home.d.ts +0 -2
  171. package/dist/agent/session/codex/home.js +0 -4
  172. package/dist/agent/session/codex/messages.d.ts +0 -30
  173. package/dist/agent/session/codex/messages.js +0 -135
  174. package/dist/agent/session/codex/transcript.d.ts +0 -12
  175. package/dist/agent/session/codex/transcript.js +0 -22
  176. package/dist/agent/session/sdkMessages.d.ts +0 -15
  177. package/dist/agent/session/sdkMessages.js +0 -28
  178. package/dist/agent/session/workerSseConnection.d.ts +0 -15
  179. package/dist/agent/session/workerSseConnection.js +0 -26
  180. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +0 -10
  181. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +0 -2
  182. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +0 -2
  183. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +0 -2
  184. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +0 -22
  185. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +0 -2
  186. package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +0 -1
  187. package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +0 -2
  188. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +0 -5
  189. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +0 -2
  190. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +0 -11
  191. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +0 -2
  192. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +0 -23
  193. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +0 -2
  194. package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +0 -7
  195. package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +0 -2
  196. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +0 -4
  197. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +0 -2
  198. package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +0 -7
  199. package/dist/agent/vendor-types/codex/app-server/SleepItem.js +0 -2
  200. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +0 -14
  201. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +0 -2
  202. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +0 -3
  203. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +0 -2
  204. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +0 -1
  205. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +0 -2
  206. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +0 -4
  207. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +0 -2
  208. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +0 -4
  209. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +0 -2
  210. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +0 -5
  211. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +0 -2
  212. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +0 -1
  213. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +0 -2
  214. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +0 -1
  215. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +0 -2
  216. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +0 -1
  217. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +0 -2
  218. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +0 -19
  219. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +0 -2
  220. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +0 -1
  221. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +0 -2
  222. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +0 -1
  223. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +0 -2
  224. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +0 -10
  225. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +0 -2
  226. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +0 -1
  227. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +0 -2
  228. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +0 -6
  229. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +0 -2
  230. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +0 -4
  231. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +0 -2
  232. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +0 -1
  233. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +0 -2
  234. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +0 -8
  235. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +0 -2
  236. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +0 -7
  237. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +0 -2
  238. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +0 -3
  239. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +0 -2
  240. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +0 -6
  241. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +0 -2
  242. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +0 -1
  243. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +0 -2
  244. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +0 -5
  245. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +0 -2
  246. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +0 -6
  247. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +0 -2
  248. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +0 -1
  249. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +0 -2
  250. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +0 -8
  251. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +0 -2
  252. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +0 -1
  253. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +0 -2
  254. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +0 -11
  255. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +0 -2
  256. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +0 -215
  257. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +0 -2
  258. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +0 -35
  259. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +0 -2
  260. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +0 -14
  261. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +0 -2
  262. package/dist/cli/services.d.ts +0 -19
  263. package/dist/cli/services.js +0 -33
  264. package/dist/dimension/http.d.ts +0 -7
  265. package/dist/dimension/http.js +0 -77
  266. package/dist/dimension/storage.d.ts +0 -39
  267. package/dist/dimension/storage.js +0 -79
  268. package/dist/portal-gateway/fakes.d.ts +0 -3
  269. package/dist/portal-gateway/fakes.js +0 -11
  270. package/dist/portal-gateway/gatewayStorage.d.ts +0 -66
  271. package/dist/portal-gateway/gatewayStorage.js +0 -125
  272. package/dist/portal-gateway/portalStorage.d.ts +0 -34
  273. package/dist/portal-gateway/portalStorage.js +0 -44
  274. package/dist/sandbox/boatService.d.ts +0 -52
  275. package/dist/sandbox/boatService.js +0 -343
  276. package/dist/sandbox/containerService.d.ts +0 -15
  277. package/dist/sandbox/containerService.js +0 -184
  278. package/dist/sandbox/endpointProxy.d.ts +0 -90
  279. package/dist/sandbox/endpointProxy.js +0 -701
  280. package/dist/sandbox/exeDevAuth.d.ts +0 -16
  281. package/dist/sandbox/exeDevAuth.js +0 -49
  282. package/dist/sandbox/localService.d.ts +0 -19
  283. package/dist/sandbox/localService.js +0 -200
  284. package/dist/sandbox/sseConnection.d.ts +0 -41
  285. package/dist/sandbox/sseConnection.js +0 -166
  286. package/dist/sandbox/sshService.d.ts +0 -30
  287. package/dist/sandbox/sshService.js +0 -680
@@ -0,0 +1,321 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { posix } from "node:path";
3
+ import { BoatApi, Configuration, ResponseError, waitUntilReady } from "@boatdev/sdk";
4
+ import ssh2 from "ssh2";
5
+ import { SandboxNotFoundError, SandboxStoppedError, } from "../../api.js";
6
+ import { commandLine, quote } from "../../lib/shellCommand.js";
7
+ import { commandFileOperations } from "../../lib/remoteFileOperations.js";
8
+ import { createDescriptor, getDescriptor, openDescriptor } from "../../storage/descriptors.js";
9
+ import { createEndpoints } from "../../data-endpoints/dataEndpoints.js";
10
+ import { createSandboxStorage } from "../../storage/sandboxStorage.js";
11
+ import { sshConnect, sshFetch } from "../ssh/sshTransport.js";
12
+ const BOAT_API_URL = "https://boat.dev/api/v1";
13
+ const COMMAND_TIMEOUT_S = 600;
14
+ const READY_TIMEOUT_MS = 180_000;
15
+ const RUNNING_STATES = ["ready", "idle", "running"];
16
+ const UNDELIVERED = ["localhost_relay_failed", "sandbox_direct_failed"];
17
+ const UNDELIVERED_RETRY_MS = [1000, 2000, 4000];
18
+ const DEFAULT_PORTS = {
19
+ from: 8900,
20
+ to: 8999,
21
+ };
22
+ class BoatError extends Error {
23
+ status;
24
+ code;
25
+ name = "BoatError";
26
+ constructor(status, code, message) {
27
+ super(message);
28
+ this.status = status;
29
+ this.code = code;
30
+ }
31
+ }
32
+ function apiKeyOf(ctx) {
33
+ const apiKey = ctx.apikey ?? process.env.BOAT_API_KEY;
34
+ if (!apiKey)
35
+ throw new Error("a Boat sandbox needs Boat's API key (apikey, or BOAT_API_KEY)");
36
+ return apiKey;
37
+ }
38
+ function apiOf(ctx) {
39
+ return new BoatApi(new Configuration({ basePath: BOAT_API_URL, accessToken: apiKeyOf(ctx) }));
40
+ }
41
+ function sandboxIdOf(ctx) {
42
+ if (!ctx.id)
43
+ throw new Error("the Boat sandbox has no id yet: it's set once it's created");
44
+ return ctx.id;
45
+ }
46
+ function where(ctx) {
47
+ return `Boat sandbox ${ctx.id ?? "(not created)"}`;
48
+ }
49
+ async function boatOnce(what, call) {
50
+ try {
51
+ return await call();
52
+ }
53
+ catch (error) {
54
+ if (!(error instanceof ResponseError))
55
+ throw error;
56
+ const text = await error.response.text().catch(() => "");
57
+ let body = {};
58
+ try {
59
+ body = JSON.parse(text);
60
+ }
61
+ catch {
62
+ body = { message: text };
63
+ }
64
+ const code = body.code ?? "";
65
+ if (code === "machine_not_running")
66
+ throw new SandboxStoppedError(`${what}: the Boat sandbox is stopped`);
67
+ throw new BoatError(error.response.status, code, `${what} failed (${error.response.status}${code ? ` ${code}` : ""}): ${body.message ?? text}`);
68
+ }
69
+ }
70
+ async function boat(what, call) {
71
+ for (const wait of [...UNDELIVERED_RETRY_MS, null]) {
72
+ try {
73
+ return await boatOnce(what, call);
74
+ }
75
+ catch (error) {
76
+ if (wait === null || !(error instanceof BoatError) || !UNDELIVERED.includes(error.code))
77
+ throw error;
78
+ await new Promise((resolve) => setTimeout(resolve, wait));
79
+ }
80
+ }
81
+ throw new Error(`${what} failed`);
82
+ }
83
+ async function ensureRunning(ctx) {
84
+ const api = apiOf(ctx);
85
+ const sandboxId = sandboxIdOf(ctx);
86
+ const deadline = Date.now() + READY_TIMEOUT_MS;
87
+ for (;;) {
88
+ const { sandbox } = await boat(`reading ${where(ctx)}`, () => api.get({ sandboxId }));
89
+ if (RUNNING_STATES.includes(sandbox.state))
90
+ return sandbox;
91
+ if (sandbox.state === "archived") {
92
+ await boat(`resuming ${where(ctx)}`, () => api.resume({ sandboxId }));
93
+ return waitUntilReady(api, sandboxId, { timeoutMs: Math.max(1, deadline - Date.now()) });
94
+ }
95
+ if (sandbox.state === "error" || sandbox.state === "cancelled")
96
+ throw new Error(`${where(ctx)} is ${sandbox.state}${sandbox.error ? `: ${sandbox.error}` : ""}`);
97
+ if (Date.now() > deadline)
98
+ throw new Error(`${where(ctx)} stayed ${sandbox.state}`);
99
+ await new Promise((resolve) => setTimeout(resolve, 2000));
100
+ }
101
+ }
102
+ async function resuming(ctx, call) {
103
+ try {
104
+ return await call();
105
+ }
106
+ catch (error) {
107
+ if (!(error instanceof SandboxStoppedError))
108
+ throw error;
109
+ await ensureRunning(ctx);
110
+ return call();
111
+ }
112
+ }
113
+ async function command(ctx, line, cwd) {
114
+ const api = apiOf(ctx);
115
+ const sandboxId = sandboxIdOf(ctx);
116
+ const result = await boat(`a command in ${where(ctx)}`, () => api.command({ sandboxId, command: line, cwd, timeoutSeconds: COMMAND_TIMEOUT_S }));
117
+ if (!("exitCode" in result))
118
+ throw new Error(`a command in ${where(ctx)} was started in the background`);
119
+ if (result.timedOut)
120
+ throw new Error(`a command in ${where(ctx)} ran past ${COMMAND_TIMEOUT_S}s`);
121
+ if (result.stdoutTruncated || result.stderrTruncated)
122
+ throw new Error(`Boat cut the output of a command in ${where(ctx)} short`);
123
+ return { exitCode: result.exitCode ?? -1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
124
+ }
125
+ async function upload(ctx, path, data) {
126
+ const api = apiOf(ctx);
127
+ const sandboxId = sandboxIdOf(ctx);
128
+ const content = Buffer.from(data).toString("base64");
129
+ await boat(`writing ${path} in ${where(ctx)}`, () => api.writeFile({ sandboxId, path, content, encoding: "base64" }));
130
+ }
131
+ async function runCommand(ctx, cmd, { cwd, env, stdin } = {}) {
132
+ const directory = cwd === undefined ? undefined : posix.resolve("/", cwd);
133
+ if (stdin === undefined)
134
+ return resuming(ctx, () => command(ctx, commandLine(cmd, { env }), directory));
135
+ return resuming(ctx, async () => {
136
+ const staged = `/tmp/.hyperbrane-stdin-${randomUUID()}`;
137
+ await upload(ctx, staged, typeof stdin === "string" ? new TextEncoder().encode(stdin) : stdin);
138
+ return command(ctx, `(${commandLine(cmd, { env })}) < ${quote(staged)}; code=$?; rm -f ${quote(staged)}; exit "$code"`, directory);
139
+ });
140
+ }
141
+ function boatMachine(ctx) {
142
+ async function readBytes(path, start, end) {
143
+ const api = apiOf(ctx);
144
+ const sandboxId = sandboxIdOf(ctx);
145
+ try {
146
+ const { content } = await boat(`reading ${path} in ${where(ctx)}`, () => api.readFile({ sandboxId, path, encoding: "base64" }));
147
+ const data = new Uint8Array(Buffer.from(content, "base64"));
148
+ return start === undefined ? data : data.subarray(start, end);
149
+ }
150
+ catch (error) {
151
+ if (error instanceof BoatError && error.status === 404)
152
+ throw new SandboxNotFoundError(`Not found: "${path}"`);
153
+ throw error;
154
+ }
155
+ }
156
+ async function putFile(path, data, exclusive) {
157
+ return resuming(ctx, async () => {
158
+ const staged = `${posix.dirname(path)}/.${posix.basename(path)}.${randomUUID()}.tmp`;
159
+ const made = await command(ctx, `mkdir -p ${quote(posix.dirname(path))}`);
160
+ if (made.exitCode !== 0)
161
+ throw new Error(`writing ${path} in ${where(ctx)} failed: ${made.stderr.trim()}`);
162
+ await upload(ctx, staged, data);
163
+ const placed = exclusive
164
+ ? await command(ctx, `ln ${quote(staged)} ${quote(path)} 2> /dev/null; code=$?; rm -f ${quote(staged)}; exit "$code"`)
165
+ : await command(ctx, `mv -f ${quote(staged)} ${quote(path)}`);
166
+ if (exclusive)
167
+ return placed.exitCode === 0;
168
+ if (placed.exitCode !== 0)
169
+ throw new Error(`writing ${path} in ${where(ctx)} failed: ${placed.stderr.trim()}`);
170
+ return true;
171
+ });
172
+ }
173
+ return {
174
+ runCommand: (cmd, options) => runCommand(ctx, cmd, options),
175
+ readBytes,
176
+ putFile,
177
+ };
178
+ }
179
+ async function sshTarget(ctx) {
180
+ const privateKey = ctx.sshKey;
181
+ if (!privateKey)
182
+ throw new Error(`${where(ctx)} has no shell: its context has no SSH key (sshKey)`);
183
+ const parsed = ssh2.utils.parseKey(privateKey);
184
+ const key = Array.isArray(parsed) ? parsed[0] : parsed;
185
+ if (!key || key instanceof Error)
186
+ throw new Error(`${where(ctx)}'s SSH key can't be read${key instanceof Error ? `: ${key.message}` : ""}`);
187
+ await ensureRunning(ctx);
188
+ const api = apiOf(ctx);
189
+ const sandboxId = sandboxIdOf(ctx);
190
+ const publicKey = `${key.type} ${key.getPublicSSH().toString("base64")}`;
191
+ const response = await boat(`adding an SSH key to ${where(ctx)}`, () => api.sshKey({ sandboxId, key: publicKey }));
192
+ const user = response.sshUser ?? "user";
193
+ const endpoint = /^(.+):(\d+)$/.exec(response.sshEndpoint ?? "");
194
+ if (endpoint)
195
+ return { host: endpoint[1], port: Number(endpoint[2]), user, privateKey };
196
+ if (response.machineIp)
197
+ return { host: response.machineIp, port: 22, user, privateKey };
198
+ throw new Error(`Boat gave no SSH endpoint for ${where(ctx)}`);
199
+ }
200
+ function groupsOf(ctx) {
201
+ const fileOps = commandFileOperations(boatMachine(ctx));
202
+ const cmdOps = {
203
+ runCommand: (cmd, options) => runCommand(ctx, cmd, options),
204
+ };
205
+ const netOps = {
206
+ fetch: async (url, init) => sshFetch(await sshTarget(ctx), url, init),
207
+ connect: async (path) => sshConnect(await sshTarget(ctx), path),
208
+ };
209
+ return { fileOps, cmdOps, netOps };
210
+ }
211
+ function storageOf(ctx) {
212
+ const { fileOps, cmdOps, netOps } = groupsOf(ctx);
213
+ return createSandboxStorage({ ...fileOps, ...cmdOps, ...netOps });
214
+ }
215
+ async function create(ctx, spec) {
216
+ const api = apiOf(ctx);
217
+ const created = await boat("creating a Boat sandbox", () => api.create({ idempotencyKey: randomUUID() }));
218
+ const sandboxId = created.sandbox.id;
219
+ try {
220
+ await boat(`naming Boat sandbox ${sandboxId}`, () => api.update({ sandboxId, name: spec.name }));
221
+ await waitUntilReady(api, sandboxId, { timeoutMs: READY_TIMEOUT_MS });
222
+ return await createDescriptor(ctx, storageOf({ ...ctx, id: sandboxId }), sandboxId, spec);
223
+ }
224
+ catch (error) {
225
+ await boat(`stopping Boat sandbox ${sandboxId}`, () => api.stop({ sandboxId })).catch(() => { });
226
+ throw error;
227
+ }
228
+ }
229
+ async function open(ctx, storage) {
230
+ return openDescriptor(ctx, storage, sandboxIdOf(ctx));
231
+ }
232
+ async function state(ctx) {
233
+ const api = apiOf(ctx);
234
+ const sandboxId = sandboxIdOf(ctx);
235
+ const { sandbox } = await boat(`reading ${where(ctx)}`, () => api.get({ sandboxId }));
236
+ return RUNNING_STATES.includes(sandbox.state) ? "running" : "stopped";
237
+ }
238
+ async function start(ctx) {
239
+ await ensureRunning(ctx);
240
+ }
241
+ async function destroy(ctx) {
242
+ const api = apiOf(ctx);
243
+ const sandboxId = sandboxIdOf(ctx);
244
+ await boat(`stopping ${where(ctx)}`, () => api.stop({ sandboxId }));
245
+ }
246
+ function boatPorts(ctx, storage) {
247
+ async function publish(port, isPublic) {
248
+ const api = apiOf(ctx);
249
+ const sandboxId = sandboxIdOf(ctx);
250
+ const response = await boat(`publishing port ${port} of ${where(ctx)}`, () => api.hostPort({ sandboxId, port, _public: isPublic }));
251
+ return response.url;
252
+ }
253
+ return {
254
+ ports: async () => (await storage.readDescriptor())?.dataEndpointOptions?.ports ?? DEFAULT_PORTS,
255
+ async expose(port) {
256
+ const url = await publish(port, true);
257
+ if (!url)
258
+ throw new Error(`Boat gave no URL for port ${port} of ${where(ctx)}`);
259
+ return url.replace(/\/$/, "");
260
+ },
261
+ async conceal(port) {
262
+ await publish(port, false);
263
+ },
264
+ };
265
+ }
266
+ function hasExpiration() {
267
+ return true;
268
+ }
269
+ async function getExpiration(ctx) {
270
+ const api = apiOf(ctx);
271
+ const sandboxId = sandboxIdOf(ctx);
272
+ const { sandbox } = await boat(`reading ${where(ctx)}`, () => api.get({ sandboxId }));
273
+ return sandbox.archiveAfter ? new Date(sandbox.archiveAfter) : null;
274
+ }
275
+ async function setExpiration(ctx, seconds) {
276
+ if (seconds !== null && !(Number.isInteger(seconds) && seconds > 0))
277
+ throw new Error(`not a number of seconds: ${seconds}`);
278
+ const api = apiOf(ctx);
279
+ const sandboxId = sandboxIdOf(ctx);
280
+ const { sandbox } = await boat(`setting when ${where(ctx)} stops`, () => api.update({ sandboxId, ttlSeconds: seconds }));
281
+ return sandbox.archiveAfter ? new Date(sandbox.archiveAfter) : null;
282
+ }
283
+ function hasShell(ctx) {
284
+ return ctx.sshKey !== undefined;
285
+ }
286
+ async function sshEndpoint(ctx) {
287
+ return sshTarget(ctx);
288
+ }
289
+ export function createBoatService(ctx) {
290
+ apiKeyOf(ctx);
291
+ const { fileOps, cmdOps, netOps } = groupsOf(ctx);
292
+ const machine = { ...fileOps, ...cmdOps, ...netOps };
293
+ const storage = createSandboxStorage(machine);
294
+ const lifecycleOps = {
295
+ create: (spec) => create(ctx, spec),
296
+ open: () => resuming(ctx, () => open(ctx, storage)),
297
+ get: () => getDescriptor(storage, where(ctx)),
298
+ state: () => state(ctx),
299
+ start: () => start(ctx),
300
+ destroy: () => destroy(ctx),
301
+ };
302
+ const dataOps = createEndpoints(machine, storage, boatPorts(ctx, storage));
303
+ const expirationOps = {
304
+ hasExpiration,
305
+ getExpiration: () => getExpiration(ctx),
306
+ setExpiration: (seconds) => setExpiration(ctx, seconds),
307
+ };
308
+ const shellOps = {
309
+ hasShell: () => hasShell(ctx),
310
+ sshEndpoint: () => sshEndpoint(ctx),
311
+ };
312
+ return {
313
+ ...fileOps,
314
+ ...lifecycleOps,
315
+ ...cmdOps,
316
+ ...dataOps,
317
+ ...expirationOps,
318
+ ...shellOps,
319
+ ...netOps,
320
+ };
321
+ }
@@ -0,0 +1,2 @@
1
+ import type { SandboxAuthContext, SandboxService } from "../../api.ts";
2
+ export declare function createExeDevService(ctx: SandboxAuthContext): SandboxService;
@@ -0,0 +1,127 @@
1
+ import { userInfo } from "node:os";
2
+ import { commandFileOperations } from "../../lib/remoteFileOperations.js";
3
+ import { createDescriptor, getDescriptor, openDescriptor } from "../../storage/descriptors.js";
4
+ import { createEndpoints } from "../../data-endpoints/dataEndpoints.js";
5
+ import { createSandboxStorage } from "../../storage/sandboxStorage.js";
6
+ import { sshCommand, sshConnect, sshFetch, sshMachine } from "../ssh/sshTransport.js";
7
+ import { signExeDevToken } from "./exeDevToken.js";
8
+ const VM_NAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
9
+ const FORWARDED = {
10
+ from: 3000,
11
+ to: 9999,
12
+ };
13
+ const DEFAULT_PORTS = {
14
+ from: 9000,
15
+ to: 9099,
16
+ };
17
+ function vmOf(ctx) {
18
+ if (!ctx.id)
19
+ throw new Error("an exe.dev VM needs its name (id)");
20
+ if (!VM_NAME.test(ctx.id))
21
+ throw new Error(`not an exe.dev VM name: "${ctx.id}"`);
22
+ return ctx.id;
23
+ }
24
+ function target(ctx) {
25
+ if (!ctx.sshKey)
26
+ throw new Error(`exe.dev VM ${ctx.id ?? "(no name)"} needs the account's SSH key (sshKey)`);
27
+ if (ctx.sshHost)
28
+ return { host: ctx.sshHost, port: ctx.sshPort ?? 22, user: ctx.sshUser ?? userInfo().username, privateKey: ctx.sshKey };
29
+ return { host: "vm.exe.xyz", port: 22, user: `vm+${vmOf(ctx)}`, privateKey: ctx.sshKey };
30
+ }
31
+ function where(ctx) {
32
+ return `exe.dev VM ${ctx.id ?? "(no name)"}`;
33
+ }
34
+ function groupsOf(ctx) {
35
+ const fileOps = commandFileOperations(sshMachine(() => target(ctx)));
36
+ const cmdOps = {
37
+ runCommand: (cmd, options) => sshCommand(target(ctx), cmd, options),
38
+ };
39
+ const netOps = {
40
+ fetch: (url, init) => sshFetch(target(ctx), url, init),
41
+ connect: (path) => sshConnect(target(ctx), path),
42
+ };
43
+ return { fileOps, cmdOps, netOps };
44
+ }
45
+ function storageOf(ctx) {
46
+ const { fileOps, cmdOps, netOps } = groupsOf(ctx);
47
+ return createSandboxStorage({ ...fileOps, ...cmdOps, ...netOps });
48
+ }
49
+ async function create(ctx, spec) {
50
+ const id = ctx.id ?? spec.name;
51
+ return createDescriptor(ctx, storageOf({ ...ctx, id }), id, spec);
52
+ }
53
+ async function open(ctx, storage) {
54
+ return openDescriptor(ctx, storage, vmOf(ctx));
55
+ }
56
+ async function state() {
57
+ return "running";
58
+ }
59
+ async function start() { }
60
+ async function destroy() { }
61
+ function hasExpiration() {
62
+ return false;
63
+ }
64
+ async function getExpiration() {
65
+ return null;
66
+ }
67
+ async function setExpiration() {
68
+ return null;
69
+ }
70
+ function hasShell(ctx) {
71
+ return ctx.id !== undefined && ctx.sshKey !== undefined;
72
+ }
73
+ async function sshEndpoint(ctx) {
74
+ return target(ctx);
75
+ }
76
+ function exeDevPorts(ctx, storage) {
77
+ return {
78
+ async ports() {
79
+ const ports = (await storage.readDescriptor())?.dataEndpointOptions?.ports ?? DEFAULT_PORTS;
80
+ if (ports.from < FORWARDED.from || ports.to > FORWARDED.to)
81
+ throw new Error(`exe.dev forwards ports ${FORWARDED.from}-${FORWARDED.to}, not ${ports.from}-${ports.to}`);
82
+ return ports;
83
+ },
84
+ async expose(port) {
85
+ return `https://${vmOf(ctx)}.exe.xyz:${port}`;
86
+ },
87
+ async conceal() { },
88
+ async credentials(_endpoint, ttlSeconds) {
89
+ if (!ctx.sshKey)
90
+ throw new Error(`${where(ctx)} needs the account's SSH key (sshKey) to sign exe.dev tokens`);
91
+ const exp = Math.floor(Date.now() / 1000) + ttlSeconds;
92
+ return { exeDev: { token: signExeDevToken(ctx.sshKey, `v0@${vmOf(ctx)}.exe.xyz`, { exp }) } };
93
+ },
94
+ };
95
+ }
96
+ export function createExeDevService(ctx) {
97
+ const { fileOps, cmdOps, netOps } = groupsOf(ctx);
98
+ const machine = { ...fileOps, ...cmdOps, ...netOps };
99
+ const storage = createSandboxStorage(machine);
100
+ const lifecycleOps = {
101
+ create: (spec) => create(ctx, spec),
102
+ open: () => open(ctx, storage),
103
+ get: () => getDescriptor(storage, where(ctx)),
104
+ state,
105
+ start,
106
+ destroy,
107
+ };
108
+ const dataOps = createEndpoints(machine, storage, exeDevPorts(ctx, storage));
109
+ const expirationOps = {
110
+ hasExpiration,
111
+ getExpiration,
112
+ setExpiration,
113
+ };
114
+ const shellOps = {
115
+ hasShell: () => hasShell(ctx),
116
+ sshEndpoint: () => sshEndpoint(ctx),
117
+ };
118
+ return {
119
+ ...fileOps,
120
+ ...lifecycleOps,
121
+ ...cmdOps,
122
+ ...dataOps,
123
+ ...expirationOps,
124
+ ...shellOps,
125
+ ...netOps,
126
+ };
127
+ }
@@ -0,0 +1 @@
1
+ export declare function signExeDevToken(privateKey: string, namespace: string, permissions: Record<string, unknown>): string;
@@ -0,0 +1,54 @@
1
+ import { createHash } from "node:crypto";
2
+ import ssh2 from "ssh2";
3
+ function sshString(value) {
4
+ const bytes = typeof value === "string" ? Buffer.from(value) : value;
5
+ const length = Buffer.alloc(4);
6
+ length.writeUInt32BE(bytes.length);
7
+ return Buffer.concat([length, bytes]);
8
+ }
9
+ function privateKeyOf(privateKey) {
10
+ const parsed = ssh2.utils.parseKey(privateKey);
11
+ const key = Array.isArray(parsed) ? parsed[0] : parsed;
12
+ if (!key || key instanceof Error || !key.isPrivateKey())
13
+ throw new Error(`the exe.dev key isn't a usable private key${key instanceof Error ? `: ${key.message}` : ""}`);
14
+ return key;
15
+ }
16
+ function ecdsaBlob(der) {
17
+ let offset = 2 + (der[1] & 0x80 ? der[1] & 0x7f : 0);
18
+ const integers = [];
19
+ for (let i = 0; i < 2; i++) {
20
+ const length = der[offset + 1];
21
+ integers.push(sshString(der.subarray(offset + 2, offset + 2 + length)));
22
+ offset += 2 + length;
23
+ }
24
+ return Buffer.concat(integers);
25
+ }
26
+ function signed(key, data) {
27
+ if (key.type === "ssh-ed25519")
28
+ return [key.type, key.sign(data)];
29
+ if (key.type === "ssh-rsa")
30
+ return ["rsa-sha2-512", key.sign(data, "sha512")];
31
+ if (key.type.startsWith("ecdsa-sha2-"))
32
+ return [key.type, ecdsaBlob(key.sign(data))];
33
+ throw new Error(`exe.dev tokens can't be signed with a ${key.type} key`);
34
+ }
35
+ function sshSignature(key, namespace, message) {
36
+ const magic = Buffer.from("SSHSIG");
37
+ const hash = createHash("sha512").update(message).digest();
38
+ const [algorithm, signature] = signed(key, Buffer.concat([magic, sshString(namespace), sshString(""), sshString("sha512"), sshString(hash)]));
39
+ const version = Buffer.alloc(4);
40
+ version.writeUInt32BE(1);
41
+ return Buffer.concat([
42
+ magic,
43
+ version,
44
+ sshString(key.getPublicSSH()),
45
+ sshString(namespace),
46
+ sshString(""),
47
+ sshString("sha512"),
48
+ sshString(Buffer.concat([sshString(algorithm), sshString(signature)])),
49
+ ]);
50
+ }
51
+ export function signExeDevToken(privateKey, namespace, permissions) {
52
+ const payload = Buffer.from(JSON.stringify(permissions));
53
+ return `exe0.${payload.toString("base64url")}.${sshSignature(privateKeyOf(privateKey), namespace, payload).toString("base64url")}`;
54
+ }
@@ -0,0 +1,2 @@
1
+ import { type SandboxAuthContext, type SandboxService } from "../../api.ts";
2
+ export declare function createLocalService(ctx: SandboxAuthContext): SandboxService;