@zackbart/connecta 0.15.1 → 0.16.1

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 (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
@@ -1,328 +0,0 @@
1
- import type {
2
- AdmittingExecutor,
3
- AdmissionSnapshot,
4
- Executor,
5
- ExecutorLease,
6
- } from "./types.js";
7
-
8
- export type ExecutorAdmissionErrorCode =
9
- | "executor_overloaded"
10
- | "executor_cancelled"
11
- | "executor_closed";
12
-
13
- /**
14
- * A stable, machine-readable admission failure. Overload is retryable; caller
15
- * cancellation and shutdown are terminal for this invocation.
16
- */
17
- export class ExecutorAdmissionError extends Error {
18
- readonly retryable: boolean;
19
- readonly retryAfterMs?: number;
20
-
21
- constructor(
22
- readonly code: ExecutorAdmissionErrorCode,
23
- message: string,
24
- opts: { retryAfterMs?: number } = {},
25
- ) {
26
- super(message);
27
- this.name = "ExecutorAdmissionError";
28
- this.retryable = code === "executor_overloaded";
29
- if (
30
- opts.retryAfterMs !== undefined &&
31
- Number.isFinite(opts.retryAfterMs) &&
32
- opts.retryAfterMs >= 0
33
- ) {
34
- this.retryAfterMs = Math.trunc(opts.retryAfterMs);
35
- }
36
- }
37
- }
38
-
39
- /**
40
- * A lifecycle failure after the sandbox started running. It keeps the stable
41
- * admission-error envelope while letting response assembly distinguish work
42
- * torn down in flight from work that never entered the executor.
43
- */
44
- export class ExecutorExecutionError extends ExecutorAdmissionError {
45
- constructor(code: ExecutorAdmissionErrorCode, message: string) {
46
- super(code, message);
47
- this.name = "ExecutorExecutionError";
48
- }
49
- }
50
-
51
- export interface AdmissionLease {
52
- /** Time spent waiting behind active work. Zero for immediate admission. */
53
- readonly waitMs: number;
54
- release(): void;
55
- }
56
-
57
- interface Waiter {
58
- queuedAt: number;
59
- resolve: (lease: AdmissionLease) => void;
60
- reject: (error: ExecutorAdmissionError) => void;
61
- signal?: AbortSignal;
62
- onAbort?: () => void;
63
- timer?: ReturnType<typeof setTimeout>;
64
- }
65
-
66
- export interface AdmissionControllerOptions {
67
- concurrency: number;
68
- maxQueueSize: number;
69
- queueTimeoutMs: number;
70
- /** Suggested delay exposed with retryable overload failures. */
71
- retryAfterMs?: number;
72
- }
73
-
74
- function positiveWhole(value: number, name: string): number {
75
- if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
76
- throw new TypeError(`${name} must be a positive whole number.`);
77
- }
78
- return value;
79
- }
80
-
81
- function nonNegativeWhole(value: number, name: string): number {
82
- if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
83
- throw new TypeError(`${name} must be a non-negative whole number.`);
84
- }
85
- return value;
86
- }
87
-
88
- /**
89
- * Runtime-portable bounded admission. It deliberately owns no executor or
90
- * request state: queued requests retain only a resolver and AbortSignal, while
91
- * provider catalogs are constructed after the returned lease is granted.
92
- */
93
- export class AdmissionController {
94
- readonly concurrency: number;
95
- readonly maxQueueSize: number;
96
- readonly queueTimeoutMs: number;
97
- readonly retryAfterMs: number;
98
-
99
- private active = 0;
100
- private closed = false;
101
- private readonly waiters: Waiter[] = [];
102
- private admittedTotal = 0;
103
- private queuedTotal = 0;
104
- private rejectedTotal = 0;
105
- private cancelledTotal = 0;
106
- private closedTotal = 0;
107
- private queueWaitCount = 0;
108
- private queueWaitTotalMs = 0;
109
- private queueWaitMaxMs = 0;
110
-
111
- constructor(options: AdmissionControllerOptions) {
112
- this.concurrency = positiveWhole(options.concurrency, "concurrency");
113
- this.maxQueueSize = nonNegativeWhole(
114
- options.maxQueueSize,
115
- "maxQueueSize",
116
- );
117
- this.queueTimeoutMs = positiveWhole(
118
- options.queueTimeoutMs,
119
- "queueTimeoutMs",
120
- );
121
- this.retryAfterMs = nonNegativeWhole(
122
- options.retryAfterMs ?? this.queueTimeoutMs,
123
- "retryAfterMs",
124
- );
125
- }
126
-
127
- get activeCount(): number {
128
- return this.active;
129
- }
130
-
131
- get queuedCount(): number {
132
- return this.waiters.length;
133
- }
134
-
135
- snapshot(): AdmissionSnapshot {
136
- return {
137
- concurrency: this.concurrency,
138
- maxQueueSize: this.maxQueueSize,
139
- queueTimeoutMs: this.queueTimeoutMs,
140
- retryAfterMs: this.retryAfterMs,
141
- active: this.active,
142
- queued: this.waiters.length,
143
- closed: this.closed,
144
- totals: {
145
- admitted: this.admittedTotal,
146
- queued: this.queuedTotal,
147
- rejected: this.rejectedTotal,
148
- cancelled: this.cancelledTotal,
149
- closed: this.closedTotal,
150
- },
151
- queueWaitMs: {
152
- count: this.queueWaitCount,
153
- total: this.queueWaitTotalMs,
154
- max: this.queueWaitMaxMs,
155
- },
156
- };
157
- }
158
-
159
- acquire(options: { signal?: AbortSignal } = {}): Promise<AdmissionLease> {
160
- if (this.closed) {
161
- this.closedTotal++;
162
- return Promise.reject(
163
- new ExecutorAdmissionError(
164
- "executor_closed",
165
- "Executor is shutting down.",
166
- ),
167
- );
168
- }
169
- if (options.signal?.aborted) {
170
- this.cancelledTotal++;
171
- return Promise.reject(
172
- new ExecutorAdmissionError(
173
- "executor_cancelled",
174
- "Execution was cancelled before admission.",
175
- ),
176
- );
177
- }
178
- if (this.active < this.concurrency) {
179
- this.active++;
180
- this.admittedTotal++;
181
- return Promise.resolve(this.makeLease(0));
182
- }
183
- if (this.waiters.length >= this.maxQueueSize) {
184
- this.rejectedTotal++;
185
- return Promise.reject(this.overloaded("Executor queue is full."));
186
- }
187
-
188
- return new Promise<AdmissionLease>((resolve, reject) => {
189
- const waiter: Waiter = {
190
- queuedAt: Date.now(),
191
- resolve,
192
- reject,
193
- ...(options.signal ? { signal: options.signal } : {}),
194
- };
195
- waiter.onAbort = () => {
196
- if (!this.remove(waiter)) return;
197
- this.cleanup(waiter);
198
- this.cancelledTotal++;
199
- reject(
200
- new ExecutorAdmissionError(
201
- "executor_cancelled",
202
- "Execution was cancelled while queued.",
203
- ),
204
- );
205
- };
206
- options.signal?.addEventListener("abort", waiter.onAbort, { once: true });
207
- waiter.timer = setTimeout(() => {
208
- if (!this.remove(waiter)) return;
209
- this.cleanup(waiter);
210
- this.rejectedTotal++;
211
- reject(
212
- this.overloaded(
213
- `Executor admission timed out after ${this.queueTimeoutMs}ms.`,
214
- ),
215
- );
216
- }, this.queueTimeoutMs);
217
- this.waiters.push(waiter);
218
- this.queuedTotal++;
219
- });
220
- }
221
-
222
- close(): void {
223
- if (this.closed) return;
224
- this.closed = true;
225
- for (const waiter of this.waiters.splice(0)) {
226
- this.cleanup(waiter);
227
- this.closedTotal++;
228
- waiter.reject(
229
- new ExecutorAdmissionError(
230
- "executor_closed",
231
- "Executor is shutting down.",
232
- ),
233
- );
234
- }
235
- }
236
-
237
- private overloaded(message: string): ExecutorAdmissionError {
238
- return new ExecutorAdmissionError("executor_overloaded", message, {
239
- retryAfterMs: this.retryAfterMs,
240
- });
241
- }
242
-
243
- private makeLease(waitMs: number): AdmissionLease {
244
- let released = false;
245
- return {
246
- waitMs,
247
- release: () => {
248
- if (released) return;
249
- released = true;
250
- this.release();
251
- },
252
- };
253
- }
254
-
255
- private release(): void {
256
- if (this.active > 0) this.active--;
257
- if (this.closed) return;
258
- const waiter = this.waiters.shift();
259
- if (!waiter) return;
260
- this.cleanup(waiter);
261
- const waitMs = Math.max(0, Date.now() - waiter.queuedAt);
262
- this.admittedTotal++;
263
- this.queueWaitCount++;
264
- this.queueWaitTotalMs += waitMs;
265
- this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
266
- this.active++;
267
- waiter.resolve(this.makeLease(waitMs));
268
- }
269
-
270
- private remove(waiter: Waiter): boolean {
271
- const index = this.waiters.indexOf(waiter);
272
- if (index < 0) return false;
273
- this.waiters.splice(index, 1);
274
- return true;
275
- }
276
-
277
- private cleanup(waiter: Waiter): void {
278
- if (waiter.timer !== undefined) clearTimeout(waiter.timer);
279
- if (waiter.onAbort) {
280
- waiter.signal?.removeEventListener("abort", waiter.onAbort);
281
- }
282
- }
283
- }
284
-
285
- /** Preserve the one-method Workers seam while recognizing richer Node executors. */
286
- export function isAdmittingExecutor(
287
- executor: Executor,
288
- ): executor is AdmittingExecutor {
289
- return (
290
- "acquire" in executor &&
291
- typeof (executor as { acquire?: unknown }).acquire === "function"
292
- );
293
- }
294
-
295
- /**
296
- * Give a structurally-compatible but otherwise unbounded executor the same
297
- * admission contract as the built-in Node executor. The wrapper owns only the
298
- * queue; closing the underlying runtime remains the Connecta lifecycle's job.
299
- */
300
- export function withExecutorAdmission(
301
- executor: Executor,
302
- admission: AdmissionController,
303
- ): AdmittingExecutor {
304
- return {
305
- async acquire(options = {}): Promise<ExecutorLease> {
306
- const token = await admission.acquire(options);
307
- let released = false;
308
- return {
309
- waitMs: token.waitMs,
310
- execute: (code, providers) => executor.execute(code, providers),
311
- release: () => {
312
- if (released) return;
313
- released = true;
314
- token.release();
315
- },
316
- };
317
- },
318
- async execute(code, providers) {
319
- const lease = await this.acquire();
320
- try {
321
- return await lease.execute(code, providers);
322
- } finally {
323
- lease.release();
324
- }
325
- },
326
- admissionSnapshot: () => admission.snapshot(),
327
- };
328
- }
@@ -1,107 +0,0 @@
1
- import type { ExecuteResult } from "./types.js";
2
-
3
- /** ~6k tokens. Sandbox code should filter data down before returning. */
4
- const MAX_EXECUTE_RESULT_CHARS = 24_000;
5
- export const MAX_EXECUTE_LOG_CHARS = 4_000;
6
-
7
- function msg(err: unknown): string {
8
- return err instanceof Error ? err.message : String(err);
9
- }
10
-
11
- function serializeExecuteValue(value: unknown): string {
12
- const serialized = JSON.stringify(value);
13
- return serialized === undefined ? String(value) : serialized;
14
- }
15
-
16
- const TRUNCATION_HINT =
17
- "filter/map/slice data inside execute_code and return only what you need";
18
-
19
- /**
20
- * Shape the over-cap notice so the **serialized envelope** fits the same cap
21
- * the raw value missed. Escaping matters: a preview sliced to the cap is JSON
22
- * text whose quotes and newlines re-escape to well over it, so a fixed slice
23
- * would leave the envelope over-cap and a second pass through this guard would
24
- * truncate the truncation — reporting the envelope's length as `totalChars` and
25
- * burying the real size. Shrinking proportionally until it fits keeps the guard
26
- * idempotent by construction: `totalChars` is always the true serialized size
27
- * of what the program returned, and truncation happens exactly once no matter
28
- * how many hops the value takes.
29
- */
30
- function truncationEnvelope(text: string): {
31
- truncated: true;
32
- preview: string;
33
- totalChars: number;
34
- hint: string;
35
- } {
36
- const base = {
37
- truncated: true as const,
38
- preview: "",
39
- totalChars: text.length,
40
- hint: TRUNCATION_HINT,
41
- };
42
- let budget = Math.max(
43
- 0,
44
- MAX_EXECUTE_RESULT_CHARS - JSON.stringify(base).length,
45
- );
46
- for (let attempt = 0; attempt < 8 && budget > 0; attempt += 1) {
47
- const candidate = { ...base, preview: text.slice(0, budget) };
48
- const size = JSON.stringify(candidate).length;
49
- if (size <= MAX_EXECUTE_RESULT_CHARS) return candidate;
50
- // Every character costs at least one serialized character, so scaling by
51
- // the overshoot ratio (minus a step) strictly shrinks the budget.
52
- budget = Math.max(
53
- 0,
54
- Math.floor(budget * (MAX_EXECUTE_RESULT_CHARS / size)) - 8,
55
- );
56
- }
57
- // The loop shrinks monotonically, so this is unreachable in practice — but an
58
- // unchecked slice is exactly how a "bounded" envelope stops being bounded.
59
- const clamped = { ...base, preview: text.slice(0, Math.max(0, budget)) };
60
- return JSON.stringify(clamped).length <= MAX_EXECUTE_RESULT_CHARS
61
- ? clamped
62
- : { ...base, preview: "" };
63
- }
64
-
65
- export function guardExecuteResultValue(value: unknown): unknown {
66
- const text = serializeExecuteValue(value);
67
- if (text.length <= MAX_EXECUTE_RESULT_CHARS) return value;
68
- return truncationEnvelope(text);
69
- }
70
-
71
- export function truncateExecuteText(text: string, max: number): string {
72
- if (text.length <= max) return text;
73
- return `${text.slice(0, max)}\n--- TRUNCATED (${text.length} chars total) — filter/map/slice data inside your code and return only what you need ---`;
74
- }
75
-
76
- /**
77
- * Apply the public execute_code result/log policy before a child result enters
78
- * IPC. The parent repeats the guard for third-party Executor implementations.
79
- */
80
- export function prepareExecuteResultForTransport(
81
- outcome: ExecuteResult,
82
- ): ExecuteResult {
83
- // QuickJS already bounds captured logs at source (entries + cumulative
84
- // characters). Preserve that Executor-level shape; createExecuteTool applies
85
- // the smaller model-facing 4k presentation cap in the parent.
86
- const logs =
87
- outcome.logs && outcome.logs.length > 0 ? outcome.logs : undefined;
88
- if (outcome.error) {
89
- return {
90
- result: undefined,
91
- error: outcome.error,
92
- ...(logs ? { logs } : {}),
93
- };
94
- }
95
- try {
96
- return {
97
- result: guardExecuteResultValue(outcome.result),
98
- ...(logs ? { logs } : {}),
99
- };
100
- } catch (err) {
101
- return {
102
- result: undefined,
103
- error: `result is not JSON-serializable: ${msg(err)}`,
104
- ...(logs ? { logs } : {}),
105
- };
106
- }
107
- }
@@ -1,172 +0,0 @@
1
- import { prepareExecuteResultForTransport } from "../executor-result.js";
2
- import type { ExecutorProvider } from "../types.js";
3
- import {
4
- MAX_QUICKJS_IPC_BYTES,
5
- MAX_QUICKJS_HOST_RPC_BYTES,
6
- type ChildToParentMessage,
7
- type ExecutionPayload,
8
- type HostCallPayload,
9
- type HostResultPayload,
10
- type ParentToChildMessage,
11
- type RunPayload,
12
- serializedBytes,
13
- stringifyBounded,
14
- } from "./quickjs-protocol.js";
15
- import { executeQuickJs, prepareQuickJs } from "./quickjs-runtime.js";
16
-
17
- let activeJobId: number | undefined;
18
- let nextCallId = 1;
19
- const pending = new Map<
20
- number,
21
- {
22
- resolve: (value: unknown) => void;
23
- reject: (error: Error) => void;
24
- }
25
- >();
26
-
27
- function msg(err: unknown): string {
28
- return err instanceof Error ? err.message : String(err);
29
- }
30
-
31
- function send(message: ChildToParentMessage): void {
32
- if (!process.send) throw new Error("QuickJS child IPC channel is unavailable.");
33
- // Bound the actual process.send envelope, not only its nested payloadJson.
34
- // Guest-controlled strings must be rejected while they are still isolated
35
- // in this disposable process, before Node materializes them in the parent.
36
- stringifyBounded(message, "QuickJS child IPC envelope");
37
- process.send(message);
38
- }
39
-
40
- function provider(
41
- name: string,
42
- jobId: number,
43
- prelude?: string,
44
- ): ExecutorProvider {
45
- const functions = new Proxy(
46
- Object.create(null) as ExecutorProvider["fns"],
47
- {
48
- get: (_target, key) => {
49
- if (typeof key !== "string") return undefined;
50
- return async (...args: unknown[]): Promise<unknown> => {
51
- const callId = nextCallId++;
52
- const payloadJson = stringifyBounded(
53
- {
54
- namespace: name,
55
- functionName: key,
56
- args,
57
- } satisfies HostCallPayload,
58
- "Host call payload",
59
- MAX_QUICKJS_HOST_RPC_BYTES,
60
- );
61
- const result = new Promise<unknown>((resolve, reject) => {
62
- pending.set(callId, { resolve, reject });
63
- });
64
- send({
65
- type: "host-call",
66
- jobId,
67
- callId,
68
- payloadJson,
69
- });
70
- return result;
71
- };
72
- },
73
- getOwnPropertyDescriptor: (_target, key) =>
74
- typeof key === "string"
75
- ? { configurable: true, enumerable: false }
76
- : undefined,
77
- },
78
- );
79
- return { name, fns: functions, ...(prelude ? { prelude } : {}) };
80
- }
81
-
82
- function fixedTransportFailure(message: string): ExecutionPayload {
83
- return {
84
- outcome: {
85
- result: undefined,
86
- error: message.slice(0, 4_000),
87
- },
88
- };
89
- }
90
-
91
- async function run(payload: RunPayload): Promise<void> {
92
- if (activeJobId !== undefined) {
93
- throw new Error("QuickJS child received overlapping executions.");
94
- }
95
- activeJobId = payload.id;
96
- try {
97
- const providers = payload.providers.map(({ name, prelude }) =>
98
- provider(name, payload.id, prelude),
99
- );
100
- const raw = await executeQuickJs(
101
- payload.code,
102
- providers,
103
- payload.options,
104
- );
105
- const prepared: ExecutionPayload = {
106
- outcome: prepareExecuteResultForTransport(raw),
107
- ...(raw.timedOut ? { timedOut: true } : {}),
108
- };
109
- let payloadJson: string;
110
- try {
111
- payloadJson = stringifyBounded(prepared, "QuickJS execution result");
112
- } catch (err) {
113
- payloadJson = JSON.stringify(
114
- fixedTransportFailure(
115
- `QuickJS execution result exceeded the ${MAX_QUICKJS_IPC_BYTES}-byte IPC limit: ${msg(err)}`,
116
- ),
117
- );
118
- }
119
- send({ type: "result", jobId: payload.id, payloadJson });
120
- } finally {
121
- activeJobId = undefined;
122
- for (const request of pending.values()) {
123
- request.reject(new Error("Execution ended before the host call settled."));
124
- }
125
- pending.clear();
126
- }
127
- }
128
-
129
- process.on("message", (message: ParentToChildMessage) => {
130
- if (!message || typeof message !== "object") return;
131
- if (message.type === "host-result") {
132
- if (message.jobId !== activeJobId) return;
133
- const request = pending.get(message.callId);
134
- if (!request) return;
135
- pending.delete(message.callId);
136
- if (
137
- serializedBytes(message.payloadJson) > MAX_QUICKJS_HOST_RPC_BYTES
138
- ) {
139
- request.reject(new Error("Host result exceeded the IPC limit."));
140
- return;
141
- }
142
- try {
143
- const result = JSON.parse(message.payloadJson) as HostResultPayload;
144
- if (result.ok) request.resolve(result.value);
145
- else request.reject(new Error(result.error));
146
- } catch (err) {
147
- request.reject(new Error(`Invalid host result: ${msg(err)}`));
148
- }
149
- return;
150
- }
151
- if (message.type !== "run") return;
152
- if (serializedBytes(message.payloadJson) > MAX_QUICKJS_IPC_BYTES) {
153
- throw new Error("Run payload exceeded the IPC limit.");
154
- }
155
- const payload = JSON.parse(message.payloadJson) as RunPayload;
156
- void run(payload).catch((err) => {
157
- const payloadJson = JSON.stringify(
158
- fixedTransportFailure(`QuickJS child failed: ${msg(err)}`),
159
- );
160
- send({ type: "result", jobId: payload.id, payloadJson });
161
- activeJobId = undefined;
162
- });
163
- });
164
-
165
- process.on("disconnect", () => process.exit(0));
166
-
167
- // The parent does not start an execution's wall budget until the trusted WASM
168
- // module is loaded. This keeps cold-start latency from being misclassified as
169
- // guest CPU/wall failure while the parent's startup timeout still bounds a
170
- // child that never becomes ready.
171
- await prepareQuickJs();
172
- send({ type: "ready" });
@@ -1,99 +0,0 @@
1
- import type { ExecuteResult } from "../types.js";
2
- import type { QuickJsRuntimeOptions } from "./quickjs-runtime.js";
3
-
4
- // An execution result is serialized once into payloadJson and again as that
5
- // string is embedded in ChildToParentMessage. Captured logs therefore get a
6
- // separate 512 KiB budget measured after both JSON encodings. The final result
7
- // is shaped to 24k characters; even if every character takes its worst-case
8
- // seven transport bytes, that leaves ample structural overhead below this hard
9
- // process.send ceiling.
10
- export const MAX_QUICKJS_IPC_BYTES = 1024 * 1024;
11
- export const MAX_QUICKJS_LOG_TRANSPORT_BYTES = 512 * 1024;
12
- /** Preserve #84's stopgap before a host value enters the child/WASM process. */
13
- export const MAX_QUICKJS_HOST_RPC_BYTES = 256 * 1024;
14
-
15
- export interface RunPayload {
16
- id: number;
17
- code: string;
18
- providers: Array<{ name: string; prelude?: string }>;
19
- options: QuickJsRuntimeOptions;
20
- }
21
-
22
- export type ParentToChildMessage =
23
- | { type: "run"; payloadJson: string }
24
- | {
25
- type: "host-result";
26
- jobId: number;
27
- callId: number;
28
- payloadJson: string;
29
- };
30
-
31
- export type ChildToParentMessage =
32
- | { type: "ready" }
33
- | {
34
- type: "host-call";
35
- jobId: number;
36
- callId: number;
37
- payloadJson: string;
38
- }
39
- | { type: "result"; jobId: number; payloadJson: string };
40
-
41
- export interface HostCallPayload {
42
- namespace: string;
43
- functionName: string;
44
- args: unknown[];
45
- }
46
-
47
- export type HostResultPayload =
48
- | { ok: true; value: unknown }
49
- | { ok: false; error: string };
50
-
51
- export interface ExecutionPayload {
52
- outcome: ExecuteResult;
53
- /**
54
- * Set by the runtime on wall-clock expiry. The parent recycles the child on
55
- * this flag, never on error text a guest can fabricate.
56
- */
57
- timedOut?: boolean;
58
- }
59
-
60
- /**
61
- * How a host call should be named in an error a program will read. The lazy
62
- * connector namespaces all dispatch through one internal function, so the raw
63
- * provider/function pair would report every shortcut call as
64
- * `connecta.__callNamespace` — an internal name that appears nowhere in the
65
- * documented surface. Report the address the program actually called.
66
- */
67
- export function hostCallLabel(payload: {
68
- namespace: string;
69
- functionName: string;
70
- args: unknown[];
71
- }): string {
72
- if (payload.functionName === "__callNamespace") {
73
- const [connectorId, toolAlias] = payload.args;
74
- return `${String(connectorId)}.${String(toolAlias)}`;
75
- }
76
- return `${payload.namespace}.${payload.functionName}`;
77
- }
78
-
79
- export function serializedBytes(text: string): number {
80
- return new TextEncoder().encode(text).length;
81
- }
82
-
83
- export function stringifyBounded(
84
- value: unknown,
85
- label: string,
86
- limit = MAX_QUICKJS_IPC_BYTES,
87
- ): string {
88
- const json = JSON.stringify(value);
89
- if (json === undefined) {
90
- throw new TypeError(`${label} is not JSON-serializable.`);
91
- }
92
- const bytes = serializedBytes(json);
93
- if (bytes > limit) {
94
- throw new RangeError(
95
- `${label} is ${bytes} UTF-8 bytes, over the ${limit}-byte IPC limit.`,
96
- );
97
- }
98
- return json;
99
- }