@zackbart/connecta 0.24.4 → 0.26.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 (155) hide show
  1. package/AGENTS.md +43 -12
  2. package/CHANGELOG.md +524 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/advertised-schema.js +53 -0
  9. package/dist/artifacts/connector.d.ts +55 -0
  10. package/dist/artifacts/connector.js +724 -0
  11. package/dist/artifacts/document.js +76 -0
  12. package/dist/artifacts/guide.js +140 -0
  13. package/dist/artifacts/html-scan.js +329 -0
  14. package/dist/artifacts/json.js +68 -0
  15. package/dist/artifacts/kv-store.d.ts +20 -0
  16. package/dist/artifacts/kv-store.js +140 -0
  17. package/dist/artifacts/markdown.js +449 -0
  18. package/dist/artifacts/module.d.ts +37 -0
  19. package/dist/artifacts/module.js +60 -0
  20. package/dist/artifacts/operations.d.ts +195 -0
  21. package/dist/artifacts/operations.js +944 -0
  22. package/dist/artifacts/refresh.d.ts +53 -0
  23. package/dist/artifacts/refresh.js +235 -0
  24. package/dist/artifacts/routes.js +194 -0
  25. package/dist/artifacts/types.d.ts +214 -0
  26. package/dist/artifacts/types.js +22 -0
  27. package/dist/artifacts/validate.d.ts +62 -0
  28. package/dist/artifacts/validate.js +652 -0
  29. package/dist/artifacts.d.ts +6 -0
  30. package/dist/artifacts.js +9 -0
  31. package/dist/auth/downstream-oauth.js +490 -106
  32. package/dist/call-admission.js +139 -113
  33. package/dist/catalog-service.js +152 -106
  34. package/dist/catalog.js +297 -38
  35. package/dist/concurrency.js +24 -23
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connector-scope.js +6 -37
  41. package/dist/connectors/api.d.ts +2 -2
  42. package/dist/connectors/remote-mcp.d.ts +6 -5
  43. package/dist/connectors/remote-mcp.js +330 -240
  44. package/dist/credential-contract.d.ts +9 -0
  45. package/dist/credential-rules.js +2 -2
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +45 -3
  48. package/dist/errors.d.ts +16 -1
  49. package/dist/errors.js +28 -0
  50. package/dist/execute.js +745 -282
  51. package/dist/executor-admission.d.ts +11 -1
  52. package/dist/executor-admission.js +145 -63
  53. package/dist/executors/quickjs-runtime.js +4 -2
  54. package/dist/executors/quickjs.js +255 -212
  55. package/dist/fix-prompt.js +37 -0
  56. package/dist/index.d.ts +72 -7
  57. package/dist/index.js +281 -26
  58. package/dist/invocation.js +371 -286
  59. package/dist/meta-tools.js +213 -94
  60. package/dist/module-contracts.d.ts +21 -1
  61. package/dist/oauth-callback-outcome.js +94 -0
  62. package/dist/oauth-sealing.js +29 -0
  63. package/dist/operator-ui/fix-prompts.js +87 -0
  64. package/dist/operator-ui/generated.js +2 -2
  65. package/dist/operator-ui/model.d.ts +103 -1
  66. package/dist/operator-ui/setup-commands.js +53 -0
  67. package/dist/operator-ui/view.js +128 -3
  68. package/dist/program-source.js +321 -0
  69. package/dist/providers/notion.js +2 -2
  70. package/dist/providers/revenuecat.js +32 -5
  71. package/dist/providers/stripe.d.ts +3 -2
  72. package/dist/providers/vercel.js +1 -1
  73. package/dist/registry.d.ts +43 -7
  74. package/dist/registry.js +434 -288
  75. package/dist/resumable.js +1291 -0
  76. package/dist/routes/activity.js +74 -154
  77. package/dist/routes/actor-labels.js +114 -0
  78. package/dist/routes/credentials.js +196 -163
  79. package/dist/routes/mcp.js +228 -112
  80. package/dist/routes/oauth-management.js +73 -71
  81. package/dist/routes/oauth.js +44 -16
  82. package/dist/routes/operator.js +83 -0
  83. package/dist/routes/shared.d.ts +25 -1
  84. package/dist/routes/shared.js +20 -2
  85. package/dist/routes/ui-data.js +211 -0
  86. package/dist/routes/ui.js +76 -35
  87. package/dist/run-journal.js +347 -0
  88. package/dist/runtime/admission.js +43 -0
  89. package/dist/runtime/call-admission.js +67 -0
  90. package/dist/runtime/connector-scope.js +72 -0
  91. package/dist/runtime/run.js +174 -0
  92. package/dist/runtime/services.js +130 -0
  93. package/dist/runtime/shared-read.js +106 -0
  94. package/dist/runtime/storage.js +66 -0
  95. package/dist/server.js +137 -89
  96. package/dist/skills.js +28 -9
  97. package/dist/storage/file.js +39 -2
  98. package/dist/storage/memory.js +28 -16
  99. package/dist/timeout.js +39 -43
  100. package/dist/tool-safety.d.ts +27 -1
  101. package/dist/tool-safety.js +31 -0
  102. package/dist/types.d.ts +45 -7
  103. package/dist/ui.d.ts +25 -4
  104. package/dist/ui.js +77 -167
  105. package/dist/url-safety.js +157 -0
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/documentation/architecture.md +367 -16
  109. package/documentation/auth.md +163 -2
  110. package/documentation/code-mode.md +274 -56
  111. package/documentation/meta-tools.md +200 -44
  112. package/ethos.md +46 -47
  113. package/examples/worker/AGENTS.md +6 -0
  114. package/examples/worker/README.md +224 -14
  115. package/examples/worker/src/d1-activity-row.ts +14 -2
  116. package/examples/worker/src/d1-activity.ts +4 -2
  117. package/examples/worker/src/d1-storage.ts +116 -0
  118. package/examples/worker/src/index.ts +42 -7
  119. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  120. package/examples/worker/wrangler.jsonc +21 -5
  121. package/package.json +16 -4
  122. package/templates/node/AGENTS.md +9 -2
  123. package/templates/node/README.md +31 -3
  124. package/templates/node/package.json +1 -1
  125. package/templates/node/src/index.ts +27 -0
  126. package/dist/auth/downstream-oauth.d.ts +0 -200
  127. package/dist/catalog-fingerprint.d.ts +0 -19
  128. package/dist/catalog-limits.d.ts +0 -9
  129. package/dist/catalog-service.d.ts +0 -263
  130. package/dist/catalog.d.ts +0 -87
  131. package/dist/concurrency.d.ts +0 -3
  132. package/dist/connectors/guarded-fetch.d.ts +0 -96
  133. package/dist/credential-rules.d.ts +0 -85
  134. package/dist/execute.d.ts +0 -154
  135. package/dist/executor-result.d.ts +0 -10
  136. package/dist/executors/quickjs-child.d.ts +0 -1
  137. package/dist/executors/quickjs-protocol.d.ts +0 -67
  138. package/dist/favicon.d.ts +0 -1
  139. package/dist/identity.d.ts +0 -4
  140. package/dist/invocation.d.ts +0 -74
  141. package/dist/mcp-result.d.ts +0 -8
  142. package/dist/meta-tools.d.ts +0 -129
  143. package/dist/operator-ui/generated.d.ts +0 -2
  144. package/dist/operator-ui/view.d.ts +0 -188
  145. package/dist/result-shapes.d.ts +0 -13
  146. package/dist/retired-toolkits.d.ts +0 -2
  147. package/dist/routes/activity.d.ts +0 -2
  148. package/dist/routes/credentials.d.ts +0 -2
  149. package/dist/routes/mcp.d.ts +0 -10
  150. package/dist/routes/oauth-management.d.ts +0 -2
  151. package/dist/routes/oauth.d.ts +0 -2
  152. package/dist/routes/ui.d.ts +0 -2
  153. package/dist/server.d.ts +0 -9
  154. package/dist/skills.d.ts +0 -44
  155. package/dist/timeout.d.ts +0 -21
@@ -23,6 +23,8 @@ export declare class ExecutorExecutionError extends ExecutorAdmissionError {
23
23
  export interface AdmissionLease {
24
24
  /** Time spent waiting behind active work. Zero for immediate admission. */
25
25
  readonly waitMs: number;
26
+ /** Time left in the optional total admitted-request lifetime. */
27
+ remainingMs(): number | undefined;
26
28
  release(): void;
27
29
  }
28
30
  export interface AdmissionControllerOptions {
@@ -31,6 +33,8 @@ export interface AdmissionControllerOptions {
31
33
  queueTimeoutMs: number;
32
34
  /** Suggested delay exposed with retryable overload failures. */
33
35
  retryAfterMs?: number;
36
+ /** Hard admitted-request lifetime; omitted for executor admission. */
37
+ maxDurationMs?: number;
34
38
  }
35
39
  /**
36
40
  * Runtime-portable bounded admission. It deliberately owns no executor or
@@ -42,7 +46,9 @@ export declare class AdmissionController {
42
46
  readonly maxQueueSize: number;
43
47
  readonly queueTimeoutMs: number;
44
48
  readonly retryAfterMs: number;
49
+ readonly maxDurationMs: number | undefined;
45
50
  private active;
51
+ private readonly leases;
46
52
  private closed;
47
53
  private readonly waiters;
48
54
  private admittedTotal;
@@ -63,7 +69,11 @@ export declare class AdmissionController {
63
69
  close(): void;
64
70
  private overloaded;
65
71
  private makeLease;
66
- private release;
72
+ private releaseSlot;
73
+ private fillSlots;
74
+ private pruneExpiredWaiters;
75
+ private reapExpired;
76
+ private nextReapDelay;
67
77
  private remove;
68
78
  private cleanup;
69
79
  }
@@ -1,3 +1,6 @@
1
+ import { Deferred, Duration, Effect } from "effect";
2
+ import { admit, provideAdmissionProgram } from "./runtime/admission.js";
3
+ import { fromSignal, runEdge } from "./runtime/run.js";
1
4
  /**
2
5
  * A stable, machine-readable admission failure. Overload is retryable; caller
3
6
  * cancellation and shutdown are terminal for this invocation.
@@ -51,7 +54,9 @@ export class AdmissionController {
51
54
  maxQueueSize;
52
55
  queueTimeoutMs;
53
56
  retryAfterMs;
57
+ maxDurationMs;
54
58
  active = 0;
59
+ leases = new Map();
55
60
  closed = false;
56
61
  waiters = [];
57
62
  admittedTotal = 0;
@@ -67,6 +72,12 @@ export class AdmissionController {
67
72
  this.maxQueueSize = nonNegativeWhole(options.maxQueueSize, "maxQueueSize");
68
73
  this.queueTimeoutMs = positiveWhole(options.queueTimeoutMs, "queueTimeoutMs");
69
74
  this.retryAfterMs = nonNegativeWhole(options.retryAfterMs ?? this.queueTimeoutMs, "retryAfterMs");
75
+ this.maxDurationMs = options.maxDurationMs === undefined
76
+ ? undefined
77
+ : positiveWhole(options.maxDurationMs, "maxDurationMs");
78
+ if (this.maxDurationMs !== undefined && this.maxDurationMs > 2_147_483_647) {
79
+ throw new TypeError("maxDurationMs must be at most 2,147,483,647 milliseconds.");
80
+ }
70
81
  }
71
82
  get activeCount() {
72
83
  return this.active;
@@ -75,11 +86,13 @@ export class AdmissionController {
75
86
  return this.waiters.length;
76
87
  }
77
88
  snapshot() {
89
+ this.reapExpired();
78
90
  return {
79
91
  concurrency: this.concurrency,
80
92
  maxQueueSize: this.maxQueueSize,
81
93
  queueTimeoutMs: this.queueTimeoutMs,
82
94
  retryAfterMs: this.retryAfterMs,
95
+ ...(this.maxDurationMs !== undefined ? { maxDurationMs: this.maxDurationMs } : {}),
83
96
  active: this.active,
84
97
  queued: this.waiters.length,
85
98
  closed: this.closed,
@@ -98,57 +111,15 @@ export class AdmissionController {
98
111
  };
99
112
  }
100
113
  acquire(options = {}) {
101
- if (this.closed) {
102
- this.closedTotal++;
103
- return Promise.reject(new ExecutorAdmissionError("executor_closed", "Executor is shutting down."));
104
- }
105
- if (options.signal?.aborted) {
106
- this.cancelledTotal++;
107
- return Promise.reject(new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled before admission."));
108
- }
109
- if (this.active < this.concurrency) {
110
- this.active++;
111
- this.admittedTotal++;
112
- return Promise.resolve(this.makeLease(0));
113
- }
114
- if (this.waiters.length >= this.maxQueueSize) {
115
- this.rejectedTotal++;
116
- return Promise.reject(this.overloaded("Executor queue is full."));
117
- }
118
- return new Promise((resolve, reject) => {
119
- const waiter = {
120
- queuedAt: Date.now(),
121
- resolve,
122
- reject,
123
- ...(options.signal ? { signal: options.signal } : {}),
124
- };
125
- waiter.onAbort = () => {
126
- if (!this.remove(waiter))
127
- return;
128
- this.cleanup(waiter);
129
- this.cancelledTotal++;
130
- reject(new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled while queued."));
131
- };
132
- options.signal?.addEventListener("abort", waiter.onAbort, { once: true });
133
- waiter.timer = setTimeout(() => {
134
- if (!this.remove(waiter))
135
- return;
136
- this.cleanup(waiter);
137
- this.rejectedTotal++;
138
- reject(this.overloaded(`Executor admission timed out after ${this.queueTimeoutMs}ms.`));
139
- }, this.queueTimeoutMs);
140
- this.waiters.push(waiter);
141
- this.queuedTotal++;
142
- });
114
+ return runEdge(admit(this, options));
143
115
  }
144
116
  close() {
145
117
  if (this.closed)
146
118
  return;
147
119
  this.closed = true;
148
120
  for (const waiter of this.waiters.splice(0)) {
149
- this.cleanup(waiter);
150
121
  this.closedTotal++;
151
- waiter.reject(new ExecutorAdmissionError("executor_closed", "Executor is shutting down."));
122
+ Deferred.doneUnsafe(waiter.outcome, Effect.fail(new ExecutorAdmissionError("executor_closed", "Executor is shutting down.")));
152
123
  }
153
124
  }
154
125
  overloaded(message) {
@@ -157,33 +128,83 @@ export class AdmissionController {
157
128
  });
158
129
  }
159
130
  makeLease(waitMs) {
160
- let released = false;
131
+ const id = Symbol("admission lease");
132
+ const record = this.maxDurationMs !== undefined
133
+ ? { expiresAt: Date.now() + this.maxDurationMs }
134
+ : {};
135
+ this.leases.set(id, record);
161
136
  return {
162
137
  waitMs,
138
+ remainingMs: () => !this.leases.has(id)
139
+ ? 0
140
+ : record.expiresAt === undefined
141
+ ? undefined
142
+ : Math.max(0, record.expiresAt - Date.now()),
163
143
  release: () => {
164
- if (released)
144
+ if (!this.leases.delete(id))
165
145
  return;
166
- released = true;
167
- this.release();
146
+ this.releaseSlot();
168
147
  },
169
148
  };
170
149
  }
171
- release() {
150
+ releaseSlot() {
172
151
  if (this.active > 0)
173
152
  this.active--;
174
153
  if (this.closed)
175
154
  return;
176
- const waiter = this.waiters.shift();
177
- if (!waiter)
155
+ this.fillSlots();
156
+ }
157
+ fillSlots() {
158
+ this.pruneExpiredWaiters();
159
+ while (this.active < this.concurrency) {
160
+ const waiter = this.waiters.shift();
161
+ if (!waiter)
162
+ return;
163
+ const waitMs = Math.max(0, Date.now() - waiter.queuedAt);
164
+ this.admittedTotal++;
165
+ this.queueWaitCount++;
166
+ this.queueWaitTotalMs += waitMs;
167
+ this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
168
+ this.active++;
169
+ Deferred.doneUnsafe(waiter.outcome, Effect.succeed(this.makeLease(waitMs)));
170
+ }
171
+ }
172
+ pruneExpiredWaiters() {
173
+ const now = Date.now();
174
+ for (let index = this.waiters.length - 1; index >= 0; index--) {
175
+ const waiter = this.waiters[index];
176
+ if (now - waiter.queuedAt < this.queueTimeoutMs)
177
+ continue;
178
+ this.waiters.splice(index, 1);
179
+ this.rejectedTotal++;
180
+ Deferred.doneUnsafe(waiter.outcome, Effect.fail(this.overloaded(`Executor admission timed out after ${this.queueTimeoutMs}ms.`)));
181
+ }
182
+ }
183
+ reapExpired() {
184
+ this.pruneExpiredWaiters();
185
+ if (this.maxDurationMs === undefined)
178
186
  return;
179
- this.cleanup(waiter);
180
- const waitMs = Math.max(0, Date.now() - waiter.queuedAt);
181
- this.admittedTotal++;
182
- this.queueWaitCount++;
183
- this.queueWaitTotalMs += waitMs;
184
- this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
185
- this.active++;
186
- waiter.resolve(this.makeLease(waitMs));
187
+ const now = Date.now();
188
+ for (const [id, lease] of this.leases) {
189
+ if (lease.expiresAt !== undefined && lease.expiresAt <= now) {
190
+ this.leases.delete(id);
191
+ this.active--;
192
+ }
193
+ }
194
+ if (!this.closed)
195
+ this.fillSlots();
196
+ }
197
+ nextReapDelay() {
198
+ let next = Number.POSITIVE_INFINITY;
199
+ for (const lease of this.leases.values()) {
200
+ if (lease.expiresAt !== undefined)
201
+ next = Math.min(next, lease.expiresAt);
202
+ }
203
+ // A queued request's own timer reaps the earliest admitted lease. A
204
+ // fallback check covers a briefly empty lease map after a handoff.
205
+ return Number.isFinite(next)
206
+ ? Math.max(1, next - Date.now())
207
+ : 250;
187
208
  }
188
209
  remove(waiter) {
189
210
  const index = this.waiters.indexOf(waiter);
@@ -192,12 +213,73 @@ export class AdmissionController {
192
213
  this.waiters.splice(index, 1);
193
214
  return true;
194
215
  }
216
+ // The waiting fiber was interrupted — an Effect caller's scope closed, not a
217
+ // signal. Withdraw the waiter if it is still queued; if release() handed it
218
+ // a slot in the same breath, give the slot back, because nobody is left to
219
+ // receive the lease. (A line comment, not JSDoc: TypeScript copies a private
220
+ // member's JSDoc into the published declaration.)
195
221
  cleanup(waiter) {
196
- if (waiter.timer !== undefined)
197
- clearTimeout(waiter.timer);
198
- if (waiter.onAbort) {
199
- waiter.signal?.removeEventListener("abort", waiter.onAbort);
222
+ if (this.remove(waiter)) {
223
+ this.cancelledTotal++;
224
+ return Effect.void;
200
225
  }
226
+ if (!Deferred.isDoneUnsafe(waiter.outcome))
227
+ return Effect.void;
228
+ return Deferred.await(waiter.outcome).pipe(Effect.tap((lease) => Effect.sync(() => lease.release())), Effect.ignore);
229
+ }
230
+ // The admission program, handed to src/runtime/admission.ts. It lives in a
231
+ // static block because the class declaration ships, private member names
232
+ // and all: a new method or field would change the published `.d.ts`, and a
233
+ // static block is the one place outside an instance method that may read
234
+ // this bookkeeping while emitting nothing there.
235
+ static {
236
+ provideAdmissionProgram((controller, signal) => Effect.suspend(() => {
237
+ controller.reapExpired();
238
+ if (controller.closed) {
239
+ controller.closedTotal++;
240
+ return Effect.fail(new ExecutorAdmissionError("executor_closed", "Executor is shutting down."));
241
+ }
242
+ if (signal?.aborted) {
243
+ controller.cancelledTotal++;
244
+ return Effect.fail(new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled before admission."));
245
+ }
246
+ if (controller.active < controller.concurrency) {
247
+ controller.active++;
248
+ controller.admittedTotal++;
249
+ return Effect.succeed(controller.makeLease(0));
250
+ }
251
+ if (controller.waiters.length >= controller.maxQueueSize) {
252
+ controller.rejectedTotal++;
253
+ return Effect.fail(controller.overloaded("Executor queue is full."));
254
+ }
255
+ const waiter = {
256
+ queuedAt: Date.now(),
257
+ outcome: Deferred.makeUnsafe(),
258
+ };
259
+ controller.waiters.push(waiter);
260
+ controller.queuedTotal++;
261
+ // A timeout or abort settles the waiter only if it takes it out of the
262
+ // queue first. Otherwise release() or close() got there in the same
263
+ // tick, and the outcome they recorded is the answer.
264
+ const giveUp = (count, error) => Effect.suspend(() => {
265
+ if (!controller.remove(waiter))
266
+ return Deferred.await(waiter.outcome);
267
+ count();
268
+ return Effect.fail(error());
269
+ });
270
+ // One flat race; each contender is a forked fiber.
271
+ const contenders = [
272
+ Deferred.await(waiter.outcome),
273
+ Effect.sleep(Duration.millis(controller.queueTimeoutMs)).pipe(Effect.andThen(giveUp(() => controller.rejectedTotal++, () => controller.overloaded(`Executor admission timed out after ${controller.queueTimeoutMs}ms.`)))),
274
+ ];
275
+ if (controller.maxDurationMs !== undefined) {
276
+ contenders.push(Effect.forever(Effect.suspend(() => Effect.sleep(Duration.millis(controller.nextReapDelay()))).pipe(Effect.andThen(Effect.sync(() => controller.reapExpired())))));
277
+ }
278
+ if (signal) {
279
+ contenders.push(fromSignal(signal).pipe(Effect.catch(() => giveUp(() => controller.cancelledTotal++, () => new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled while queued.")))));
280
+ }
281
+ return Effect.raceAllFirst(contenders).pipe(Effect.onInterrupt(() => controller.cleanup(waiter)));
282
+ }));
201
283
  }
202
284
  }
203
285
  /** Preserve the one-method Workers seam while recognizing richer Node executors. */
@@ -9,6 +9,7 @@
9
9
  // provider args/results must be JSON-serializable.
10
10
  import { getQuickJS, } from "quickjs-emscripten";
11
11
  import { msg } from "../errors.js";
12
+ import { withoutProgramTerminator } from "../program-source.js";
12
13
  import { hostCallLabel, MAX_QUICKJS_LOG_TRANSPORT_BYTES, serializedBytes, } from "./quickjs-protocol.js";
13
14
  /** Load and compile the shared QuickJS WASM module before a run budget starts. */
14
15
  export async function prepareQuickJs() {
@@ -70,8 +71,9 @@ export function normalizeCode(code) {
70
71
  // lines — models routinely prefix their code with a `//` or `/* */` note,
71
72
  // which would otherwise defeat the detection and wrap the whole thing.
72
73
  const head = c.replace(/^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/, "");
73
- if (/^async\b/.test(head) || head.startsWith("("))
74
- return c;
74
+ if (/^async\b/.test(head) || head.startsWith("(")) {
75
+ return withoutProgramTerminator(c);
76
+ }
75
77
  return `async () => {\n${c}\n}`;
76
78
  }
77
79
  /**