@boxes-dev/dvb-runtime 1.0.181 → 1.0.183
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/dist/bin/dvb.cjs +284 -150
- package/dist/bin/dvb.cjs.map +1 -1
- package/dist/bin/dvbd.cjs +6 -6
- package/dist/devbox/commands/init/codex/execConfig.d.ts +1 -1
- package/dist/devbox/commands/init/codex/execConfig.d.ts.map +1 -1
- package/dist/devbox/commands/init/codex/execConfig.js +1 -1
- package/dist/devbox/commands/init/codex/execConfig.js.map +1 -1
- package/dist/devbox/commands/provider/modalRuntime.d.ts +1 -5
- package/dist/devbox/commands/provider/modalRuntime.d.ts.map +1 -1
- package/dist/devbox/commands/provider/modalRuntime.js +209 -99
- package/dist/devbox/commands/provider/modalRuntime.js.map +1 -1
- package/dist/devbox/daemonClient.d.ts +1 -0
- package/dist/devbox/daemonClient.d.ts.map +1 -1
- package/dist/devbox/daemonClient.js +7 -2
- package/dist/devbox/daemonClient.js.map +1 -1
- package/package.json +2 -2
package/dist/bin/dvbd.cjs
CHANGED
|
@@ -88612,8 +88612,8 @@ var init_otel = __esm({
|
|
|
88612
88612
|
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
88613
88613
|
};
|
|
88614
88614
|
readBuildMetadata = () => {
|
|
88615
|
-
const rawPackageVersion = "1.0.
|
|
88616
|
-
const rawGitSha = "
|
|
88615
|
+
const rawPackageVersion = "1.0.183";
|
|
88616
|
+
const rawGitSha = "91c94b669da1929b3b7db136d8ccbb5e06200520";
|
|
88617
88617
|
const packageVersion = typeof rawPackageVersion === "string" ? rawPackageVersion : void 0;
|
|
88618
88618
|
const gitSha = typeof rawGitSha === "string" ? rawGitSha : void 0;
|
|
88619
88619
|
return { packageVersion, gitSha };
|
|
@@ -120734,9 +120734,9 @@ var init_sentry = __esm({
|
|
|
120734
120734
|
sentryEnabled = false;
|
|
120735
120735
|
uncaughtExceptionMonitorInstalled = false;
|
|
120736
120736
|
readBuildMetadata2 = () => {
|
|
120737
|
-
const rawPackageVersion = "1.0.
|
|
120738
|
-
const rawGitSha = "
|
|
120739
|
-
const rawSentryRelease = "boxes-dev-dvb@1.0.
|
|
120737
|
+
const rawPackageVersion = "1.0.183";
|
|
120738
|
+
const rawGitSha = "91c94b669da1929b3b7db136d8ccbb5e06200520";
|
|
120739
|
+
const rawSentryRelease = "boxes-dev-dvb@1.0.183+91c94b669da1929b3b7db136d8ccbb5e06200520";
|
|
120740
120740
|
const packageVersion = typeof rawPackageVersion === "string" ? rawPackageVersion : void 0;
|
|
120741
120741
|
const gitSha = typeof rawGitSha === "string" ? rawGitSha : void 0;
|
|
120742
120742
|
const sentryRelease = typeof rawSentryRelease === "string" ? rawSentryRelease : void 0;
|
|
@@ -124679,7 +124679,7 @@ var init_packageVersion = __esm({
|
|
|
124679
124679
|
return import_node_path7.default.join(process.cwd(), "dvb");
|
|
124680
124680
|
};
|
|
124681
124681
|
readEmbeddedPackageVersion = () => {
|
|
124682
|
-
const raw = "1.0.
|
|
124682
|
+
const raw = "1.0.183";
|
|
124683
124683
|
return trimVersion(raw);
|
|
124684
124684
|
};
|
|
124685
124685
|
readNearestPackageMetadata = (basePath) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const CODEX_INIT_EXEC_MODEL = "gpt-5.
|
|
1
|
+
declare const CODEX_INIT_EXEC_MODEL = "gpt-5.4";
|
|
2
2
|
declare const CODEX_INIT_EXEC_REASONING_EFFORT = "high";
|
|
3
3
|
declare const buildCodexInitExecArgs: () => string[];
|
|
4
4
|
export { CODEX_INIT_EXEC_MODEL, CODEX_INIT_EXEC_REASONING_EFFORT, buildCodexInitExecArgs, };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execConfig.d.ts","sourceRoot":"","sources":["../../../../../src/devbox/commands/init/codex/execConfig.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"execConfig.d.ts","sourceRoot":"","sources":["../../../../../src/devbox/commands/init/codex/execConfig.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,qBAAqB,YAAY,CAAC;AACxC,QAAA,MAAM,gCAAgC,SAAS,CAAC;AAEhD,QAAA,MAAM,sBAAsB,gBAK3B,CAAC;AAEF,OAAO,EACL,qBAAqB,EACrB,gCAAgC,EAChC,sBAAsB,GACvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execConfig.js","sourceRoot":"","sources":["../../../../../src/devbox/commands/init/codex/execConfig.ts"],"names":[],"mappings":"AAAA,MAAM,qBAAqB,GAAG,
|
|
1
|
+
{"version":3,"file":"execConfig.js","sourceRoot":"","sources":["../../../../../src/devbox/commands/init/codex/execConfig.ts"],"names":[],"mappings":"AAAA,MAAM,qBAAqB,GAAG,SAAS,CAAC;AACxC,MAAM,gCAAgC,GAAG,MAAM,CAAC;AAEhD,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAAC;IACnC,SAAS;IACT,qBAAqB;IACrB,UAAU;IACV,0BAA0B,gCAAgC,EAAE;CAC7D,CAAC;AAEF,OAAO,EACL,qBAAqB,EACrB,gCAAgC,EAChC,sBAAsB,GACvB,CAAC"}
|
|
@@ -19,11 +19,7 @@ type ModalConnectToken = {
|
|
|
19
19
|
url: string;
|
|
20
20
|
token: string;
|
|
21
21
|
};
|
|
22
|
-
declare const shouldChunkModalFsWrite: (byteLength: number) => boolean;
|
|
23
|
-
declare const resolveModalFsWriteControlTimeoutMs: (byteLength: number) => number;
|
|
24
|
-
declare const resolveModalFsWriteAssembleTimeoutMs: (byteLength: number) => number;
|
|
25
|
-
declare const buildModalFsWriteAssembleScript: (targetPath: string, assembledPath: string, partPaths: string[]) => string;
|
|
26
22
|
declare const createModalRuntimeClient: ({ alias, appName: initialAppName, sandboxId: initialSandboxId, ensureActive, credentialMode, credentials, createConnectToken: _createConnectToken, createCheckpoint: createCheckpointViaControlPlane, cwd, serviceRole: requestedServiceRole, }: CreateModalRuntimeClientOptions) => InitRuntimeClient;
|
|
27
|
-
export {
|
|
23
|
+
export { createModalRuntimeClient };
|
|
28
24
|
export type { CreateModalRuntimeClientOptions };
|
|
29
25
|
//# sourceMappingURL=modalRuntime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modalRuntime.d.ts","sourceRoot":"","sources":["../../../../src/devbox/commands/provider/modalRuntime.ts"],"names":[],"mappings":"AAKA,OAAO,EAYL,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAG5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,kBAAkB,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"modalRuntime.d.ts","sourceRoot":"","sources":["../../../../src/devbox/commands/provider/modalRuntime.ts"],"names":[],"mappings":"AAKA,OAAO,EAYL,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAG5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,kBAAkB,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAiBjF,KAAK,+BAA+B,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,CACb,MAAM,EAAE,OAAO,GAAG,OAAO,KACtB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,WAAW,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CACnB,MAAM,EAAE,OAAO,GAAG,OAAO,KACtB,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAChC,gBAAgB,CAAC,EAAE,CACjB,OAAO,CAAC,EAAE,MAAM,KACb,UAAU,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CACjC,CAAC;AAKF,KAAK,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAuuBxD,QAAA,MAAM,wBAAwB,GAAI,iPAW/B,+BAA+B,KAAG,iBA48BpC,CAAC;AAEF,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,YAAY,EAAE,+BAA+B,EAAE,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import { ModalClient, NotFoundError } from "modal";
|
|
5
5
|
import WebSocket, {} from "ws";
|
|
6
6
|
import { createLogger, ProviderClientError, SpritesApiError, resolveModalClientCredentials, resolveSocketInfo, withDevboxSpan, } from "@boxes-dev/core";
|
|
7
|
-
import { DAEMON_TIMEOUT_MS, ensureDaemonRunning, requestJson, requireDaemonFeatures, formatDaemonResponseError, } from "../../daemonClient.js";
|
|
7
|
+
import { DAEMON_TIMEOUT_MS, ensureDaemonRunning, requestJson, requireDaemonFeatures, formatDaemonResponseError, isDaemonConnectionError, } from "../../daemonClient.js";
|
|
8
8
|
import { resolveModalEnvironmentName, resolveModalSandboxName, } from "./modalLifecycle.js";
|
|
9
9
|
const SERVICE_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
10
10
|
const CORE_SERVICE_NAMES = new Set(["devbox-sprite-daemon", "devbox-sshd"]);
|
|
@@ -14,9 +14,13 @@ const MODAL_DAEMON_TTY_HANDSHAKE_TIMEOUT_MS = 30_000;
|
|
|
14
14
|
const MODAL_SANDBOX_RESOLVE_TIMEOUT_MS = 30_000;
|
|
15
15
|
const MODAL_DAEMON_CONTROL_HTTP_TIMEOUT_MS = 25_000;
|
|
16
16
|
const MODAL_DAEMON_CONTROL_HTTP_TIMEOUT_PADDING_MS = 10_000;
|
|
17
|
-
const MODAL_FS_WRITE_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
18
|
-
const MODAL_FS_WRITE_TIMEOUT_PER_MIB_MS = 4_000;
|
|
19
17
|
const MODAL_DAEMON_TTY_WS_PATH = "/modal/tty";
|
|
18
|
+
const MODAL_DAEMON_INLINE_WRITE_MAX_BYTES = 8 * 1024 * 1024;
|
|
19
|
+
const MODAL_DAEMON_MULTIPART_WRITE_PART_SIZE_BYTES = 8 * 1024 * 1024;
|
|
20
|
+
const MODAL_DAEMON_MULTIPART_WRITE_CONCURRENCY = 3;
|
|
21
|
+
const MODAL_DAEMON_WRITE_TIMEOUT_BASE_MS = 5_000;
|
|
22
|
+
const MODAL_DAEMON_WRITE_TIMEOUT_PER_MIB_MS = 2_000;
|
|
23
|
+
const MODAL_DAEMON_MULTIPART_ASSEMBLE_MIN_TIMEOUT_MS = 30_000;
|
|
20
24
|
const logger = createLogger("modal_runtime");
|
|
21
25
|
const validateServiceName = (service) => {
|
|
22
26
|
if (!SERVICE_NAME_PATTERN.test(service)) {
|
|
@@ -31,34 +35,90 @@ const clampModalControlTimeoutMs = (timeoutMs) => {
|
|
|
31
35
|
}
|
|
32
36
|
return Math.min(Math.max(Math.ceil(timeoutMs), MODAL_DAEMON_REQUEST_TIMEOUT_MS), MODAL_DAEMON_REQUEST_TIMEOUT_MAX_MS);
|
|
33
37
|
};
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
return clampModalControlTimeoutMs(
|
|
38
|
+
const sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
|
|
39
|
+
const estimateModalWriteTimeoutMs = (bytes) => {
|
|
40
|
+
const wholeMiB = Math.max(1, Math.ceil(bytes / (1024 * 1024)));
|
|
41
|
+
return clampModalControlTimeoutMs(MODAL_DAEMON_WRITE_TIMEOUT_BASE_MS +
|
|
42
|
+
wholeMiB * MODAL_DAEMON_WRITE_TIMEOUT_PER_MIB_MS);
|
|
38
43
|
};
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
const normalizeConcurrency = (concurrency) => Math.max(1, Math.floor(concurrency));
|
|
45
|
+
const resolveBoundedConcurrency = (itemCount, concurrency) => Math.max(1, Math.min(itemCount, normalizeConcurrency(concurrency)));
|
|
46
|
+
const estimateModalMultipartPartTimeoutMs = (bytes, concurrency) => estimateModalWriteTimeoutMs(bytes * normalizeConcurrency(concurrency));
|
|
47
|
+
const estimateModalMultipartAssembleTimeoutMs = (bytes) => clampModalControlTimeoutMs(Math.max(MODAL_DAEMON_MULTIPART_ASSEMBLE_MIN_TIMEOUT_MS, estimateModalWriteTimeoutMs(bytes) * 2));
|
|
48
|
+
const buildModalMultipartWritePlan = (remotePath, data) => {
|
|
49
|
+
const normalizedRemotePath = path.posix.normalize(remotePath);
|
|
50
|
+
const remoteDir = path.posix.dirname(normalizedRemotePath);
|
|
51
|
+
const remoteBaseName = path.posix.basename(normalizedRemotePath);
|
|
52
|
+
const uploadId = randomUUID();
|
|
53
|
+
const partDir = path.posix.join(remoteDir, `.${remoteBaseName}.tmp-upload-${uploadId}`);
|
|
54
|
+
const assembledTmpPath = path.posix.join(remoteDir, `.${remoteBaseName}.tmp-assemble-${uploadId}`);
|
|
55
|
+
const parts = [];
|
|
56
|
+
for (let offset = 0, index = 0; offset < data.length; offset += MODAL_DAEMON_MULTIPART_WRITE_PART_SIZE_BYTES, index += 1) {
|
|
57
|
+
const partData = data.subarray(offset, Math.min(offset + MODAL_DAEMON_MULTIPART_WRITE_PART_SIZE_BYTES, data.length));
|
|
58
|
+
parts.push({
|
|
59
|
+
index,
|
|
60
|
+
remotePath: path.posix.join(partDir, `part-${String(index).padStart(5, "0")}`),
|
|
61
|
+
data: partData,
|
|
62
|
+
sizeBytes: partData.length,
|
|
63
|
+
sha256: sha256Hex(partData),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
partDir,
|
|
68
|
+
assembledTmpPath,
|
|
69
|
+
expectedSizeBytes: data.length,
|
|
70
|
+
expectedSha256: sha256Hex(data),
|
|
71
|
+
parts,
|
|
72
|
+
};
|
|
61
73
|
};
|
|
74
|
+
const buildModalMultipartAssembleScript = (remotePath, plan) => [
|
|
75
|
+
"set -euo pipefail",
|
|
76
|
+
`target=${shellQuote(path.posix.normalize(remotePath))}`,
|
|
77
|
+
`part_dir=${shellQuote(plan.partDir)}`,
|
|
78
|
+
`assembled_tmp=${shellQuote(plan.assembledTmpPath)}`,
|
|
79
|
+
`expected_size=${plan.expectedSizeBytes}`,
|
|
80
|
+
`expected_sha=${shellQuote(plan.expectedSha256)}`,
|
|
81
|
+
"hash_file() {",
|
|
82
|
+
" if command -v sha256sum >/dev/null 2>&1; then",
|
|
83
|
+
" sha256sum \"$1\" | awk '{print $1}'",
|
|
84
|
+
" return 0",
|
|
85
|
+
" fi",
|
|
86
|
+
" if command -v shasum >/dev/null 2>&1; then",
|
|
87
|
+
" shasum -a 256 \"$1\" | awk '{print $1}'",
|
|
88
|
+
" return 0",
|
|
89
|
+
" fi",
|
|
90
|
+
' echo "No SHA256 tool available (sha256sum/shasum)." >&2',
|
|
91
|
+
" return 1",
|
|
92
|
+
"}",
|
|
93
|
+
'cleanup() { rm -f "$assembled_tmp"; }',
|
|
94
|
+
"trap cleanup EXIT",
|
|
95
|
+
'mkdir -p "$(dirname "$target")"',
|
|
96
|
+
'[ -d "$part_dir" ] || { echo "Missing multipart upload directory: $part_dir" >&2; exit 1; }',
|
|
97
|
+
': > "$assembled_tmp"',
|
|
98
|
+
...plan.parts.flatMap((part) => [
|
|
99
|
+
`part_path=${shellQuote(part.remotePath)}`,
|
|
100
|
+
`part_expected_size=${part.sizeBytes}`,
|
|
101
|
+
`part_expected_sha=${shellQuote(part.sha256)}`,
|
|
102
|
+
'[ -f "$part_path" ] || { echo "Missing upload part: $part_path" >&2; exit 1; }',
|
|
103
|
+
'part_size="$(wc -c < "$part_path" | tr -d \'[:space:]\')"',
|
|
104
|
+
'if [ "$part_size" != "$part_expected_size" ]; then echo "Part size mismatch for $part_path: expected $part_expected_size got $part_size" >&2; exit 1; fi',
|
|
105
|
+
'part_sha="$(hash_file "$part_path")"',
|
|
106
|
+
'if [ "$part_sha" != "$part_expected_sha" ]; then echo "Part checksum mismatch for $part_path" >&2; exit 1; fi',
|
|
107
|
+
'cat "$part_path" >> "$assembled_tmp"',
|
|
108
|
+
]),
|
|
109
|
+
'assembled_size="$(wc -c < "$assembled_tmp" | tr -d \'[:space:]\')"',
|
|
110
|
+
'if [ "$assembled_size" != "$expected_size" ]; then echo "Assembled file size mismatch: expected $expected_size got $assembled_size" >&2; exit 1; fi',
|
|
111
|
+
'assembled_sha="$(hash_file "$assembled_tmp")"',
|
|
112
|
+
'if [ "$assembled_sha" != "$expected_sha" ]; then echo "Assembled file checksum mismatch" >&2; exit 1; fi',
|
|
113
|
+
'mv "$assembled_tmp" "$target"',
|
|
114
|
+
'rm -rf "$part_dir"',
|
|
115
|
+
"trap - EXIT",
|
|
116
|
+
].join("\n");
|
|
117
|
+
const buildModalMultipartCleanupScript = (plan) => [
|
|
118
|
+
"set -euo pipefail",
|
|
119
|
+
`rm -rf ${shellQuote(plan.partDir)}`,
|
|
120
|
+
`rm -f ${shellQuote(plan.assembledTmpPath)}`,
|
|
121
|
+
].join("\n");
|
|
62
122
|
const buildModalControlTraceAttributes = (alias, requestPayload, expectedType, responseTimeoutMs) => {
|
|
63
123
|
const requestType = typeof requestPayload.type === "string" ? requestPayload.type : "unknown";
|
|
64
124
|
return {
|
|
@@ -88,6 +148,43 @@ const buildModalControlTraceAttributes = (alias, requestPayload, expectedType, r
|
|
|
88
148
|
: {}),
|
|
89
149
|
};
|
|
90
150
|
};
|
|
151
|
+
const runWithConcurrency = async (items, concurrency, worker) => {
|
|
152
|
+
if (items.length === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const boundedConcurrency = resolveBoundedConcurrency(items.length, concurrency);
|
|
156
|
+
let nextIndex = 0;
|
|
157
|
+
let firstError = null;
|
|
158
|
+
const workers = Array.from({ length: boundedConcurrency }, async () => {
|
|
159
|
+
while (true) {
|
|
160
|
+
if (firstError) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const currentIndex = nextIndex;
|
|
164
|
+
nextIndex += 1;
|
|
165
|
+
if (currentIndex >= items.length) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const item = items.at(currentIndex);
|
|
169
|
+
if (item === undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await worker(item);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (!firstError) {
|
|
177
|
+
firstError = error;
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await Promise.all(workers);
|
|
184
|
+
if (firstError) {
|
|
185
|
+
throw firstError;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
91
188
|
const resolveReadPath = (readPath, workingDir) => {
|
|
92
189
|
if (path.posix.isAbsolute(readPath))
|
|
93
190
|
return path.posix.normalize(readPath);
|
|
@@ -527,6 +624,7 @@ class ModalDaemonExecSocket {
|
|
|
527
624
|
const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: initialSandboxId, ensureActive, credentialMode, credentials, createConnectToken: _createConnectToken, createCheckpoint: createCheckpointViaControlPlane, cwd, serviceRole: requestedServiceRole, }) => {
|
|
528
625
|
let modal = null;
|
|
529
626
|
let modalSandboxName = alias;
|
|
627
|
+
const socketPath = resolveSocketInfo().socketPath;
|
|
530
628
|
try {
|
|
531
629
|
const modalCredentials = resolveModalClientCredentials({
|
|
532
630
|
...(credentialMode ? { credentialMode } : {}),
|
|
@@ -612,6 +710,22 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
612
710
|
const stderr = typeof response.stderr === "string" ? response.stderr : "";
|
|
613
711
|
return { exitCode, stdout, stderr };
|
|
614
712
|
};
|
|
713
|
+
const execShellScript = async (script, timeoutMs, failureMessage) => {
|
|
714
|
+
const result = await exec(alias, [
|
|
715
|
+
"/bin/bash",
|
|
716
|
+
"--noprofile",
|
|
717
|
+
"--norc",
|
|
718
|
+
"-e",
|
|
719
|
+
"-u",
|
|
720
|
+
"-o",
|
|
721
|
+
"pipefail",
|
|
722
|
+
"-c",
|
|
723
|
+
script,
|
|
724
|
+
], { timeoutMs });
|
|
725
|
+
if (result.exitCode !== 0) {
|
|
726
|
+
throw new Error(result.stderr || result.stdout || failureMessage);
|
|
727
|
+
}
|
|
728
|
+
};
|
|
615
729
|
const readFile = async (name, options) => {
|
|
616
730
|
requireAlias(name);
|
|
617
731
|
const resolvedPath = resolveReadPath(options.path, options.workingDir);
|
|
@@ -636,10 +750,14 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
636
750
|
requireAlias(name);
|
|
637
751
|
const total = data.length;
|
|
638
752
|
options?.onProgress?.(0, total);
|
|
639
|
-
const chunked =
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
753
|
+
const chunked = total > MODAL_DAEMON_INLINE_WRITE_MAX_BYTES;
|
|
754
|
+
const chunkCount = chunked
|
|
755
|
+
? Math.ceil(total / MODAL_DAEMON_MULTIPART_WRITE_PART_SIZE_BYTES)
|
|
756
|
+
: 0;
|
|
757
|
+
const plan = chunked ? buildModalMultipartWritePlan(remotePath, data) : null;
|
|
758
|
+
const multipartConcurrency = plan
|
|
759
|
+
? resolveBoundedConcurrency(plan.parts.length, MODAL_DAEMON_MULTIPART_WRITE_CONCURRENCY)
|
|
760
|
+
: 1;
|
|
643
761
|
await withDevboxSpan("modal.write_file", {
|
|
644
762
|
"devbox.modal.alias": alias,
|
|
645
763
|
"devbox.modal.path": remotePath,
|
|
@@ -654,8 +772,9 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
654
772
|
chunked,
|
|
655
773
|
...(chunked
|
|
656
774
|
? {
|
|
657
|
-
chunkBytes:
|
|
775
|
+
chunkBytes: MODAL_DAEMON_MULTIPART_WRITE_PART_SIZE_BYTES,
|
|
658
776
|
chunkCount,
|
|
777
|
+
multipartConcurrency,
|
|
659
778
|
}
|
|
660
779
|
: {}),
|
|
661
780
|
});
|
|
@@ -667,7 +786,7 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
667
786
|
mkdir: true,
|
|
668
787
|
data: Buffer.from(data).toString("base64"),
|
|
669
788
|
}, "fs_write_result", {
|
|
670
|
-
responseTimeoutMs:
|
|
789
|
+
responseTimeoutMs: estimateModalWriteTimeoutMs(total),
|
|
671
790
|
});
|
|
672
791
|
if (response.status !== "ok") {
|
|
673
792
|
const { text } = parseControlError(response);
|
|
@@ -682,99 +801,54 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
682
801
|
});
|
|
683
802
|
return;
|
|
684
803
|
}
|
|
685
|
-
|
|
686
|
-
|
|
804
|
+
if (!plan) {
|
|
805
|
+
throw new Error("Missing multipart upload plan.");
|
|
806
|
+
}
|
|
687
807
|
let uploadedBytes = 0;
|
|
688
|
-
|
|
689
|
-
for (let offset = 0; offset < total; offset += MODAL_FS_WRITE_CHUNK_BYTES) {
|
|
690
|
-
const nextOffset = Math.min(offset + MODAL_FS_WRITE_CHUNK_BYTES, total);
|
|
691
|
-
const chunk = data.subarray(offset, nextOffset);
|
|
692
|
-
const partPath = `${remotePath}.tmp-upload-${uploadId}.part-${String(chunkIndex).padStart(4, "0")}`;
|
|
693
|
-
partPaths.push(partPath);
|
|
808
|
+
await runWithConcurrency(plan.parts, multipartConcurrency, async (part) => {
|
|
694
809
|
const response = await runControlRequest({
|
|
695
810
|
type: "fs_write",
|
|
696
|
-
path:
|
|
811
|
+
path: part.remotePath,
|
|
697
812
|
mkdir: true,
|
|
698
|
-
data: Buffer.from(
|
|
813
|
+
data: Buffer.from(part.data).toString("base64"),
|
|
699
814
|
}, "fs_write_result", {
|
|
700
|
-
responseTimeoutMs:
|
|
815
|
+
responseTimeoutMs: estimateModalMultipartPartTimeoutMs(part.sizeBytes, multipartConcurrency),
|
|
701
816
|
});
|
|
702
817
|
if (response.status !== "ok") {
|
|
703
818
|
const { text } = parseControlError(response);
|
|
704
819
|
throw new Error(text);
|
|
705
820
|
}
|
|
706
|
-
uploadedBytes +=
|
|
821
|
+
uploadedBytes += part.sizeBytes;
|
|
707
822
|
logger.info("modal_fs_write_chunk_complete", {
|
|
708
823
|
alias,
|
|
709
824
|
remotePath,
|
|
710
|
-
chunkIndex,
|
|
711
|
-
chunkCount,
|
|
712
|
-
chunkSizeBytes:
|
|
825
|
+
chunkIndex: part.index,
|
|
826
|
+
chunkCount: plan.parts.length,
|
|
827
|
+
chunkSizeBytes: part.sizeBytes,
|
|
713
828
|
uploadedBytes,
|
|
714
829
|
totalBytes: total,
|
|
715
830
|
});
|
|
716
|
-
chunkIndex += 1;
|
|
717
831
|
options?.onProgress?.(uploadedBytes, total);
|
|
718
|
-
}
|
|
719
|
-
const assembleResult = await exec(name, [
|
|
720
|
-
"/bin/bash",
|
|
721
|
-
"--noprofile",
|
|
722
|
-
"--norc",
|
|
723
|
-
"-e",
|
|
724
|
-
"-u",
|
|
725
|
-
"-o",
|
|
726
|
-
"pipefail",
|
|
727
|
-
"-c",
|
|
728
|
-
buildModalFsWriteAssembleScript(remotePath, assembledPath, partPaths),
|
|
729
|
-
], {
|
|
730
|
-
timeoutMs: resolveModalFsWriteAssembleTimeoutMs(total),
|
|
731
832
|
});
|
|
732
|
-
|
|
733
|
-
throw new Error(assembleResult.stderr ||
|
|
734
|
-
assembleResult.stdout ||
|
|
735
|
-
"Failed to assemble chunked Modal upload.");
|
|
736
|
-
}
|
|
833
|
+
await execShellScript(buildModalMultipartAssembleScript(remotePath, plan), estimateModalMultipartAssembleTimeoutMs(total), "Failed to assemble multipart Modal upload.");
|
|
737
834
|
options?.onProgress?.(total, total);
|
|
738
835
|
logger.info("modal_fs_write_complete", {
|
|
739
836
|
alias,
|
|
740
837
|
remotePath,
|
|
741
838
|
sizeBytes: total,
|
|
742
839
|
chunked: true,
|
|
743
|
-
chunkCount:
|
|
840
|
+
chunkCount: plan.parts.length,
|
|
744
841
|
});
|
|
745
842
|
}
|
|
746
843
|
catch (error) {
|
|
747
|
-
|
|
748
|
-
? [...partPaths, ...(assembledPath ? [assembledPath] : [])]
|
|
749
|
-
: [];
|
|
750
|
-
if (cleanupPaths.length > 0) {
|
|
844
|
+
if (plan) {
|
|
751
845
|
try {
|
|
752
|
-
|
|
753
|
-
"/bin/bash",
|
|
754
|
-
"--noprofile",
|
|
755
|
-
"--norc",
|
|
756
|
-
"-e",
|
|
757
|
-
"-u",
|
|
758
|
-
"-o",
|
|
759
|
-
"pipefail",
|
|
760
|
-
"-c",
|
|
761
|
-
buildModalFsWriteCleanupScript(cleanupPaths),
|
|
762
|
-
], { timeoutMs: MODAL_DAEMON_REQUEST_TIMEOUT_MS });
|
|
763
|
-
if (cleanupResult.exitCode !== 0) {
|
|
764
|
-
logger.warn("modal_fs_write_cleanup_failed", {
|
|
765
|
-
alias,
|
|
766
|
-
remotePath,
|
|
767
|
-
cleanupTargetCount: cleanupPaths.length,
|
|
768
|
-
stderr: cleanupResult.stderr || undefined,
|
|
769
|
-
stdout: cleanupResult.stdout || undefined,
|
|
770
|
-
});
|
|
771
|
-
}
|
|
846
|
+
await execShellScript(buildModalMultipartCleanupScript(plan), MODAL_DAEMON_REQUEST_TIMEOUT_MS, "Failed to clean up multipart Modal upload.");
|
|
772
847
|
}
|
|
773
848
|
catch (cleanupError) {
|
|
774
849
|
logger.warn("modal_fs_write_cleanup_failed", {
|
|
775
850
|
alias,
|
|
776
851
|
remotePath,
|
|
777
|
-
cleanupTargetCount: cleanupPaths.length,
|
|
778
852
|
error: cleanupError instanceof Error
|
|
779
853
|
? cleanupError.message
|
|
780
854
|
: String(cleanupError),
|
|
@@ -786,7 +860,6 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
786
860
|
remotePath,
|
|
787
861
|
sizeBytes: total,
|
|
788
862
|
chunked,
|
|
789
|
-
cleanupAttempted: cleanupPaths.length > 0,
|
|
790
863
|
error: error instanceof Error ? error.message : String(error),
|
|
791
864
|
});
|
|
792
865
|
throw error;
|
|
@@ -1088,7 +1161,28 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
1088
1161
|
return amount * 60_000;
|
|
1089
1162
|
return undefined;
|
|
1090
1163
|
};
|
|
1091
|
-
|
|
1164
|
+
let modalControlReadyPromise = null;
|
|
1165
|
+
const ensureModalControlReady = async (force = false) => {
|
|
1166
|
+
if (!force && modalControlReadyPromise) {
|
|
1167
|
+
await modalControlReadyPromise;
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const ready = (async () => {
|
|
1171
|
+
await ensureDaemonRunning(socketPath);
|
|
1172
|
+
await requireDaemonFeatures(socketPath, ["modal.control"]);
|
|
1173
|
+
})();
|
|
1174
|
+
modalControlReadyPromise = ready;
|
|
1175
|
+
try {
|
|
1176
|
+
await ready;
|
|
1177
|
+
}
|
|
1178
|
+
catch (error) {
|
|
1179
|
+
if (modalControlReadyPromise === ready) {
|
|
1180
|
+
modalControlReadyPromise = null;
|
|
1181
|
+
}
|
|
1182
|
+
throw error;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
const postControlRequest = async (requestPayload, expectedType, options) => {
|
|
1092
1186
|
const socketInfo = resolveSocketInfo();
|
|
1093
1187
|
await ensureDaemonRunning(socketInfo.socketPath);
|
|
1094
1188
|
await requireDaemonFeatures(socketInfo.socketPath, ["modal.control"]);
|
|
@@ -1128,6 +1222,22 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
1128
1222
|
return payload;
|
|
1129
1223
|
});
|
|
1130
1224
|
};
|
|
1225
|
+
const runControlRequest = async (requestPayload, expectedType, options) => {
|
|
1226
|
+
await ensureModalControlReady();
|
|
1227
|
+
let response;
|
|
1228
|
+
try {
|
|
1229
|
+
response = await postControlRequest(requestPayload, expectedType, options);
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
if (!isDaemonConnectionError(error)) {
|
|
1233
|
+
throw error;
|
|
1234
|
+
}
|
|
1235
|
+
modalControlReadyPromise = null;
|
|
1236
|
+
await ensureModalControlReady(true);
|
|
1237
|
+
response = await postControlRequest(requestPayload, expectedType, options);
|
|
1238
|
+
}
|
|
1239
|
+
return response;
|
|
1240
|
+
};
|
|
1131
1241
|
const listExecSessions = async (name) => {
|
|
1132
1242
|
requireAlias(name);
|
|
1133
1243
|
const response = await runControlRequest({ type: "sessions_list" }, "sessions_list_result");
|
|
@@ -1229,5 +1339,5 @@ const createModalRuntimeClient = ({ alias, appName: initialAppName, sandboxId: i
|
|
|
1229
1339
|
attachExecSession,
|
|
1230
1340
|
};
|
|
1231
1341
|
};
|
|
1232
|
-
export {
|
|
1342
|
+
export { createModalRuntimeClient };
|
|
1233
1343
|
//# sourceMappingURL=modalRuntime.js.map
|