hyperbrane 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -3
- package/dist/agent/agentService.d.ts +11 -3
- package/dist/agent/agentService.js +63 -19
- package/dist/agent/agentStorage.d.ts +2 -0
- package/dist/agent/agentStorage.js +4 -2
- package/dist/agent/api.d.ts +83 -32
- package/dist/agent/api.js +7 -0
- package/dist/agent/client.d.ts +3 -0
- package/dist/agent/client.js +5 -0
- package/dist/agent/hostTranscript.d.ts +13 -0
- package/dist/agent/hostTranscript.js +190 -0
- package/dist/agent/session/backend.d.ts +82 -0
- package/dist/agent/session/backend.js +1 -0
- package/dist/agent/session/claude/home.d.ts +2 -0
- package/dist/agent/session/claude/home.js +4 -0
- package/dist/agent/session/claude/transcript.d.ts +13 -0
- package/dist/agent/session/claude/transcript.js +29 -0
- package/dist/agent/session/client.d.ts +5 -1
- package/dist/agent/session/codex/home.d.ts +2 -0
- package/dist/agent/session/codex/home.js +4 -0
- package/dist/agent/session/codex/messages.d.ts +30 -0
- package/dist/agent/session/codex/messages.js +135 -0
- package/dist/agent/session/codex/transcript.d.ts +12 -0
- package/dist/agent/session/codex/transcript.js +22 -0
- package/dist/agent/session/fileTranscript.d.ts +92 -0
- package/dist/agent/session/fileTranscript.js +454 -0
- package/dist/agent/session/sdkMessages.d.ts +15 -0
- package/dist/agent/session/sdkMessages.js +28 -0
- package/dist/agent/session/sessionStorage.d.ts +4 -0
- package/dist/agent/session/sessionStorage.js +13 -2
- package/dist/agent/session/sharedConnection.d.ts +1 -0
- package/dist/agent/session/storedTranscripts.d.ts +13 -0
- package/dist/agent/session/storedTranscripts.js +8 -0
- package/dist/agent/session/worker/protocol.d.ts +5 -1
- package/dist/agent/session/worker/protocol.js +6 -1
- package/dist/agent/session/worker/workerParams.d.ts +11 -0
- package/dist/agent/session/worker/workerParams.js +163 -0
- package/dist/agent/session/workerSseConnection.d.ts +20 -4
- package/dist/agent/session/workerSseConnection.js +57 -7
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +10 -0
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +22 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +11 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +23 -0
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/SleepItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +14 -0
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +3 -0
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +19 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +10 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +8 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +3 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +8 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +11 -0
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +215 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +35 -0
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +14 -0
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +2 -0
- package/dist/agent/workerBundle.generated.js +1 -1
- package/dist/cli/agent.js +30 -4
- package/dist/cli/services.d.ts +6 -5
- package/dist/cli/services.js +4 -3
- package/dist/dimension/api.d.ts +5 -4
- package/dist/dimension/paths.d.ts +4 -0
- package/dist/dimension/paths.js +10 -2
- package/dist/dimension/service.d.ts +5 -1
- package/dist/dimension/service.js +5 -1
- package/dist/dimension/storage.js +3 -2
- package/dist/index.d.ts +2 -3
- package/dist/index.js +7 -5
- package/dist/lib/docker.d.ts +1 -0
- package/dist/portal-gateway/api.d.ts +6 -5
- package/dist/portal-gateway/gatewayService.d.ts +5 -1
- package/dist/portal-gateway/gatewayService.js +25 -13
- package/dist/portal-gateway/image.d.ts +1 -1
- package/dist/portal-gateway/image.js +1 -1
- package/dist/portal-gateway/imageFiles.generated.js +1 -1
- package/dist/portal-gateway/portalService.d.ts +2 -0
- package/dist/portal-gateway/portalService.js +61 -29
- package/dist/portal-gateway/upstream.d.ts +22 -11
- package/dist/sandbox/api.d.ts +60 -23
- package/dist/sandbox/boatService.js +11 -3
- package/dist/sandbox/containerService.js +10 -3
- package/dist/sandbox/endpointProxy.js +16 -1
- package/dist/sandbox/localService.js +20 -2
- package/dist/sandbox/sshService.js +37 -4
- package/package.json +25 -8
|
@@ -180,17 +180,25 @@ export function createPortalService(storage, gateways, docker, image, host) {
|
|
|
180
180
|
return;
|
|
181
181
|
if (record.state !== "destroying")
|
|
182
182
|
await storage.savePortal({ ...record, state: "destroying" });
|
|
183
|
+
// A restore under way finishes first (seeing the mark, it undoes what it wired), so nothing it
|
|
184
|
+
// does lands after this returns; one that starts from now on sees the mark and does nothing.
|
|
185
|
+
while (restoring.has(portalName))
|
|
186
|
+
await restoring.get(portalName).catch(() => { });
|
|
183
187
|
if (record.subnet !== undefined) {
|
|
184
188
|
await ignoringStopped(() => unwire(record.gatewayName, portalLink(portalName, record.subnet)));
|
|
185
189
|
await storage.releaseSubnet(record.subnet);
|
|
186
190
|
}
|
|
187
|
-
|
|
191
|
+
await untrustUnlessShared(record);
|
|
192
|
+
await storage.removePortal(portalName);
|
|
193
|
+
}
|
|
194
|
+
/** Takes the gateway's CA out of the dimension, unless another of its portals goes to that gateway. */
|
|
195
|
+
async function untrustUnlessShared(record) {
|
|
196
|
+
const sameGateway = (await storage.listPortals()).some((other) => other.name !== record.name &&
|
|
188
197
|
other.dimensionName === record.dimensionName &&
|
|
189
198
|
other.gatewayName === record.gatewayName);
|
|
190
199
|
if (!sameGateway) {
|
|
191
200
|
await ignoringStopped(() => trustGateway(dimensionContainerName(record.dimensionName), record.gatewayName, false));
|
|
192
201
|
}
|
|
193
|
-
await storage.removePortal(portalName);
|
|
194
202
|
}
|
|
195
203
|
async function unwire(gatewayName, link) {
|
|
196
204
|
const result = await docker.exec(gatewayContainerName(gatewayName), {
|
|
@@ -217,38 +225,62 @@ export function createPortalService(storage, gateways, docker, image, host) {
|
|
|
217
225
|
await Promise.all(records.map(async (record) => {
|
|
218
226
|
if ((await gatewayInterfaces(record.gatewayName))?.has(portalLink(record.name, record.subnet).gatewayIf))
|
|
219
227
|
return;
|
|
220
|
-
|
|
228
|
+
await restoreOnce(record);
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
// One restore of a portal at a time, however many ask (a restart's own and the watcher's): two
|
|
232
|
+
// wiring the same link at once fail each other, and the retry could land after a destroy. Each
|
|
233
|
+
// waits for the one before rather than joining it: that one may have given up on a container that
|
|
234
|
+
// has since restarted, so once it's done the link is looked at again.
|
|
235
|
+
const restoring = new Map();
|
|
236
|
+
async function restoreOnce(record) {
|
|
237
|
+
while (restoring.has(record.name)) {
|
|
238
|
+
await restoring.get(record.name).catch(() => { });
|
|
239
|
+
const link = portalLink(record.name, record.subnet);
|
|
240
|
+
if ((await listInterfaces(gatewayContainerName(record.gatewayName)))?.has(link.gatewayIf))
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const restored = restore(record).finally(() => restoring.delete(record.name));
|
|
244
|
+
restoring.set(record.name, restored);
|
|
245
|
+
return restored;
|
|
246
|
+
}
|
|
247
|
+
async function restore(listed) {
|
|
248
|
+
// As it is now: one being destroyed is left alone.
|
|
249
|
+
const record = await storage.readPortal(listed.name);
|
|
250
|
+
if (record?.state !== "ready")
|
|
251
|
+
return;
|
|
252
|
+
let pids;
|
|
253
|
+
try {
|
|
254
|
+
pids = await runningPids(record);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
// A stopped or removed container gets wired when it starts again, or never.
|
|
258
|
+
if (error instanceof GatewayConflictError || error instanceof GatewayNotFoundError)
|
|
259
|
+
return;
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
for (let attempt = 1;; attempt++) {
|
|
221
263
|
try {
|
|
222
|
-
|
|
264
|
+
await connect(record, pids);
|
|
265
|
+
// Destroyed meanwhile: neither the link nor the trust just made may outlive it (see destroyPortal).
|
|
266
|
+
if ((await storage.readPortal(record.name))?.state !== "ready") {
|
|
267
|
+
await ignoringStopped(() => unwire(record.gatewayName, portalLink(record.name, record.subnet)));
|
|
268
|
+
await untrustUnlessShared(record);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
223
271
|
}
|
|
224
272
|
catch (error) {
|
|
225
|
-
// A stopped or
|
|
226
|
-
|
|
273
|
+
// A container that stopped or restarted meanwhile (killing what ran in it) is restored by
|
|
274
|
+
// its own start event. Docker kills a restarting container's processes before its state
|
|
275
|
+
// changes, so give it a moment before deciding it's still the same container.
|
|
276
|
+
await sleep(RESTORE_RECHECK_MS);
|
|
277
|
+
const now = await runningPids(record).catch(() => null);
|
|
278
|
+
if (now?.gateway !== pids.gateway || now?.dimension !== pids.dimension)
|
|
227
279
|
return;
|
|
228
|
-
|
|
280
|
+
if (attempt === 2)
|
|
281
|
+
return console.warn(`restoring portal ${record.name} failed:`, error);
|
|
229
282
|
}
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
await connect(record, pids);
|
|
233
|
-
// Destroyed meanwhile: the link just made must not outlive it (see destroyPortal).
|
|
234
|
-
if ((await storage.readPortal(record.name))?.state !== "ready") {
|
|
235
|
-
await ignoringStopped(() => unwire(record.gatewayName, portalLink(record.name, record.subnet)));
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
// A container that stopped or restarted meanwhile (killing what ran in it) is restored by
|
|
241
|
-
// its own start event. Docker kills a restarting container's processes before its state
|
|
242
|
-
// changes, so give it a moment before deciding it's still the same container.
|
|
243
|
-
await sleep(RESTORE_RECHECK_MS);
|
|
244
|
-
const now = await runningPids(record).catch(() => null);
|
|
245
|
-
if (now?.gateway !== pids.gateway || now?.dimension !== pids.dimension)
|
|
246
|
-
return;
|
|
247
|
-
if (attempt === 2)
|
|
248
|
-
return console.warn(`restoring portal ${record.name} failed:`, error);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}));
|
|
283
|
+
}
|
|
252
284
|
}
|
|
253
285
|
/** Null when the container isn't running. */
|
|
254
286
|
async function listInterfaces(container) {
|
|
@@ -19,8 +19,20 @@ export type UpstreamDefinition = {
|
|
|
19
19
|
modes: {
|
|
20
20
|
oauth?: OAuthMode;
|
|
21
21
|
apiKey?: ApiKeyMode;
|
|
22
|
+
forced?: ForcedMode;
|
|
22
23
|
};
|
|
23
24
|
};
|
|
25
|
+
/** What to force onto every matching request, whatever the client sent. */
|
|
26
|
+
export type UpstreamForce = {
|
|
27
|
+
/** Retarget the request, e.g. "https://api.internal:8443". */
|
|
28
|
+
target?: string;
|
|
29
|
+
/** Prefix prepended to the request path, so dimensions needn't know where the API is mounted. */
|
|
30
|
+
path?: string;
|
|
31
|
+
/** Query params the client doesn't get to choose. */
|
|
32
|
+
query?: Record<string, string>;
|
|
33
|
+
/** Headers whose values the dimension never sees. */
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
};
|
|
24
36
|
type ModeBase = {
|
|
25
37
|
/**
|
|
26
38
|
* Requests to these hosts get the credential. Matched on host alone: v1 also matched the
|
|
@@ -34,17 +46,16 @@ type ModeBase = {
|
|
|
34
46
|
} | {
|
|
35
47
|
query: string;
|
|
36
48
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
49
|
+
force?: UpstreamForce;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* No credential of the gateway's own, so nothing to present and no fakes: only what `force` writes
|
|
53
|
+
* onto every request to `hosts`, e.g. a service's own auth header, set here and never seen by the
|
|
54
|
+
* dimension. Applies as soon as it's registered.
|
|
55
|
+
*/
|
|
56
|
+
export type ForcedMode = {
|
|
57
|
+
hosts: string[];
|
|
58
|
+
force: UpstreamForce;
|
|
48
59
|
};
|
|
49
60
|
export type OAuthMode = ModeBase & {
|
|
50
61
|
/**
|
package/dist/sandbox/api.d.ts
CHANGED
|
@@ -5,14 +5,16 @@ export type RunCommandOptions = {
|
|
|
5
5
|
stdin?: string | Uint8Array;
|
|
6
6
|
};
|
|
7
7
|
export type RunCommandResult = {
|
|
8
|
-
exitCode: number;
|
|
9
|
-
stdout: string;
|
|
10
|
-
stderr: string;
|
|
8
|
+
readonly exitCode: number;
|
|
9
|
+
readonly stdout: string;
|
|
10
|
+
readonly stderr: string;
|
|
11
11
|
};
|
|
12
12
|
export type SandboxFileInfo = {
|
|
13
|
-
type: "file" | "directory" | "other";
|
|
14
|
-
uid: number;
|
|
15
|
-
gid: number;
|
|
13
|
+
readonly type: "file" | "directory" | "other";
|
|
14
|
+
readonly uid: number;
|
|
15
|
+
readonly gid: number;
|
|
16
|
+
/** In bytes; what the system reports for anything that isn't a file. */
|
|
17
|
+
readonly size: number;
|
|
16
18
|
};
|
|
17
19
|
export type SandboxFetchInit = {
|
|
18
20
|
method?: string;
|
|
@@ -27,7 +29,11 @@ export type SandboxFetchInit = {
|
|
|
27
29
|
*/
|
|
28
30
|
timeout?: number | false;
|
|
29
31
|
};
|
|
30
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* A stream connection to a unix socket inside a sandbox (see Sandbox.connect).
|
|
34
|
+
*
|
|
35
|
+
* @sealed
|
|
36
|
+
*/
|
|
31
37
|
export type SandboxConnection = {
|
|
32
38
|
write(data: Uint8Array): void;
|
|
33
39
|
/** Receives every chunk read from the socket, in order. */
|
|
@@ -49,14 +55,14 @@ export type SandboxEndpointTarget = {
|
|
|
49
55
|
export type SandboxEndpointCredentials =
|
|
50
56
|
/** `Authorization: Bearer <token>`. */
|
|
51
57
|
{
|
|
52
|
-
scheme: "bearer";
|
|
53
|
-
token: string;
|
|
58
|
+
readonly scheme: "bearer";
|
|
59
|
+
readonly token: string;
|
|
54
60
|
}
|
|
55
61
|
/** `Authorization: Basic ...`; browsers prompt for it on their own. */
|
|
56
62
|
| {
|
|
57
|
-
scheme: "basic";
|
|
58
|
-
username: string;
|
|
59
|
-
password: string;
|
|
63
|
+
readonly scheme: "basic";
|
|
64
|
+
readonly username: string;
|
|
65
|
+
readonly password: string;
|
|
60
66
|
}
|
|
61
67
|
/**
|
|
62
68
|
* Logged in with exe.dev, as a user the VM is shared with; exe.dev's proxy checks it (exeDevAuth.ts).
|
|
@@ -64,8 +70,8 @@ export type SandboxEndpointCredentials =
|
|
|
64
70
|
* first (exeDevLoginUrl in agent/session/workerSseConnection.ts).
|
|
65
71
|
*/
|
|
66
72
|
| {
|
|
67
|
-
scheme: "exe.dev";
|
|
68
|
-
loginUrl: string;
|
|
73
|
+
readonly scheme: "exe.dev";
|
|
74
|
+
readonly loginUrl: string;
|
|
69
75
|
};
|
|
70
76
|
export type SandboxEndpointScheme = SandboxEndpointCredentials["scheme"];
|
|
71
77
|
export type SandboxEndpointRequest = {
|
|
@@ -88,24 +94,33 @@ export type SandboxEndpointRequest = {
|
|
|
88
94
|
allowedOrigins?: string[];
|
|
89
95
|
};
|
|
90
96
|
export type SandboxEndpoint = {
|
|
91
|
-
id: string;
|
|
97
|
+
readonly id: string;
|
|
92
98
|
/**
|
|
93
99
|
* Where the client reaches the endpoint, directly over the network (not through the backend's own
|
|
94
|
-
* connection), at the host the sandbox was created with.
|
|
100
|
+
* connection), at the host the sandbox was created with. It lasts until the endpoint is closed,
|
|
101
|
+
* but what serves it may stop (a reboot, an archived Boat sandbox): ensureEndpoint starts it again.
|
|
102
|
+
* Until then a browser reports a request to it as a CORS error, since nothing of the endpoint's answers.
|
|
95
103
|
*/
|
|
96
|
-
url: string;
|
|
104
|
+
readonly url: string;
|
|
97
105
|
/** What the client presents; requests without it are refused before reaching the target. */
|
|
98
|
-
credentials: SandboxEndpointCredentials;
|
|
99
|
-
/** As requested
|
|
100
|
-
allowedOrigins: string[];
|
|
106
|
+
readonly credentials: SandboxEndpointCredentials;
|
|
107
|
+
/** As requested, or as updateEndpoint changed them since. */
|
|
108
|
+
readonly allowedOrigins: string[];
|
|
101
109
|
};
|
|
102
110
|
/**
|
|
103
111
|
* Paths are sandbox paths: relative or absolute, both resolved against the sandbox root.
|
|
104
112
|
* Operations that report a missing path throw SandboxNotFoundError, whatever the backend.
|
|
113
|
+
*
|
|
114
|
+
* @sealed
|
|
105
115
|
*/
|
|
106
116
|
export interface SandboxFileOperations {
|
|
107
117
|
/** Throws SandboxNotFoundError if missing. */
|
|
108
118
|
readFile(path: string): Promise<Uint8Array>;
|
|
119
|
+
/**
|
|
120
|
+
* Bytes `start` up to `end` (exclusive) of the file, fewer where the file ends sooner, so a large
|
|
121
|
+
* file can be read a part at a time. Throws SandboxNotFoundError if missing.
|
|
122
|
+
*/
|
|
123
|
+
readFileRange(path: string, start: number, end: number): Promise<Uint8Array>;
|
|
109
124
|
/** Atomically replaces the file (readers never see partial content). Creates missing parent directories. */
|
|
110
125
|
writeFile(path: string, data: string | Uint8Array): Promise<void>;
|
|
111
126
|
/**
|
|
@@ -126,10 +141,15 @@ export interface SandboxFileOperations {
|
|
|
126
141
|
/** Sandbox path of the home directory of the user operations run as. */
|
|
127
142
|
homeDirectory(): Promise<string>;
|
|
128
143
|
}
|
|
144
|
+
/** @sealed */
|
|
129
145
|
export interface SandboxCommands {
|
|
130
146
|
runCommand(cmd: string[], options?: RunCommandOptions): Promise<RunCommandResult>;
|
|
131
147
|
}
|
|
132
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Reaching into the sandbox from the caller's side.
|
|
150
|
+
*
|
|
151
|
+
* @sealed
|
|
152
|
+
*/
|
|
133
153
|
export interface SandboxNetwork {
|
|
134
154
|
/** HTTP request made from inside the sandbox. */
|
|
135
155
|
fetch(url: string, init?: SandboxFetchInit): Promise<Response>;
|
|
@@ -142,6 +162,8 @@ export interface SandboxNetwork {
|
|
|
142
162
|
* clients reach it at), by the backends that can. Always authenticated: a scheme the sandbox's auth
|
|
143
163
|
* can't enforce is refused with SandboxEndpointError. An endpoint lasts until it is closed, past the
|
|
144
164
|
* sandbox object that exposed it.
|
|
165
|
+
*
|
|
166
|
+
* @sealed
|
|
145
167
|
*/
|
|
146
168
|
export interface SandboxAccessibleEndpoint {
|
|
147
169
|
/**
|
|
@@ -157,8 +179,19 @@ export interface SandboxAccessibleEndpoint {
|
|
|
157
179
|
* and credentials; false if it was closed, or its port has since been taken (expose another).
|
|
158
180
|
*/
|
|
159
181
|
ensureEndpoint(endpointId: string): Promise<boolean>;
|
|
182
|
+
/**
|
|
183
|
+
* Changes which web origins may call the endpoint, in place: its URL and credentials stay, and so
|
|
184
|
+
* do connections already open. False if it was closed.
|
|
185
|
+
*/
|
|
186
|
+
updateEndpoint(endpointId: string, update: {
|
|
187
|
+
allowedOrigins: string[];
|
|
188
|
+
}): Promise<boolean>;
|
|
160
189
|
}
|
|
161
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* A sandbox that stops itself on a timer (Boat).
|
|
192
|
+
*
|
|
193
|
+
* @sealed
|
|
194
|
+
*/
|
|
162
195
|
export interface SandboxExpiration {
|
|
163
196
|
/** When it stops itself; null if it never does. */
|
|
164
197
|
getExpiration(): Promise<Date | null>;
|
|
@@ -167,7 +200,11 @@ export interface SandboxExpiration {
|
|
|
167
200
|
}
|
|
168
201
|
/** Whether the sandbox has the SandboxExpiration capability. */
|
|
169
202
|
export declare function hasExpiration<T extends object>(sandbox: T): sandbox is T & SandboxExpiration;
|
|
170
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* What every backend provides.
|
|
205
|
+
*
|
|
206
|
+
* @sealed
|
|
207
|
+
*/
|
|
171
208
|
export type Sandbox = SandboxFileOperations & SandboxCommands & SandboxNetwork & {
|
|
172
209
|
/** Releases what the sandbox holds open (a remote connection). Optional: most hold nothing. */
|
|
173
210
|
close?(): void;
|
|
@@ -103,6 +103,13 @@ export function createBoatSandbox(api, sandboxId, { proxyPort = DEFAULT_PROXY_PO
|
|
|
103
103
|
throw error;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
// Boat's file call reads whole files only.
|
|
107
|
+
async function readFileRange(path, start, end) {
|
|
108
|
+
const target = full(path);
|
|
109
|
+
const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; tail -c +$(($2 + 1)) -- "$1" | head -c "$3" | base64 -w0`, target, String(start), String(Math.max(0, end - start)));
|
|
110
|
+
check(result, "read", target);
|
|
111
|
+
return new Uint8Array(Buffer.from(result.stdout, "base64"));
|
|
112
|
+
}
|
|
106
113
|
/**
|
|
107
114
|
* Uploads `data`, copies it beside `target` (creating its directory) and runs `publish` on the copy
|
|
108
115
|
* ($3) and the target ($2), so readers never see part of it.
|
|
@@ -140,11 +147,11 @@ export function createBoatSandbox(api, sandboxId, { proxyPort = DEFAULT_PROXY_PO
|
|
|
140
147
|
return result.stdout.split("\n").filter(Boolean);
|
|
141
148
|
}
|
|
142
149
|
async function stat(path) {
|
|
143
|
-
const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%F' -- "$1"`, full(path));
|
|
150
|
+
const result = await sh(`[ -e "$1" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%s:%F' -- "$1"`, full(path));
|
|
144
151
|
check(result, "stat", full(path));
|
|
145
|
-
const [uid = "", gid = "", kind = ""] = result.stdout.trim().split(":");
|
|
152
|
+
const [uid = "", gid = "", size = "", kind = ""] = result.stdout.trim().split(":");
|
|
146
153
|
const type = kind === "directory" ? "directory" : kind.startsWith("regular") ? "file" : "other";
|
|
147
|
-
return { type, uid: Number(uid), gid: Number(gid) };
|
|
154
|
+
return { type, uid: Number(uid), gid: Number(gid), size: Number(size) };
|
|
148
155
|
}
|
|
149
156
|
let home = null;
|
|
150
157
|
function homeDirectory() {
|
|
@@ -233,6 +240,7 @@ export function createBoatSandbox(api, sandboxId, { proxyPort = DEFAULT_PROXY_PO
|
|
|
233
240
|
getExpiration,
|
|
234
241
|
setExpiration,
|
|
235
242
|
readFile,
|
|
243
|
+
readFileRange,
|
|
236
244
|
writeFile,
|
|
237
245
|
writeFileExclusive,
|
|
238
246
|
removeFile,
|
|
@@ -38,6 +38,12 @@ export function createContainerSandbox(docker, container, { user } = {}) {
|
|
|
38
38
|
const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; base64 -w0 -- "$P"`, { P: full }), "read", full);
|
|
39
39
|
return new Uint8Array(Buffer.from(result.stdout, "base64"));
|
|
40
40
|
}
|
|
41
|
+
async function readFileRange(path, start, end) {
|
|
42
|
+
const full = containerPath(path);
|
|
43
|
+
const script = `[ -e "$P" ] || exit ${NOT_FOUND}; tail -c +$(($S + 1)) -- "$P" | head -c "$L" | base64 -w0`;
|
|
44
|
+
const result = check(await sh(script, { P: full, S: String(start), L: String(Math.max(0, end - start)) }), "read", full);
|
|
45
|
+
return new Uint8Array(Buffer.from(result.stdout, "base64"));
|
|
46
|
+
}
|
|
41
47
|
/**
|
|
42
48
|
* Extracts `data` next to `full` under a unique hidden name, handed to the sandbox user, and runs
|
|
43
49
|
* `publish` (as root, with $T the temp file and $F the target) to move it into place.
|
|
@@ -81,10 +87,10 @@ export function createContainerSandbox(docker, container, { user } = {}) {
|
|
|
81
87
|
}
|
|
82
88
|
async function stat(path) {
|
|
83
89
|
const full = containerPath(path);
|
|
84
|
-
const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%F' -- "$P"`, { P: full }), "stat", full);
|
|
85
|
-
const [uid = "", gid = "", kind = ""] = result.stdout.trim().split(":");
|
|
90
|
+
const result = check(await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%s:%F' -- "$P"`, { P: full }), "stat", full);
|
|
91
|
+
const [uid = "", gid = "", size = "", kind = ""] = result.stdout.trim().split(":");
|
|
86
92
|
const type = kind === "directory" ? "directory" : kind.startsWith("regular") ? "file" : "other";
|
|
87
|
-
return { type, uid: Number(uid), gid: Number(gid) };
|
|
93
|
+
return { type, uid: Number(uid), gid: Number(gid), size: Number(size) };
|
|
88
94
|
}
|
|
89
95
|
// docker exec doesn't always set HOME for a numeric user, so ask the user database.
|
|
90
96
|
async function homeDirectory() {
|
|
@@ -136,6 +142,7 @@ export function createContainerSandbox(docker, container, { user } = {}) {
|
|
|
136
142
|
}
|
|
137
143
|
return {
|
|
138
144
|
readFile,
|
|
145
|
+
readFileRange,
|
|
139
146
|
writeFile,
|
|
140
147
|
writeFileExclusive,
|
|
141
148
|
removeFile,
|
|
@@ -185,6 +185,21 @@ export function createProxyEndpoints(commands, options, where) {
|
|
|
185
185
|
if (result.exitCode === 10 && firewall)
|
|
186
186
|
await ufw("delete", "allow", `${port}/tcp`);
|
|
187
187
|
}
|
|
188
|
+
// The proxy reads the record on every request, so the change applies to the next one.
|
|
189
|
+
async function updateEndpoint(endpointId, { allowedOrigins }) {
|
|
190
|
+
const { port, name } = parseEndpointId(endpointId);
|
|
191
|
+
const read = await sh(READ_SCRIPT, { P: port, N: name });
|
|
192
|
+
if (read.exitCode !== 0)
|
|
193
|
+
return false;
|
|
194
|
+
const record = { ...JSON.parse(read.stdout), origins: allowedOrigins };
|
|
195
|
+
const written = await sh(WRITE_SCRIPT, { P: port, N: name }, JSON.stringify(record));
|
|
196
|
+
if (written.exitCode === 44)
|
|
197
|
+
return false; // its proxy's directory went with its last endpoint
|
|
198
|
+
if (written.exitCode !== 0) {
|
|
199
|
+
throw new Error(`updating endpoint ${endpointId} on ${where} failed (exit ${written.exitCode}): ${written.stderr.trim()}`);
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
188
203
|
// Its record says what to serve; the proxy is started again on the port the endpoint's URL names.
|
|
189
204
|
async function ensureEndpoint(endpointId) {
|
|
190
205
|
const { port, name } = parseEndpointId(endpointId);
|
|
@@ -202,7 +217,7 @@ export function createProxyEndpoints(commands, options, where) {
|
|
|
202
217
|
await closeEndpoint(endpointId);
|
|
203
218
|
return false;
|
|
204
219
|
}
|
|
205
|
-
return { accessibleHost: options.accessibleHost, exposeEndpoint, exposeEndpointAs, closeEndpoint, ensureEndpoint };
|
|
220
|
+
return { accessibleHost: options.accessibleHost, exposeEndpoint, exposeEndpointAs, closeEndpoint, ensureEndpoint, updateEndpoint };
|
|
206
221
|
}
|
|
207
222
|
/** The credentials a client presents for a recorded check; `none` when the endpoint checks nothing itself. */
|
|
208
223
|
function credentialsOf(check, none) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { link, mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { link, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { request as httpRequest } from "node:http";
|
|
4
4
|
import { createConnection } from "node:net";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -36,6 +36,23 @@ export function createLocalSandbox(root, { endpoints } = {}) {
|
|
|
36
36
|
async function readFileLocal(path) {
|
|
37
37
|
return new Uint8Array(await notFoundAs(path, readFile(hostPath(path))));
|
|
38
38
|
}
|
|
39
|
+
async function readFileRangeLocal(path, start, end) {
|
|
40
|
+
const file = await notFoundAs(path, open(hostPath(path), "r"));
|
|
41
|
+
try {
|
|
42
|
+
const buffer = new Uint8Array(Math.max(0, end - start));
|
|
43
|
+
let filled = 0;
|
|
44
|
+
while (filled < buffer.length) {
|
|
45
|
+
const { bytesRead } = await file.read(buffer, filled, buffer.length - filled, start + filled);
|
|
46
|
+
if (bytesRead === 0)
|
|
47
|
+
break;
|
|
48
|
+
filled += bytesRead;
|
|
49
|
+
}
|
|
50
|
+
return buffer.subarray(0, filled);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await file.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
56
|
async function writeFileLocal(path, data) {
|
|
40
57
|
const full = hostPath(path);
|
|
41
58
|
await rename(await writeTemp(full, data), full);
|
|
@@ -72,7 +89,7 @@ export function createLocalSandbox(root, { endpoints } = {}) {
|
|
|
72
89
|
async function statLocal(path) {
|
|
73
90
|
const stats = await notFoundAs(path, stat(hostPath(path)));
|
|
74
91
|
const type = stats.isFile() ? "file" : stats.isDirectory() ? "directory" : "other";
|
|
75
|
-
return { type, uid: stats.uid, gid: stats.gid };
|
|
92
|
+
return { type, uid: stats.uid, gid: stats.gid, size: stats.size };
|
|
76
93
|
}
|
|
77
94
|
async function homeDirectory() {
|
|
78
95
|
const rel = relative(base, homedir());
|
|
@@ -112,6 +129,7 @@ export function createLocalSandbox(root, { endpoints } = {}) {
|
|
|
112
129
|
}
|
|
113
130
|
const sandbox = {
|
|
114
131
|
readFile: readFileLocal,
|
|
132
|
+
readFileRange: readFileRangeLocal,
|
|
115
133
|
writeFile: writeFileLocal,
|
|
116
134
|
writeFileExclusive,
|
|
117
135
|
removeFile,
|
|
@@ -5,6 +5,7 @@ import { createProxyEndpoints } from "./endpointProxy.js";
|
|
|
5
5
|
import { socketConnection } from "./localService.js";
|
|
6
6
|
/** SFTP status codes (draft-ietf-secsh-filexfer-02). */
|
|
7
7
|
const SFTP_NO_SUCH_FILE = 2;
|
|
8
|
+
const SFTP_EOF = 1;
|
|
8
9
|
/**
|
|
9
10
|
* Commands running at once on the connection. sshd refuses sessions past its MaxSessions (10 by
|
|
10
11
|
* default) per connection, and counts a closed one until it has cleaned up after it, so this stays
|
|
@@ -181,6 +182,30 @@ export function createSshSandbox(host, options) {
|
|
|
181
182
|
const data = await sftpCall("read", full, (channel, done) => channel.readFile(full, done));
|
|
182
183
|
return new Uint8Array(data);
|
|
183
184
|
}
|
|
185
|
+
async function sftpReadFileRange(path, start, end) {
|
|
186
|
+
const full = remotePath(path);
|
|
187
|
+
const channel = await sftp();
|
|
188
|
+
const handle = await sftpCall("read", full, (sftpChannel, done) => sftpChannel.open(full, "r", done));
|
|
189
|
+
try {
|
|
190
|
+
const buffer = Buffer.alloc(Math.max(0, end - start));
|
|
191
|
+
let filled = 0;
|
|
192
|
+
while (filled < buffer.length) {
|
|
193
|
+
const read = await new Promise((resolve, reject) => channel.read(handle, buffer, filled, buffer.length - filled, start + filled, (error, bytesRead) => {
|
|
194
|
+
// The end of the file comes as an EOF status rather than a short read.
|
|
195
|
+
if (error && error.code !== SFTP_EOF)
|
|
196
|
+
return reject(new Error(`read ${full} on ${host} failed: ${error.message}`));
|
|
197
|
+
resolve(error ? 0 : bytesRead);
|
|
198
|
+
}));
|
|
199
|
+
if (read === 0)
|
|
200
|
+
break;
|
|
201
|
+
filled += read;
|
|
202
|
+
}
|
|
203
|
+
return new Uint8Array(buffer.subarray(0, filled));
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
channel.close(handle, () => { });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
184
209
|
/** Writes `data` to a unique hidden file next to `full` (creating its directory) and returns its path. */
|
|
185
210
|
async function writeTemp(full, data) {
|
|
186
211
|
await createDirectory(posix.dirname(full));
|
|
@@ -268,7 +293,7 @@ export function createSshSandbox(host, options) {
|
|
|
268
293
|
async function sftpStat(path) {
|
|
269
294
|
const info = await statOf(remotePath(path));
|
|
270
295
|
const type = info.isDirectory() ? "directory" : info.isFile() ? "file" : "other";
|
|
271
|
-
return { type, uid: info.uid, gid: info.gid };
|
|
296
|
+
return { type, uid: info.uid, gid: info.gid, size: info.size };
|
|
272
297
|
}
|
|
273
298
|
// SFTP starts in the user's home directory. Asked once: storage paths start from it on every call.
|
|
274
299
|
let home = null;
|
|
@@ -301,6 +326,12 @@ export function createSshSandbox(host, options) {
|
|
|
301
326
|
checkShell(result, "read", full);
|
|
302
327
|
return new Uint8Array(result.stdout);
|
|
303
328
|
},
|
|
329
|
+
async readFileRange(full, start, end) {
|
|
330
|
+
const script = `[ -e "$P" ] || exit ${NOT_FOUND}; tail -c +$(($S + 1)) -- "$P" | head -c "$L"`;
|
|
331
|
+
const result = await runBytes(commandLine(["sh", "-c", script], { P: full, S: String(start), L: String(Math.max(0, end - start)) }));
|
|
332
|
+
checkShell(result, "read", full);
|
|
333
|
+
return new Uint8Array(result.stdout);
|
|
334
|
+
},
|
|
304
335
|
/** Streams `data` into a temp file beside `full`, then runs `publish` ($T the temp file, $F the target). */
|
|
305
336
|
writeVia(full, data, publish) {
|
|
306
337
|
const tmp = posix.join(posix.dirname(full), `.${posix.basename(full)}.${crypto.randomUUID()}.tmp`);
|
|
@@ -329,11 +360,11 @@ export function createSshSandbox(host, options) {
|
|
|
329
360
|
return result.stdout.split("\n").filter(Boolean);
|
|
330
361
|
},
|
|
331
362
|
async stat(full) {
|
|
332
|
-
const result = await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%F' -- "$P"`, { P: full });
|
|
363
|
+
const result = await sh(`[ -e "$P" ] || exit ${NOT_FOUND}; stat -c '%u:%g:%s:%F' -- "$P"`, { P: full });
|
|
333
364
|
checkShell(result, "stat", full);
|
|
334
|
-
const [uid = "", gid = "", kind = ""] = result.stdout.trim().split(":");
|
|
365
|
+
const [uid = "", gid = "", size = "", kind = ""] = result.stdout.trim().split(":");
|
|
335
366
|
const type = kind === "directory" ? "directory" : kind.startsWith("regular") ? "file" : "other";
|
|
336
|
-
return { type, uid: Number(uid), gid: Number(gid) };
|
|
367
|
+
return { type, uid: Number(uid), gid: Number(gid), size: Number(size) };
|
|
337
368
|
},
|
|
338
369
|
// sshd always sets HOME for the session.
|
|
339
370
|
async homeDirectory() {
|
|
@@ -343,6 +374,7 @@ export function createSshSandbox(host, options) {
|
|
|
343
374
|
},
|
|
344
375
|
};
|
|
345
376
|
const readFile = async (path) => ((await sftpAvailable()) ? sftpReadFile(path) : shell.readFile(remotePath(path)));
|
|
377
|
+
const readFileRange = async (path, start, end) => (await sftpAvailable()) ? sftpReadFileRange(path, start, end) : shell.readFileRange(remotePath(path), start, end);
|
|
346
378
|
const writeFile = async (path, data) => (await sftpAvailable()) ? sftpWriteFile(path, data) : shell.writeFile(remotePath(path), data);
|
|
347
379
|
const writeFileExclusive = async (path, data) => (await sftpAvailable()) ? sftpWriteFileExclusive(path, data) : shell.writeFileExclusive(remotePath(path), data);
|
|
348
380
|
const removeFile = async (path) => ((await sftpAvailable()) ? sftpRemoveFile(path) : shell.removeFile(remotePath(path)));
|
|
@@ -592,6 +624,7 @@ export function createSshSandbox(host, options) {
|
|
|
592
624
|
}
|
|
593
625
|
const sandbox = {
|
|
594
626
|
readFile,
|
|
627
|
+
readFileRange,
|
|
595
628
|
writeFile,
|
|
596
629
|
writeFileExclusive,
|
|
597
630
|
removeFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyperbrane",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Dimensions, gateways and agents on a machine reached locally, over SSH or on Boat: SDK and CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,11 +11,27 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
"./sandbox/api": {
|
|
15
|
+
"types": "./dist/sandbox/api.d.ts",
|
|
16
|
+
"import": "./dist/sandbox/api.js"
|
|
17
|
+
},
|
|
18
|
+
"./dimension/api": {
|
|
19
|
+
"types": "./dist/dimension/api.d.ts",
|
|
20
|
+
"import": "./dist/dimension/api.js"
|
|
21
|
+
},
|
|
22
|
+
"./portal-gateway/api": {
|
|
23
|
+
"types": "./dist/portal-gateway/api.d.ts",
|
|
24
|
+
"import": "./dist/portal-gateway/api.js"
|
|
25
|
+
},
|
|
26
|
+
"./agent/api": {
|
|
27
|
+
"types": "./dist/agent/api.d.ts",
|
|
28
|
+
"import": "./dist/agent/api.js"
|
|
29
|
+
},
|
|
30
|
+
"./agent/client": {
|
|
31
|
+
"types": "./dist/agent/client.d.ts",
|
|
32
|
+
"import": "./dist/agent/client.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
19
35
|
},
|
|
20
36
|
"bin": {
|
|
21
37
|
"hyperbrane": "./dist/cli/main.js"
|
|
@@ -36,9 +52,10 @@
|
|
|
36
52
|
"embed:images": "bun run scripts/embed-image-files.ts",
|
|
37
53
|
"bundle:worker": "bun run scripts/bundle-worker.ts",
|
|
38
54
|
"build": "bun run build:vendor && bun run bundle:worker && bun run embed:images && rm -rf dist && tsc -p tsconfig.build.json && bun run scripts/finish-build.ts",
|
|
39
|
-
"build:vendor": "test -d agent/vendor-types/claude || bun run scripts/pull-vendor-types.ts claude",
|
|
55
|
+
"build:vendor": "test -d agent/vendor-types/claude && test -d agent/vendor-types/codex || bun run scripts/pull-vendor-types.ts claude codex",
|
|
40
56
|
"prepack": "bun run build",
|
|
41
|
-
"check:package": "bun run scripts/check-package.ts"
|
|
57
|
+
"check:package": "bun run scripts/check-package.ts",
|
|
58
|
+
"api:diff": "bun run scripts/api-diff.ts"
|
|
42
59
|
},
|
|
43
60
|
"dependencies": {
|
|
44
61
|
"@boatdev/sdk": "1.5.0",
|