@zackbart/connecta 0.39.0 → 0.41.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 +115 -0
- package/bin/connecta.mjs +4 -8
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +6 -4
- package/dist/activity.d.ts +30 -4
- package/dist/activity.js +28 -2
- package/dist/agent-observation.js +24 -2
- package/dist/agent-routes.js +4 -41
- package/dist/auth/clerk.d.ts +6 -1
- package/dist/auth/clerk.js +16 -0
- package/dist/auth/cloudflare-access.js +49 -7
- package/dist/auth/downstream-oauth/consent.js +79 -29
- package/dist/auth/downstream-oauth/grant-store.js +58 -7
- package/dist/auth/downstream-oauth/refresh-records.js +6 -5
- package/dist/auth/downstream-oauth.js +12 -4
- package/dist/auth/native.js +10 -0
- package/dist/auth/oauth-request-not-sent.js +4 -0
- package/dist/auth/static-oauth.js +51 -21
- package/dist/catalog-cache.js +151 -37
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-service.js +39 -12
- package/dist/catalog.js +103 -38
- package/dist/config.d.ts +10 -4
- package/dist/config.js +30 -7
- package/dist/connectors/remote-mcp/call.js +1 -1
- package/dist/connectors/remote-mcp/connection.js +52 -7
- package/dist/connectors/remote-mcp/oauth.js +6 -2
- package/dist/connectors/remote-mcp/resources.js +5 -2
- package/dist/connectors/remote-mcp/transport.js +37 -8
- package/dist/connectors/remote-mcp.js +8 -5
- package/dist/credential-contract.d.ts +3 -1
- package/dist/describe-config.d.ts +1 -0
- package/dist/describe-config.js +1 -0
- package/dist/downstream-input.js +11 -2
- package/dist/errors.d.ts +8 -1
- package/dist/errors.js +2 -0
- package/dist/execute.js +20 -7
- package/dist/executor-result.js +1 -1
- package/dist/executors/quickjs-child.js +1 -1
- package/dist/executors/quickjs-runtime.js +1 -1
- package/dist/executors/quickjs.js +1 -1
- package/dist/guarded-transport.js +2 -0
- package/dist/http/client.js +7 -0
- package/dist/http/hatch.js +2 -0
- package/dist/http/node-transport.js +110 -4
- package/dist/http/oauth.js +17 -6
- package/dist/index.js +10 -21
- package/dist/invocation-failure.js +28 -0
- package/dist/invocation.js +77 -35
- package/dist/membership.d.ts +1 -1
- package/dist/meta-tools.js +58 -36
- package/dist/module-contracts.d.ts +3 -1
- package/dist/native-tools.js +12 -5
- package/dist/node.d.ts +7 -0
- package/dist/node.js +82 -3
- package/dist/operation-activity.js +103 -0
- package/dist/operator-record.d.ts +8 -0
- package/dist/operator-record.js +15 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/provider-call-facts.js +6 -2
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/breeze/index.js +1 -1
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/cloudflare/index.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/forms/index.js +1 -1
- package/dist/providers/github/index.js +1 -1
- package/dist/providers/gmail/index.js +1 -1
- package/dist/providers/granola/index.js +1 -1
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/quickbooks/api.js +28 -25
- package/dist/providers/quickbooks/index.js +1 -1
- package/dist/providers/quickbooks/lib/writes.js +55 -0
- package/dist/providers/quickbooks/safety.js +17 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +1 -1
- package/dist/providers/slides/index.js +1 -1
- package/dist/providers/stripe/index.js +2 -2
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/api.js +1 -0
- package/dist/registry.js +16 -4
- package/dist/resource-read.js +21 -2
- package/dist/route-names.js +44 -0
- package/dist/routes/activity-context.js +1 -0
- package/dist/routes/activity-disclosure.js +26 -4
- package/dist/routes/activity.js +2 -1
- package/dist/routes/mcp.js +32 -13
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui-data.js +3 -2
- package/dist/runtime/native-storage.js +15 -0
- package/dist/runtime/request-reads.js +87 -10
- package/dist/runtime/request-scope.js +2 -1
- package/dist/skill-origin.js +16 -0
- package/dist/skills.js +132 -43
- package/dist/storage/d1-driver.js +33 -5
- package/dist/storage/keys.js +7 -1
- package/dist/storage/sql-native.js +85 -3
- package/dist/storage/sql.js +6 -0
- package/dist/timing-trace.js +84 -0
- package/dist/types.d.ts +5 -0
- package/dist/url-safety.js +38 -20
- package/dist/utility-recovery.js +33 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +33 -8
- package/documentation/architecture.md +83 -2
- package/documentation/auth.md +132 -37
- package/documentation/code-mode.md +61 -46
- package/documentation/deploying.md +25 -1
- package/documentation/integrating.md +2 -2
- package/documentation/meta-tools.md +134 -15
- package/documentation/operating.md +86 -1
- package/documentation/operator-ui.md +51 -2
- package/documentation/providers.md +16 -14
- package/package.json +10 -8
- package/templates/node/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -189,7 +189,7 @@ function assertFlag(value, path) {
|
|
|
189
189
|
if (typeof value !== "boolean")
|
|
190
190
|
throw new ConfigError(`${path} must be true or false.`);
|
|
191
191
|
}
|
|
192
|
-
/** Longest pool `description`, in
|
|
192
|
+
/** Longest pool `description`, in UTF-16 code units, after trimming. */
|
|
193
193
|
const MAX_POOL_DESCRIPTION_LENGTH = 500;
|
|
194
194
|
/**
|
|
195
195
|
* A pool description is one line of prose for the model. C0 and C1 controls
|
|
@@ -209,9 +209,9 @@ function assertPoolDescription(value, path) {
|
|
|
209
209
|
const trimmed = typeof value === "string" ? value.trim() : undefined;
|
|
210
210
|
if (trimmed === undefined ||
|
|
211
211
|
trimmed.length === 0 ||
|
|
212
|
-
|
|
212
|
+
trimmed.length > MAX_POOL_DESCRIPTION_LENGTH ||
|
|
213
213
|
hasLineBreakOrControlCharacter(trimmed)) {
|
|
214
|
-
throw new ConfigError(`${path} must be text of 1 to ${MAX_POOL_DESCRIPTION_LENGTH}
|
|
214
|
+
throw new ConfigError(`${path} must be text of 1 to ${MAX_POOL_DESCRIPTION_LENGTH} UTF-16 code units after trimming, with no line breaks or control characters.`);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
/** Shape only; each reference resolves against auth and agents in createConnecta. */
|
|
@@ -263,7 +263,7 @@ const pool = {
|
|
|
263
263
|
* What this pool is for, in plain words for the model: appended to the MCP
|
|
264
264
|
* `initialize` instructions at `/mcp/<name>` and `/agent/<name>`, so a
|
|
265
265
|
* caller learns its scope ("Team tier: shared tools only."). Trimmed, 1 to
|
|
266
|
-
* 500
|
|
266
|
+
* 500 UTF-16 code units, no line breaks or control characters. It grants and hides nothing; plain
|
|
267
267
|
* `/mcp` never shows it.
|
|
268
268
|
*/
|
|
269
269
|
description: opaque({ check: assertPoolDescription }),
|
|
@@ -396,8 +396,9 @@ const connectaConfig = {
|
|
|
396
396
|
resolve: (value) => value ?? memoryStorage(),
|
|
397
397
|
}),
|
|
398
398
|
/**
|
|
399
|
-
* Public base URL. Defaults to the request origin per-request
|
|
400
|
-
*
|
|
399
|
+
* Public base URL. Defaults to the request origin per-request, except that
|
|
400
|
+
* clerkAuth requires it here or as its own `publicUrl`. Configuring an HTTPS
|
|
401
|
+
* URL also redirects matching inbound HTTP requests to HTTPS.
|
|
401
402
|
*/
|
|
402
403
|
publicUrl: opaque({ check: httpUrl }),
|
|
403
404
|
/**
|
|
@@ -419,7 +420,10 @@ const connectaConfig = {
|
|
|
419
420
|
}
|
|
420
421
|
},
|
|
421
422
|
}),
|
|
422
|
-
/**
|
|
423
|
+
/**
|
|
424
|
+
* Replaceable owner-partitioned credential storage. Omit for config-owned
|
|
425
|
+
* secrets. With any OAuth connector it must implement `seal` and `open`.
|
|
426
|
+
*/
|
|
423
427
|
vault: opaque({
|
|
424
428
|
check: (value) => {
|
|
425
429
|
if (!hasMethods(value, ["get", "getAll", "set", "setAll", "metadata", "delete"]) ||
|
|
@@ -464,6 +468,14 @@ const connectaConfig = {
|
|
|
464
468
|
* saturation.
|
|
465
469
|
*/
|
|
466
470
|
admission: object(admission),
|
|
471
|
+
/** Payload-free request timing logs; 0 disables tracing, 1 samples every MCP request. */
|
|
472
|
+
timingTraceSampleRate: opaque({
|
|
473
|
+
check: (value, path) => {
|
|
474
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1)
|
|
475
|
+
throw new ConfigError(`${path} must be a finite number between 0 and 1.`);
|
|
476
|
+
},
|
|
477
|
+
resolve: (value) => value ?? 0,
|
|
478
|
+
}),
|
|
467
479
|
/** Diagnostic output. Use "silent" to disable all diagnostic logging. */
|
|
468
480
|
logger: opaque({
|
|
469
481
|
check: (value, path) => {
|
|
@@ -528,10 +540,21 @@ function assertCoherent(config) {
|
|
|
528
540
|
config.discovery.catalogMaxTtlSeconds > 86_400) {
|
|
529
541
|
throw new ConfigError("ConnectaConfig.discovery requires catalogMinTtlSeconds <= catalogMaxTtlSeconds <= 86400.");
|
|
530
542
|
}
|
|
543
|
+
const { vault } = config;
|
|
544
|
+
const oauth = config.connectors.find((connector) => connector.startAuth);
|
|
545
|
+
if (oauth && vault && (typeof vault.seal !== "function" || typeof vault.open !== "function")) {
|
|
546
|
+
throw new ConfigError(`ConnectaConfig.vault must implement both seal and open: connector "${oauth.id}" stores downstream OAuth ` +
|
|
547
|
+
"tokens, client registration and PKCE verifiers through it. Use encryptedCredentialVault(storage, key) " +
|
|
548
|
+
"from @zackbart/connecta/credentials, or add both methods.");
|
|
549
|
+
}
|
|
531
550
|
if (config.agents.some((agent) => !agent.vercel) && !config.accessTokens) {
|
|
532
551
|
throw new ConfigError("ConnectaConfig.agents needs accessTokens: accessTokens(storage) from @zackbart/connecta/auth/access-tokens " +
|
|
533
552
|
"holds each agent's token slot.");
|
|
534
553
|
}
|
|
554
|
+
if (!config.publicUrl && config.auth.some((provider) => provider.requiresPublicUrl)) {
|
|
555
|
+
throw new ConfigError("ConnectaConfig.publicUrl is required with clerkAuth, which checks tokens and sessions against it: " +
|
|
556
|
+
"set it, or clerkAuth({ publicUrl }), to this deployment's URL, http://localhost:<port> for local development.");
|
|
557
|
+
}
|
|
535
558
|
}
|
|
536
559
|
/**
|
|
537
560
|
* Validate a configuration and apply every default. Unknown keys are refused
|
|
@@ -55,7 +55,7 @@ export function remoteMcpCall(rt) {
|
|
|
55
55
|
}
|
|
56
56
|
: state.toolDefinitions.get(name);
|
|
57
57
|
if (toolDefinition?.execution?.taskSupport === "required") {
|
|
58
|
-
throw new ConnectorCallError("
|
|
58
|
+
throw new ConnectorCallError("unsupported_capability", `Tool "${name}" requires task-based execution, which Connecta does not support.`);
|
|
59
59
|
}
|
|
60
60
|
let output;
|
|
61
61
|
const outputError = (code, message) => rt.atMcpBoundary(ctx, new ProtocolError(code, message), "tools/call", client.transport, [ctx.signal]);
|
|
@@ -12,7 +12,7 @@ import { Deferred, Effect, Exit, Scope } from "effect";
|
|
|
12
12
|
import { KvOAuthProvider, refreshCoordinatorsByPartition } from "../../auth/downstream-oauth.js";
|
|
13
13
|
import { trackOAuthStartReset } from "../../auth/oauth-start-reset.js";
|
|
14
14
|
import { sentSecretsFor } from "../../sent-secrets.js";
|
|
15
|
-
import { catalogClientOptions, observeCatalogChange } from "../../catalog-cache.js";
|
|
15
|
+
import { catalogClientOptions, prefetchCatalog, observeCatalogChange } from "../../catalog-cache.js";
|
|
16
16
|
import { ConnectorCallError, resendAdvice } from "../../errors.js";
|
|
17
17
|
import { carryFailureFacts, failureRecord, logFailure } from "../../operator-record.js";
|
|
18
18
|
import { CONNECTA_VERSION } from "../../version.js";
|
|
@@ -23,11 +23,42 @@ import { downstreamInputCapabilities } from "../../downstream-input-context.js";
|
|
|
23
23
|
import { detach, runEdge } from "../../runtime/run.js";
|
|
24
24
|
import { requestPrincipalFor } from "../../request-principal.js";
|
|
25
25
|
import { callerOf } from "../../connector-caller.js";
|
|
26
|
+
import { negotiationKeys } from "../../storage/keys.js";
|
|
26
27
|
import { forgetNegotiation, readNegotiation, storeNegotiation } from "../negotiation-cache.js";
|
|
27
28
|
import { carriesIllegalHeaderChar, credentialHeaderValue } from "./config.js";
|
|
28
29
|
import { AUTH_OR_THROTTLE, promised, recordWireErrors, requiresAuthorization } from "./errors.js";
|
|
29
30
|
import { MAX_TOOL_PAGES } from "./catalog.js";
|
|
30
31
|
import { SKILLS_EXTENSION } from "./resources.js";
|
|
32
|
+
/** Retain every abandoned lease release until the final scope close joins it. */
|
|
33
|
+
export function retainCleanup(state, release) {
|
|
34
|
+
const pending = detach(release);
|
|
35
|
+
state.cleanup.add(pending);
|
|
36
|
+
if (state.closing)
|
|
37
|
+
state.closing = Promise.all([state.closing, pending]).then(() => { });
|
|
38
|
+
// Register at the release itself as well: a late connect can acquire a
|
|
39
|
+
// session after the owner's first close has already settled.
|
|
40
|
+
try {
|
|
41
|
+
state.defer?.(pending);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Runtime hooks are best-effort; the bounded cleanup is already running.
|
|
45
|
+
}
|
|
46
|
+
void pending.then(() => state.cleanup.delete(pending));
|
|
47
|
+
return pending;
|
|
48
|
+
}
|
|
49
|
+
/** Scope.closeUnsafe returns nothing on a second close, even during release. */
|
|
50
|
+
export function closeConnectionScope(state) {
|
|
51
|
+
if (!state.closing) {
|
|
52
|
+
const release = Scope.closeUnsafe(state.scope, Exit.void);
|
|
53
|
+
if (release)
|
|
54
|
+
retainCleanup(state, release);
|
|
55
|
+
state.closing = (async () => {
|
|
56
|
+
while (state.cleanup.size)
|
|
57
|
+
await Promise.all(state.cleanup);
|
|
58
|
+
})();
|
|
59
|
+
}
|
|
60
|
+
return state.closing;
|
|
61
|
+
}
|
|
31
62
|
/** One connector's connection states, credential reads and connect attempts. */
|
|
32
63
|
export function remoteMcpConnection(rt) {
|
|
33
64
|
const { requestAuth, forwarding, id, staticClient, clientMetadataUrl, opts, oauthScope, authorizationParams, destination, isOauth, extraHeaders, credentialHeader, credentialScheme, skillsEnabled, principalPolicy, credentialAuth, } = rt;
|
|
@@ -103,6 +134,9 @@ export function remoteMcpConnection(rt) {
|
|
|
103
134
|
if (!state) {
|
|
104
135
|
state = {
|
|
105
136
|
scope: Scope.makeUnsafe(),
|
|
137
|
+
closing: null,
|
|
138
|
+
cleanup: new Set(),
|
|
139
|
+
defer: ctx.defer,
|
|
106
140
|
lease: null,
|
|
107
141
|
client: null,
|
|
108
142
|
transport: null,
|
|
@@ -118,10 +152,8 @@ export function remoteMcpConnection(rt) {
|
|
|
118
152
|
states.set(key, state);
|
|
119
153
|
const owned = state;
|
|
120
154
|
owner.own(() => {
|
|
121
|
-
|
|
155
|
+
void closeConnectionScope(owned);
|
|
122
156
|
reset(owned);
|
|
123
|
-
if (closing)
|
|
124
|
-
detach(closing);
|
|
125
157
|
});
|
|
126
158
|
}
|
|
127
159
|
return state;
|
|
@@ -189,7 +221,7 @@ export function remoteMcpConnection(rt) {
|
|
|
189
221
|
reset(state);
|
|
190
222
|
const release = lease && Scope.closeUnsafe(lease, Exit.void);
|
|
191
223
|
if (release)
|
|
192
|
-
|
|
224
|
+
retainCleanup(state, release);
|
|
193
225
|
};
|
|
194
226
|
const waitForConnection = (attempt, signal) => runEdge(Deferred.await(attempt), signal ? { signal } : {});
|
|
195
227
|
const oauthConfigFor = (ctx) => isOauth
|
|
@@ -272,6 +304,7 @@ export function remoteMcpConnection(rt) {
|
|
|
272
304
|
try {
|
|
273
305
|
let epoch = "";
|
|
274
306
|
let identity;
|
|
307
|
+
let pinnedIdentity;
|
|
275
308
|
if (isOauth) {
|
|
276
309
|
// Read-only grant snapshots: no in-place sealing, epoch binding or
|
|
277
310
|
// refresh. Every identity check during cache acceptance re-reads the
|
|
@@ -282,6 +315,7 @@ export function remoteMcpConnection(rt) {
|
|
|
282
315
|
epoch = snapshot.epoch;
|
|
283
316
|
if (provider.isOperatorDisconnectedEpoch(epoch) || !snapshot.tokens)
|
|
284
317
|
return preread;
|
|
318
|
+
pinnedIdentity = yield authCall(secretDigest, [ctx, JSON.stringify(snapshot.tokens)]);
|
|
285
319
|
const bound = epoch;
|
|
286
320
|
identity = authMethod(() => authFlow(function* () {
|
|
287
321
|
const current = (yield authInvoke(provider, "peekGrant", []));
|
|
@@ -293,6 +327,17 @@ export function remoteMcpConnection(rt) {
|
|
|
293
327
|
}
|
|
294
328
|
if (opts.versionNegotiation !== "legacy") {
|
|
295
329
|
const digest = (yield authCall(negotiationKey, [ctx, credentialDigest, epoch]));
|
|
330
|
+
// Both reads only locate cached data. Acceptance and dispatch keep
|
|
331
|
+
// their fresh generation and credential checks.
|
|
332
|
+
yield authCall(prefetchCatalog, [
|
|
333
|
+
ctx,
|
|
334
|
+
id,
|
|
335
|
+
(yield authCall(catalogConfigFor, [ctx])),
|
|
336
|
+
JSON.stringify([epoch, credentialDigest]),
|
|
337
|
+
catalogSharing,
|
|
338
|
+
pinnedIdentity,
|
|
339
|
+
negotiationKeys.verdict(digest),
|
|
340
|
+
]);
|
|
296
341
|
const prior = (yield authCall(readNegotiation, [ctx, digest]));
|
|
297
342
|
preread.negotiation = { digest, prior };
|
|
298
343
|
if (prior?.kind === "modern")
|
|
@@ -633,13 +678,13 @@ export function remoteMcpConnection(rt) {
|
|
|
633
678
|
state.authRequired = true;
|
|
634
679
|
const release = Scope.closeUnsafe(lease, Exit.void);
|
|
635
680
|
if (release)
|
|
636
|
-
|
|
681
|
+
retainCleanup(state, release);
|
|
637
682
|
}
|
|
638
683
|
else {
|
|
639
684
|
// Whoever abandoned this attempt closed the lease already, while
|
|
640
685
|
// the connect was in flight. A session it acquired after that
|
|
641
686
|
// close still owes its DELETE.
|
|
642
|
-
|
|
687
|
+
retainCleanup(state, rt.closeConnection(held.client, held.transport, ctx.logger));
|
|
643
688
|
}
|
|
644
689
|
if (err instanceof UnauthorizedError) {
|
|
645
690
|
return Effect.fail(carryFailureFacts(err, rt.authRequiredError(ctx)));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The downstream OAuth surface: bounded SDK flows, early renewal before a
|
|
2
2
|
// call, unchallenged consent starts, and the connector's auth operations.
|
|
3
|
+
import { retainCleanup } from "./connection.js";
|
|
3
4
|
import { authView } from "../../auth/native-call.js";
|
|
4
5
|
import { authFlow, authCall, authInvoke, authMethod } from "../../auth/native.js";
|
|
5
6
|
import { viewSignal } from "../../runtime/call.js";
|
|
@@ -10,7 +11,6 @@ import { byteReadResponse } from "../../byte-read-response.js";
|
|
|
10
11
|
import { requestSentSecrets, sentSecretsFor } from "../../sent-secrets.js";
|
|
11
12
|
import { ConnectorCallError, resendAdvice, WithheldTextError } from "../../errors.js";
|
|
12
13
|
import { attachFailureFacts, carryFailureFacts, OAUTH_ERROR_CODES } from "../../operator-record.js";
|
|
13
|
-
import { detach } from "../../runtime/run.js";
|
|
14
14
|
import { retryAfterMs } from "../../http-policy.js";
|
|
15
15
|
import { sendCredentialPost } from "../../guarded-transport.js";
|
|
16
16
|
import { UnboundOAuthFlowsError, downstreamCallError, errorKind, hasSdkPayload, ownAbortReason, requiresAuthorization, startMessage, withheldAs, } from "./errors.js";
|
|
@@ -376,7 +376,7 @@ export function remoteMcpOAuth(rt) {
|
|
|
376
376
|
]));
|
|
377
377
|
}
|
|
378
378
|
finally {
|
|
379
|
-
|
|
379
|
+
retainCleanup(state, rt.closeConnection(null, t, ctx.logger));
|
|
380
380
|
}
|
|
381
381
|
})), true));
|
|
382
382
|
connector.verifyState = authMethod((oauthState, ctx) => authFlow(function* () {
|
|
@@ -399,6 +399,10 @@ export function remoteMcpOAuth(rt) {
|
|
|
399
399
|
connector.startAuth = authMethod((ctx, startOpts) => authFlow(function* () {
|
|
400
400
|
ctx = authorizingContext(ctx);
|
|
401
401
|
const state = rt.stateFor(ctx);
|
|
402
|
+
// A passive tokenless handshake was refused locally. An explicit
|
|
403
|
+
// start gets its own challenge-capable connection attempt.
|
|
404
|
+
if (state.authRequired && !state.client)
|
|
405
|
+
rt.closeHalf(state);
|
|
402
406
|
state.provider = null;
|
|
403
407
|
const p = rt.newProvider(ctx, state);
|
|
404
408
|
if (ctx.signal?.aborted)
|
|
@@ -345,8 +345,11 @@ export function remoteMcpResources(rt) {
|
|
|
345
345
|
if (client.getProtocolEra() !== "modern" ||
|
|
346
346
|
!skillObject(capabilities?.resources) ||
|
|
347
347
|
!skillObject(extension) ||
|
|
348
|
+
// The released Beta specification defines no revision setting.
|
|
349
|
+
// Never enable a future breaking revision under the same ID.
|
|
350
|
+
extension.revision !== undefined ||
|
|
348
351
|
(extension.directoryRead !== undefined && typeof extension.directoryRead !== "boolean")) {
|
|
349
|
-
throw new ConnectorCallError("
|
|
352
|
+
throw new ConnectorCallError("unsupported_capability", "The downstream did not declare a valid Skills extension and resources capability.");
|
|
350
353
|
}
|
|
351
354
|
const result = (yield authCall(run, [client, state]));
|
|
352
355
|
skillScopeOpen(ctx, state);
|
|
@@ -415,7 +418,7 @@ export function remoteMcpResources(rt) {
|
|
|
415
418
|
rt.listingContexts.set(options, ctx);
|
|
416
419
|
try {
|
|
417
420
|
if (!client.getServerCapabilities()?.resources)
|
|
418
|
-
throw
|
|
421
|
+
throw new ConnectorCallError("unsupported_capability", "The downstream did not declare resources support.");
|
|
419
422
|
const resources = (yield authInvoke(client, "listResources", [undefined, options]));
|
|
420
423
|
const templates = (yield authInvoke(client, "listResourceTemplates", [
|
|
421
424
|
undefined,
|
|
@@ -18,8 +18,8 @@ import { learnedUrlRefusal } from "../../url-safety.js";
|
|
|
18
18
|
import { RequestScope } from "../../runtime/request-scope.js";
|
|
19
19
|
import { Context } from "effect";
|
|
20
20
|
import { downstreamWriteContinuation } from "../../downstream-input-context.js";
|
|
21
|
-
import { guardedTransport, sendGuarded, sendCredentialPost } from "../../guarded-transport.js";
|
|
22
|
-
import { dispatchProviderRequest, recordProviderResponse, providerRequest, renewedAfterUnauthorized, } from "../../provider-call.js";
|
|
21
|
+
import { learnedDestination, guardedTransport, sendGuarded, sendCredentialPost, } from "../../guarded-transport.js";
|
|
22
|
+
import { dispatchProviderRequest, recordProviderResponse, knownProviderFailure, providerRequest, renewedAfterUnauthorized, } from "../../provider-call.js";
|
|
23
23
|
import { recordRequestField } from "../../provider-call-facts.js";
|
|
24
24
|
import { AUTH_OR_THROTTLE, errorBodyText, jsonRpcErrorBody, keepsModernVerdict, promised } from "./errors.js";
|
|
25
25
|
import { MAX_SKILL_READ_RPC_BYTES, boundedSkillResponse, skillObject } from "./resources.js";
|
|
@@ -128,11 +128,25 @@ labelErrorClass(RemoteMcpDestinationError, "RemoteMcpDestinationError");
|
|
|
128
128
|
*/
|
|
129
129
|
export function learnedUrlSafeFetch(connectorId, configured, baseFetch) {
|
|
130
130
|
return authMethod((input, init) => authFlow(function* () {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
let target = new URL(input);
|
|
132
|
+
for (let hop = 0; hop < 10; hop++) {
|
|
133
|
+
const reason = learnedUrlRefusal(configured, target);
|
|
134
|
+
if (reason !== undefined)
|
|
135
|
+
throw new RemoteMcpDestinationError(connectorId, reason);
|
|
136
|
+
const marked = { ...init, redirect: "manual", [learnedDestination]: configured };
|
|
137
|
+
const response = (yield authCall(baseFetch, [target, marked]));
|
|
138
|
+
if (!REDIRECT_STATUSES.has(response.status) ||
|
|
139
|
+
!response.headers.has("location") ||
|
|
140
|
+
(init?.method ?? "GET").toUpperCase() !== "GET")
|
|
141
|
+
return byteReadResponse(response);
|
|
142
|
+
const location = response.headers.get("location");
|
|
143
|
+
void response.body?.cancel().catch(() => { });
|
|
144
|
+
const redirected = new URL(location, target);
|
|
145
|
+
if (redirected.origin !== target.origin)
|
|
146
|
+
throw new RemoteMcpDestinationError(connectorId, "cross-origin redirect refused");
|
|
147
|
+
target = redirected;
|
|
134
148
|
}
|
|
135
|
-
|
|
149
|
+
throw new RemoteMcpDestinationError(connectorId, "redirect limit exceeded");
|
|
136
150
|
}));
|
|
137
151
|
}
|
|
138
152
|
/**
|
|
@@ -273,12 +287,27 @@ export function remoteMcpTransport(rt) {
|
|
|
273
287
|
if (toolCall && !flight && !untrackedRead(toolCall.params))
|
|
274
288
|
throw missingDispatch();
|
|
275
289
|
const credentialPost = isOauth && (init?.method ?? "GET").toUpperCase() === "POST" && (!skillObject(rpc) || rpc.jsonrpc !== "2.0");
|
|
290
|
+
// OAuth discovery, registration and exchange share this fetch, but do
|
|
291
|
+
// not use the resource credential. Guard the assembled MCP request,
|
|
292
|
+
// after the SDK token read and before dispatch bookkeeping or fetch.
|
|
293
|
+
// OAuth is required on passive paths regardless of which provider
|
|
294
|
+
// read the grant. Only an explicit authorization start may probe the
|
|
295
|
+
// resource anonymously for an SDK challenge, never a tools/call.
|
|
296
|
+
if (isOauth && !credentialPost && new URL(input).href === url.href) {
|
|
297
|
+
const authorization = new Headers(init?.headers).get("authorization");
|
|
298
|
+
if ((provider?.allowAuthorization !== true || toolCall) && !authorization)
|
|
299
|
+
throw knownProviderFailure(rt.authRequiredError(ctx), flight?.sent ? undefined : (flight?.request?.entry ?? providerRequest(ctx, true, id)));
|
|
300
|
+
}
|
|
276
301
|
// A custom transport reaches only the configured origin; redirects are
|
|
277
302
|
// already same-origin, and OAuth cannot be combined with it.
|
|
278
303
|
if (customFetch && new URL(input instanceof Request ? input.url : String(input)).origin !== destination.origin)
|
|
279
304
|
throw new ConnectorCallError("connector_call_failed", `Connector "${id}" refused a request outside its origin; nothing was sent.`, { retryable: false });
|
|
280
305
|
// The deployment's fetch owns the wire: each write is handed over once.
|
|
281
|
-
const sender = flight?.write
|
|
306
|
+
const sender = flight?.write
|
|
307
|
+
? (customSender ?? guardedTransport(true))
|
|
308
|
+
: init?.[learnedDestination]
|
|
309
|
+
? guardedTransport(false)
|
|
310
|
+
: undefined;
|
|
282
311
|
if (flight?.write) {
|
|
283
312
|
if (flight.sent) {
|
|
284
313
|
if (flight.request && flight.authorization)
|
|
@@ -410,7 +439,7 @@ export function remoteMcpTransport(rt) {
|
|
|
410
439
|
}
|
|
411
440
|
})));
|
|
412
441
|
if (opts.auth?.type === "oauth") {
|
|
413
|
-
const oauthProvider = provider
|
|
442
|
+
const oauthProvider = (provider ??= rt.newProvider(ctx, undefined, signal));
|
|
414
443
|
// The session/provider belong to the request. A renewal belongs to the
|
|
415
444
|
// exact call whose HTTP response challenged it, until claim submission.
|
|
416
445
|
// Carry that ownership on the response the SDK passes to onUnauthorized;
|
|
@@ -5,7 +5,7 @@ import { authFlow, authCall, authInvoke, authValue, authMethod, authCatch } from
|
|
|
5
5
|
import { viewServices, viewSignal } from "../runtime/call.js";
|
|
6
6
|
import { declareSelfHostedClient } from "../auth/downstream-client-metadata.js";
|
|
7
7
|
import { isInputRequiredResult } from "@modelcontextprotocol/client";
|
|
8
|
-
import { Duration, Effect
|
|
8
|
+
import { Duration, Effect } from "effect";
|
|
9
9
|
import { byteReadResponse } from "../byte-read-response.js";
|
|
10
10
|
import { redactSentSecrets, requestSentSecrets, sentSecretsFor } from "../sent-secrets.js";
|
|
11
11
|
import { closeCatalogCacheScope } from "../catalog-cache.js";
|
|
@@ -28,7 +28,7 @@ import { providerBoundary } from "../provider-call.js";
|
|
|
28
28
|
import { downstreamCallError, hasSdkPayload, payloadFree, remoteMcpErrors } from "./remote-mcp/errors.js";
|
|
29
29
|
import { LOCAL_CLOSE_BUDGET_MS, REDIRECT_STATUSES, remoteMcpTransport } from "./remote-mcp/transport.js";
|
|
30
30
|
import { remoteMcpConfig } from "./remote-mcp/config.js";
|
|
31
|
-
import { remoteMcpConnection } from "./remote-mcp/connection.js";
|
|
31
|
+
import { closeConnectionScope, remoteMcpConnection } from "./remote-mcp/connection.js";
|
|
32
32
|
import { remoteMcpOAuth } from "./remote-mcp/oauth.js";
|
|
33
33
|
import { remoteMcpCatalog } from "./remote-mcp/catalog.js";
|
|
34
34
|
import { remoteMcpResources } from "./remote-mcp/resources.js";
|
|
@@ -267,11 +267,11 @@ export function remoteMcp(id, opts) {
|
|
|
267
267
|
// Revocation and all finalizers start before any caller guard. Cleanup
|
|
268
268
|
// retains its own bounded settlement even after the request has closed.
|
|
269
269
|
const state = entryFor(ctx);
|
|
270
|
-
const release =
|
|
270
|
+
const release = closeConnectionScope(state);
|
|
271
271
|
const cacheClosing = closeCatalogCacheScope(ctx, id, cacheOwner);
|
|
272
272
|
reset(state);
|
|
273
273
|
const closing = runEdge(Effect.all([
|
|
274
|
-
|
|
274
|
+
Effect.promise(() => release),
|
|
275
275
|
cacheClosing
|
|
276
276
|
? Effect.raceAllFirst([
|
|
277
277
|
Effect.promise(() => cacheClosing),
|
|
@@ -481,9 +481,12 @@ export function remoteMcp(id, opts) {
|
|
|
481
481
|
const provider = newProvider(ctx, stateFor(ctx));
|
|
482
482
|
if ((yield authInvoke(provider, "operatorDisconnected", [])))
|
|
483
483
|
throw operatorDisconnectedError();
|
|
484
|
+
// Keep this separate from the disconnect read: invalidation and
|
|
485
|
+
// refresh can remove or replace tokens without changing the epoch.
|
|
486
|
+
// The SDK may otherwise send anonymously when its tokens() is empty.
|
|
484
487
|
if (!(yield authInvoke(provider, "tokens", [])))
|
|
485
488
|
throw authRequiredError(ctx);
|
|
486
|
-
// That read decides whether the token is about to expire.
|
|
489
|
+
// That read also decides whether the token is about to expire.
|
|
487
490
|
const due = provider.renewalDue();
|
|
488
491
|
if (due !== undefined)
|
|
489
492
|
(yield authCall(renewBeforeCall, [ctx, due]));
|
|
@@ -39,7 +39,9 @@ export interface CredentialVault {
|
|
|
39
39
|
* Encrypt downstream OAuth state (tokens, a registered client, a PKCE
|
|
40
40
|
* verifier) before it enters storage. `purpose` is the storage key the value
|
|
41
41
|
* is written under, so the result is bound to one connector, owner, and key.
|
|
42
|
-
*
|
|
42
|
+
* Construction refuses a vault without both `seal` and `open` when any
|
|
43
|
+
* connector has `startAuth`, so that state is never left plaintext beside
|
|
44
|
+
* credentials this vault encrypts.
|
|
43
45
|
*/
|
|
44
46
|
seal?(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
|
|
45
47
|
/** Sign browser OAuth handoffs with a purpose-specific key; nothing is stored. */
|
package/dist/describe-config.js
CHANGED
|
@@ -269,6 +269,7 @@ export function describeConfig(input) {
|
|
|
269
269
|
? {
|
|
270
270
|
enabled: true,
|
|
271
271
|
readable: typeof config.activity.store.list === "function",
|
|
272
|
+
...(config.activity.operations ? { operations: true } : {}),
|
|
272
273
|
...(str(config.activity.deploymentId) !== undefined ? { deploymentId: config.activity.deploymentId } : {}),
|
|
273
274
|
store: {
|
|
274
275
|
kind: storeKind(activityStore),
|
package/dist/downstream-input.js
CHANGED
|
@@ -316,12 +316,21 @@ export class DownstreamElicitation {
|
|
|
316
316
|
// The nonce is spent even when the downstream failure would normally
|
|
317
317
|
// allow a retry. Preserve write reconciliation and auth prerequisites.
|
|
318
318
|
const echoed = downstreamArgumentEcho(self.options.requestScope);
|
|
319
|
+
const recoveryError = { ...error };
|
|
320
|
+
const freshRead = state.tool === "call_tool" &&
|
|
321
|
+
error.code !== "provider_permission_denied" &&
|
|
322
|
+
error.nextAction === undefined &&
|
|
323
|
+
error.recovery === undefined &&
|
|
324
|
+
error.writeOutcome !== "unknown" &&
|
|
325
|
+
error.uncertainCall === undefined;
|
|
326
|
+
if (freshRead)
|
|
327
|
+
delete recoveryError.retry;
|
|
319
328
|
const structuredContent = {
|
|
320
329
|
...result.structuredContent,
|
|
321
330
|
error: {
|
|
322
|
-
...
|
|
331
|
+
...recoveryError,
|
|
323
332
|
retryable: false,
|
|
324
|
-
...(
|
|
333
|
+
...(freshRead
|
|
325
334
|
? {
|
|
326
335
|
nextAction: {
|
|
327
336
|
tool: state.tool,
|
package/dist/errors.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "downstream_o
|
|
|
24
24
|
* change, and retries with the base `current` reports — repeating the same
|
|
25
25
|
* call can never succeed, and waiting will not help either.
|
|
26
26
|
*/
|
|
27
|
-
| "conflict" | "resource_template_ambiguous" | "resource_match_budget_exceeded" | "input_required_unsupported" | "input_required_invalid" | "input_required_limit" | "connector_call_failed";
|
|
27
|
+
| "conflict" | "resource_template_ambiguous" | "resource_match_budget_exceeded" | "unsupported_capability" | "input_required_unsupported" | "input_required_invalid" | "input_required_limit" | "connector_call_failed";
|
|
28
28
|
/**
|
|
29
29
|
* Every classification code connecta assigns: a connector's typed codes, the
|
|
30
30
|
* executor's admission codes, and the refusals connecta frames itself.
|
|
@@ -285,6 +285,13 @@ export interface CallErrorDetails extends Partial<AuthorizationHandoff> {
|
|
|
285
285
|
writeOutcome?: "unknown";
|
|
286
286
|
/** The single model-facing entry point for every credential class. */
|
|
287
287
|
nextAction?: {
|
|
288
|
+
tool: "skills";
|
|
289
|
+
arguments: Record<string, never>;
|
|
290
|
+
purpose: string;
|
|
291
|
+
} | {
|
|
292
|
+
action: "reconnect_host" | "contact_operator" | "inspect_capabilities" | "recover_result";
|
|
293
|
+
purpose: string;
|
|
294
|
+
} | {
|
|
288
295
|
tool: "authorize_connector";
|
|
289
296
|
arguments: {
|
|
290
297
|
connector: string;
|
package/dist/errors.js
CHANGED
|
@@ -15,6 +15,7 @@ const CLASSIFICATION_CODE_TABLE = {
|
|
|
15
15
|
conflict: true,
|
|
16
16
|
resource_template_ambiguous: true,
|
|
17
17
|
resource_match_budget_exceeded: true,
|
|
18
|
+
unsupported_capability: true,
|
|
18
19
|
input_required_unsupported: true,
|
|
19
20
|
input_required_invalid: true,
|
|
20
21
|
input_required_limit: true,
|
|
@@ -337,6 +338,7 @@ const RETRYABLE_BY_CODE = {
|
|
|
337
338
|
conflict: false,
|
|
338
339
|
resource_template_ambiguous: false,
|
|
339
340
|
resource_match_budget_exceeded: false,
|
|
341
|
+
unsupported_capability: false,
|
|
340
342
|
input_required_unsupported: false,
|
|
341
343
|
input_required_invalid: false,
|
|
342
344
|
input_required_limit: false,
|
package/dist/execute.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { operationActivity } from "./operation-activity.js";
|
|
2
|
+
import { SkillLookupError } from "./skills.js";
|
|
3
|
+
import { skillLookupRecovery, utilityRecovery } from "./utility-recovery.js";
|
|
1
4
|
import { META_ROUTES, routeNames } from "./agent-routes.js";
|
|
2
5
|
import { authCall, authFlow, authInvoke, authMethod } from "./auth/native.js";
|
|
3
6
|
import { nativeRecovery } from "./native-recovery.js";
|
|
@@ -342,7 +345,9 @@ function serializedDiagnosticBytes(value) {
|
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
function guestFailure(code, message, retryable = false) {
|
|
345
|
-
return new InvocationFailure(
|
|
348
|
+
return new InvocationFailure(["unavailable", "unsupported_capability", "input_required_unsupported"].includes(code)
|
|
349
|
+
? utilityRecovery(code, message, retryable)
|
|
350
|
+
: { code, message, retryable });
|
|
346
351
|
}
|
|
347
352
|
/**
|
|
348
353
|
* E1/X11: bound typed host failure metadata, including JSON escapes, before transport.
|
|
@@ -724,7 +729,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
724
729
|
if (typeof id !== "string" ||
|
|
725
730
|
(!options && options !== undefined) ||
|
|
726
731
|
(options !== undefined && (typeof options !== "object" || Array.isArray(options)))) {
|
|
727
|
-
throw
|
|
732
|
+
throw new InvocationFailure(utilityRecovery("invalid_args", "Use connecta.result(handleOrId, { page?, offset?, maxBytes? })."));
|
|
728
733
|
}
|
|
729
734
|
const args = { ...(options ?? {}) };
|
|
730
735
|
if (Object.keys(args).some((key) => !["page", "offset", "maxBytes"].includes(key)) ||
|
|
@@ -737,7 +742,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
737
742
|
(typeof args.offset !== "number" || !Number.isSafeInteger(args.offset) || args.offset < 0)) ||
|
|
738
743
|
(args.maxBytes !== undefined &&
|
|
739
744
|
(typeof args.maxBytes !== "number" || !Number.isSafeInteger(args.maxBytes) || args.maxBytes < 1))) {
|
|
740
|
-
throw
|
|
745
|
+
throw new InvocationFailure(utilityRecovery("invalid_args", "Use connecta.result(handleOrId, { page?, offset?, maxBytes? }) with whole page/offset >= 0 and maxBytes >= 1; page and offset are mutually exclusive."));
|
|
741
746
|
}
|
|
742
747
|
// A page must fit every shipped bridge even when the connector cap is raised.
|
|
743
748
|
args.maxBytes = Math.min(args.maxBytes ?? PROGRAM_RESULT_PAGE_BYTES, PROGRAM_RESULT_PAGE_BYTES);
|
|
@@ -768,6 +773,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
768
773
|
if (typeof skillName !== "string")
|
|
769
774
|
throw guestFailure("invalid_args", 'Use connecta.skill("usage") or connecta.skill("connector:<id>") with an exact skill name; the skills tool lists names.');
|
|
770
775
|
const skills = new SkillsRegistry(registry, baseUrl, {
|
|
776
|
+
readOnly: limits.readOnly,
|
|
771
777
|
requestScope,
|
|
772
778
|
requestSignal: utilitySignal ?? hostAccessSignal,
|
|
773
779
|
onRead: (connector, source) => limits.resultDependencies?.push({
|
|
@@ -780,7 +786,11 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
780
786
|
});
|
|
781
787
|
return { name: skillName, format: "text", text: await skills.text(skillName) };
|
|
782
788
|
},
|
|
783
|
-
catch: (err) => err instanceof
|
|
789
|
+
catch: (err) => err instanceof SkillLookupError
|
|
790
|
+
? new InvocationFailure(skillLookupRecovery(err.message))
|
|
791
|
+
: err instanceof ConnectorCallError
|
|
792
|
+
? guestFailure(err.code, "Skills are unavailable.", err.retryable)
|
|
793
|
+
: err,
|
|
784
794
|
}),
|
|
785
795
|
read: (uri) => Effect.suspend(() => {
|
|
786
796
|
const started = Date.now();
|
|
@@ -923,7 +933,10 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
923
933
|
timeoutError: guestFailure("timeout", `connecta.${name} timed out.`),
|
|
924
934
|
})
|
|
925
935
|
: invoke;
|
|
926
|
-
const
|
|
936
|
+
const operationEffect = activity?.recordOperation && ["search", "describe", "result", "skill"].includes(name)
|
|
937
|
+
? operationActivity(activity, registry, name, guarded)
|
|
938
|
+
: guarded;
|
|
939
|
+
const settled = runEdge(operationEffect.pipe(Effect.catch((error) => {
|
|
927
940
|
if (deadline && hostAccessSignal?.aborted)
|
|
928
941
|
return typedFailure(guestFailure("cancelled", `connecta.${name} was cancelled because the run ended.`));
|
|
929
942
|
if (deadline && error instanceof InvocationFailure && error.code === "timeout") {
|
|
@@ -1230,7 +1243,7 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
|
|
|
1230
1243
|
}
|
|
1231
1244
|
return play(normalizeProgramSource(code), diagnostics, options.signal, requestScope, hostCalls, programWrites);
|
|
1232
1245
|
});
|
|
1233
|
-
return response.pipe(Effect.provideService(RequestScope, owner), Effect.map((unfinished) => {
|
|
1246
|
+
return operationActivity(activity, registry, "program", response.pipe(Effect.provideService(RequestScope, owner), Effect.map((unfinished) => {
|
|
1234
1247
|
// Every response, including intake refusals, crosses accounting and
|
|
1235
1248
|
// exactly one envelope check at this operation boundary.
|
|
1236
1249
|
const result = redactAgentOutput(sentSecrets, programWrites.finish(unfinished));
|
|
@@ -1241,7 +1254,7 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
|
|
|
1241
1254
|
if (result.content[0]?.type === "text")
|
|
1242
1255
|
result.content[0].text = JSON.stringify(result.structuredContent);
|
|
1243
1256
|
return result;
|
|
1244
|
-
}), Effect.catchDefect(Effect.fail), Effect.mapError((error) => redactAgentOutput(sentSecrets, error)));
|
|
1257
|
+
}), Effect.catchDefect(Effect.fail), Effect.mapError((error) => redactAgentOutput(sentSecrets, error))), () => ({ ...hostCalls, failed: hostCalls.attempted - hostCalls.succeeded }));
|
|
1245
1258
|
}));
|
|
1246
1259
|
}
|
|
1247
1260
|
/** An execution that never produced an ExecuteResult, as the model sees it. */
|
package/dist/executor-result.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { prepareExecuteResultForTransport } from "../executor-result.js";
|
|
2
|
-
import { InvocationFailure } from "../invocation.js";
|
|
2
|
+
import { InvocationFailure } from "../invocation-failure.js";
|
|
3
3
|
import { msg } from "../errors.js";
|
|
4
4
|
import { MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_RESULT_BYTES, QUICKJS_RESULT_CHUNK_CHARS, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
5
5
|
import { executeQuickJs, prepareQuickJs } from "./quickjs-runtime.js";
|
|
@@ -9,7 +9,7 @@ import { utf8Length } from "../bytes.js";
|
|
|
9
9
|
// continuations from a host-side loop. Values cross the boundary as JSON, so
|
|
10
10
|
// provider args/results must be JSON-serializable.
|
|
11
11
|
import { getQuickJS } from "quickjs-emscripten";
|
|
12
|
-
import { InvocationFailure } from "../invocation.js";
|
|
12
|
+
import { InvocationFailure } from "../invocation-failure.js";
|
|
13
13
|
import { msg } from "../errors.js";
|
|
14
14
|
import { withoutProgramTerminator } from "../program-source.js";
|
|
15
15
|
import { hostCallLabel, MAX_QUICKJS_LOG_TRANSPORT_BYTES, serializedBytes } from "./quickjs-protocol.js";
|
|
@@ -18,7 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import { Deferred, Duration, Effect, Exit, Schedule, Scope } from "effect";
|
|
19
19
|
import { AdmissionController, ExecutorAdmissionError, ExecutorExecutionError } from "../executor-admission.js";
|
|
20
20
|
import { msg } from "../errors.js";
|
|
21
|
-
import { InvocationFailure } from "../invocation.js";
|
|
21
|
+
import { InvocationFailure } from "../invocation-failure.js";
|
|
22
22
|
import { brandExecutor } from "../executor-contract.js";
|
|
23
23
|
import { MAX_EXECUTE_LOG_CHARS } from "../executor-result.js";
|
|
24
24
|
import { admit } from "../runtime/admission.js";
|