@zackbart/connecta 0.28.0 → 0.29.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/CHANGELOG.md +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1632 -1509
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +526 -0
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1780 -351
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-partition.js +17 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/overflow/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/tithely/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +125 -35
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -14,9 +14,7 @@ export class ExecutorAdmissionError extends Error {
|
|
|
14
14
|
this.code = code;
|
|
15
15
|
this.name = "ExecutorAdmissionError";
|
|
16
16
|
this.retryable = code === "executor_overloaded";
|
|
17
|
-
if (opts.retryAfterMs !== undefined &&
|
|
18
|
-
Number.isFinite(opts.retryAfterMs) &&
|
|
19
|
-
opts.retryAfterMs >= 0) {
|
|
17
|
+
if (opts.retryAfterMs !== undefined && Number.isFinite(opts.retryAfterMs) && opts.retryAfterMs >= 0) {
|
|
20
18
|
this.retryAfterMs = Math.trunc(opts.retryAfterMs);
|
|
21
19
|
}
|
|
22
20
|
}
|
|
@@ -72,9 +70,8 @@ export class AdmissionController {
|
|
|
72
70
|
this.maxQueueSize = nonNegativeWhole(options.maxQueueSize, "maxQueueSize");
|
|
73
71
|
this.queueTimeoutMs = positiveWhole(options.queueTimeoutMs, "queueTimeoutMs");
|
|
74
72
|
this.retryAfterMs = nonNegativeWhole(options.retryAfterMs ?? this.queueTimeoutMs, "retryAfterMs");
|
|
75
|
-
this.maxDurationMs =
|
|
76
|
-
? undefined
|
|
77
|
-
: positiveWhole(options.maxDurationMs, "maxDurationMs");
|
|
73
|
+
this.maxDurationMs =
|
|
74
|
+
options.maxDurationMs === undefined ? undefined : positiveWhole(options.maxDurationMs, "maxDurationMs");
|
|
78
75
|
if (this.maxDurationMs !== undefined && this.maxDurationMs > 2_147_483_647) {
|
|
79
76
|
throw new TypeError("maxDurationMs must be at most 2,147,483,647 milliseconds.");
|
|
80
77
|
}
|
|
@@ -129,9 +126,7 @@ export class AdmissionController {
|
|
|
129
126
|
}
|
|
130
127
|
makeLease(waitMs) {
|
|
131
128
|
const id = Symbol("admission lease");
|
|
132
|
-
const record = this.maxDurationMs !== undefined
|
|
133
|
-
? { expiresAt: Date.now() + this.maxDurationMs }
|
|
134
|
-
: {};
|
|
129
|
+
const record = this.maxDurationMs !== undefined ? { expiresAt: Date.now() + this.maxDurationMs } : {};
|
|
135
130
|
this.leases.set(id, record);
|
|
136
131
|
return {
|
|
137
132
|
waitMs,
|
|
@@ -202,9 +197,7 @@ export class AdmissionController {
|
|
|
202
197
|
}
|
|
203
198
|
// A queued request's own timer reaps the earliest admitted lease. A
|
|
204
199
|
// fallback check covers a briefly empty lease map after a handoff.
|
|
205
|
-
return Number.isFinite(next)
|
|
206
|
-
? Math.max(1, next - Date.now())
|
|
207
|
-
: 250;
|
|
200
|
+
return Number.isFinite(next) ? Math.max(1, next - Date.now()) : 250;
|
|
208
201
|
}
|
|
209
202
|
remove(waiter) {
|
|
210
203
|
const index = this.waiters.indexOf(waiter);
|
|
@@ -233,7 +226,7 @@ export class AdmissionController {
|
|
|
233
226
|
// static block is the one place outside an instance method that may read
|
|
234
227
|
// this bookkeeping while emitting nothing there.
|
|
235
228
|
static {
|
|
236
|
-
provideAdmissionProgram((controller, signal) => Effect.suspend(() => {
|
|
229
|
+
provideAdmissionProgram((controller, signal, wait) => Effect.suspend(() => {
|
|
237
230
|
controller.reapExpired();
|
|
238
231
|
if (controller.closed) {
|
|
239
232
|
controller.closedTotal++;
|
|
@@ -248,6 +241,10 @@ export class AdmissionController {
|
|
|
248
241
|
controller.admittedTotal++;
|
|
249
242
|
return Effect.succeed(controller.makeLease(0));
|
|
250
243
|
}
|
|
244
|
+
if (!wait) {
|
|
245
|
+
controller.rejectedTotal++;
|
|
246
|
+
return Effect.fail(controller.overloaded("No executor slot is free for nested execution. Invoke the operation directly, after this program ends."));
|
|
247
|
+
}
|
|
251
248
|
if (controller.waiters.length >= controller.maxQueueSize) {
|
|
252
249
|
controller.rejectedTotal++;
|
|
253
250
|
return Effect.fail(controller.overloaded("Executor queue is full."));
|
|
@@ -284,8 +281,7 @@ export class AdmissionController {
|
|
|
284
281
|
}
|
|
285
282
|
/** Preserve the one-method Workers seam while recognizing richer Node executors. */
|
|
286
283
|
export function isAdmittingExecutor(executor) {
|
|
287
|
-
return
|
|
288
|
-
typeof executor.acquire === "function");
|
|
284
|
+
return "acquire" in executor && typeof executor.acquire === "function";
|
|
289
285
|
}
|
|
290
286
|
/** Terminal- and JSON-safe upper bound for an executor's self-reported name. */
|
|
291
287
|
const MAX_EXECUTOR_NAME_LENGTH = 40;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AdmittingExecutor, Executor } from "./types.js";
|
|
2
|
+
type ExecutorLifecycle = "leased" | "self-managed";
|
|
3
|
+
export declare function brandExecutor<T extends Executor>(executor: T, lifecycle: ExecutorLifecycle): T;
|
|
4
|
+
export declare function assertExecutor(executor: Executor): void;
|
|
5
|
+
/** Explicitly accept responsibility for a custom sandbox's run resources. */
|
|
6
|
+
export interface CustomExecutorOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The custom executor owns guest termination and resource cleanup, including
|
|
9
|
+
* budget exhaustion, cancellation, and deadlines. Connecta cannot add these
|
|
10
|
+
* guarantees to an executor that exposes only execute().
|
|
11
|
+
*/
|
|
12
|
+
lifecycle: "self-managed";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Opt a custom sandbox into createConnecta without changing the original object.
|
|
16
|
+
* Delegated methods retain their original receiver, including private fields.
|
|
17
|
+
*/
|
|
18
|
+
export declare function customExecutor(executor: AdmittingExecutor, options: CustomExecutorOptions): AdmittingExecutor;
|
|
19
|
+
export declare function customExecutor(executor: Executor, options: CustomExecutorOptions): Executor;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { executorName, isAdmittingExecutor } from "./executor-admission.js";
|
|
2
|
+
// Symbol.for and a data descriptor survive duplicate packages and minification.
|
|
3
|
+
// This is a construction contract, not a security boundary against host config.
|
|
4
|
+
const EXECUTOR_BRAND = Symbol.for("connecta.executor");
|
|
5
|
+
export function brandExecutor(executor, lifecycle) {
|
|
6
|
+
Object.defineProperty(executor, EXECUTOR_BRAND, {
|
|
7
|
+
value: Object.freeze({ version: 1, lifecycle }),
|
|
8
|
+
enumerable: false,
|
|
9
|
+
writable: false,
|
|
10
|
+
configurable: false,
|
|
11
|
+
});
|
|
12
|
+
return executor;
|
|
13
|
+
}
|
|
14
|
+
export function assertExecutor(executor) {
|
|
15
|
+
const descriptor = Object.getOwnPropertyDescriptor(executor, EXECUTOR_BRAND);
|
|
16
|
+
const brand = descriptor?.value;
|
|
17
|
+
if (typeof executor.execute === "function" &&
|
|
18
|
+
descriptor?.enumerable === false &&
|
|
19
|
+
brand?.version === 1 &&
|
|
20
|
+
(brand.lifecycle === "leased" || brand.lifecycle === "self-managed"))
|
|
21
|
+
return;
|
|
22
|
+
throw new Error("ConnectaConfig.executor must declare its lifecycle. Unbranded executors, including " +
|
|
23
|
+
"upstream DynamicWorkerExecutor, are unsupported because request-owned resources " +
|
|
24
|
+
"must be released when a run ends. Use one of these options: " +
|
|
25
|
+
'import { workerExecutor } from "@zackbart/connecta/worker"; ' +
|
|
26
|
+
"executor: workerExecutor({ loader: env.LOADER }); " +
|
|
27
|
+
'import { quickJsExecutor } from "@zackbart/connecta/quickjs"; executor: quickJsExecutor(); ' +
|
|
28
|
+
'or import { customExecutor } from "@zackbart/connecta"; ' +
|
|
29
|
+
'executor: customExecutor(myExecutor, { lifecycle: "self-managed" }).');
|
|
30
|
+
}
|
|
31
|
+
export function customExecutor(executor, options) {
|
|
32
|
+
if (options?.lifecycle !== "self-managed" || typeof executor?.execute !== "function") {
|
|
33
|
+
throw new Error('customExecutor requires an executor and { lifecycle: "self-managed" }.');
|
|
34
|
+
}
|
|
35
|
+
const name = executorName(executor);
|
|
36
|
+
const wrapper = {
|
|
37
|
+
...(name ? { name } : {}),
|
|
38
|
+
execute: executor.execute.bind(executor),
|
|
39
|
+
...(executor.close ? { close: executor.close.bind(executor) } : {}),
|
|
40
|
+
...(isAdmittingExecutor(executor)
|
|
41
|
+
? {
|
|
42
|
+
acquire: executor.acquire.bind(executor),
|
|
43
|
+
...(executor.admissionSnapshot ? { admissionSnapshot: executor.admissionSnapshot.bind(executor) } : {}),
|
|
44
|
+
}
|
|
45
|
+
: {}),
|
|
46
|
+
};
|
|
47
|
+
return brandExecutor(wrapper, "self-managed");
|
|
48
|
+
}
|
package/dist/executor-result.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { msg } from "./errors.js";
|
|
2
2
|
/** ~6k tokens. Sandbox code should filter data down before returning. */
|
|
3
|
-
const MAX_EXECUTE_RESULT_CHARS = 24_000;
|
|
3
|
+
export const MAX_EXECUTE_RESULT_CHARS = 24_000;
|
|
4
4
|
export const MAX_EXECUTE_LOG_CHARS = 4_000;
|
|
5
5
|
export function serializeResultText(value) {
|
|
6
6
|
const serialized = JSON.stringify(value);
|
|
@@ -18,30 +18,44 @@ const TRUNCATION_HINT = "filter/map/slice data inside execute_code and return on
|
|
|
18
18
|
* of what the program returned, and truncation happens exactly once no matter
|
|
19
19
|
* how many hops the value takes.
|
|
20
20
|
*/
|
|
21
|
-
function truncationEnvelope(text) {
|
|
21
|
+
function truncationEnvelope(text, maxChars, totalChars = text.length) {
|
|
22
22
|
const base = {
|
|
23
23
|
truncated: true,
|
|
24
24
|
preview: "",
|
|
25
|
-
totalChars
|
|
25
|
+
totalChars,
|
|
26
26
|
hint: TRUNCATION_HINT,
|
|
27
27
|
};
|
|
28
|
-
let budget = Math.max(0,
|
|
28
|
+
let budget = Math.max(0, maxChars - JSON.stringify(base).length);
|
|
29
29
|
for (let attempt = 0; attempt < 8 && budget > 0; attempt += 1) {
|
|
30
30
|
const candidate = { ...base, preview: text.slice(0, budget) };
|
|
31
31
|
const size = JSON.stringify(candidate).length;
|
|
32
|
-
if (size <=
|
|
32
|
+
if (size <= maxChars)
|
|
33
33
|
return candidate;
|
|
34
34
|
// Every character costs at least one serialized character, so scaling by
|
|
35
35
|
// the overshoot ratio (minus a step) strictly shrinks the budget.
|
|
36
|
-
budget = Math.max(0, Math.floor(budget * (
|
|
36
|
+
budget = Math.max(0, Math.floor(budget * (maxChars / size)) - 8);
|
|
37
37
|
}
|
|
38
38
|
return { ...base, preview: text.slice(0, budget) };
|
|
39
39
|
}
|
|
40
|
-
export function guardExecuteResultValue(value) {
|
|
40
|
+
export function guardExecuteResultValue(value, maxChars = MAX_EXECUTE_RESULT_CHARS) {
|
|
41
41
|
const text = serializeResultText(value);
|
|
42
|
-
if (text.length <=
|
|
42
|
+
if (text.length <= maxChars)
|
|
43
43
|
return value;
|
|
44
|
-
|
|
44
|
+
// A child may already have guarded this value at a larger transport cap.
|
|
45
|
+
// Shrink its preview once more without nesting notices or losing original size.
|
|
46
|
+
if (value !== null && typeof value === "object") {
|
|
47
|
+
const prior = value;
|
|
48
|
+
if (prior.truncated === true &&
|
|
49
|
+
prior.hint === TRUNCATION_HINT &&
|
|
50
|
+
typeof prior.preview === "string" &&
|
|
51
|
+
typeof prior.totalChars === "number" &&
|
|
52
|
+
Number.isSafeInteger(prior.totalChars) &&
|
|
53
|
+
prior.totalChars >= prior.preview.length &&
|
|
54
|
+
Object.keys(value).length === 4) {
|
|
55
|
+
return truncationEnvelope(prior.preview, maxChars, prior.totalChars);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return truncationEnvelope(text, maxChars);
|
|
45
59
|
}
|
|
46
60
|
export function truncateExecuteText(text, max) {
|
|
47
61
|
if (text.length <= max)
|
|
@@ -63,6 +77,7 @@ export function prepareExecuteResultForTransport(outcome) {
|
|
|
63
77
|
return {
|
|
64
78
|
result: undefined,
|
|
65
79
|
error: outcome.error,
|
|
80
|
+
...(outcome.failure ? { failure: outcome.failure } : {}),
|
|
66
81
|
...(logs ? { logs } : {}),
|
|
67
82
|
};
|
|
68
83
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareExecuteResultForTransport } from "../executor-result.js";
|
|
2
|
+
import { InvocationFailure } from "../invocation.js";
|
|
2
3
|
import { msg } from "../errors.js";
|
|
3
4
|
import { MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
4
5
|
import { executeQuickJs, prepareQuickJs } from "./quickjs-runtime.js";
|
|
@@ -38,9 +39,7 @@ function provider(name, jobId, prelude) {
|
|
|
38
39
|
return result;
|
|
39
40
|
};
|
|
40
41
|
},
|
|
41
|
-
getOwnPropertyDescriptor: (_target, key) => typeof key === "string"
|
|
42
|
-
? { configurable: true, enumerable: false }
|
|
43
|
-
: undefined,
|
|
42
|
+
getOwnPropertyDescriptor: (_target, key) => typeof key === "string" ? { configurable: true, enumerable: false } : undefined,
|
|
44
43
|
});
|
|
45
44
|
return { name, fns: functions, ...(prelude ? { prelude } : {}) };
|
|
46
45
|
}
|
|
@@ -104,7 +103,7 @@ process.on("message", (message) => {
|
|
|
104
103
|
if (result.ok)
|
|
105
104
|
request.resolve(result.value);
|
|
106
105
|
else
|
|
107
|
-
request.reject(new Error(result.error));
|
|
106
|
+
request.reject(result.call ? new InvocationFailure(result.call) : new Error(result.error));
|
|
108
107
|
}
|
|
109
108
|
catch (err) {
|
|
110
109
|
request.reject(new Error(`Invalid host result: ${msg(err)}`));
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
// running the provider function; `executePendingJobs` drives guest
|
|
8
8
|
// continuations from a host-side loop. Values cross the boundary as JSON, so
|
|
9
9
|
// provider args/results must be JSON-serializable.
|
|
10
|
-
import { getQuickJS
|
|
10
|
+
import { getQuickJS } from "quickjs-emscripten";
|
|
11
|
+
import { InvocationFailure } from "../invocation.js";
|
|
11
12
|
import { msg } from "../errors.js";
|
|
12
13
|
import { withoutProgramTerminator } from "../program-source.js";
|
|
13
|
-
import { hostCallLabel, MAX_QUICKJS_LOG_TRANSPORT_BYTES, serializedBytes
|
|
14
|
+
import { hostCallLabel, MAX_QUICKJS_LOG_TRANSPORT_BYTES, serializedBytes } from "./quickjs-protocol.js";
|
|
14
15
|
/** Load and compile the shared QuickJS WASM module before a run budget starts. */
|
|
15
16
|
export async function prepareQuickJs() {
|
|
16
17
|
await getQuickJS();
|
|
@@ -82,8 +83,8 @@ export function normalizeCode(code) {
|
|
|
82
83
|
* and fail closed there.
|
|
83
84
|
*/
|
|
84
85
|
function setupScript(providers) {
|
|
85
|
-
//
|
|
86
|
-
//
|
|
86
|
+
// Capture the host bridge and codec before guest code runs.
|
|
87
|
+
// Rejections are native guest Errors created and retained by the host.
|
|
87
88
|
const lines = [
|
|
88
89
|
`(() => {
|
|
89
90
|
const call = globalThis.__call;
|
|
@@ -102,7 +103,6 @@ function setupScript(providers) {
|
|
|
102
103
|
})();
|
|
103
104
|
const invoke = async (ns, fn, args) => {
|
|
104
105
|
const r = parse(await call(ns, fn, stringify(args)));
|
|
105
|
-
if (!r.ok) throw new GuestError(r.error);
|
|
106
106
|
return r.value;
|
|
107
107
|
};
|
|
108
108
|
const namespace = (ns) => freeze(new ProxyConstructor(create(null), {
|
|
@@ -117,24 +117,14 @@ function setupScript(providers) {
|
|
|
117
117
|
if (p.prelude)
|
|
118
118
|
lines.push(p.prelude);
|
|
119
119
|
}
|
|
120
|
-
lines.push(`
|
|
121
|
-
})();`);
|
|
120
|
+
lines.push(`})();`);
|
|
122
121
|
return lines.join("\n");
|
|
123
122
|
}
|
|
124
|
-
/** True when a dumped error is QuickJS's deadline-interrupt signal. */
|
|
125
|
-
function isInterrupt(dumped) {
|
|
126
|
-
if (!dumped || typeof dumped !== "object")
|
|
127
|
-
return false;
|
|
128
|
-
const e = dumped;
|
|
129
|
-
return e.name === "InternalError" && e.message === "interrupted";
|
|
130
|
-
}
|
|
131
123
|
function formatGuestError(dumped) {
|
|
132
124
|
if (dumped && typeof dumped === "object") {
|
|
133
125
|
const e = dumped;
|
|
134
126
|
if (e.message != null) {
|
|
135
|
-
return e.name != null && e.name !== "Error"
|
|
136
|
-
? `${String(e.name)}: ${String(e.message)}`
|
|
137
|
-
: String(e.message);
|
|
127
|
+
return e.name != null && e.name !== "Error" ? `${String(e.name)}: ${String(e.message)}` : String(e.message);
|
|
138
128
|
}
|
|
139
129
|
}
|
|
140
130
|
return typeof dumped === "string" ? dumped : JSON.stringify(dumped);
|
|
@@ -166,6 +156,7 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
166
156
|
aborted: false,
|
|
167
157
|
wake: () => { },
|
|
168
158
|
waitForSettle: Promise.resolve(),
|
|
159
|
+
failures: [],
|
|
169
160
|
};
|
|
170
161
|
armWake(bridge);
|
|
171
162
|
// Keep both the in-memory character budget and the twice-JSON-encoded
|
|
@@ -195,8 +186,7 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
195
186
|
}
|
|
196
187
|
const entryTransportBytes = logTransportBytes(entry);
|
|
197
188
|
if (logTotalChars + entry.length > MAX_LOG_TOTAL_CHARS ||
|
|
198
|
-
logTotalTransportBytes + entryTransportBytes >
|
|
199
|
-
MAX_QUICKJS_LOG_TRANSPORT_BYTES - MAX_LOG_MARKER_TRANSPORT_BYTES) {
|
|
189
|
+
logTotalTransportBytes + entryTransportBytes > MAX_QUICKJS_LOG_TRANSPORT_BYTES - MAX_LOG_MARKER_TRANSPORT_BYTES) {
|
|
200
190
|
captureLog(LOG_SIZE_LIMIT_MARKER);
|
|
201
191
|
logBudgetSpent = true;
|
|
202
192
|
return;
|
|
@@ -211,9 +201,7 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
211
201
|
const invoke = async (ns, fn, argsJson) => {
|
|
212
202
|
try {
|
|
213
203
|
const provider = byName.get(ns);
|
|
214
|
-
const f = provider && Object.hasOwn(provider.fns, fn)
|
|
215
|
-
? provider.fns[fn]
|
|
216
|
-
: undefined;
|
|
204
|
+
const f = provider && Object.hasOwn(provider.fns, fn) ? provider.fns[fn] : undefined;
|
|
217
205
|
if (!f)
|
|
218
206
|
throw new Error(`Unknown function ${ns}.${fn}`);
|
|
219
207
|
const args = JSON.parse(argsJson);
|
|
@@ -231,10 +219,10 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
231
219
|
if (exceedsUtf8ByteLimit(json, MAX_HOST_RESULT_BYTES)) {
|
|
232
220
|
throw new Error(`Host result from ${label} exceeds the ${MAX_HOST_RESULT_BYTES}-byte serialized bridge limit.`);
|
|
233
221
|
}
|
|
234
|
-
return json;
|
|
222
|
+
return { json };
|
|
235
223
|
}
|
|
236
224
|
catch (err) {
|
|
237
|
-
return
|
|
225
|
+
return { error: err };
|
|
238
226
|
}
|
|
239
227
|
};
|
|
240
228
|
const callFn = ctx.newFunction("__call", (nsH, fnH, argsH) => {
|
|
@@ -243,16 +231,37 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
243
231
|
const argsJson = ctx.getString(argsH);
|
|
244
232
|
const deferred = ctx.newPromise();
|
|
245
233
|
bridge.pending++;
|
|
246
|
-
void invoke(ns, fn, argsJson).then((
|
|
234
|
+
void invoke(ns, fn, argsJson).then((reply) => {
|
|
247
235
|
bridge.pending--;
|
|
248
236
|
if (bridge.aborted) {
|
|
249
237
|
// Context is (about to be) torn down — settle nothing, free our handles.
|
|
250
238
|
deferred.dispose();
|
|
251
239
|
}
|
|
252
240
|
else {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
241
|
+
if ("json" in reply) {
|
|
242
|
+
const h = ctx.newString(reply.json);
|
|
243
|
+
deferred.resolve(h);
|
|
244
|
+
h.dispose();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const error = ctx.newError();
|
|
248
|
+
const message = ctx.unwrapResult(ctx.evalCode(JSON.stringify(msg(reply.error).slice(0, 4_000))));
|
|
249
|
+
ctx.setProp(error, "message", message);
|
|
250
|
+
message.dispose();
|
|
251
|
+
if (reply.error instanceof InvocationFailure) {
|
|
252
|
+
const call = reply.error.details;
|
|
253
|
+
// The error identity and typed facts stay in host state. A guest
|
|
254
|
+
// may edit public fields without changing the retained failure.
|
|
255
|
+
bridge.failures.push({ error: error.dup(), call });
|
|
256
|
+
for (const [key, value] of Object.entries({ code: call.code, retryable: call.retryable, details: call })) {
|
|
257
|
+
const property = ctx.unwrapResult(ctx.evalCode(`(${JSON.stringify(value)})`));
|
|
258
|
+
ctx.setProp(error, key, property);
|
|
259
|
+
property.dispose();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
deferred.reject(error);
|
|
263
|
+
error.dispose();
|
|
264
|
+
}
|
|
256
265
|
}
|
|
257
266
|
bridge.wake();
|
|
258
267
|
});
|
|
@@ -296,14 +305,18 @@ export async function executeQuickJs(code, providers, options, onLog) {
|
|
|
296
305
|
if (!inGuest)
|
|
297
306
|
return false;
|
|
298
307
|
wallInterrupted = Date.now() >= deadline;
|
|
299
|
-
cpuInterrupted =
|
|
300
|
-
cpuUsedMs + (Date.now() - segmentStarted) >= cpuTimeMs;
|
|
308
|
+
cpuInterrupted = cpuUsedMs + (Date.now() - segmentStarted) >= cpuTimeMs;
|
|
301
309
|
return wallInterrupted || cpuInterrupted;
|
|
302
310
|
});
|
|
311
|
+
let describeError;
|
|
303
312
|
const logs = [];
|
|
304
313
|
const bridge = installBridge(ctx, providers, logs, onLog);
|
|
305
314
|
const finish = (r) => {
|
|
306
315
|
bridge.aborted = true;
|
|
316
|
+
describeError?.dispose();
|
|
317
|
+
for (const failure of bridge.failures)
|
|
318
|
+
failure.error.dispose();
|
|
319
|
+
bridge.failures.length = 0;
|
|
307
320
|
// Outstanding host calls still hold deferred-promise handles; their
|
|
308
321
|
// callbacks free them and wake the drain, which disposes the context
|
|
309
322
|
// once the last straggler settles. Re-arm before every wait so the
|
|
@@ -326,32 +339,119 @@ export async function executeQuickJs(code, providers, options, onLog) {
|
|
|
326
339
|
}
|
|
327
340
|
return logs.length > 0 ? { ...r, logs } : r;
|
|
328
341
|
};
|
|
329
|
-
const
|
|
342
|
+
const rejected = (error) => {
|
|
343
|
+
// Interrupt/deadline facts are host state, never inferred by inspecting
|
|
344
|
+
// the thrown value. Do not reenter a guest whose budget has ended.
|
|
345
|
+
if (wallInterrupted || Date.now() >= deadline)
|
|
346
|
+
return timedOut();
|
|
347
|
+
if (cpuInterrupted || cpuUsedMs >= cpuTimeMs)
|
|
348
|
+
return { result: undefined, error: cpuTimeoutError };
|
|
349
|
+
const call = bridge.failures.find((failure) => ctx.eq(failure.error, error))?.call;
|
|
350
|
+
let description = { name: "Error", message: "Program threw a value." };
|
|
351
|
+
const type = ctx.typeof(error);
|
|
352
|
+
if (type === "string")
|
|
353
|
+
description = ctx.getString(error).slice(0, 4000);
|
|
354
|
+
else if (type === "number")
|
|
355
|
+
description = ctx.getNumber(error);
|
|
356
|
+
else if (type === "undefined")
|
|
357
|
+
description = "undefined";
|
|
358
|
+
else if (type === "boolean")
|
|
359
|
+
description = ctx.eq(error, ctx.true);
|
|
360
|
+
else if (ctx.eq(error, ctx.null))
|
|
361
|
+
description = null;
|
|
362
|
+
else if (describeError) {
|
|
363
|
+
const describe = describeError;
|
|
364
|
+
const described = runGuest(() => ctx.callFunction(describe, ctx.undefined, error));
|
|
365
|
+
if (described.error)
|
|
366
|
+
described.error.dispose();
|
|
367
|
+
else {
|
|
368
|
+
description = JSON.parse(ctx.getString(described.value)) ?? description;
|
|
369
|
+
described.value.dispose();
|
|
370
|
+
}
|
|
371
|
+
if (wallInterrupted || Date.now() >= deadline)
|
|
372
|
+
return timedOut();
|
|
373
|
+
if (cpuInterrupted || cpuUsedMs >= cpuTimeMs)
|
|
374
|
+
return { result: undefined, error: cpuTimeoutError };
|
|
375
|
+
}
|
|
376
|
+
const name = description && typeof description === "object" && "name" in description && typeof description.name === "string"
|
|
377
|
+
? description.name
|
|
378
|
+
: "Error";
|
|
379
|
+
const stack = description && typeof description === "object" && "stack" in description && typeof description.stack === "string"
|
|
380
|
+
? description.stack
|
|
381
|
+
: "";
|
|
382
|
+
const location = /:(\d+)(?::\d+)?\)?(?:\n|$)/.exec(stack);
|
|
383
|
+
return {
|
|
384
|
+
result: undefined,
|
|
385
|
+
error: formatGuestError(description).slice(0, 4000),
|
|
386
|
+
failure: {
|
|
387
|
+
name,
|
|
388
|
+
...(call ? { call } : {}),
|
|
389
|
+
...(location ? { line: Math.max(1, Number(location[1]) - 1) } : {}),
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
};
|
|
330
393
|
const timedOut = () => ({
|
|
331
394
|
result: undefined,
|
|
332
395
|
error: timeoutError,
|
|
396
|
+
failure: {
|
|
397
|
+
name: "TimeoutError",
|
|
398
|
+
timeout: { elapsedMs: Date.now() - (deadline - timeoutMs), deadlineMs: timeoutMs },
|
|
399
|
+
},
|
|
333
400
|
timedOut: true,
|
|
334
401
|
});
|
|
402
|
+
// Keep this diagnostic function in a host-owned handle. Error.isError
|
|
403
|
+
// checks the native brand without Proxy traps. Own data descriptors of
|
|
404
|
+
// native Errors are safe to read; accessors and arbitrary thrown objects
|
|
405
|
+
// get a fixed description instead of dump's guest serialization/getters.
|
|
406
|
+
const described = runGuest(() => ctx.evalCode(`(() => {
|
|
407
|
+
const isError = Error.isError;
|
|
408
|
+
const descriptor = Object.getOwnPropertyDescriptor;
|
|
409
|
+
const prototypeOf = Object.getPrototypeOf;
|
|
410
|
+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
|
|
411
|
+
const slice = Function.prototype.call.bind(String.prototype.slice);
|
|
412
|
+
const stringify = JSON.stringify;
|
|
413
|
+
const prototypes = [
|
|
414
|
+
[Error.prototype, "Error"], [TypeError.prototype, "TypeError"],
|
|
415
|
+
[SyntaxError.prototype, "SyntaxError"], [ReferenceError.prototype, "ReferenceError"],
|
|
416
|
+
[RangeError.prototype, "RangeError"], [EvalError.prototype, "EvalError"],
|
|
417
|
+
[URIError.prototype, "URIError"], [AggregateError.prototype, "AggregateError"]
|
|
418
|
+
];
|
|
419
|
+
return (error) => {
|
|
420
|
+
if (!isError(error)) return "null";
|
|
421
|
+
const text = (key, fallback, limit) => {
|
|
422
|
+
const own = descriptor(error, key);
|
|
423
|
+
return own && hasOwn(own, "value") && typeof own.value === "string"
|
|
424
|
+
? slice(own.value, 0, limit) : fallback;
|
|
425
|
+
};
|
|
426
|
+
const prototype = prototypeOf(error);
|
|
427
|
+
let name = "Error";
|
|
428
|
+
for (let i = 0; i < prototypes.length; i++) {
|
|
429
|
+
if (prototype === prototypes[i][0]) { name = prototypes[i][1]; break; }
|
|
430
|
+
}
|
|
431
|
+
return stringify({ __proto__: null, name: text("name", name, 64),
|
|
432
|
+
message: text("message", "Program threw a value.", 4000), stack: text("stack", "", 1000) });
|
|
433
|
+
};
|
|
434
|
+
})()`));
|
|
435
|
+
if (described.error) {
|
|
436
|
+
const rejection = rejected(described.error);
|
|
437
|
+
described.error.dispose();
|
|
438
|
+
return finish(rejection);
|
|
439
|
+
}
|
|
440
|
+
describeError = described.value;
|
|
335
441
|
const setup = runGuest(() => ctx.evalCode(setupScript(providers)));
|
|
336
442
|
if (setup.error) {
|
|
337
|
-
const
|
|
443
|
+
const rejection = rejected(setup.error);
|
|
338
444
|
setup.error.dispose();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return fail(cpuTimeoutError);
|
|
343
|
-
return fail(`Sandbox setup failed: ${detail}`);
|
|
445
|
+
return finish(rejection.timedOut || cpuInterrupted
|
|
446
|
+
? rejection
|
|
447
|
+
: { ...rejection, error: `Sandbox setup failed: ${rejection.error}` });
|
|
344
448
|
}
|
|
345
449
|
setup.value.dispose();
|
|
346
450
|
const evaluated = runGuest(() => ctx.evalCode(`Promise.resolve((\n${normalizeCode(code)}\n)())`));
|
|
347
451
|
if (evaluated.error) {
|
|
348
|
-
const
|
|
452
|
+
const rejection = rejected(evaluated.error);
|
|
349
453
|
evaluated.error.dispose();
|
|
350
|
-
|
|
351
|
-
return finish(timedOut());
|
|
352
|
-
if (isInterrupt(dumped) && cpuInterrupted)
|
|
353
|
-
return fail(cpuTimeoutError);
|
|
354
|
-
return fail(formatGuestError(dumped));
|
|
454
|
+
return finish(rejection);
|
|
355
455
|
}
|
|
356
456
|
const promiseHandle = evaluated.value;
|
|
357
457
|
// Drive the guest: run microtask jobs, inspect the result promise,
|
|
@@ -364,15 +464,9 @@ export async function executeQuickJs(code, providers, options, onLog) {
|
|
|
364
464
|
}
|
|
365
465
|
const jobs = runGuest(() => ctx.runtime.executePendingJobs());
|
|
366
466
|
if (jobs.error) {
|
|
367
|
-
const
|
|
467
|
+
const rejection = rejected(jobs.error);
|
|
368
468
|
jobs.error.dispose();
|
|
369
|
-
|
|
370
|
-
return timedOut();
|
|
371
|
-
}
|
|
372
|
-
if (isInterrupt(dumped) && cpuInterrupted) {
|
|
373
|
-
return { result: undefined, error: cpuTimeoutError };
|
|
374
|
-
}
|
|
375
|
-
return { result: undefined, error: formatGuestError(dumped) };
|
|
469
|
+
return rejection;
|
|
376
470
|
}
|
|
377
471
|
const state = ctx.getPromiseState(promiseHandle);
|
|
378
472
|
if (state.type === "fulfilled") {
|
|
@@ -381,15 +475,9 @@ export async function executeQuickJs(code, providers, options, onLog) {
|
|
|
381
475
|
return { result };
|
|
382
476
|
}
|
|
383
477
|
if (state.type === "rejected") {
|
|
384
|
-
const
|
|
478
|
+
const rejection = rejected(state.error);
|
|
385
479
|
state.error.dispose();
|
|
386
|
-
|
|
387
|
-
return timedOut();
|
|
388
|
-
}
|
|
389
|
-
if (isInterrupt(dumped) && cpuInterrupted) {
|
|
390
|
-
return { result: undefined, error: cpuTimeoutError };
|
|
391
|
-
}
|
|
392
|
-
return { result: undefined, error: formatGuestError(dumped) };
|
|
480
|
+
return rejection;
|
|
393
481
|
}
|
|
394
482
|
if (bridge.pending === 0) {
|
|
395
483
|
return {
|