@zackbart/connecta 0.40.0 → 0.42.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/bin/connecta.mjs +26 -17
  3. package/bin/meta-tool-names.d.mts +3 -1
  4. package/bin/meta-tool-names.mjs +4 -1
  5. package/bin/version.mjs +1 -1
  6. package/dist/activity.d.ts +32 -5
  7. package/dist/activity.js +32 -6
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-observation.js +24 -2
  10. package/dist/agent-routes.js +33 -44
  11. package/dist/auth/clerk.d.ts +6 -1
  12. package/dist/auth/clerk.js +16 -0
  13. package/dist/auth/cloudflare-access.js +49 -7
  14. package/dist/auth/downstream-oauth/consent.js +79 -29
  15. package/dist/auth/downstream-oauth/grant-store.js +58 -7
  16. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  17. package/dist/auth/downstream-oauth/refresh-records.js +6 -5
  18. package/dist/auth/downstream-oauth.js +15 -4
  19. package/dist/auth/oauth-request-not-sent.js +4 -0
  20. package/dist/auth/static-oauth.js +77 -39
  21. package/dist/auth-elicitation.js +118 -61
  22. package/dist/authorization-handoff.js +36 -3
  23. package/dist/catalog-cache.js +352 -43
  24. package/dist/catalog-freshness.js +25 -0
  25. package/dist/catalog-service.js +408 -78
  26. package/dist/catalog.js +216 -62
  27. package/dist/config-defaults.d.ts +2 -1
  28. package/dist/config-defaults.js +4 -1
  29. package/dist/config.d.ts +53 -21
  30. package/dist/config.js +56 -17
  31. package/dist/connectors/remote-mcp/call.js +1 -1
  32. package/dist/connectors/remote-mcp/catalog.js +52 -28
  33. package/dist/connectors/remote-mcp/connection.js +113 -22
  34. package/dist/connectors/remote-mcp/oauth.js +2 -2
  35. package/dist/connectors/remote-mcp/resources.js +5 -2
  36. package/dist/connectors/remote-mcp/transport.js +24 -6
  37. package/dist/connectors/remote-mcp.js +14 -5
  38. package/dist/credential-contract.d.ts +3 -1
  39. package/dist/describe-config.d.ts +4 -2
  40. package/dist/describe-config.js +1 -0
  41. package/dist/discovery-health.js +26 -11
  42. package/dist/downstream-input.js +11 -2
  43. package/dist/errors.d.ts +60 -4
  44. package/dist/errors.js +88 -3
  45. package/dist/execute.js +491 -202
  46. package/dist/executor-admission.js +2 -0
  47. package/dist/executor-contract.js +6 -0
  48. package/dist/executor-result.js +3 -3
  49. package/dist/executors/quickjs-child.js +14 -5
  50. package/dist/executors/quickjs-runtime.d.ts +2 -0
  51. package/dist/executors/quickjs-runtime.js +37 -20
  52. package/dist/executors/quickjs.js +24 -7
  53. package/dist/guarded-transport.js +2 -0
  54. package/dist/guest-functions.js +5 -4
  55. package/dist/guest-runtime.js +37 -1
  56. package/dist/guest-types.d.ts +18 -0
  57. package/dist/http/client.js +14 -2
  58. package/dist/http/errors.js +9 -4
  59. package/dist/http/fields.js +17 -5
  60. package/dist/http/hatch.js +11 -3
  61. package/dist/http/node-transport.js +110 -4
  62. package/dist/http/oauth.js +17 -6
  63. package/dist/http/paging.js +1 -1
  64. package/dist/http/provider.js +2 -6
  65. package/dist/http/vendor.js +6 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.js +15 -21
  68. package/dist/invocation-failure.js +28 -0
  69. package/dist/invocation.js +167 -64
  70. package/dist/membership.d.ts +1 -1
  71. package/dist/meta-output.js +24 -18
  72. package/dist/meta-tool-names.d.ts +1 -1
  73. package/dist/meta-tool-names.js +1 -1
  74. package/dist/meta-tools.js +286 -73
  75. package/dist/module-contracts.d.ts +3 -1
  76. package/dist/native-recovery.js +4 -1
  77. package/dist/native-tools.js +177 -55
  78. package/dist/node.d.ts +7 -0
  79. package/dist/node.js +85 -4
  80. package/dist/oauth-handoff.js +53 -43
  81. package/dist/operation-activity.js +104 -0
  82. package/dist/operation-vocabulary.js +6 -0
  83. package/dist/operator-record.d.ts +9 -1
  84. package/dist/operator-record.js +16 -0
  85. package/dist/operator-ui/generated.js +2 -2
  86. package/dist/program-result.js +2 -0
  87. package/dist/program-source.js +1 -1
  88. package/dist/program-writes.js +112 -19
  89. package/dist/provider.d.ts +1 -14
  90. package/dist/provider.js +0 -3
  91. package/dist/providers/_shared/google/kit.js +2 -2
  92. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  93. package/dist/providers/breeze/api.js +2 -2
  94. package/dist/providers/breeze/index.js +1 -1
  95. package/dist/providers/breeze/skill.generated.js +1 -1
  96. package/dist/providers/ccb/api.js +2 -2
  97. package/dist/providers/ccb/index.js +1 -1
  98. package/dist/providers/ccb/skill.generated.js +1 -1
  99. package/dist/providers/cloudflare/api.js +2 -2
  100. package/dist/providers/cloudflare/index.js +3 -1
  101. package/dist/providers/cloudflare/skill.generated.js +1 -1
  102. package/dist/providers/docs/index.js +1 -1
  103. package/dist/providers/docs/skill.generated.js +1 -1
  104. package/dist/providers/drive/index.js +1 -1
  105. package/dist/providers/drive/skill.generated.js +1 -1
  106. package/dist/providers/forms/index.js +2 -1
  107. package/dist/providers/forms/skill.generated.js +1 -1
  108. package/dist/providers/github/api.js +98 -6
  109. package/dist/providers/github/index.js +8 -3
  110. package/dist/providers/github/lib/auth.d.ts +3 -1
  111. package/dist/providers/github/lib/auth.js +43 -12
  112. package/dist/providers/github/lib/discovery.js +11 -1
  113. package/dist/providers/github/lib/operations.js +8 -0
  114. package/dist/providers/github/safety.js +39 -0
  115. package/dist/providers/github/skill.generated.js +1 -1
  116. package/dist/providers/github/tools.js +12 -5
  117. package/dist/providers/gmail/index.js +2 -1
  118. package/dist/providers/gmail/skill.generated.js +1 -1
  119. package/dist/providers/granola/index.js +1 -1
  120. package/dist/providers/granola/skill.generated.js +1 -1
  121. package/dist/providers/overflow/index.js +3 -1
  122. package/dist/providers/overflow/skill.generated.js +1 -1
  123. package/dist/providers/planning-center/index.js +12 -6
  124. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  125. package/dist/providers/planning-center/skill.generated.js +1 -1
  126. package/dist/providers/quickbooks/api.js +28 -25
  127. package/dist/providers/quickbooks/index.js +3 -6
  128. package/dist/providers/quickbooks/lib/writes.js +55 -0
  129. package/dist/providers/quickbooks/safety.js +17 -5
  130. package/dist/providers/quickbooks/skill.generated.js +1 -1
  131. package/dist/providers/sheets/index.js +2 -1
  132. package/dist/providers/sheets/skill.generated.js +1 -1
  133. package/dist/providers/slides/index.js +3 -3
  134. package/dist/providers/slides/skill.generated.js +1 -1
  135. package/dist/providers/stripe/index.js +2 -2
  136. package/dist/providers/stripe/skill.generated.js +1 -1
  137. package/dist/providers/tithely/index.js +2 -1
  138. package/dist/providers/tithely/skill.generated.js +1 -1
  139. package/dist/providers/vercel/api.js +3 -2
  140. package/dist/providers/vercel/skill.generated.js +1 -1
  141. package/dist/registry.d.ts +36 -4
  142. package/dist/registry.js +142 -30
  143. package/dist/resource-read.js +21 -2
  144. package/dist/result-preview.js +210 -0
  145. package/dist/result-seal.js +168 -0
  146. package/dist/route-names.js +53 -0
  147. package/dist/routes/activity-context.js +1 -0
  148. package/dist/routes/activity-disclosure.js +26 -4
  149. package/dist/routes/activity.js +2 -1
  150. package/dist/routes/connect.js +21 -13
  151. package/dist/routes/mcp.js +83 -35
  152. package/dist/routes/shared.d.ts +6 -0
  153. package/dist/routes/ui-config.js +1 -1
  154. package/dist/routes/ui-data.js +3 -2
  155. package/dist/runtime/call.js +15 -10
  156. package/dist/runtime/native-storage.js +12 -0
  157. package/dist/runtime/request-reads.js +66 -14
  158. package/dist/runtime/request-scope.js +38 -2
  159. package/dist/runtime/run.js +4 -1
  160. package/dist/sent-secrets.d.ts +20 -0
  161. package/dist/sent-secrets.js +49 -1
  162. package/dist/skill-origin.js +16 -0
  163. package/dist/skills.js +213 -57
  164. package/dist/storage/d1-driver.js +33 -5
  165. package/dist/storage/keys.js +16 -7
  166. package/dist/storage/sql-native.js +60 -3
  167. package/dist/storage/sql.js +6 -0
  168. package/dist/timing-trace.js +84 -0
  169. package/dist/tool-safety.d.ts +33 -0
  170. package/dist/tool-safety.js +51 -15
  171. package/dist/types.d.ts +36 -0
  172. package/dist/url-safety.js +38 -20
  173. package/dist/usage-guide.js +14 -13
  174. package/dist/utility-recovery.js +33 -0
  175. package/dist/validate.js +29 -10
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/dist/worker.js +60 -15
  179. package/documentation/architecture.md +152 -43
  180. package/documentation/auth.md +160 -53
  181. package/documentation/code-mode.md +396 -119
  182. package/documentation/deploying.md +35 -10
  183. package/documentation/hosted-mcp-migration.md +9 -3
  184. package/documentation/integrating.md +27 -5
  185. package/documentation/meta-tools.md +761 -157
  186. package/documentation/operating.md +91 -4
  187. package/documentation/operator-ui.md +51 -2
  188. package/documentation/providers.md +34 -20
  189. package/examples/worker/README.md +3 -3
  190. package/guides/how-it-works.md +2 -1
  191. package/guides/safety-and-trust.md +1 -3
  192. package/package.json +10 -8
  193. package/templates/node/AGENTS.md +3 -3
  194. package/templates/node/README.md +1 -1
  195. package/templates/node/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { META_ROUTES } from "./agent-routes.js";
2
+ import { READ_PROGRAM_SOURCE } from "./tool-safety.js";
2
3
  import { UNCOUNTED_GUEST_FUNCTIONS } from "./guest-functions.js";
3
4
  /** Self-contained content for the usage skill; independent of skill registration. */
4
5
  export const USAGE_SKILL = `---
@@ -14,7 +15,7 @@ Route as the server instructions and ${META_ROUTES.program}'s first sentence say
14
15
  Read one account with ${META_ROUTES.read}: \`{ "address": "crm.get_account", "args": { "id": "acct_42" } }\`.
15
16
  On a read-only endpoint search omits writes (guides may name them) and each
16
17
  fails read_only_endpoint; tell the user. Unannotated tools fail closed to writes.
17
- Connecta classifies and routes a requested write; the host owns approval.
18
+ Where listed, ${READ_PROGRAM_SOURCE} refuses writes, marked \`callable: false\`.
18
19
 
19
20
  ## The global and the envelopes
20
21
 
@@ -28,7 +29,7 @@ Parse \`format: "text"\` only when documented as JSON. Verify collection roots.
28
29
  \`connecta.read("resource://id/"+encodeURIComponent(uri))\` returns \`{ contents }\` for advertised URIs.
29
30
 
30
31
  Search/describe return \`{ tools }\`, never an array. Check search's
31
- \`catalogErrors\` and \`absence\` before selecting a match. Search defaults to a
32
+ \`catalogErrors\` and \`absence\` before selecting a match; a \`stale: true\` error's tools are listed from an older catalog, and a call still checks the live one. Search defaults to a
32
33
  TypeScript \`signature\`; text schemas have \`schemaFormat: "text"\` and no .properties.
33
34
  A \`readyCalls\` entry is a complete read: \`connecta.call(entry.address, entry.args)\`. Select the exact tool and
34
35
  account/environment, inspect its schema, and supply its required keys and types.
@@ -85,14 +86,14 @@ Otherwise fetch for units, pagination, aliases, or sequences.
85
86
 
86
87
  ## Large results and recovery
87
88
 
88
- Large direct results and program returns carry resultId when paging is available. Follow
89
- nextOffset in UTF-8 bytes, reassemble and reduce inside one program. Use the
90
- returned id in place of result-id below; this example assumes documented JSON.
89
+ Large results carry resultId. Follow nextOffset in UTF-8 bytes, reassemble and reduce
90
+ inside one program. Use that id for result-id below; this example assumes documented JSON.
91
91
 
92
- Calls reconstruct up to 1 MiB through stash pages. Larger values return
93
- \`{ format: "paged", valueFormat, resultId?, totalBytes, hint }\`. Pass the handle
94
- to \`connecta.result\`. No resultId means no paging. IDs expire after
95
- 15 minutes; only explicit paging spends extra host calls.
92
+ Calls inline up to 1 MiB; larger return
93
+ \`{ format: "paged", valueFormat, resultId?, totalBytes, hint }\`. Follow its hint:
94
+ \`connecta.result(handle, { all: true })\` gives \`{ data, format }\` for one host call,
95
+ or throws result_too_large; then page. IDs last 15 minutes; only explicit
96
+ paging spends extra host calls.
96
97
  Use zero-based \`{ page: 0 }\` or UTF-8 \`{ offset }\`, never both. Keep maxBytes
97
98
  fixed across pages; connector caps and the 32 KiB bridge ceiling apply.
98
99
 
@@ -100,7 +101,7 @@ fixed across pages; connector caps and the 32 KiB bridge ceiling apply.
100
101
  async () => {
101
102
  let text = "", offset = 0, page;
102
103
  do {
103
- page = await connecta.result("result-id", { offset, maxBytes: 8000 });
104
+ page = await connecta.result("result-id", { offset });
104
105
  text += page.text;
105
106
  offset = page.nextOffset;
106
107
  } while (page.hasMore);
@@ -108,13 +109,13 @@ async () => {
108
109
  }
109
110
  \`\`\`
110
111
 
111
- Oversized returns lead with nextAction. Never rerun writes for output. write_outcome_unknown or writeOutcome forbids retry, even if caught.
112
+ Oversized returns lead with nextAction. Never rerun writes for output. write_outcome_unknown or writeOutcome forbids retry, even if caught. Give the user any approval link in the tool's words; a call after approval is new.
112
113
  Recovery args may be partial (argsRedacted) or absent (argsOmitted). Reconcile
113
114
  before retrying with the original args or repeating a program that sent writes.
114
115
  Non-retryable errors need repair.
115
116
  host_auth_required needs host connection repair; provider_permission_denied needs
116
- resource-owner access. Auth failures carry the handoff in error.data. When an auth
117
- error includes a handoff, give it to the user or operator.
117
+ resource-owner access. Auth failures carry the handoff in error.data; give it to the
118
+ user or operator.
118
119
 
119
120
  ## Output
120
121
 
@@ -0,0 +1,33 @@
1
+ /** Actions outside the tool namespace never invent a route on a serving surface. */
2
+ export function utilityRecovery(code, message, retryable = false) {
3
+ return {
4
+ code,
5
+ message,
6
+ retryable,
7
+ nextAction: {
8
+ action: ["unsupported_capability", "input_required_unsupported", "invalid_args"].includes(code)
9
+ ? "inspect_capabilities"
10
+ : code === "not_found"
11
+ ? "recover_result"
12
+ : "contact_operator",
13
+ purpose: ["unsupported_capability", "input_required_unsupported", "invalid_args"].includes(code)
14
+ ? "Inspect the supported utility input and client capabilities before a fresh call. Never repeat a write whose outcome is unknown."
15
+ : code === "not_found"
16
+ ? "Use a retained result handle if available. Otherwise report the missing output; never repeat a write to recover output."
17
+ : "Ask the operator to restore availability. Retry reads only after recovery; reconcile any write whose outcome is unknown.",
18
+ },
19
+ };
20
+ }
21
+ /** Missing skill names need local discovery, never service restoration or result recovery. */
22
+ export function skillLookupRecovery(message) {
23
+ return {
24
+ code: "not_found",
25
+ message,
26
+ retryable: false,
27
+ nextAction: {
28
+ tool: "skills",
29
+ arguments: {},
30
+ purpose: "List available skills, then fetch an exact name from that list.",
31
+ },
32
+ };
33
+ }
package/dist/validate.js CHANGED
@@ -190,7 +190,7 @@ function receivedType(value) {
190
190
  * No caller values are used to invent an example, and a writeOnly position or
191
191
  * anything inside one never renders a schema literal. A candidate must validate.
192
192
  */
193
- function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set() }, secret = false) {
193
+ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set(), placeholders: false }, secret = false, key) {
194
194
  if (--budget.remaining < 0)
195
195
  throw new Error("Example synthesis budget exhausted");
196
196
  if (depth > 12 || schema === false)
@@ -224,7 +224,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
224
224
  candidate = Object.fromEntries(required
225
225
  .filter((key) => typeof key === "string")
226
226
  .slice(0, 30)
227
- .map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget, secret)]));
227
+ .map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget, secret, key)]));
228
228
  if (Array.isArray(node.allOf)) {
229
229
  for (const branch of node.allOf.slice(0, 20)) {
230
230
  candidate = mergeExample(candidate, exampleCandidate(branch, root, depth + 1, budget, secret));
@@ -258,7 +258,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
258
258
  // Branches may declare required keys while their types live on the parent.
259
259
  for (const key of Object.keys(schemaObject(candidate))) {
260
260
  if (schemaObject(candidate)[key] === undefined) {
261
- schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget, secret);
261
+ schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget, secret, key);
262
262
  }
263
263
  }
264
264
  }
@@ -290,10 +290,11 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
290
290
  uri: "https://example.com",
291
291
  uuid: "00000000-0000-4000-8000-000000000000",
292
292
  };
293
+ const named = budget.placeholders && !secret && key !== undefined ? namedPlaceholder(key, node) : undefined;
293
294
  candidate =
294
295
  typeof node.format === "string" && formats[node.format]
295
296
  ? formats[node.format]
296
- : "x".repeat(Math.min(typeof node.minLength === "number" ? node.minLength : 0, 128));
297
+ : (named ?? "x".repeat(Math.min(typeof node.minLength === "number" ? node.minLength : 0, 128)));
297
298
  }
298
299
  return candidate;
299
300
  }
@@ -301,6 +302,19 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
301
302
  budget.active.delete(node);
302
303
  }
303
304
  }
305
+ /**
306
+ * `<id>` for a plain string argument: an empty string would satisfy local
307
+ * validation and reach the downstream looking like a real value. A secret
308
+ * position, an odd key, and a length bound it misses keep the plain sample.
309
+ */
310
+ function namedPlaceholder(key, node) {
311
+ if (!/^[\w.-]{1,64}$/.test(key))
312
+ return undefined;
313
+ const text = `<${key}>`;
314
+ const min = typeof node.minLength === "number" ? node.minLength : 0;
315
+ const max = typeof node.maxLength === "number" ? node.maxLength : Infinity;
316
+ return text.length >= min && text.length <= max ? text : undefined;
317
+ }
304
318
  function mergeExample(a, b) {
305
319
  if (a !== null &&
306
320
  b !== null &&
@@ -501,12 +515,17 @@ function validationIssues(schema, args, units) {
501
515
  export function schemaExample(schema) {
502
516
  try {
503
517
  const validator = validators.get(schema) ?? new Validator(schema, "2020-12", false);
504
- // Serialization makes the validation cover exactly what the agent receives.
505
- const example = JSON.parse(JSON.stringify(exampleCandidate(schema, schema)));
506
- if (validator.validate(example).valid) {
507
- minimizeExample(example, validator);
508
- if (validator.validate(example).valid)
509
- return { example };
518
+ // Named placeholders first; a schema that refuses them, such as one with a
519
+ // pattern, falls back to the plain sample rather than losing its example.
520
+ for (const placeholders of [true, false]) {
521
+ const budget = { remaining: 128, active: new Set(), placeholders };
522
+ // Serialization makes the validation cover exactly what the agent receives.
523
+ const example = JSON.parse(JSON.stringify(exampleCandidate(schema, schema, 0, budget)));
524
+ if (validator.validate(example).valid) {
525
+ minimizeExample(example, validator);
526
+ if (validator.validate(example).valid)
527
+ return { example };
528
+ }
510
529
  }
511
530
  }
512
531
  catch {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CONNECTA_VERSION = "0.40.0";
1
+ export declare const CONNECTA_VERSION = "0.42.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.40.0";
3
+ export const CONNECTA_VERSION = "0.42.0";
package/dist/worker.js CHANGED
@@ -7,6 +7,7 @@ import { awaitExecutorLease, provideExecutorProgram, executorProgram } from "./r
7
7
  import { DynamicWorkerExecutor, normalizeCode } from "@cloudflare/codemode";
8
8
  import { AdmissionController, ExecutorExecutionError } from "./executor-admission.js";
9
9
  import { brandExecutor } from "./executor-contract.js";
10
+ import { TOKEN_END } from "./errors.js";
10
11
  import { InvocationFailure } from "./invocation.js";
11
12
  import { parse } from "acorn";
12
13
  import { guestInitializer, guestPrelude, guestPromiseInitializer, isolateGuestProgram } from "./guest-runtime.js";
@@ -43,15 +44,25 @@ function routeGuestImports(source) {
43
44
  }
44
45
  return source;
45
46
  }
47
+ /**
48
+ * The guest deadline when `timeout` is unset: below `execute.watchdogMs`, and
49
+ * so below the 60 s MCP clients give one request. Upstream's own default is
50
+ * that 60 s, which races them.
51
+ */
52
+ const DEFAULT_TIMEOUT_MS = 50_000;
53
+ const DEFAULT_QUEUE_TIMEOUT_MS = 40_000;
46
54
  /** Loader-only upstream sandbox whose lease disposes handles at run end. */
47
55
  export function workerExecutor(options) {
48
- // One pool per executor, so per isolate. A queued run may wait out one
56
+ const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
57
+ // One pool per executor, so per isolate. A queued run may wait most of a
49
58
  // default guest deadline: programs spend it awaiting host calls, and a
50
- // five-second wait refused nearly every run queued behind one.
59
+ // five-second wait refused nearly every run queued behind one. The wait
60
+ // spends the request's response ceiling, so it stops short enough to leave
61
+ // the run time to work and the refusal time to reach the client.
51
62
  const admission = new AdmissionController(options.admission ?? {
52
63
  concurrency: 2,
53
64
  maxQueueSize: 8,
54
- queueTimeoutMs: 60_000,
65
+ queueTimeoutMs: DEFAULT_QUEUE_TIMEOUT_MS,
55
66
  retryAfterMs: 1_000,
56
67
  });
57
68
  const active = new Set();
@@ -99,6 +110,20 @@ export function workerExecutor(options) {
99
110
  // The SDK prepares its source/dispatchers against this inert loader.
100
111
  // Actual Loader and RPC acquisition belongs to the native program below.
101
112
  let prepared;
113
+ // Upstream 0.5.5+ answers every host call that settles after its
114
+ // evaluate() with a late-call error. Its inert evaluate stays pending
115
+ // until the native evaluation ends, so the SDK's gate stays open for the
116
+ // whole run and closes once the run is over or the lease is released.
117
+ let evaluating;
118
+ const evaluated = new Promise((resolve) => {
119
+ evaluating = resolve;
120
+ });
121
+ let settleUpstream;
122
+ const endUpstream = () => {
123
+ const settle = settleUpstream;
124
+ settleUpstream = undefined;
125
+ settle?.();
126
+ };
102
127
  const dispose = (resource) => Effect.sync(() => {
103
128
  try {
104
129
  const method = Reflect.get(resource, Symbol.dispose);
@@ -163,19 +188,27 @@ export function workerExecutor(options) {
163
188
  isolated = isolated.replace("setTimeout(() => reject(timeoutError)", "nativeSetTimeout(() => reject(timeoutError)");
164
189
  isolated = isolated.replace("return { result: undefined, error: err.message, logs: __logs };", `
165
190
  const failureId = lookup(failures, err);
191
+ // tokenPrefix, for text the host redacts after this cut.
192
+ const tokenEnd = (unit) => ${[...TOKEN_END].map((unit) => `unit === ${unit}`).join(" || ")};
193
+ const prefix = (text, units) => {
194
+ if (units >= text.length) return text;
195
+ let end = units;
196
+ while (end > 0 && !tokenEnd(charCodeAt(text, end))) end--;
197
+ return slice(text, 0, end);
198
+ };
166
199
  let name = "Error", message = "Program threw a value.", stack = "";
167
200
  try {
168
- if (typeof err === "string") message = slice(err, 0, 1000);
201
+ if (typeof err === "string") message = prefix(err, 1000);
169
202
  else if (err) {
170
- if (typeof err.name === "string") name = slice(err.name, 0, 64);
171
- if (typeof err.message === "string") message = slice(err.message, 0, 1000);
203
+ if (typeof err.name === "string") name = prefix(err.name, 64);
204
+ if (typeof err.message === "string") message = prefix(err.message, 1000);
172
205
  if (typeof err.stack === "string") stack = slice(err.stack, 0, 1000);
173
206
  }
174
207
  } catch {}
175
208
  const location = exec(/connecta-guest\\.js:(\\d+):\\d+/, stack);
176
209
  return { result: undefined, error: message, logs: __logs, failure: {
177
210
  name, ...(failureId ? { failureId } : {}), ...(location ? { line: toNumber(location[1]) - 1 } : {}),
178
- ...(err === timeoutError ? { timeout: { elapsedMs: ${options.timeout ?? 60_000}, deadlineMs: ${options.timeout ?? 60_000} } } : {})
211
+ ...(err === timeoutError ? { timeout: { elapsedMs: ${timeoutMs}, deadlineMs: ${timeoutMs} } } : {})
179
212
  } };`);
180
213
  const preludes = [];
181
214
  let firstInitializer;
@@ -309,7 +342,10 @@ const {
309
342
  getEntrypoint: () => ({
310
343
  evaluate: (...evaluateArgs) => {
311
344
  acquisition.args = evaluateArgs;
312
- return Promise.resolve({ result: undefined });
345
+ return new Promise((resolve) => {
346
+ settleUpstream = () => resolve({ result: undefined });
347
+ evaluating?.();
348
+ });
313
349
  },
314
350
  }),
315
351
  };
@@ -342,15 +378,21 @@ const {
342
378
  const started = Date.now();
343
379
  const operation = Effect.gen(function* () {
344
380
  const setup = yield* Effect.tryPromise({
345
- try: () => new DynamicWorkerExecutor({
346
- loader,
347
- ...(options.timeout !== undefined ? { timeout: options.timeout } : {}),
348
- }).execute(code, detached),
381
+ try: () => {
382
+ const upstream = new DynamicWorkerExecutor({
383
+ loader,
384
+ timeout: timeoutMs,
385
+ }).execute(code, detached);
386
+ // The SDK's own result, which settles only after endUpstream(),
387
+ // carries nothing; the native evaluation below owns the outcome.
388
+ upstream.catch(() => undefined);
389
+ return Promise.race([upstream, evaluated.then(() => undefined)]);
390
+ },
349
391
  catch: (error) => error,
350
392
  });
351
393
  // Provider validation can return before the SDK reaches its loader.
352
- if (!prepared)
353
- return setup;
394
+ if (setup !== undefined || !prepared)
395
+ return setup ?? { result: undefined };
354
396
  const load = prepared.load;
355
397
  const args = prepared.args;
356
398
  const open = () => Effect.suspend(() => released || resources.state._tag === "Closed"
@@ -369,7 +411,7 @@ const {
369
411
  try: () => Reflect.apply(Reflect.get(entrypoint, "evaluate"), entrypoint, args ?? []),
370
412
  catch: (error) => error,
371
413
  });
372
- }).pipe(Scope.provide(resources));
414
+ }).pipe(Scope.provide(resources), Effect.ensuring(Effect.sync(endUpstream)));
373
415
  const result = yield* evaluation;
374
416
  const outcome = result;
375
417
  if (outcome.failure) {
@@ -407,6 +449,7 @@ const {
407
449
  }));
408
450
  }).pipe(Effect.raceFirst(Deferred.await(stopped)), Effect.catchDefect((error) => Effect.fail(error)), Effect.ensuring(Effect.sync(() => {
409
451
  runEnded = true;
452
+ endUpstream();
410
453
  hostProviders = undefined;
411
454
  prepared = undefined;
412
455
  hostFailures.clear();
@@ -420,6 +463,7 @@ const {
420
463
  if (released)
421
464
  return;
422
465
  released = true;
466
+ endUpstream();
423
467
  hostProviders = undefined;
424
468
  prepared = undefined;
425
469
  hostFailures.clear();
@@ -438,6 +482,7 @@ const {
438
482
  const acquireShell = (request) => runEdge(acquire(request));
439
483
  const executor = {
440
484
  name: "DynamicWorkerExecutor",
485
+ deadlineMs: timeoutMs,
441
486
  acquire: acquireShell,
442
487
  execute: (code, providers) => runEdge(execute(code, providers)),
443
488
  admissionSnapshot: () => admission.snapshot(),