@zackbart/connecta 0.28.1 → 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.
Files changed (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1584 -1580
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +93 -71
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. package/templates/node/src/file-activity.ts +0 -182
@@ -15,8 +15,10 @@ import { fork } from "node:child_process";
15
15
  import { existsSync } from "node:fs";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { Deferred, Duration, Effect, Exit, Schedule, Scope } from "effect";
18
- import { AdmissionController, ExecutorAdmissionError, ExecutorExecutionError, } from "../executor-admission.js";
18
+ import { AdmissionController, ExecutorAdmissionError, ExecutorExecutionError } from "../executor-admission.js";
19
19
  import { msg } from "../errors.js";
20
+ import { InvocationFailure } from "../invocation.js";
21
+ import { brandExecutor } from "../executor-contract.js";
20
22
  import { MAX_EXECUTE_LOG_CHARS } from "../executor-result.js";
21
23
  import { detach, fromSignal, runEdge } from "../runtime/run.js";
22
24
  import { hostCallLabel, MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
@@ -36,10 +38,7 @@ const MAX_CHILD_STDERR_BYTES = 8 * 1024;
36
38
  const MAX_ERROR_CHARS = 4_000;
37
39
  // Respawn backoff after consecutive crashes: 100 ms, doubling, capped at 5 s.
38
40
  // A value, not a running effect: stepping it happens inside a request's fiber.
39
- const CRASH_BACKOFF = Schedule.min([
40
- Schedule.exponential(Duration.millis(100)),
41
- Schedule.spaced(Duration.seconds(5)),
42
- ]);
41
+ const CRASH_BACKOFF = Schedule.min([Schedule.exponential(Duration.millis(100)), Schedule.spaced(Duration.seconds(5))]);
43
42
  const crashBackoff = Effect.map(Schedule.toStep(CRASH_BACKOFF), (step) => (crashedAt) => step(crashedAt, undefined).pipe(Effect.map(([, delay]) => crashedAt + Duration.toMillis(delay)),
44
43
  // Neither schedule ever halts; a halt here would be a broken invariant.
45
44
  Effect.orDie));
@@ -56,36 +55,27 @@ function retainStderrTail(current, chunk) {
56
55
  function childExitError(message, stderrTail) {
57
56
  if (stderrTail.length === 0)
58
57
  return new Error(message);
59
- return new Error(`${message}\nRecent child stderr (last ${stderrTail.length} bytes):\n` +
60
- stderrTail.toString("utf8"));
58
+ return new Error(`${message}\nRecent child stderr (last ${stderrTail.length} bytes):\n` + stderrTail.toString("utf8"));
61
59
  }
62
60
  function positiveWhole(value, fallback, name) {
63
61
  const resolved = value ?? fallback;
64
- if (!Number.isFinite(resolved) ||
65
- !Number.isInteger(resolved) ||
66
- resolved < 1) {
62
+ if (!Number.isFinite(resolved) || !Number.isInteger(resolved) || resolved < 1) {
67
63
  throw new TypeError(`${name} must be a positive whole number.`);
68
64
  }
69
65
  return resolved;
70
66
  }
71
67
  function nonNegativeWhole(value, fallback, name) {
72
68
  const resolved = value ?? fallback;
73
- if (!Number.isFinite(resolved) ||
74
- !Number.isInteger(resolved) ||
75
- resolved < 0) {
69
+ if (!Number.isFinite(resolved) || !Number.isInteger(resolved) || resolved < 0) {
76
70
  throw new TypeError(`${name} must be a non-negative whole number.`);
77
71
  }
78
72
  return resolved;
79
73
  }
80
74
  function errorPayload(error) {
81
- // X11: a partial authenticated frame exposes its secret instead of decoding.
82
- // Refuse it whole if a host ever bypasses execute.ts's framing bound.
83
- const message = error.length > MAX_ERROR_CHARS && error.includes("\u001econnecta-error:")
84
- ? `Host failure exceeded the ${MAX_ERROR_CHARS}-character bridge limit.`
85
- : error.slice(0, MAX_ERROR_CHARS);
86
75
  return JSON.stringify({
87
76
  ok: false,
88
- error: message,
77
+ error: msg(error).slice(0, MAX_ERROR_CHARS),
78
+ ...(error instanceof InvocationFailure ? { call: error.details } : {}),
89
79
  });
90
80
  }
91
81
  function cancelled(message) {
@@ -104,10 +94,7 @@ function unlessCancelled(effect, signal, message) {
104
94
  return effect;
105
95
  if (signal.aborted)
106
96
  return Effect.fail(cancelled(message));
107
- return Effect.raceAllFirst([
108
- effect,
109
- fromSignal(signal).pipe(Effect.mapError(() => cancelled(message))),
110
- ]);
97
+ return Effect.raceAllFirst([effect, fromSignal(signal).pipe(Effect.mapError(() => cancelled(message)))]);
111
98
  }
112
99
  /**
113
100
  * The child's release: SIGTERM, then wait for the exit, bounded — a child
@@ -282,9 +269,19 @@ class QuickJsChildPool {
282
269
  this.rejectActive(slot, error);
283
270
  this.recycle(slot);
284
271
  }).pipe(Effect.andThen(Effect.never));
272
+ const started = Date.now();
285
273
  const contenders = [
286
274
  Deferred.await(active.outcome),
287
- Effect.sleep(Duration.millis(this.runtimeOptions.timeoutMs + CHILD_EXIT_GRACE_MS)).pipe(Effect.andThen(endWith(new Error(`QuickJS child exceeded the ${this.runtimeOptions.timeoutMs}ms wall budget and was terminated.`)))),
275
+ Effect.sleep(Duration.millis(this.runtimeOptions.timeoutMs + CHILD_EXIT_GRACE_MS)).pipe(Effect.andThen(Effect.suspend(() => {
276
+ const elapsedMs = Date.now() - started;
277
+ const deadlineMs = this.runtimeOptions.timeoutMs;
278
+ return endWith(new InvocationFailure({
279
+ code: "timeout",
280
+ message: `Operation "execute_code" timed out during sandbox termination after ${elapsedMs}ms (effective deadline ${deadlineMs}ms).`,
281
+ retryable: false,
282
+ details: { operation: "execute_code", stage: "sandbox termination", elapsedMs, deadlineMs },
283
+ }));
284
+ }))),
288
285
  ];
289
286
  if (signal) {
290
287
  contenders.push(fromSignal(signal).pipe(Effect.catch(() => endWith(cancelled("Execution was cancelled.")))));
@@ -448,7 +445,9 @@ class QuickJsChildPool {
448
445
  active.logs.push(retained);
449
446
  active.logChars += separator + retained.length;
450
447
  }
451
- catch { /* Malformed log messages carry no output. */ }
448
+ catch {
449
+ /* Malformed log messages carry no output. */
450
+ }
452
451
  return;
453
452
  }
454
453
  if (message.type === "host-call") {
@@ -487,8 +486,7 @@ class QuickJsChildPool {
487
486
  async handleHostCall(slot, child, active, message) {
488
487
  let payloadJson;
489
488
  try {
490
- if (serializedBytes(message.payloadJson) >
491
- MAX_QUICKJS_HOST_RPC_BYTES) {
489
+ if (serializedBytes(message.payloadJson) > MAX_QUICKJS_HOST_RPC_BYTES) {
492
490
  // Refused before parsing, so there is no address to name here: parsing
493
491
  // an over-limit payload to improve its error message would spend the
494
492
  // work the limit exists to refuse.
@@ -496,9 +494,7 @@ class QuickJsChildPool {
496
494
  }
497
495
  const payload = JSON.parse(message.payloadJson);
498
496
  const provider = active.providers.get(payload.namespace);
499
- const fn = provider && Object.hasOwn(provider.fns, payload.functionName)
500
- ? provider.fns[payload.functionName]
501
- : undefined;
497
+ const fn = provider && Object.hasOwn(provider.fns, payload.functionName) ? provider.fns[payload.functionName] : undefined;
502
498
  if (!fn) {
503
499
  throw new Error(`Unknown function ${payload.namespace}.${payload.functionName}`);
504
500
  }
@@ -517,7 +513,7 @@ class QuickJsChildPool {
517
513
  }
518
514
  }
519
515
  catch (err) {
520
- payloadJson = errorPayload(msg(err));
516
+ payloadJson = errorPayload(err);
521
517
  }
522
518
  if (!child.connected || slot.active !== active)
523
519
  return;
@@ -554,9 +550,7 @@ class QuickJsChildPool {
554
550
  return;
555
551
  // Never concatenate the stream with the final reply: those entries are
556
552
  // the same logs. A normal reply retains its existing full log contract.
557
- const resolved = outcome.logs === undefined && active.logs.length > 0
558
- ? { ...outcome, logs: active.logs }
559
- : outcome;
553
+ const resolved = outcome.logs === undefined && active.logs.length > 0 ? { ...outcome, logs: active.logs } : outcome;
560
554
  this.clearActive(slot);
561
555
  Deferred.doneUnsafe(active.outcome, Exit.succeed(resolved));
562
556
  }
@@ -621,5 +615,5 @@ class QuickJsChildPool {
621
615
  * double-acquire deadlock at concurrency 1.
622
616
  */
623
617
  export function quickJsExecutor(options = {}) {
624
- return new QuickJsChildPool(options);
618
+ return brandExecutor(new QuickJsChildPool(options), "leased");
625
619
  }
package/dist/favicon.js CHANGED
@@ -32,8 +32,7 @@ function buildIco() {
32
32
  const dx = x - 15.5;
33
33
  const dy = y - 15.5;
34
34
  const radius = Math.sqrt(dx * dx + dy * dy);
35
- const opaque = radius >= 8 && radius <= 13 &&
36
- (x <= 18 || Math.abs(dy) >= 7);
35
+ const opaque = radius >= 8 && radius <= 13 && (x <= 18 || Math.abs(dy) >= 7);
37
36
  const row = size - 1 - y;
38
37
  const pixel = pixels + (row * size + x) * 4;
39
38
  ico[pixel + 3] = opaque ? 255 : 0;
@@ -18,9 +18,7 @@ const CONNECTOR_ID_RE = /^[a-z0-9_-]+$/;
18
18
  * than escaped, since a value that is not a connector id is not config.
19
19
  */
20
20
  export function renderFixPrompt(spec, connectorId) {
21
- const id = connectorId !== undefined && CONNECTOR_ID_RE.test(connectorId)
22
- ? connectorId
23
- : undefined;
21
+ const id = connectorId !== undefined && CONNECTOR_ID_RE.test(connectorId) ? connectorId : undefined;
24
22
  return [
25
23
  "Diagnose and fix a problem in this connecta deployment (the @zackbart/connecta package). " +
26
24
  "It is configured as code: connectors, credential slots, OAuth clients, pools, and inbound auth " +
@@ -0,0 +1,188 @@
1
+ /** Capture the single-use runner before evaluating the guest expression. */
2
+ export function wrapGuestProgram(code) {
3
+ return `async () => globalThis.__connecta_run()(async (connecta) => await (\n${code}\n)())`;
4
+ }
5
+ /** Separate user source from the trusted wrapper before publishing Worker modules. */
6
+ export function isolateGuestProgram(code) {
7
+ const prefix = "async () => globalThis.__connecta_run()(";
8
+ const callback = "async (connecta) => await (\n";
9
+ const suffix = "\n)())";
10
+ if (!code.startsWith(prefix + callback) || !code.endsWith(suffix))
11
+ return undefined;
12
+ return {
13
+ program: code.slice(prefix.length, -1),
14
+ // Only this fixed wrapper enters the private runner module. Matching text
15
+ // inside an arbitrary body/string is guest data, never a trusted wrapper.
16
+ wrapper: `${prefix}__connecta_user_program)`,
17
+ };
18
+ }
19
+ /**
20
+ * Emissions are acknowledged before success, even when the guest omits await.
21
+ * Capture intrinsics before user code can replace them.
22
+ */
23
+ export function guestPrelude() {
24
+ return `${guestInitializer()}(connecta);`;
25
+ }
26
+ /** Protect completion promises before any Worker or QuickJS guest runs. */
27
+ export function guestPromiseInitializer() {
28
+ return `(() => {
29
+ const NativePromise = Promise;
30
+ const nativeResolve = Function.prototype.call.bind(NativePromise.resolve);
31
+ const nativeRace = Function.prototype.call.bind(NativePromise.race);
32
+ const isArray = Array.isArray;
33
+ const iteratorKey = Symbol.iterator;
34
+ const arrayValues = Function.prototype.call.bind(Array.prototype.values);
35
+ const arrayNext = Function.prototype.call.bind(Object.getPrototypeOf([][Symbol.iterator]()).next);
36
+ function safeRace(values) {
37
+ // Upstream constructs its completion array after the program starts.
38
+ // Use captured array iteration, with private own methods, so neither a
39
+ // replaced iterator factory nor iterator.next can observe its promises.
40
+ if (isArray(values)) {
41
+ const iterator = arrayValues(values);
42
+ values = { __proto__: null, [iteratorKey]: () => ({ __proto__: null,
43
+ next: () => arrayNext(iterator) }) };
44
+ }
45
+ return nativeRace(HostPromise, values);
46
+ }
47
+ // The upstream Worker starts its Promise.race after invoking the program.
48
+ // Its deadline must not use a resolve function replaced during that invocation.
49
+ class HostPromise extends NativePromise {}
50
+ Object.defineProperties(HostPromise, {
51
+ resolve: { value: value => nativeResolve(HostPromise, value) },
52
+ [Symbol.species]: { value: HostPromise }
53
+ });
54
+ Object.freeze(HostPromise.prototype);
55
+ Object.freeze(HostPromise);
56
+ Object.defineProperties(NativePromise, {
57
+ race: { value: safeRace, writable: false, configurable: false },
58
+ [Symbol.species]: { value: NativePromise, configurable: false }
59
+ });
60
+ // Async return values are adopted through their prototype's then method.
61
+ // Guest callbacks must not change host completion.
62
+ Object.freeze(NativePromise.prototype);
63
+ })()`;
64
+ }
65
+ /** Capture a private initializer before a Worker evaluates its guest module. */
66
+ export function guestInitializer() {
67
+ return `(() => {
68
+ ${guestPromiseInitializer()};
69
+ const NativePromise = Promise;
70
+ const freeze = Object.freeze;
71
+ const defineProperties = Object.defineProperties;
72
+ // Protect the bridge codec and its object prototypes from guest hooks.
73
+ freeze(JSON);
74
+ freeze(Object.prototype);
75
+ defineProperties(globalThis, {
76
+ JSON: { value: JSON, writable: false, configurable: false },
77
+ Promise: { value: NativePromise, writable: false, configurable: false }
78
+ });
79
+ const promiseThen = Function.prototype.call.bind(NativePromise.prototype.then);
80
+ const promiseCatch = Function.prototype.call.bind(NativePromise.prototype.catch);
81
+ const promiseFinally = Function.prototype.call.bind(NativePromise.prototype.finally);
82
+ const push = Function.prototype.call.bind(Array.prototype.push);
83
+ const NativeError = Error;
84
+ return freeze((provider) => {
85
+ const pending = [];
86
+ const emit = provider.emit;
87
+ function trackEmission(task) {
88
+ const entry = { handled: false, settled: promiseThen(task,
89
+ () => ({ ok: true }), error => ({ ok: false, error })) };
90
+ push(pending, entry);
91
+ // Every derived promise owns propagated rejection; only an unhandled
92
+ // leaf fails the run, so a later catch also handles its ancestors.
93
+ return freeze({
94
+ then(resolve, reject) {
95
+ entry.handled = true;
96
+ return trackEmission(promiseThen(task, resolve, reject));
97
+ },
98
+ catch(reject) {
99
+ entry.handled = true;
100
+ return trackEmission(promiseCatch(task, reject));
101
+ },
102
+ finally(callback) {
103
+ entry.handled = true;
104
+ return trackEmission(promiseFinally(task, callback));
105
+ }
106
+ });
107
+ }
108
+ const namespace = freeze({
109
+ __proto__: null,
110
+ search: provider.search,
111
+ describe: provider.describe,
112
+ call: provider.call,
113
+ read: provider.read,
114
+ result: provider.result,
115
+ skill: provider.skill,
116
+ emit(block) { return trackEmission(emit(block)); }
117
+ });
118
+ globalThis.connecta = namespace;
119
+ defineProperties(globalThis, {
120
+ Error: { value: NativeError, writable: false, configurable: false },
121
+ __connecta_run: { configurable: true, value: () => {
122
+ delete globalThis.__connecta_run;
123
+ let used = false;
124
+ return async (program) => {
125
+ if (used) throw new NativeError("Guest runner initialization is single-use.");
126
+ used = true;
127
+ const result = await program(namespace);
128
+ for (let i = 0; i < pending.length; i++) {
129
+ const report = await pending[i].settled;
130
+ if (!report.ok && !pending[i].handled) throw report.error;
131
+ }
132
+ return result;
133
+ };
134
+ }}
135
+ });
136
+ });
137
+ })()`;
138
+ }
139
+ const ERROR_NAMES = new Set([
140
+ "Error",
141
+ "TypeError",
142
+ "SyntaxError",
143
+ "ReferenceError",
144
+ "RangeError",
145
+ "EvalError",
146
+ "URIError",
147
+ "AggregateError",
148
+ ]);
149
+ /** Fixed repair guidance, used only in agent-facing results. */
150
+ export function programError(raw, code) {
151
+ const message = typeof raw.message === "string" ? raw.message : "Program failed.";
152
+ const name = typeof raw.name === "string" && ERROR_NAMES.has(raw.name) ? raw.name : "Error";
153
+ const stack = typeof raw.stack === "string" ? raw.stack : "";
154
+ // Compare QuickJS/V8 locations to the wrapper baseline after the source.
155
+ const location = /:(\d+)(?::\d+)?\)?(?:\n|$)/.exec(stack);
156
+ const baseline = typeof raw.baseline === "string" ? /:(\d+)(?::\d+)?\)?(?:\n|$)/.exec(raw.baseline) : null;
157
+ // Workers publish only the user callback in this module, with one prefix line.
158
+ // Its trusted wrapper and baseline live in a separate, private module scope.
159
+ const workerLocation = /connecta-guest\.js:(\d+):\d+/.exec(stack);
160
+ const line = typeof raw.line === "number" && Number.isFinite(raw.line)
161
+ ? Math.max(1, raw.line)
162
+ : workerLocation
163
+ ? Math.max(1, Number(workerLocation[1]) - 1)
164
+ : location && baseline
165
+ ? Math.max(1, Number(location[1]) - Number(baseline[1]) + code.split("\n").length + 1)
166
+ : null;
167
+ let hint = "Use plain JavaScript in one async () => { ... } expression and the connecta global.";
168
+ if (/\brequire\b|\b[Ii]mports?\b|\bfs\b|filesystem|node:/.test(message) ||
169
+ (name === "SyntaxError" && /\bimport\b/.test(code))) {
170
+ hint =
171
+ "Imports, require, and filesystem access are outside the guest API. Use connecta.call to access configured services.";
172
+ }
173
+ else if (["TypeError", "ReferenceError"].includes(name) &&
174
+ /(?:const|let|var|function|\()\s*connecta\b/.test(code)) {
175
+ hint = "Do not shadow connecta. Use the host-provided connecta global.";
176
+ }
177
+ else if (/callTool|mixpanel|\bskills\b|connecta\.(?:guide|skills)/.test(message) ||
178
+ (["TypeError", "ReferenceError"].includes(name) && /connecta\.(?:guide|skills)\b/.test(code))) {
179
+ hint =
180
+ "Use connecta.call(address, args), connecta.search, connecta.describe, connecta.result, and connecta.skill(name). Services are addressed by connector.tool, never guest globals.";
181
+ }
182
+ return {
183
+ code: "program_error",
184
+ message: `Program ${name}: ${message}`,
185
+ retryable: false,
186
+ details: { name, line, hint },
187
+ };
188
+ }
@@ -0,0 +1,176 @@
1
+ import type { ToolDef } from "./types.js";
2
+ import type { CallErrorDetails } from "./errors.js";
3
+ /** TypeScript notation for the JavaScript guest global; programs receive no arguments. */
4
+ export interface GuestApi {
5
+ search(args?: CatalogSearchArgs): Promise<CatalogSearchResult>;
6
+ describe(args?: CatalogDescribeArgs): Promise<{
7
+ tools: CatalogDescription[];
8
+ }>;
9
+ call(address: string, args?: unknown, options?: {
10
+ timeoutMs?: number;
11
+ }): Promise<GuestResult>;
12
+ call(request: {
13
+ address: string;
14
+ args?: unknown;
15
+ timeoutMs?: number;
16
+ }): Promise<GuestResult>;
17
+ read(uri: string): Promise<GuestResourceResult>;
18
+ result(id: string, options?: {
19
+ offset?: number;
20
+ maxBytes?: number;
21
+ }): Promise<GuestResultPage>;
22
+ skill(name: string): Promise<{
23
+ name: string;
24
+ text: string;
25
+ format: "text";
26
+ }>;
27
+ emit(block: GuestBlock): PromiseLike<void>;
28
+ }
29
+ export interface GuestResourceResult {
30
+ contents: Array<{
31
+ uri: string;
32
+ mimeType?: string;
33
+ } & ({
34
+ text: string;
35
+ } | {
36
+ blob: string;
37
+ })>;
38
+ }
39
+ export type GuestResult = {
40
+ data: unknown;
41
+ format: "json";
42
+ } | {
43
+ data: string;
44
+ format: "text";
45
+ };
46
+ export interface GuestResultPage {
47
+ resultId: string;
48
+ offset: number;
49
+ bytes: number;
50
+ totalBytes: number;
51
+ hasMore: boolean;
52
+ nextOffset?: number;
53
+ format: "text";
54
+ text: string;
55
+ }
56
+ export type GuestBlock = {
57
+ type: "text";
58
+ text: string;
59
+ } | {
60
+ type: "image" | "audio";
61
+ data: string;
62
+ mimeType: string;
63
+ };
64
+ type SchemaFormat = "json" | "compact" | "typescript";
65
+ export interface CatalogSearchArgs {
66
+ query?: string;
67
+ connector?: string;
68
+ safety?: "readOnly" | "approvalRequired" | "all";
69
+ limit?: number;
70
+ offset?: number;
71
+ fullDescriptions?: boolean;
72
+ includeSchemas?: SchemaFormat;
73
+ includeSchemaKeys?: boolean;
74
+ }
75
+ export interface CatalogDescribeArgs {
76
+ address?: unknown;
77
+ addresses?: unknown;
78
+ format?: SchemaFormat;
79
+ fullDescriptions?: boolean;
80
+ }
81
+ type GuideRequiredReason = "connector_required" | "approval_required" | "schema_truncated";
82
+ interface CatalogFailureDetail {
83
+ code: string;
84
+ message: string;
85
+ retryable: boolean;
86
+ retryAfterMs?: number;
87
+ }
88
+ interface CatalogDescriptionFailureDetail extends CatalogFailureDetail {
89
+ configuredConnectors?: string[];
90
+ nextAction?: NonNullable<CallErrorDetails["nextAction"]>;
91
+ suggestions?: string[];
92
+ }
93
+ interface CatalogSearchFailure extends CatalogFailureDetail {
94
+ connector: string;
95
+ recovery?: CallErrorDetails["recovery"];
96
+ nextAction?: Extract<NonNullable<CallErrorDetails["nextAction"]>, {
97
+ tool: "authorize_connector";
98
+ }>;
99
+ retry?: string;
100
+ }
101
+ export interface CatalogSearchPage {
102
+ catalogErrors: CatalogSearchFailure[];
103
+ absence?: {
104
+ service: string;
105
+ message: string;
106
+ configuredConnectors: string[];
107
+ };
108
+ total: number;
109
+ offset: number;
110
+ limit: number;
111
+ hasMore: boolean;
112
+ nextOffset?: number;
113
+ matchMode?: "partial";
114
+ queryAnalysis?: {
115
+ representedTerms: string[];
116
+ otherResultTerms: string[];
117
+ unmatchedTerms: string[];
118
+ truncated?: true;
119
+ connectorScope?: string;
120
+ unknownConnector?: true;
121
+ configuredConnectors?: string[];
122
+ unavailableConnectorCount?: number;
123
+ catalogError?: CatalogFailureDetail;
124
+ guide?: string;
125
+ guideSummary?: string;
126
+ guideRequired?: true;
127
+ guideRequiredReasons?: GuideRequiredReason[];
128
+ guidance?: string;
129
+ };
130
+ }
131
+ export type CatalogSearchResult = CatalogSearchPage & {
132
+ tools: Array<CatalogSearchTool & {
133
+ connectorTitle?: string;
134
+ guide?: string;
135
+ guideSummary?: string;
136
+ }>;
137
+ };
138
+ export interface CatalogDescription {
139
+ address: string;
140
+ schemaFormat?: "json" | "text";
141
+ name?: string;
142
+ description?: string;
143
+ guide?: string;
144
+ guideSummary?: string;
145
+ guideRequired?: true;
146
+ guideRequiredReasons?: GuideRequiredReason[];
147
+ inputSchema?: unknown;
148
+ outputSchema?: unknown;
149
+ signature?: string;
150
+ outputSchemaSource?: "observed";
151
+ inputSchemaTruncated?: true;
152
+ outputSchemaTruncated?: true;
153
+ annotations?: ToolDef["annotations"];
154
+ error?: string;
155
+ errorDetails?: CatalogDescriptionFailureDetail;
156
+ }
157
+ type CatalogSearchTool = {
158
+ name: string;
159
+ address: string;
160
+ description?: string;
161
+ inputSchema?: unknown;
162
+ outputSchema?: unknown;
163
+ signature?: string;
164
+ schemaFormat?: "json" | "text";
165
+ outputSchemaSource?: "observed";
166
+ inputSchemaTruncated?: true;
167
+ outputSchemaTruncated?: true;
168
+ inputKeys?: string[];
169
+ requiredInputKeys?: string[];
170
+ outputKeys?: string[];
171
+ annotations?: ToolDef["annotations"];
172
+ classification?: ToolDef["classification"];
173
+ guideRequired?: true;
174
+ guideRequiredReasons?: GuideRequiredReason[];
175
+ };
176
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ /** Common headers for HTML, including script-free status and error pages. */
2
+ export function htmlSecurityHeaders(initial, options = {}) {
3
+ const headers = new Headers(initial);
4
+ const { clerkOrigin, nonce } = options;
5
+ // Clerk's CAPTCHA host is exact and only admitted with a validated loader
6
+ // origin. No wildcard or general HTTPS script permission is needed.
7
+ const scripts = `${nonce ? `'nonce-${nonce}' ` : ""}'self'` +
8
+ (clerkOrigin ? ` ${clerkOrigin} https://challenges.cloudflare.com` : "");
9
+ if (!headers.has("Content-Security-Policy")) {
10
+ headers.set("Content-Security-Policy", `script-src ${scripts}; object-src 'none'; base-uri 'none'; frame-ancestors 'none'` +
11
+ (clerkOrigin
12
+ ? `; connect-src 'self' ${clerkOrigin}; img-src 'self' https://img.clerk.com; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'`
13
+ : "") +
14
+ (clerkOrigin ? "; frame-src https://challenges.cloudflare.com" : ""));
15
+ }
16
+ if (headers.get("Content-Security-Policy").includes("frame-ancestors 'none'")) {
17
+ headers.set("X-Frame-Options", "DENY");
18
+ }
19
+ headers.set("X-Content-Type-Options", "nosniff");
20
+ headers.set("Referrer-Policy", "no-referrer");
21
+ if (!headers.has("Cache-Control"))
22
+ headers.set("Cache-Control", "no-store");
23
+ return headers;
24
+ }
package/dist/identity.js CHANGED
@@ -8,9 +8,7 @@ export function validIdentityReference(value) {
8
8
  IDENTITY_PART_RE.test(value.id));
9
9
  }
10
10
  function bytesToHex(bytes) {
11
- return [...bytes]
12
- .map((byte) => byte.toString(16).padStart(2, "0"))
13
- .join("");
11
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
14
12
  }
15
13
  /** Deterministic pseudonymous partition; raw emails and ids do not enter keys. */
16
14
  export async function identityStorageKey(identity) {
@@ -0,0 +1,52 @@
1
+ const parsed = new WeakMap();
2
+ /** One strict parse shared by recognition and verification. Schemes ignore case. */
3
+ export function authorizationCredential(request) {
4
+ const header = request.headers.get("authorization");
5
+ const cached = parsed.get(request);
6
+ if (cached?.header === header)
7
+ return cached.credential;
8
+ // Exactly one separator and one token68 value. Reject combined duplicate
9
+ // headers and malformed spacing rather than letting an SDK repair them.
10
+ const match = header === null ? null : /^Bearer ([A-Za-z0-9._~+/-]+=*)$/i.exec(header);
11
+ const credential = header === null
12
+ ? { kind: "absent" }
13
+ : match
14
+ ? { kind: "bearer", token: match[1], value: `Bearer ${match[1]}` }
15
+ : { kind: "invalid" };
16
+ parsed.set(request, { header, credential });
17
+ return credential;
18
+ }
19
+ /** Explicit headers exclude cookies and Clerk's browser handshake credentials. */
20
+ export function authorizationRequest(request) {
21
+ const credential = authorizationCredential(request);
22
+ if (credential.kind === "absent")
23
+ return request;
24
+ const headers = new Headers(request.headers);
25
+ headers.delete("cookie");
26
+ if (credential.kind === "bearer")
27
+ headers.set("authorization", credential.value);
28
+ const url = new URL(request.url);
29
+ for (const key of new Set(url.searchParams.keys())) {
30
+ if (key.startsWith("__clerk") || key === "__session" || key === "__dev_session")
31
+ url.searchParams.delete(key);
32
+ }
33
+ if (url.href === request.url &&
34
+ !request.headers.has("cookie") &&
35
+ headers.get("authorization") === request.headers.get("authorization"))
36
+ return request;
37
+ // Share the body without cloning/teeing or transferring the original
38
+ // request: the route must still be able to read it after authentication.
39
+ const normalized = new Request(url, {
40
+ method: request.method,
41
+ headers,
42
+ signal: request.signal,
43
+ ...(request.body ? { body: request.body, duplex: "half" } : {}),
44
+ });
45
+ parsed.set(normalized, { header: headers.get("authorization"), credential });
46
+ return normalized;
47
+ }
48
+ /** Reserved machine syntax, even when the optional verifier is not installed. */
49
+ export function isMachineCredential(request) {
50
+ const credential = authorizationCredential(request);
51
+ return credential.kind === "bearer" && credential.token.startsWith("cta_");
52
+ }