@tangle-network/agent-app 0.36.0 → 0.38.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.
- package/dist/{chunk-EHPK7GKR.js → chunk-7W5XSTUF.js} +11 -2
- package/dist/chunk-7W5XSTUF.js.map +1 -0
- package/dist/{chunk-GZGF3JWQ.js → chunk-MJ3RUHD4.js} +374 -171
- package/dist/chunk-MJ3RUHD4.js.map +1 -0
- package/dist/{chunk-MH6AVXQ7.js → chunk-VT5DI6GL.js} +40 -34
- package/dist/chunk-VT5DI6GL.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +22 -4
- package/dist/knowledge-loop/index.d.ts +1 -1
- package/dist/{model-CKzniMMr.d.ts → model-dF2h4xT9.d.ts} +17 -1
- package/dist/platform/index.d.ts +33 -1
- package/dist/platform/index.js +22 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +7 -3
- package/dist/sandbox/index.d.ts +92 -27
- package/dist/sandbox/index.js +17 -2
- package/dist/studio-react/index.js +2 -1
- package/dist/studio-react/index.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/web-react/index.js +31 -13
- package/dist/web-react/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-EHPK7GKR.js.map +0 -1
- package/dist/chunk-GZGF3JWQ.js.map +0 -1
- package/dist/chunk-MH6AVXQ7.js.map +0 -1
|
@@ -1,15 +1,60 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertHarnessModelCompatible
|
|
3
3
|
} from "./chunk-5VXPDXZJ.js";
|
|
4
|
+
import {
|
|
5
|
+
base64UrlDecodeText,
|
|
6
|
+
base64UrlEncodeText,
|
|
7
|
+
constantTimeEqual,
|
|
8
|
+
hmacSha256Base64Url
|
|
9
|
+
} from "./chunk-VT5DI6GL.js";
|
|
4
10
|
import {
|
|
5
11
|
buildAppToolMcpServer
|
|
6
12
|
} from "./chunk-A76ZHWNF.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveTangleExecutionEnvironment,
|
|
15
|
+
trimOrNull
|
|
16
|
+
} from "./chunk-7W5XSTUF.js";
|
|
7
17
|
|
|
8
18
|
// src/sandbox/index.ts
|
|
9
19
|
import {
|
|
10
20
|
Sandbox
|
|
11
21
|
} from "@tangle-network/sandbox";
|
|
12
22
|
|
|
23
|
+
// src/sandbox/outcome.ts
|
|
24
|
+
var ok = (value) => ({ succeeded: true, value });
|
|
25
|
+
var fail = (error) => ({
|
|
26
|
+
succeeded: false,
|
|
27
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/sandbox/terminal-proxy-token.ts
|
|
31
|
+
var TERMINAL_PROXY_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
32
|
+
async function mintTerminalProxyToken(secret, identity, ttlMs = TERMINAL_PROXY_TOKEN_TTL_MS, now = Date.now) {
|
|
33
|
+
if (!secret) return fail(new Error("mintTerminalProxyToken: secret is required"));
|
|
34
|
+
if (!identity.userId || !identity.workspaceId || !identity.sandboxId) {
|
|
35
|
+
return fail(new Error("mintTerminalProxyToken: userId/workspaceId/sandboxId are required"));
|
|
36
|
+
}
|
|
37
|
+
const expiresAt = new Date(now() + ttlMs);
|
|
38
|
+
const payload = { ...identity, exp: Math.floor(expiresAt.getTime() / 1e3) };
|
|
39
|
+
const encoded = base64UrlEncodeText(JSON.stringify(payload));
|
|
40
|
+
const sig = await hmacSha256Base64Url(encoded, secret);
|
|
41
|
+
return ok({ token: `${encoded}.${sig}`, expiresAt });
|
|
42
|
+
}
|
|
43
|
+
async function verifyTerminalProxyToken(secret, token, expected, now = Date.now) {
|
|
44
|
+
if (!secret) return false;
|
|
45
|
+
const [encoded, sig, extra] = token.split(".");
|
|
46
|
+
if (!encoded || !sig || extra !== void 0) return false;
|
|
47
|
+
const expectedSig = await hmacSha256Base64Url(encoded, secret);
|
|
48
|
+
if (!constantTimeEqual(sig, expectedSig)) return false;
|
|
49
|
+
let payload;
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(base64UrlDecodeText(encoded));
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return payload.userId === expected.userId && payload.workspaceId === expected.workspaceId && payload.sandboxId === expected.sandboxId && Number.isFinite(payload.exp) && payload.exp > Math.floor(now() / 1e3);
|
|
56
|
+
}
|
|
57
|
+
|
|
13
58
|
// src/sandbox/workspace-terminal.ts
|
|
14
59
|
function createWorkspaceSandboxManager(opts) {
|
|
15
60
|
return {
|
|
@@ -43,9 +88,9 @@ function createWorkspaceSandboxManager(opts) {
|
|
|
43
88
|
}
|
|
44
89
|
};
|
|
45
90
|
}
|
|
46
|
-
var DEFAULT_TERMINAL_TOKEN_PREFIX = "sbxt_";
|
|
47
91
|
var DEFAULT_TERMINAL_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
48
92
|
var BEARER_SUBPROTOCOL_PREFIX = "bearer.";
|
|
93
|
+
var LEGACY_TERMINAL_TOKEN_PREFIX = "sbxt_";
|
|
49
94
|
async function createSandboxTerminalToken(subject, opts) {
|
|
50
95
|
validateTerminalSubject(subject);
|
|
51
96
|
const secret = opts.secret?.trim();
|
|
@@ -53,38 +98,16 @@ async function createSandboxTerminalToken(subject, opts) {
|
|
|
53
98
|
const now = opts.now ?? Date.now;
|
|
54
99
|
const expiresInMs = opts.expiresInMs ?? DEFAULT_TERMINAL_TOKEN_TTL_MS;
|
|
55
100
|
if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
exp: Math.floor(expiresAt.getTime() / 1e3),
|
|
60
|
-
n: crypto.randomUUID()
|
|
61
|
-
};
|
|
62
|
-
const encodedPayload = base64urlText(JSON.stringify(payload));
|
|
63
|
-
const signature = await signText(encodedPayload, secret);
|
|
64
|
-
return {
|
|
65
|
-
token: `${opts.prefix ?? DEFAULT_TERMINAL_TOKEN_PREFIX}${encodedPayload}.${signature}`,
|
|
66
|
-
expiresAt
|
|
67
|
-
};
|
|
101
|
+
const minted = await mintTerminalProxyToken(secret, subject, expiresInMs, now);
|
|
102
|
+
if (!minted.succeeded) throw minted.error;
|
|
103
|
+
return minted.value;
|
|
68
104
|
}
|
|
69
105
|
async function verifySandboxTerminalToken(token, expected, opts) {
|
|
70
106
|
validateTerminalSubject(expected);
|
|
71
107
|
const secret = opts.secret?.trim();
|
|
72
|
-
const prefix = opts.prefix ?? DEFAULT_TERMINAL_TOKEN_PREFIX;
|
|
73
|
-
if (!secret || !token.startsWith(prefix)) return false;
|
|
74
|
-
const body = token.slice(prefix.length);
|
|
75
|
-
const dot = body.lastIndexOf(".");
|
|
76
|
-
if (dot <= 0 || dot === body.length - 1) return false;
|
|
77
|
-
const encodedPayload = body.slice(0, dot);
|
|
78
|
-
const signature = body.slice(dot + 1);
|
|
79
|
-
if (!timingSafeEqual(signature, await signText(encodedPayload, secret))) return false;
|
|
80
|
-
let payload;
|
|
81
|
-
try {
|
|
82
|
-
payload = JSON.parse(textFromBase64url(encodedPayload));
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
108
|
const now = opts.now ?? Date.now;
|
|
87
|
-
|
|
109
|
+
const normalized = token.startsWith(LEGACY_TERMINAL_TOKEN_PREFIX) ? token.slice(LEGACY_TERMINAL_TOKEN_PREFIX.length) : token;
|
|
110
|
+
return verifyTerminalProxyToken(secret ?? "", normalized, expected, now);
|
|
88
111
|
}
|
|
89
112
|
function createWorkspaceSandboxConnectionHandler(opts) {
|
|
90
113
|
return async function handleWorkspaceSandboxConnection({ request, params }) {
|
|
@@ -101,7 +124,7 @@ function createWorkspaceSandboxConnectionHandler(opts) {
|
|
|
101
124
|
{ status: 500 }
|
|
102
125
|
);
|
|
103
126
|
}
|
|
104
|
-
const directSidecarUrl = box.connection?.sidecarUrl ??
|
|
127
|
+
const directSidecarUrl = box.connection?.sidecarUrl ?? box.connection?.runtimeUrl;
|
|
105
128
|
const directSidecarToken = box.connection?.authToken ?? box.connection?.sidecarToken;
|
|
106
129
|
const directSidecarExpiresAt = box.connection?.authTokenExpiresAt;
|
|
107
130
|
if (opts.exposeDirectSidecar && directSidecarUrl && directSidecarToken && directSidecarExpiresAt) {
|
|
@@ -114,7 +137,7 @@ function createWorkspaceSandboxConnectionHandler(opts) {
|
|
|
114
137
|
sandboxId: box.id
|
|
115
138
|
});
|
|
116
139
|
}
|
|
117
|
-
if (!
|
|
140
|
+
if (!directSidecarUrl) {
|
|
118
141
|
return Response.json(
|
|
119
142
|
{
|
|
120
143
|
error: "Workspace sandbox runtime not ready. The sandbox is still initializing -- retry in a few seconds.",
|
|
@@ -128,7 +151,7 @@ function createWorkspaceSandboxConnectionHandler(opts) {
|
|
|
128
151
|
try {
|
|
129
152
|
scoped = await createSandboxTerminalToken(
|
|
130
153
|
{ userId: user.id, workspaceId, sandboxId: box.id },
|
|
131
|
-
{ secret, expiresInMs: opts.tokenExpiresInMs
|
|
154
|
+
{ secret, expiresInMs: opts.tokenExpiresInMs }
|
|
132
155
|
);
|
|
133
156
|
} catch (err) {
|
|
134
157
|
return Response.json(
|
|
@@ -161,20 +184,21 @@ function createWorkspaceSandboxRuntimeProxyHandler(opts) {
|
|
|
161
184
|
await opts.requireWorkspaceAccess({ request, userId: user.id, workspaceId, sandboxId });
|
|
162
185
|
const token = terminalTokenFromRequest(request.headers);
|
|
163
186
|
const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
|
|
164
|
-
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret
|
|
187
|
+
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
|
|
165
188
|
return Response.json({ error: "Invalid terminal token" }, { status: 403 });
|
|
166
189
|
}
|
|
167
190
|
const requestUrl = new URL(request.url);
|
|
168
191
|
const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
|
|
169
|
-
const
|
|
170
|
-
const
|
|
192
|
+
const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
|
|
193
|
+
const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
|
|
194
|
+
const upstreamUrl = directRuntimeConnection ? new URL(encodedRuntimePath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(
|
|
171
195
|
`/v1/sandboxes/${encodeURIComponent(sandboxId)}/runtime/${encodedRuntimePath}`,
|
|
172
196
|
credentials.baseUrl
|
|
173
197
|
);
|
|
174
198
|
upstreamUrl.search = requestUrl.search;
|
|
175
199
|
const headers = buildSandboxRuntimeProxyHeaders(
|
|
176
200
|
request.headers,
|
|
177
|
-
|
|
201
|
+
directRuntimeConnection?.authToken ?? credentials.apiKey,
|
|
178
202
|
opts.forwardHeaders
|
|
179
203
|
);
|
|
180
204
|
const init = {
|
|
@@ -203,7 +227,10 @@ function matchSandboxTerminalWsPath(pathname) {
|
|
|
203
227
|
if (!m) return null;
|
|
204
228
|
const [, workspaceId, sandboxId, subPath] = m;
|
|
205
229
|
if (!workspaceId || !sandboxId || !subPath) return null;
|
|
206
|
-
|
|
230
|
+
const decodedWorkspaceId = safeDecodeURIComponent(workspaceId);
|
|
231
|
+
const decodedSandboxId = safeDecodeURIComponent(sandboxId);
|
|
232
|
+
if (!decodedWorkspaceId || !decodedSandboxId) return null;
|
|
233
|
+
return { workspaceId: decodedWorkspaceId, sandboxId: decodedSandboxId, subPath };
|
|
207
234
|
}
|
|
208
235
|
function isSandboxTerminalWsUpgrade(request) {
|
|
209
236
|
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return false;
|
|
@@ -238,16 +265,19 @@ function createWorkspaceSandboxTerminalUpgradeHandler(opts) {
|
|
|
238
265
|
}
|
|
239
266
|
const token = terminalTokenFromRequest(request.headers);
|
|
240
267
|
const secret = typeof opts.tokenSecret === "function" ? opts.tokenSecret() : opts.tokenSecret;
|
|
241
|
-
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret
|
|
268
|
+
if (!token || !await verifySandboxTerminalToken(token, { userId: user.id, workspaceId, sandboxId }, { secret })) {
|
|
242
269
|
return new Response("Invalid terminal token", { status: 403 });
|
|
243
270
|
}
|
|
244
271
|
const runtimeConnection = await opts.getSandboxRuntimeConnection?.({ request, userId: user.id, workspaceId, sandboxId });
|
|
245
|
-
const
|
|
246
|
-
const
|
|
272
|
+
const directRuntimeConnection = runtimeConnection?.runtimeUrl && runtimeConnection.authToken ? runtimeConnection : null;
|
|
273
|
+
const credentials = directRuntimeConnection ? null : await opts.getSandboxApiCredentials({ request, userId: user.id, workspaceId, sandboxId });
|
|
274
|
+
const upstreamUrl = directRuntimeConnection ? new URL(subPath, `${directRuntimeConnection.runtimeUrl.replace(/\/+$/, "")}/`) : new URL(`/v1/sandboxes/${encodeURIComponent(sandboxId)}/runtime/${subPath}`, credentials.baseUrl);
|
|
247
275
|
upstreamUrl.search = url.search;
|
|
248
276
|
const upstreamHeaders = new Headers(request.headers);
|
|
249
|
-
|
|
277
|
+
const upstreamBearer = directRuntimeConnection?.authToken ?? credentials.apiKey;
|
|
278
|
+
upstreamHeaders.set("Authorization", `Bearer ${upstreamBearer}`);
|
|
250
279
|
upstreamHeaders.delete("host");
|
|
280
|
+
stripBearerSubprotocol(upstreamHeaders);
|
|
251
281
|
const fetchImpl = opts.fetch ?? fetch;
|
|
252
282
|
return fetchImpl(upstreamUrl.toString(), { method: request.method, headers: upstreamHeaders });
|
|
253
283
|
};
|
|
@@ -286,7 +316,7 @@ function bearerSubprotocolToken(value) {
|
|
|
286
316
|
const encoded = protocol.slice(BEARER_SUBPROTOCOL_PREFIX.length);
|
|
287
317
|
if (!encoded) return null;
|
|
288
318
|
try {
|
|
289
|
-
const token =
|
|
319
|
+
const token = base64UrlDecodeText(encoded).trim();
|
|
290
320
|
return token || null;
|
|
291
321
|
} catch {
|
|
292
322
|
return null;
|
|
@@ -297,45 +327,94 @@ function bearerSubprotocolToken(value) {
|
|
|
297
327
|
function terminalTokenFromRequest(headers) {
|
|
298
328
|
return bearerToken(headers.get("Authorization")) ?? bearerSubprotocolToken(headers.get("Sec-WebSocket-Protocol"));
|
|
299
329
|
}
|
|
330
|
+
function safeDecodeURIComponent(value) {
|
|
331
|
+
try {
|
|
332
|
+
return decodeURIComponent(value);
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function stripBearerSubprotocol(headers) {
|
|
338
|
+
const value = headers.get("Sec-WebSocket-Protocol");
|
|
339
|
+
if (!value) return;
|
|
340
|
+
const protocols = value.split(",").map((part) => part.trim()).filter((part) => part && !part.toLowerCase().startsWith(BEARER_SUBPROTOCOL_PREFIX));
|
|
341
|
+
if (protocols.length) {
|
|
342
|
+
headers.set("Sec-WebSocket-Protocol", protocols.join(", "));
|
|
343
|
+
} else {
|
|
344
|
+
headers.delete("Sec-WebSocket-Protocol");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
300
347
|
function validateTerminalSubject(subject) {
|
|
301
348
|
if (!subject.userId) throw new Error("userId is required");
|
|
302
349
|
if (!subject.workspaceId) throw new Error("workspaceId is required");
|
|
303
350
|
if (!subject.sandboxId) throw new Error("sandboxId is required");
|
|
304
351
|
}
|
|
305
|
-
async function signText(message, secret) {
|
|
306
|
-
const enc = new TextEncoder();
|
|
307
|
-
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
308
|
-
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
|
|
309
|
-
return base64url(new Uint8Array(sig));
|
|
310
|
-
}
|
|
311
|
-
function base64urlText(text) {
|
|
312
|
-
return base64url(new TextEncoder().encode(text));
|
|
313
|
-
}
|
|
314
|
-
function textFromBase64url(value) {
|
|
315
|
-
const b64 = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
316
|
-
const bin = atob(b64);
|
|
317
|
-
const bytes = new Uint8Array(bin.length);
|
|
318
|
-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
319
|
-
return new TextDecoder().decode(bytes);
|
|
320
|
-
}
|
|
321
|
-
function base64url(bytes) {
|
|
322
|
-
let s = "";
|
|
323
|
-
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
324
|
-
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
325
|
-
}
|
|
326
|
-
function timingSafeEqual(a, b) {
|
|
327
|
-
if (a.length !== b.length) return false;
|
|
328
|
-
let diff = 0;
|
|
329
|
-
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
330
|
-
return diff === 0;
|
|
331
|
-
}
|
|
332
352
|
|
|
333
353
|
// src/sandbox/index.ts
|
|
334
|
-
var
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
var DEFAULT_SANDBOX_DIRECT_KEY_NAMES = [
|
|
355
|
+
"TCLOUD_SANDBOX_API_KEY",
|
|
356
|
+
"SANDBOX_API_KEY",
|
|
357
|
+
"TANGLE_API_KEY"
|
|
358
|
+
];
|
|
359
|
+
var DEFAULT_SANDBOX_BASE_URL_NAMES = ["SANDBOX_GATEWAY_URL", "SANDBOX_API_URL"];
|
|
360
|
+
function normalizeBaseUrl(value) {
|
|
361
|
+
return value.trim().replace(/\/v1\/?$/, "").replace(/\/+$/, "");
|
|
362
|
+
}
|
|
363
|
+
function processEnv() {
|
|
364
|
+
return typeof process === "undefined" ? {} : process.env;
|
|
365
|
+
}
|
|
366
|
+
function directEnvCredentialsAllowed(environment, allow) {
|
|
367
|
+
if (typeof allow === "function") return allow(environment);
|
|
368
|
+
if (typeof allow === "boolean") return allow;
|
|
369
|
+
return environment === "development" || environment === "test";
|
|
370
|
+
}
|
|
371
|
+
function resolveSandboxBaseUrl(env, names, defaultBaseUrl) {
|
|
372
|
+
for (const name of names) {
|
|
373
|
+
const value2 = trimOrNull(env[name]);
|
|
374
|
+
if (value2) return normalizeBaseUrl(value2);
|
|
375
|
+
}
|
|
376
|
+
const value = trimOrNull(defaultBaseUrl);
|
|
377
|
+
if (value) return normalizeBaseUrl(value);
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Sandbox base URL is required (set one of ${names.join(", ")} or pass defaultBaseUrl).`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
function resolveDirectSandboxCredentials(env, keyNames, baseUrlNames, defaultBaseUrl) {
|
|
383
|
+
for (const name of keyNames) {
|
|
384
|
+
const apiKey = trimOrNull(env[name]);
|
|
385
|
+
if (!apiKey) continue;
|
|
386
|
+
return {
|
|
387
|
+
apiKey,
|
|
388
|
+
baseUrl: resolveSandboxBaseUrl(env, baseUrlNames, defaultBaseUrl)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
async function resolveSandboxClientCredentials(options = {}) {
|
|
394
|
+
const env = options.env ?? processEnv();
|
|
395
|
+
const environment = options.environment ?? resolveTangleExecutionEnvironment(env);
|
|
396
|
+
const keyNames = options.directKeyNames ?? DEFAULT_SANDBOX_DIRECT_KEY_NAMES;
|
|
397
|
+
const baseUrlNames = options.baseUrlNames ?? DEFAULT_SANDBOX_BASE_URL_NAMES;
|
|
398
|
+
const directAllowed = directEnvCredentialsAllowed(environment, options.allowDirectEnvCredentials);
|
|
399
|
+
const direct = () => directAllowed ? resolveDirectSandboxCredentials(env, keyNames, baseUrlNames, options.defaultBaseUrl) : null;
|
|
400
|
+
if (environment === "development" || environment === "test") {
|
|
401
|
+
const credentials2 = direct();
|
|
402
|
+
if (credentials2) return credentials2;
|
|
403
|
+
}
|
|
404
|
+
const provisioned = await options.provision?.({ environment, env });
|
|
405
|
+
if (provisioned) {
|
|
406
|
+
return {
|
|
407
|
+
apiKey: provisioned.apiKey,
|
|
408
|
+
baseUrl: normalizeBaseUrl(provisioned.baseUrl)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const credentials = direct();
|
|
412
|
+
if (credentials) return credentials;
|
|
413
|
+
const directHint = directAllowed ? ` or set one of ${keyNames.join(", ")}` : "";
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Sandbox credentials are required for ${environment} (provide a provision callback${directHint}).`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
339
418
|
var DEFAULT_SANDBOX_RESOURCES = {
|
|
340
419
|
image: "universal",
|
|
341
420
|
cpuCores: 2,
|
|
@@ -380,6 +459,87 @@ function buildAppToolMcpServers(options) {
|
|
|
380
459
|
function shellSingleQuote(value) {
|
|
381
460
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
382
461
|
}
|
|
462
|
+
var DEFAULT_SANDBOX_TOOL_BASE_DIR = "/home/agent/tools";
|
|
463
|
+
var SAFE_TOOL_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
464
|
+
function normalizeSandboxToolSegment(value, label) {
|
|
465
|
+
const segment = value.trim();
|
|
466
|
+
if (!segment || segment === "." || segment === ".." || !SAFE_TOOL_SEGMENT.test(segment)) {
|
|
467
|
+
throw new Error(`${label} must contain only letters, numbers, dots, underscores, or hyphens.`);
|
|
468
|
+
}
|
|
469
|
+
return segment;
|
|
470
|
+
}
|
|
471
|
+
function normalizeSandboxToolDir(value, label) {
|
|
472
|
+
const dir = value.trim().replace(/\/+$/, "");
|
|
473
|
+
if (!dir || !dir.startsWith("/") || dir.includes("\0") || dir.includes("\n")) {
|
|
474
|
+
throw new Error(`${label} must be an absolute sandbox path.`);
|
|
475
|
+
}
|
|
476
|
+
return dir === "" ? "/" : dir;
|
|
477
|
+
}
|
|
478
|
+
function sandboxToolRootDir(options) {
|
|
479
|
+
const appName = normalizeSandboxToolSegment(options.appName, "sandbox tool appName");
|
|
480
|
+
const baseDir = normalizeSandboxToolDir(
|
|
481
|
+
options.baseDir ?? DEFAULT_SANDBOX_TOOL_BASE_DIR,
|
|
482
|
+
"sandbox tool baseDir"
|
|
483
|
+
);
|
|
484
|
+
return `${baseDir}/${appName}`;
|
|
485
|
+
}
|
|
486
|
+
function sandboxToolBinDir(options) {
|
|
487
|
+
normalizeSandboxToolSegment(options.appName, "sandbox tool appName");
|
|
488
|
+
if (options.binDir) return normalizeSandboxToolDir(options.binDir, "sandbox tool binDir");
|
|
489
|
+
return `${sandboxToolRootDir(options)}/bin`;
|
|
490
|
+
}
|
|
491
|
+
function sandboxToolPath(options) {
|
|
492
|
+
const toolName = normalizeSandboxToolSegment(options.toolName, "sandbox tool name");
|
|
493
|
+
return `${sandboxToolBinDir(options)}/${toolName}`;
|
|
494
|
+
}
|
|
495
|
+
function buildSandboxToolFileMounts(options) {
|
|
496
|
+
return options.tools.map((tool) => {
|
|
497
|
+
const name = normalizeSandboxToolSegment(tool.name, "sandbox tool name");
|
|
498
|
+
return {
|
|
499
|
+
path: sandboxToolPath({ ...options, toolName: name }),
|
|
500
|
+
resource: { kind: "inline", name, content: tool.content },
|
|
501
|
+
executable: tool.executable ?? true
|
|
502
|
+
};
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function buildSandboxToolPathSetupScript(options) {
|
|
506
|
+
const binDir = sandboxToolBinDir(options);
|
|
507
|
+
const exportLine = `export PATH=${binDir}:$PATH`;
|
|
508
|
+
return [
|
|
509
|
+
"set -eu",
|
|
510
|
+
`mkdir -p ${shellSingleQuote(binDir)}`,
|
|
511
|
+
`PATH=${shellSingleQuote(binDir)}:$PATH`,
|
|
512
|
+
"export PATH",
|
|
513
|
+
'for profile in "${HOME:-/home/agent}/.profile" "${HOME:-/home/agent}/.bashrc" "${HOME:-/home/agent}/.zshrc"; do',
|
|
514
|
+
' mkdir -p "$(dirname "$profile")"',
|
|
515
|
+
' touch "$profile"',
|
|
516
|
+
` grep -Fqx ${shellSingleQuote(exportLine)} "$profile" || printf '\\n%s\\n' ${shellSingleQuote(exportLine)} >> "$profile"`,
|
|
517
|
+
"done"
|
|
518
|
+
].join("\n");
|
|
519
|
+
}
|
|
520
|
+
async function runSandboxToolPathSetup(box, options) {
|
|
521
|
+
try {
|
|
522
|
+
const res = await box.exec(buildSandboxToolPathSetupScript(options));
|
|
523
|
+
if (res.exitCode !== 0) {
|
|
524
|
+
return fail(
|
|
525
|
+
new Error(
|
|
526
|
+
`runSandboxToolPathSetup: failed to configure PATH for ${sandboxToolBinDir(options)} (exit ${res.exitCode}): ${res.stderr.slice(0, 500)}`
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
return ok(void 0);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
return fail(new Error("runSandboxToolPathSetup: exec failed", { cause: err }));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function shellPath(path) {
|
|
536
|
+
if (path === "~") return '"$HOME"';
|
|
537
|
+
if (path.startsWith("~/")) {
|
|
538
|
+
const rest = path.slice(2);
|
|
539
|
+
return rest ? `"$HOME"/${shellSingleQuote(rest)}` : '"$HOME"';
|
|
540
|
+
}
|
|
541
|
+
return shellSingleQuote(path);
|
|
542
|
+
}
|
|
383
543
|
function splitDeferredProfileFiles(profile) {
|
|
384
544
|
const files = profile.resources?.files ?? [];
|
|
385
545
|
const deferredFiles = [];
|
|
@@ -395,6 +555,25 @@ function splitDeferredProfileFiles(profile) {
|
|
|
395
555
|
};
|
|
396
556
|
return { leanProfile, deferredFiles };
|
|
397
557
|
}
|
|
558
|
+
var PROFILE_WRITE_B64_CHUNK_CHARS = 3e3;
|
|
559
|
+
var PROFILE_WRITE_MAX_429_RETRIES = 4;
|
|
560
|
+
var PROFILE_WRITE_RETRY_BASE_MS = 250;
|
|
561
|
+
var PROFILE_WRITE_RETRY_MAX_MS = 2e3;
|
|
562
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
563
|
+
function rateLimit(err) {
|
|
564
|
+
if (err && typeof err === "object") {
|
|
565
|
+
const e = err;
|
|
566
|
+
const retryAfterMs = typeof e.retryAfterMs === "number" ? e.retryAfterMs : void 0;
|
|
567
|
+
if (e.status === 429) return { is429: true, retryAfterMs };
|
|
568
|
+
if (typeof e.code === "string" && /rate.?limit|too.?many.?requests|429/i.test(e.code)) {
|
|
569
|
+
return { is429: true, retryAfterMs };
|
|
570
|
+
}
|
|
571
|
+
if (typeof e.message === "string" && /\b429\b|rate.?limit|too many requests/i.test(e.message)) {
|
|
572
|
+
return { is429: true, retryAfterMs };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return { is429: false };
|
|
576
|
+
}
|
|
398
577
|
async function writeProfileFilesToBox(box, files) {
|
|
399
578
|
for (const mount of files) {
|
|
400
579
|
if (mount.resource.kind !== "inline") continue;
|
|
@@ -404,22 +583,45 @@ async function writeProfileFilesToBox(box, files) {
|
|
|
404
583
|
const dir = path.replace(/\/[^/]*$/, "");
|
|
405
584
|
const isBin = /(^|\/)(s?bin)\//.test(path);
|
|
406
585
|
const executable = mount.executable ?? isBin;
|
|
407
|
-
const q =
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
586
|
+
const q = shellPath(path);
|
|
587
|
+
const qb64 = shellPath(`${path}.b64`);
|
|
588
|
+
const step = async (cmd) => {
|
|
589
|
+
for (let attempt = 0; ; attempt++) {
|
|
590
|
+
try {
|
|
591
|
+
const res2 = await box.exec(cmd);
|
|
592
|
+
if (res2.exitCode !== 0) {
|
|
593
|
+
return fail(
|
|
594
|
+
new Error(
|
|
595
|
+
`writeProfileFilesToBox: failed to write ${path} (exit ${res2.exitCode}): ${res2.stderr.slice(0, 500)}`
|
|
596
|
+
)
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return ok(void 0);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
const { is429, retryAfterMs } = rateLimit(err);
|
|
602
|
+
if (is429 && attempt < PROFILE_WRITE_MAX_429_RETRIES) {
|
|
603
|
+
const backoff = Math.min(
|
|
604
|
+
PROFILE_WRITE_RETRY_BASE_MS * 2 ** attempt,
|
|
605
|
+
PROFILE_WRITE_RETRY_MAX_MS
|
|
606
|
+
);
|
|
607
|
+
await sleep(retryAfterMs ?? backoff);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
return fail(new Error(`writeProfileFilesToBox: exec failed for ${path}`, { cause: err }));
|
|
611
|
+
}
|
|
419
612
|
}
|
|
420
|
-
}
|
|
421
|
-
|
|
613
|
+
};
|
|
614
|
+
const mkdir = dir && dir !== path ? `mkdir -p ${shellPath(dir)} && ` : "";
|
|
615
|
+
let res = await step(`${mkdir}: > ${qb64}`);
|
|
616
|
+
if (!res.succeeded) return res;
|
|
617
|
+
for (let i = 0; i < b64.length; i += PROFILE_WRITE_B64_CHUNK_CHARS) {
|
|
618
|
+
const slice = b64.slice(i, i + PROFILE_WRITE_B64_CHUNK_CHARS);
|
|
619
|
+
res = await step(`printf '%s' '${slice}' >> ${qb64}`);
|
|
620
|
+
if (!res.succeeded) return res;
|
|
422
621
|
}
|
|
622
|
+
const chmod = executable ? ` && chmod +x ${q}` : "";
|
|
623
|
+
res = await step(`base64 -d ${qb64} > ${q} && rm -f ${qb64}${chmod}`);
|
|
624
|
+
if (!res.succeeded) return res;
|
|
423
625
|
}
|
|
424
626
|
return ok(void 0);
|
|
425
627
|
}
|
|
@@ -479,6 +681,38 @@ async function isBoxAlive(box, harness, probe) {
|
|
|
479
681
|
return false;
|
|
480
682
|
}
|
|
481
683
|
}
|
|
684
|
+
var RUNTIME_CONNECTION_WAIT_MS = 3e4;
|
|
685
|
+
var RUNTIME_CONNECTION_POLL_MS = 1e3;
|
|
686
|
+
function sandboxRuntimeUrl(box) {
|
|
687
|
+
const connection = box.connection;
|
|
688
|
+
return connection?.sidecarUrl ?? connection?.runtimeUrl;
|
|
689
|
+
}
|
|
690
|
+
function sandboxEdgeFailed(box) {
|
|
691
|
+
const connection = box.connection;
|
|
692
|
+
return connection?.edgeStatus === "failed" || Boolean(connection?.edgeError);
|
|
693
|
+
}
|
|
694
|
+
async function refreshRuntimeConnection(client, box) {
|
|
695
|
+
let current = box;
|
|
696
|
+
if (sandboxRuntimeUrl(current)) return current;
|
|
697
|
+
const deadline = Date.now() + RUNTIME_CONNECTION_WAIT_MS;
|
|
698
|
+
while (Date.now() < deadline) {
|
|
699
|
+
try {
|
|
700
|
+
await current.refresh();
|
|
701
|
+
if (sandboxRuntimeUrl(current)) return current;
|
|
702
|
+
const latest = await client.get(current.id);
|
|
703
|
+
if (latest) current = latest;
|
|
704
|
+
if (sandboxRuntimeUrl(current)) return current;
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
await new Promise((resolve) => setTimeout(resolve, RUNTIME_CONNECTION_POLL_MS));
|
|
708
|
+
}
|
|
709
|
+
return current;
|
|
710
|
+
}
|
|
711
|
+
async function isReusableBox(box, harness, probe) {
|
|
712
|
+
if (sandboxEdgeFailed(box)) return false;
|
|
713
|
+
if (!sandboxRuntimeUrl(box)) return false;
|
|
714
|
+
return isBoxAlive(box, harness, probe);
|
|
715
|
+
}
|
|
482
716
|
async function resumeStoppedBox(client, name, timeoutMs) {
|
|
483
717
|
try {
|
|
484
718
|
const stopped = await client.list({ status: "stopped" });
|
|
@@ -508,18 +742,28 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
508
742
|
if (!dropped.succeeded) {
|
|
509
743
|
throw new Error(`forceNew: sandbox ${name} could not be deleted`, { cause: dropped.error });
|
|
510
744
|
}
|
|
511
|
-
} else if (found.metadata?.harness === harness
|
|
512
|
-
const
|
|
513
|
-
if (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
745
|
+
} else if (found.metadata?.harness === harness) {
|
|
746
|
+
const ready = await refreshRuntimeConnection(client, found);
|
|
747
|
+
if (await isReusableBox(ready, harness, shell.livenessProbe)) {
|
|
748
|
+
const written = await materializeDeferredFilesForExistingBox(shell, ready, workspaceId, userId);
|
|
749
|
+
if (!written.succeeded) {
|
|
750
|
+
throw new Error(`deferred file write failed on reused box ${name}`, { cause: written.error });
|
|
751
|
+
}
|
|
752
|
+
if (shell.bootstrap) {
|
|
753
|
+
const boot = await shell.bootstrap(ready, scope);
|
|
754
|
+
if (!boot.succeeded) {
|
|
755
|
+
throw new Error(`bootstrap failed on reused box ${name}`, { cause: boot.error });
|
|
756
|
+
}
|
|
520
757
|
}
|
|
758
|
+
return ready;
|
|
759
|
+
}
|
|
760
|
+
const dropped = await deleteBox(ready);
|
|
761
|
+
if (!dropped.succeeded) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`sandbox ${name} (was ${String(found.metadata?.harness ?? "unknown")}, want ${harness}, or unresponsive) could not be deleted`,
|
|
764
|
+
{ cause: dropped.error }
|
|
765
|
+
);
|
|
521
766
|
}
|
|
522
|
-
return found;
|
|
523
767
|
} else {
|
|
524
768
|
const dropped = await deleteBox(found);
|
|
525
769
|
if (!dropped.succeeded) {
|
|
@@ -532,19 +776,28 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
532
776
|
}
|
|
533
777
|
if (!forceNew && shell.resumeStopped !== false) {
|
|
534
778
|
const resumed = await resumeStoppedBox(client, name, resumeTimeout);
|
|
535
|
-
if (resumed.succeeded && resumed.value
|
|
536
|
-
const box2 = resumed.value;
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (shell.bootstrap) {
|
|
542
|
-
const boot = await shell.bootstrap(box2, scope);
|
|
543
|
-
if (!boot.succeeded) {
|
|
544
|
-
throw new Error(`bootstrap failed on resumed box ${name}`, { cause: boot.error });
|
|
779
|
+
if (resumed.succeeded && resumed.value) {
|
|
780
|
+
const box2 = await refreshRuntimeConnection(client, resumed.value);
|
|
781
|
+
if (await isReusableBox(box2, harness, shell.livenessProbe)) {
|
|
782
|
+
const written = await materializeDeferredFilesForExistingBox(shell, box2, workspaceId, userId);
|
|
783
|
+
if (!written.succeeded) {
|
|
784
|
+
throw new Error(`deferred file write failed on resumed box ${name}`, { cause: written.error });
|
|
545
785
|
}
|
|
786
|
+
if (shell.bootstrap) {
|
|
787
|
+
const boot = await shell.bootstrap(box2, scope);
|
|
788
|
+
if (!boot.succeeded) {
|
|
789
|
+
throw new Error(`bootstrap failed on resumed box ${name}`, { cause: boot.error });
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return box2;
|
|
793
|
+
}
|
|
794
|
+
const dropped = await deleteBox(box2);
|
|
795
|
+
if (!dropped.succeeded) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
`resumed sandbox ${name} (was ${String(box2.metadata?.harness ?? "unknown")}, want ${harness}, or unresponsive) could not be deleted`,
|
|
798
|
+
{ cause: dropped.error }
|
|
799
|
+
);
|
|
546
800
|
}
|
|
547
|
-
return box2;
|
|
548
801
|
}
|
|
549
802
|
}
|
|
550
803
|
const connectedIntegrationIds = await shell.connectedIntegrationIds(workspaceId);
|
|
@@ -585,6 +838,7 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
585
838
|
backend: { type: harness, profile, ...model ? { model } : {} },
|
|
586
839
|
...storage ? { storage } : {},
|
|
587
840
|
...restore ? restore : {},
|
|
841
|
+
...shell.webTerminalEnabled ? { webTerminalEnabled: true } : {},
|
|
588
842
|
maxLifetimeSeconds: resources.maxLifetimeSeconds,
|
|
589
843
|
idleTimeoutSeconds: resources.idleTimeoutSeconds,
|
|
590
844
|
resources: {
|
|
@@ -593,9 +847,9 @@ async function ensureWorkspaceSandbox(shell, options) {
|
|
|
593
847
|
diskGB: resources.diskGB
|
|
594
848
|
}
|
|
595
849
|
};
|
|
596
|
-
|
|
850
|
+
let box = await client.create(payload);
|
|
597
851
|
await box.waitFor("running", { timeoutMs: 12e4 });
|
|
598
|
-
|
|
852
|
+
box = await refreshRuntimeConnection(client, box);
|
|
599
853
|
if (deferredFiles.length > 0) {
|
|
600
854
|
const written = await writeProfileFilesToBox(box, deferredFiles);
|
|
601
855
|
if (!written.succeeded) {
|
|
@@ -828,64 +1082,6 @@ async function driveSandboxTurn(shell, box, message, options) {
|
|
|
828
1082
|
return fail(err);
|
|
829
1083
|
}
|
|
830
1084
|
}
|
|
831
|
-
var TERMINAL_PROXY_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
832
|
-
async function signTerminalProxyToken(secret, encodedPayload) {
|
|
833
|
-
const key = await crypto.subtle.importKey(
|
|
834
|
-
"raw",
|
|
835
|
-
new TextEncoder().encode(secret),
|
|
836
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
837
|
-
false,
|
|
838
|
-
["sign"]
|
|
839
|
-
);
|
|
840
|
-
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(encodedPayload));
|
|
841
|
-
return base64UrlEncodeBytes(new Uint8Array(sig));
|
|
842
|
-
}
|
|
843
|
-
async function mintTerminalProxyToken(secret, identity, ttlMs = TERMINAL_PROXY_TOKEN_TTL_MS) {
|
|
844
|
-
if (!secret) return fail(new Error("mintTerminalProxyToken: secret is required"));
|
|
845
|
-
if (!identity.userId || !identity.workspaceId || !identity.sandboxId) {
|
|
846
|
-
return fail(new Error("mintTerminalProxyToken: userId/workspaceId/sandboxId are required"));
|
|
847
|
-
}
|
|
848
|
-
const expiresAt = new Date(Date.now() + ttlMs);
|
|
849
|
-
const payload = { ...identity, exp: Math.floor(expiresAt.getTime() / 1e3) };
|
|
850
|
-
const encoded = base64UrlEncodeUtf8(JSON.stringify(payload));
|
|
851
|
-
const sig = await signTerminalProxyToken(secret, encoded);
|
|
852
|
-
return ok({ token: `${encoded}.${sig}`, expiresAt });
|
|
853
|
-
}
|
|
854
|
-
async function verifyTerminalProxyToken(secret, token, expected) {
|
|
855
|
-
if (!secret) return false;
|
|
856
|
-
const [encoded, sig, extra] = token.split(".");
|
|
857
|
-
if (!encoded || !sig || extra !== void 0) return false;
|
|
858
|
-
const expectedSig = await signTerminalProxyToken(secret, encoded);
|
|
859
|
-
if (!constantTimeEqual(sig, expectedSig)) return false;
|
|
860
|
-
let payload;
|
|
861
|
-
try {
|
|
862
|
-
payload = JSON.parse(base64UrlDecodeUtf8(encoded));
|
|
863
|
-
} catch {
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
return payload.userId === expected.userId && payload.workspaceId === expected.workspaceId && payload.sandboxId === expected.sandboxId && Number.isFinite(payload.exp) && payload.exp > Math.floor(Date.now() / 1e3);
|
|
867
|
-
}
|
|
868
|
-
function base64UrlEncodeBytes(bytes) {
|
|
869
|
-
let bin = "";
|
|
870
|
-
for (const b of bytes) bin += String.fromCharCode(b);
|
|
871
|
-
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
872
|
-
}
|
|
873
|
-
function base64UrlEncodeUtf8(v) {
|
|
874
|
-
return base64UrlEncodeBytes(new TextEncoder().encode(v));
|
|
875
|
-
}
|
|
876
|
-
function base64UrlDecodeUtf8(v) {
|
|
877
|
-
const padded = v.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(v.length / 4) * 4, "=");
|
|
878
|
-
const bin = atob(padded);
|
|
879
|
-
const bytes = new Uint8Array(bin.length);
|
|
880
|
-
for (let i = 0; i < bin.length; i += 1) bytes[i] = bin.charCodeAt(i);
|
|
881
|
-
return new TextDecoder().decode(bytes);
|
|
882
|
-
}
|
|
883
|
-
function constantTimeEqual(a, b) {
|
|
884
|
-
if (a.length !== b.length) return false;
|
|
885
|
-
let r = 0;
|
|
886
|
-
for (let i = 0; i < a.length; i += 1) r |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
887
|
-
return r === 0;
|
|
888
|
-
}
|
|
889
1085
|
var SEVERED_FINISH_REASONS = /* @__PURE__ */ new Set(["error", "other", "unknown"]);
|
|
890
1086
|
function asPlainRecord(v) {
|
|
891
1087
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
@@ -928,6 +1124,8 @@ function firstQuestionText(value) {
|
|
|
928
1124
|
}
|
|
929
1125
|
|
|
930
1126
|
export {
|
|
1127
|
+
mintTerminalProxyToken,
|
|
1128
|
+
verifyTerminalProxyToken,
|
|
931
1129
|
createWorkspaceSandboxManager,
|
|
932
1130
|
createSandboxTerminalToken,
|
|
933
1131
|
verifySandboxTerminalToken,
|
|
@@ -941,10 +1139,17 @@ export {
|
|
|
941
1139
|
bearerToken,
|
|
942
1140
|
bearerSubprotocolToken,
|
|
943
1141
|
terminalTokenFromRequest,
|
|
1142
|
+
resolveSandboxClientCredentials,
|
|
944
1143
|
DEFAULT_SANDBOX_RESOURCES,
|
|
945
1144
|
getClient,
|
|
946
1145
|
resetClientCache,
|
|
947
1146
|
buildAppToolMcpServers,
|
|
1147
|
+
sandboxToolRootDir,
|
|
1148
|
+
sandboxToolBinDir,
|
|
1149
|
+
sandboxToolPath,
|
|
1150
|
+
buildSandboxToolFileMounts,
|
|
1151
|
+
buildSandboxToolPathSetupScript,
|
|
1152
|
+
runSandboxToolPathSetup,
|
|
948
1153
|
splitDeferredProfileFiles,
|
|
949
1154
|
writeProfileFilesToBox,
|
|
950
1155
|
ensureWorkspaceSandbox,
|
|
@@ -963,10 +1168,8 @@ export {
|
|
|
963
1168
|
deleteSecret,
|
|
964
1169
|
mintSandboxScopedToken,
|
|
965
1170
|
driveSandboxTurn,
|
|
966
|
-
mintTerminalProxyToken,
|
|
967
|
-
verifyTerminalProxyToken,
|
|
968
1171
|
classifySeveredStream,
|
|
969
1172
|
isTerminalPromptEvent,
|
|
970
1173
|
detectInteractiveQuestion
|
|
971
1174
|
};
|
|
972
|
-
//# sourceMappingURL=chunk-
|
|
1175
|
+
//# sourceMappingURL=chunk-MJ3RUHD4.js.map
|