@zackbart/connecta 0.27.0 → 0.28.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 (73) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +229 -0
  3. package/README.md +23 -24
  4. package/bin/connecta.mjs +1 -5
  5. package/dist/activity.d.ts +5 -4
  6. package/dist/activity.js +1 -10
  7. package/dist/artifacts/connector.js +1 -1
  8. package/dist/artifacts/json.js +10 -0
  9. package/dist/artifacts/operations.js +1 -1
  10. package/dist/artifacts/validate.js +1 -2
  11. package/dist/auth/downstream-oauth.js +147 -28
  12. package/dist/auth/static-oauth.js +504 -0
  13. package/dist/catalog-service.js +1 -1
  14. package/dist/connectors/api-connector.d.ts +168 -0
  15. package/dist/connectors/api-connector.js +127 -0
  16. package/dist/connectors/api.d.ts +8 -71
  17. package/dist/connectors/api.js +10 -99
  18. package/dist/connectors/guarded-fetch.js +37 -3
  19. package/dist/connectors/remote-mcp.js +7 -32
  20. package/dist/execute.js +92 -368
  21. package/dist/executors/quickjs.js +1 -1
  22. package/dist/exempt-writes.js +162 -0
  23. package/dist/index.d.ts +13 -32
  24. package/dist/index.js +14 -47
  25. package/dist/invocation.js +6 -19
  26. package/dist/meta-tools.js +1 -2
  27. package/dist/oauth-partition.js +17 -0
  28. package/dist/operator-ui/generated.js +1 -1
  29. package/dist/operator-ui/model.d.ts +3 -3
  30. package/dist/operator-ui/view.js +6 -4
  31. package/dist/providers/basecamp.d.ts +65 -0
  32. package/dist/providers/basecamp.js +445 -0
  33. package/dist/providers/breeze.d.ts +34 -0
  34. package/dist/providers/breeze.js +1402 -0
  35. package/dist/providers/ccb.d.ts +103 -0
  36. package/dist/providers/ccb.js +1570 -0
  37. package/dist/providers/cloudflare.js +1 -1
  38. package/dist/providers/linear.js +1 -1
  39. package/dist/providers/mixpanel.js +1 -1
  40. package/dist/providers/notion.js +1 -1
  41. package/dist/providers/overflow.d.ts +60 -0
  42. package/dist/providers/overflow.js +1572 -0
  43. package/dist/providers/planning-center.d.ts +59 -0
  44. package/dist/providers/planning-center.js +2039 -0
  45. package/dist/providers/revenuecat.js +1 -1
  46. package/dist/providers/stripe.js +1 -1
  47. package/dist/providers/tithely.d.ts +49 -0
  48. package/dist/providers/tithely.js +1034 -0
  49. package/dist/providers/vercel.js +1 -1
  50. package/dist/routes/mcp.js +5 -21
  51. package/dist/routes/oauth.js +29 -3
  52. package/dist/routes/shared.d.ts +1 -6
  53. package/dist/server.js +0 -4
  54. package/dist/skills.js +8 -23
  55. package/dist/tool-safety.d.ts +1 -1
  56. package/dist/tool-safety.js +1 -1
  57. package/dist/types.d.ts +2 -2
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/documentation/architecture.md +15 -17
  61. package/documentation/auth.md +120 -6
  62. package/documentation/code-mode.md +91 -194
  63. package/documentation/meta-tools.md +38 -41
  64. package/ethos.md +8 -9
  65. package/examples/worker/README.md +10 -14
  66. package/examples/worker/src/d1-activity-row.ts +4 -1
  67. package/examples/worker/src/index.ts +1 -9
  68. package/package.json +26 -2
  69. package/templates/node/AGENTS.md +4 -3
  70. package/templates/node/README.md +3 -4
  71. package/templates/node/package.json +1 -1
  72. package/dist/resumable.js +0 -1323
  73. package/dist/run-journal.js +0 -347
package/dist/execute.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Cause, Deferred, Duration, Effect, Exit } from "effect";
1
+ import { Cause, Duration, Effect, Exit } from "effect";
2
2
  import { z } from "zod";
3
3
  import { advertisedSchema } from "./advertised-schema.js";
4
4
  import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, flatSearchResult, } from "./catalog-service.js";
@@ -6,10 +6,9 @@ import { errorResult, jsonResult } from "./meta-tools.js";
6
6
  import { guardExecuteResultValue, MAX_EXECUTE_LOG_CHARS, truncateExecuteText, } from "./executor-result.js";
7
7
  import { ExecutorAdmissionError, ExecutorExecutionError, isAdmittingExecutor, } from "./executor-admission.js";
8
8
  import { boundedEchoText, msg } from "./errors.js";
9
+ import { DEFAULT_MAX_WRITES, ExemptWrites, writeStateOf, } from "./exempt-writes.js";
9
10
  import { InvocationFailure, InvocationService, timed, } from "./invocation.js";
10
11
  import { normalizeProgramSource } from "./program-source.js";
11
- import { DEFAULT_MAX_WRITES, ExemptWrites, MIN_STORAGE_TIMEOUT_MS, RunState, writeStateOf, } from "./resumable.js";
12
- import { journalKey } from "./run-journal.js";
13
12
  import { underAnySignal } from "./timeout.js";
14
13
  import { isApprovalExempt, NO_EXEMPTIONS, } from "./tool-safety.js";
15
14
  import { fromSignal, runEdge } from "./runtime/run.js";
@@ -278,129 +277,6 @@ function guestErrorPrelude(failureSecret) {
278
277
  });
279
278
  })(${JSON.stringify(`${GUEST_FAILURE_FRAME}${failureSecret}:`)});`;
280
279
  }
281
- /** A clock read now and a seed drawn from the host's CSPRNG. */
282
- function freshEnvironment() {
283
- const words = new Uint32Array(4);
284
- crypto.getRandomValues(words);
285
- return {
286
- clockMs: Date.now(),
287
- seed: [words[0] ?? 0, words[1] ?? 0, words[2] ?? 0, words[3] ?? 0],
288
- };
289
- }
290
- /**
291
- * Trusted guest code installing the pinned clock and stream, evaluated after
292
- * the error prelude and before the program. Every replacement is locked the
293
- * way `Error` is — non-writable and non-configurable — so a program can read
294
- * the pinned values and nothing else.
295
- *
296
- * `Date` becomes a wrapper whose argument-free forms (`new Date()`, `Date()`,
297
- * `Date.now()`) read the pinned instant; with arguments it is the native
298
- * constructor, and instances are ordinary dates. The native constructor's own
299
- * `now` and the prototype's `constructor` are pinned too, so the usual ways
300
- * back to it read the same instant. `Math.random` is sfc32 over the seed:
301
- * small, fast, and specified in a few lines anyone can check. Where the
302
- * runtime has them (a Dynamic Worker; QuickJS has neither), `crypto`'s
303
- * `getRandomValues` and `randomUUID` draw from the same stream and
304
- * `performance.now()` stays at 0, the run's start. None of that makes
305
- * `crypto` contract (`P2`): it only keeps a Workers-only program from being
306
- * the one that cannot replay.
307
- */
308
- function pinnedEnvironmentPrelude(environment) {
309
- return `((clock, seed) => {
310
- const NativeDate = globalThis.Date;
311
- const construct = Reflect.construct;
312
- const defineProperty = Object.defineProperty;
313
- const lock = (target, key, value) => defineProperty(target, key, {
314
- value, writable: false, enumerable: false, configurable: false
315
- });
316
- let a = seed[0] | 0, b = seed[1] | 0, c = seed[2] | 0, d = seed[3] | 0;
317
- const next = () => {
318
- const t = (((a + b) | 0) + d) | 0;
319
- d = (d + 1) | 0;
320
- a = b ^ (b >>> 9);
321
- b = (c + (c << 3)) | 0;
322
- c = (c << 21) | (c >>> 11);
323
- c = (c + t) | 0;
324
- return t >>> 0;
325
- };
326
- const now = function now() { return clock; };
327
- function Date(...args) {
328
- if (new.target === undefined) return new NativeDate(clock).toString();
329
- return construct(NativeDate, args.length === 0 ? [clock] : args, new.target);
330
- }
331
- Date.prototype = NativeDate.prototype;
332
- lock(Date, "now", now);
333
- lock(Date, "parse", NativeDate.parse);
334
- lock(Date, "UTC", NativeDate.UTC);
335
- lock(NativeDate, "now", now);
336
- lock(NativeDate.prototype, "constructor", Date);
337
- lock(globalThis, "Date", Date);
338
- lock(Math, "random", function random() { return next() / 4294967296; });
339
- // Lock the replacement on the object and on its prototype, where the
340
- // native method lives: \`Object.getPrototypeOf(crypto).getRandomValues\`
341
- // would otherwise still reach the real stream. A prototype the runtime
342
- // will not let us redefine keeps its native method (\`tryLock\`), and a
343
- // program that uses it diverges on replay rather than sending anything.
344
- const tryLock = (target, key, value) => {
345
- try { lock(target, key, value); } catch {}
346
- };
347
- if (typeof crypto === "object" && crypto !== null) {
348
- const getRandomValues = function getRandomValues(array) {
349
- const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
350
- for (let i = 0; i < bytes.length; i++) bytes[i] = next() >>> 24;
351
- return array;
352
- };
353
- const randomUUID = function randomUUID() {
354
- const bytes = getRandomValues(new Uint8Array(16));
355
- bytes[6] = (bytes[6] & 0x0f) | 0x40;
356
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
357
- let hex = "";
358
- for (let i = 0; i < 16; i++) {
359
- hex += (bytes[i] + 0x100).toString(16).slice(1);
360
- if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
361
- }
362
- return hex;
363
- };
364
- lock(crypto, "getRandomValues", getRandomValues);
365
- lock(crypto, "randomUUID", randomUUID);
366
- const cryptoProto = Object.getPrototypeOf(crypto);
367
- if (cryptoProto) {
368
- tryLock(cryptoProto, "getRandomValues", getRandomValues);
369
- tryLock(cryptoProto, "randomUUID", randomUUID);
370
- }
371
- }
372
- if (typeof performance === "object" && performance !== null) {
373
- const perfNow = function now() { return 0; };
374
- lock(performance, "now", perfNow);
375
- const performanceProto = Object.getPrototypeOf(performance);
376
- if (performanceProto) tryLock(performanceProto, "now", perfNow);
377
- }
378
- // An Intl formatter asked for "now" (no date) reads the native clock.
379
- if (typeof Intl === "object" && Intl !== null && typeof Intl.DateTimeFormat === "function") {
380
- const proto = Intl.DateTimeFormat.prototype;
381
- const formatGetter = Object.getOwnPropertyDescriptor(proto, "format");
382
- if (formatGetter && typeof formatGetter.get === "function") {
383
- const nativeFormat = formatGetter.get;
384
- try {
385
- defineProperty(proto, "format", {
386
- get() {
387
- const bound = nativeFormat.call(this);
388
- return function format(date) { return bound(date === undefined ? clock : date); };
389
- },
390
- enumerable: false,
391
- configurable: false,
392
- });
393
- } catch {}
394
- }
395
- const nativeFormatToParts = proto.formatToParts;
396
- if (typeof nativeFormatToParts === "function") {
397
- tryLock(proto, "formatToParts", function formatToParts(date) {
398
- return nativeFormatToParts.call(this, date === undefined ? clock : date);
399
- });
400
- }
401
- }
402
- })(${JSON.stringify(environment.clockMs)}, ${JSON.stringify([...environment.seed])});`;
403
- }
404
280
  /**
405
281
  * Expose one host provider and typed guest errors. Catalogs load only when
406
282
  * the program calls a tool or asks search/describe.
@@ -415,7 +291,7 @@ export async function buildSandboxProviders(registry, baseUrl, _logger, activity
415
291
  * edge the executor awaits, and behind it one host call is one fiber: spend
416
292
  * the budget, do the operation, and turn a typed failure into the frame the
417
293
  * prelude rebuilds inside the guest. Nothing is shared between those fibers
418
- * but the budget counter and this request's catalog, so a call the program
294
+ * but the budget counters and this request's catalog, so a call the program
419
295
  * never awaits cannot disturb the others.
420
296
  */
421
297
  function sandboxProvider(registry, baseUrl, activity, limits) {
@@ -435,26 +311,28 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
435
311
  const maxHostCalls = Math.max(1, Math.trunc(limits.maxHostCalls ?? EXECUTE_MAX_HOST_CALLS));
436
312
  const hostCallTimeoutMs = Math.max(1, Math.trunc(limits.hostCallTimeoutMs ?? EXECUTE_HOST_CALL_TIMEOUT_MS));
437
313
  const failureSecret = guestFailureSecret();
438
- const { signal, diagnostics } = limits;
314
+ const { signal, diagnostics, exemptWrites } = limits;
439
315
  let hostCalls = 0;
440
316
  // L4/M7: discovery and invocation spend the same budget, on entry; emit
441
317
  // does not.
442
318
  const spendHostCall = Effect.suspend(() => ++hostCalls > maxHostCalls
443
319
  ? Effect.fail(guestFailure("budget_exceeded", `execute_code host-call budget exceeded (${maxHostCalls} calls maximum)`))
444
320
  : Effect.void);
445
- const { runState, exemptWrites } = limits;
446
321
  const approval = limits.approval ?? NO_EXEMPTIONS;
447
322
  const maxWrites = resolveBudget(limits.maxWrites, DEFAULT_MAX_WRITES);
448
- let exemptWriteCount = 0;
323
+ let writes = 0;
449
324
  /**
450
- * Without resumable writes nothing can pause, but a config exemption
451
- * (#566) still lets its writes run: the gate covers exactly the exempt
452
- * calls, spends the write budget on each, and every other write keeps
453
- * E4's refusal, ahead of validation as it always was. A dispatched write
454
- * is tracked until it settles (`ExemptWrites`), so the play can wait for
455
- * it rather than abort it.
325
+ * The write gate covers exactly the config-exempt calls (#566): each spends
326
+ * the write budget and is tracked until it settles (`ExemptWrites`), so the
327
+ * play can wait for it rather than abort it. Every other write keeps E4's
328
+ * refusal, ahead of validation, so the host's prompt on
329
+ * call_destructive_tool stays the only approval there is.
456
330
  */
457
- const exemptOnly = (sending) => ({
331
+ const invocationContext = (sending) => ({
332
+ source: "execute_code",
333
+ timeoutMs: hostCallTimeoutMs,
334
+ ...(signal !== undefined ? { requestSignal: signal } : {}),
335
+ unwrapResult: true,
458
336
  gates: (target) => isApprovalExempt(approval, target.connector, target.toolName, target.definition),
459
337
  writeGate: (target) => Effect.sync(() => {
460
338
  if (exemptWrites?.isClosed) {
@@ -465,10 +343,10 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
465
343
  message: "The program had already returned, so this write was not sent.",
466
344
  retryable: false,
467
345
  },
468
- activity: "none",
346
+ unrecorded: true,
469
347
  };
470
348
  }
471
- if (exemptWriteCount >= maxWrites) {
349
+ if (writes >= maxWrites) {
472
350
  return {
473
351
  kind: "refuse",
474
352
  error: {
@@ -478,89 +356,22 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
478
356
  },
479
357
  };
480
358
  }
481
- exemptWriteCount++;
359
+ writes++;
482
360
  if (exemptWrites)
483
361
  sending.settle = exemptWrites.begin();
484
362
  return { kind: "dispatch" };
485
363
  }),
486
364
  });
487
- const invocationContext = (seq, key, sending) => ({
488
- source: runState?.source ?? "execute_code",
489
- timeoutMs: hostCallTimeoutMs,
490
- ...(signal !== undefined ? { requestSignal: signal } : {}),
491
- unwrapResult: true,
492
- // With resumable writes, a consequential call reaches the run's gate
493
- // instead of the flat E4 refusal, and a read's decision is marked the
494
- // moment it passes the same point.
495
- ...(runState && seq !== undefined && key !== undefined
496
- ? {
497
- beforeDispatch: () => runState.decide(seq),
498
- writeGate: (target, args) => runState.gate(seq, key, target, args),
499
- }
500
- : runState
501
- ? {}
502
- : exemptOnly(sending)),
503
- });
504
- /**
505
- * The front of every numbered host call once resumable writes are on: a
506
- * stopped run makes no call at all (not journaled, no activity), a
507
- * recorded one is answered from the journal before the budget-free part
508
- * of the path, and one the journal cannot account for is divergence.
509
- * Replayed calls spend the host-call budget again, so a play's total is
510
- * the run's total and nothing is counted twice.
511
- */
512
- const fromJournal = (seq, op, address, args) => Effect.gen(function* () {
513
- const halted = runState?.halted();
514
- if (halted)
515
- return yield* Effect.fail(halted);
516
- yield* spendHostCall;
517
- if (!runState || seq === undefined)
518
- return { kind: "live", key: undefined };
519
- const key = journalKey(op, address, args);
520
- const found = runState.lookup(key);
521
- if (found.kind === "diverged") {
522
- return yield* Effect.fail(runState.diverge(found.reason));
523
- }
524
- if (found.kind === "live")
525
- return { kind: "live", key };
526
- runState.decide(seq);
527
- const { outcome } = found.entry;
528
- return outcome.ok
529
- ? { kind: "replayed", value: outcome.value }
530
- : yield* Effect.fail(new InvocationFailure(outcome.error));
531
- });
532
365
  // A call brings its own cancellation: the invocation pipeline reads the
533
366
  // run's signal, refuses a call that starts after it, and records the
534
367
  // cancelled attempt in activity like any other outcome.
535
- const call = (seq, address, args) => Effect.gen(function* () {
536
- const addressText = String(address);
537
- const callArgs = args ?? {};
538
- const front = yield* fromJournal(seq, "call", addressText, callArgs);
539
- if (front.kind === "replayed")
540
- return front.value;
541
- // An exempt write dispatched without a run state settles here —
542
- // unknown if the call never returned an outcome.
368
+ const call = (address, args) => Effect.gen(function* () {
369
+ yield* spendHostCall;
370
+ // An exempt write settles here — unknown if the call never returned an
371
+ // outcome.
543
372
  const sending = {};
544
- const outcome = yield* invocation.pipeline(addressText, callArgs, invocationContext(seq, front.key, sending)).pipe(Effect.onExit((exit) => Effect.sync(() => sending.settle?.(Exit.isSuccess(exit) ? writeStateOf(exit.value) : "unknown"))));
373
+ const outcome = yield* invocation.pipeline(String(address), args ?? {}, invocationContext(sending)).pipe(Effect.onExit((exit) => Effect.sync(() => sending.settle?.(Exit.isSuccess(exit) ? writeStateOf(exit.value) : "unknown"))));
545
374
  diagnostics?.recordCall(outcome);
546
- if (runState && seq !== undefined && front.key !== undefined) {
547
- if (runState.isLiveWrite(seq)) {
548
- const resolved = outcome.resolved;
549
- const replaced = yield* runState.settleWrite(seq, front.key, {
550
- address: resolved
551
- ? `${resolved.connector.id}.${resolved.toolName}`
552
- : addressText,
553
- args: callArgs,
554
- }, outcome);
555
- if (replaced)
556
- return yield* Effect.fail(replaced);
557
- }
558
- else {
559
- runState.record(seq, "call", front.key, outcome.ok
560
- ? { ok: true, value: outcome.value }
561
- : { ok: false, error: outcome.error });
562
- }
563
- }
564
375
  return outcome.ok
565
376
  ? outcome.value
566
377
  : yield* Effect.fail(new InvocationFailure(outcome.error));
@@ -570,7 +381,7 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
570
381
  // `cancelled` instead of holding the program until its probe deadline.
571
382
  // Policy failures use the same thrown vocabulary as calls and utilities;
572
383
  // the transport below reconstructs their code inside the guest.
573
- const discovery = (operation, seq, raw, read) => Effect.suspend(() => {
384
+ const discovery = (operation, read) => Effect.suspend(() => {
574
385
  const started = Date.now();
575
386
  const cancelled = () => guestFailure("cancelled", `connecta.${operation} was cancelled because the run ended.`);
576
387
  const reading = Effect.tryPromise({
@@ -579,49 +390,22 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
579
390
  ? guestFailure(err.code, err.message)
580
391
  : err,
581
392
  });
582
- return fromJournal(seq, operation, "", raw ?? {}).pipe(Effect.flatMap((front) => {
583
- if (front.kind === "replayed")
584
- return Effect.succeed(front.value);
585
- const live = (!signal
586
- ? reading
587
- : signal.aborted
588
- ? Effect.fail(cancelled())
589
- : Effect.raceAllFirst([
590
- reading,
591
- fromSignal(signal).pipe(Effect.mapError(cancelled)),
592
- ])).pipe(Effect.onExit((exit) => Effect.sync(() => {
593
- if (!runState || seq === undefined || front.key === undefined)
594
- return;
595
- // Typed outcomes replay; an untyped throw is a bug, not an
596
- // answer, and is not journaled — so a replay that re-issues
597
- // the call before repeating the approved write finds no record
598
- // and fails `execution_diverged`.
599
- if (Exit.isSuccess(exit)) {
600
- runState.record(seq, operation, front.key, {
601
- ok: true,
602
- value: exit.value,
603
- });
604
- return;
605
- }
606
- const error = Cause.squash(exit.cause);
607
- if (error instanceof InvocationFailure) {
608
- runState.record(seq, operation, front.key, {
609
- ok: false,
610
- error: error.details,
611
- });
612
- }
613
- })));
614
- return live;
615
- }), Effect.onExit((exit) => Effect.sync(() => diagnostics?.recordCatalog(operation, Date.now() - started, Exit.isSuccess(exit), Exit.isSuccess(exit) ? exit.value : undefined))));
393
+ return spendHostCall.pipe(Effect.andThen(!signal
394
+ ? reading
395
+ : signal.aborted
396
+ ? Effect.fail(cancelled())
397
+ : Effect.raceAllFirst([
398
+ reading,
399
+ fromSignal(signal).pipe(Effect.mapError(cancelled)),
400
+ ])), Effect.onExit((exit) => Effect.sync(() => diagnostics?.recordCatalog(operation, Date.now() - started, Exit.isSuccess(exit), Exit.isSuccess(exit) ? exit.value : undefined))));
616
401
  });
617
402
  const operations = {
618
403
  call,
619
404
  // Emission is a provider function, never an ExecuteResult field —
620
405
  // that is what keeps the Executor contract untouched and parity
621
406
  // structural (M8). It spends no host-call budget (M7); its own
622
- // budgets live in the collector. It is never journaled: a replay emits
623
- // again, and only the play that completes delivers.
624
- emit: (_seq, block) => Effect.try({
407
+ // budgets live in the collector.
408
+ emit: (block) => Effect.try({
625
409
  try: () => {
626
410
  if (!limits.emitCollector) {
627
411
  throw guestFailure("unavailable", "connecta.emit is unavailable: no emission collector was configured for this execution", true);
@@ -630,7 +414,7 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
630
414
  },
631
415
  catch: (err) => err,
632
416
  }),
633
- search: (seq, raw) => discovery("search", seq, raw, async () => {
417
+ search: (raw) => discovery("search", async () => {
634
418
  const args = (raw ?? {});
635
419
  const result = flatSearchResult(await catalog.search({
636
420
  ...args,
@@ -639,7 +423,7 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
639
423
  boundedDiscoveryText(result, "Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.");
640
424
  return result;
641
425
  }),
642
- describe: (seq, raw) => discovery("describe", seq, raw, async () => {
426
+ describe: (raw) => discovery("describe", async () => {
643
427
  const args = (raw ?? {});
644
428
  const result = { tools: await catalog.describe(args) };
645
429
  boundedDiscoveryText(result, 'Split the address list or use format: "compact".');
@@ -659,22 +443,12 @@ function sandboxProvider(registry, baseUrl, activity, limits) {
659
443
  });
660
444
  return {
661
445
  name: "connecta",
662
- // Typed errors first, then the pinned clock and randomness: both are
663
- // trusted host code the program runs after and cannot undo.
664
- prelude: `${guestErrorPrelude(failureSecret)}\n${pinnedEnvironmentPrelude(runState?.environment ?? limits.environment ?? freshEnvironment())}`,
446
+ // Trusted host code the program runs after and cannot undo.
447
+ prelude: guestErrorPrelude(failureSecret),
665
448
  fns: Object.fromEntries(Object.entries(operations).map(([name, operation]) => [
666
449
  name,
667
450
  (...args) => {
668
- // Numbered here, synchronously, as the program makes the call: the
669
- // numbering is the program's own issue order, which is what makes
670
- // a pause point and a replay reproducible.
671
- const seq = runState && name !== "emit" ? runState.begin() : undefined;
672
- const settled = runEdge(Effect.suspend(() => operation(seq, ...args)).pipe(Effect.catch(framed), Effect.ensuring(Effect.sync(() => {
673
- if (seq !== undefined)
674
- runState?.decide(seq);
675
- }))));
676
- if (seq !== undefined)
677
- runState?.track(seq, settled);
451
+ const settled = runEdge(Effect.suspend(() => operation(...args)).pipe(Effect.catch(framed)));
678
452
  // The executor owns this promise, and a program may abandon a call
679
453
  // that the run's end then cancels before anyone has awaited it.
680
454
  // The rejection is still there for whoever does; it is just not an
@@ -770,34 +544,32 @@ function leased(executor, signal) {
770
544
  late: (lease) => lease.release(),
771
545
  }), (lease) => Effect.sync(() => lease.release()));
772
546
  }
547
+ /** Room for the executor queue a run waits in before its watchdog starts. */
548
+ const RUN_CLAIM_SLACK_MS = 10_000;
773
549
  /**
774
- * Room a claim leaves beyond the watchdog and one host-call deadline, for the
775
- * executor queue a resumed play waits in first. Only liveness rides on it: a
776
- * claim that lapses early lets a takeover start, and the write-ahead mark
777
- * still keeps any write from being sent twice.
550
+ * How long one run may take before whoever claimed it may give up on it: the
551
+ * watchdog, one more host-call deadline for an exempt write the play drains,
552
+ * and queue slack. Artifact refresh leases its claim for this long.
778
553
  */
779
- const CLAIM_SLACK_MS = 10_000;
780
- /** The runner behind both program tools. */
781
- export function createProgramRunner(registry, baseUrl, executor, logger, activity, config = {}) {
554
+ export function runClaimMs(config) {
555
+ return resolveBudget(config.watchdogMs, EXECUTE_WATCHDOG_MS) +
556
+ resolveBudget(config.hostCallTimeoutMs, EXECUTE_HOST_CALL_TIMEOUT_MS) +
557
+ RUN_CLAIM_SLACK_MS;
558
+ }
559
+ /** The execute_code handler. Exported for direct testing and artifact refresh. */
560
+ export function createExecuteTool(registry, baseUrl, executor, logger, activity, config = {}) {
782
561
  const watchdog = {
783
562
  ms: resolveBudget(config.watchdogMs, EXECUTE_WATCHDOG_MS),
784
563
  logger,
785
564
  };
786
565
  const hostCallTimeoutMs = resolveBudget(config.hostCallTimeoutMs, EXECUTE_HOST_CALL_TIMEOUT_MS);
787
- const storageTimeoutMs = resolveBudget(config.storageTimeoutMs, Math.max(hostCallTimeoutMs, MIN_STORAGE_TIMEOUT_MS));
788
- /**
789
- * One play of a program. Without a run state it is the execute_code run it
790
- * always was. With one, the play also ends when the run stops — a pause or
791
- * a typed failure — and then waits for what is still on the wire, persists
792
- * what it must, and discards the program's own result.
793
- */
794
- const play = (program, runState, diagnosticsRequested, callerSignal) => Effect.suspend(() => {
566
+ const play = (program, diagnosticsRequested, callerSignal) => Effect.suspend(() => {
795
567
  const diagnostics = diagnosticsRequested
796
568
  ? new ExecuteDiagnostics()
797
569
  : undefined;
798
570
  const emitted = new EmitCollector(resolveBudget(config.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES), resolveBudget(config.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS), diagnostics);
799
571
  const invocationFailures = [];
800
- const exemptWrites = runState ? undefined : new ExemptWrites();
572
+ const exemptWrites = new ExemptWrites();
801
573
  // The run's scope holds its signal and its lease. However the run
802
574
  // ends — a result, a thrown executor, the watchdog, cancellation —
803
575
  // closing it releases the lease and then aborts the signal, so
@@ -835,8 +607,6 @@ export function createProgramRunner(registry, baseUrl, executor, logger, activit
835
607
  maxHostCalls: config.maxHostCalls,
836
608
  hostCallTimeoutMs,
837
609
  defer: config.defer,
838
- environment: config.environment,
839
- runState,
840
610
  approval: config.approval,
841
611
  maxWrites: config.maxWrites,
842
612
  exemptWrites,
@@ -845,92 +615,48 @@ export function createProgramRunner(registry, baseUrl, executor, logger, activit
845
615
  return yield* Effect.fail(new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled during sandbox setup."));
846
616
  }
847
617
  const admitted = lease;
848
- const executing = awaitExecutor(() => admitted
849
- ? admitted.execute(program, [provider])
850
- : executor.execute(program, [provider]), signal, { watchdog }).pipe(Effect.map((settled) => ({ settled })));
851
- if (!runState) {
852
- // Nothing pauses, but an exempt write may be on the wire: close,
853
- // then let it finish before the scope aborts it.
854
- return yield* timed((elapsed) => {
855
- if (diagnostics)
856
- diagnostics.executorWallMs = elapsed;
857
- }, executing.pipe(Effect.ensuring(Effect.suspend(() => {
858
- exemptWrites?.close();
859
- return exemptWrites?.drain() ?? Effect.void;
860
- }))));
861
- }
862
- // The run stopping wins the race as soon as its gate decides, before
863
- // the program even sees the rejection. Either way the play closes —
864
- // a write gated after this is not sent — and what is still on the
865
- // wire finishes before the scope aborts it: every call after a stop,
866
- // since each is journaled, and every write otherwise, since an
867
- // abandoned write would be an unknown one.
868
- const ended = yield* timed((elapsed) => {
618
+ // An exempt write may still be on the wire when the program settles:
619
+ // close, so a write gated after this is not sent, then let the ones
620
+ // already dispatched finish before the scope aborts them — an
621
+ // abandoned write would be one whose outcome nobody knows.
622
+ return yield* timed((elapsed) => {
869
623
  if (diagnostics)
870
624
  diagnostics.executorWallMs = elapsed;
871
- }, Effect.raceFirst(executing, Deferred.await(runState.stopped).pipe(Effect.map((stop) => ({ stop })))).pipe(Effect.ensuring(Effect.suspend(() => {
872
- runState.close();
873
- return runState.drain("writes");
625
+ }, awaitExecutor(() => admitted
626
+ ? admitted.execute(program, [provider])
627
+ : executor.execute(program, [provider]), signal, { watchdog }).pipe(Effect.ensuring(Effect.suspend(() => {
628
+ exemptWrites.close();
629
+ return exemptWrites.drain();
874
630
  }))));
875
- if ("stop" in ended)
876
- yield* runState.drain("all");
877
- return ended;
878
631
  });
879
632
  const reported = { emitted, diagnostics, invocationFailures };
880
- return Effect.flatMap(Effect.exit(Effect.scoped(run)), (exit) => {
633
+ return Effect.map(Effect.exit(Effect.scoped(run)), (exit) => {
881
634
  if (Exit.isFailure(exit)) {
882
- const failed = failedRun(Cause.squash(exit.cause), logger, reported);
883
- return runState
884
- ? runState.finishExecutorFailure(failed)
885
- : Effect.succeed(exemptWrites?.finish(failed) ?? failed);
635
+ return exemptWrites.finish(failedRun(Cause.squash(exit.cause), logger, reported));
886
636
  }
887
- const ended = exit.value;
888
- if ("stop" in ended) {
889
- return runState
890
- ? runState.finishStopped()
891
- : Effect.die(new Error("a run without state cannot stop"));
892
- }
893
- const finished = finishedRun(ended.settled, reported);
637
+ const finished = finishedRun(exit.value, reported);
894
638
  if (config.failOnInvocationFailure && invocationFailures.length > 0) {
895
639
  const refusal = invocationFailures[0];
896
- return Effect.succeed(failureResponse(refusal.details.message, {
640
+ return failureResponse(refusal.details.message, {
897
641
  code: refusal.details,
898
- }));
642
+ });
899
643
  }
900
- return runState
901
- ? runState.finishSettled(finished)
902
- : Effect.succeed(exemptWrites?.finish(finished) ?? finished);
644
+ return exemptWrites.finish(finished);
903
645
  });
904
646
  });
905
- return {
906
- claimMs: watchdog.ms + hostCallTimeoutMs + CLAIM_SLACK_MS,
907
- storageTimeoutMs,
908
- execute: ({ code, diagnostics }, options = {}) => {
909
- // A code-unit count above the cap is already too large in UTF-8. Check
910
- // that first so a huge direct-call string is never encoded in full.
911
- if (code.length > EXECUTE_MAX_CODE_BYTES ||
912
- new TextEncoder().encode(code).byteLength > EXECUTE_MAX_CODE_BYTES) {
913
- const message = `execute_code code exceeds the ${EXECUTE_MAX_CODE_BYTES}-byte UTF-8 limit.`;
914
- return Promise.resolve(failureResponse(message, {
915
- code: { code: "invalid_args", message, retryable: false },
916
- }));
917
- }
918
- return runEdge(Effect.suspend(() => {
919
- const program = normalizeProgramSource(code);
920
- const storage = config.resumable ? registry.resultsStorage() : undefined;
921
- const runState = config.resumable && storage?.compareAndSet
922
- ? RunState.fresh(storage, program, config.environment ?? freshEnvironment(), config.resumable, storageTimeoutMs)
923
- : undefined;
924
- return play(program, runState, diagnostics, options.signal);
647
+ return ({ code, diagnostics }, options = {}) => {
648
+ // A code-unit count above the cap is already too large in UTF-8. Check
649
+ // that first so a huge direct-call string is never encoded in full.
650
+ if (code.length > EXECUTE_MAX_CODE_BYTES ||
651
+ new TextEncoder().encode(code).byteLength > EXECUTE_MAX_CODE_BYTES) {
652
+ const message = `execute_code code exceeds the ${EXECUTE_MAX_CODE_BYTES}-byte UTF-8 limit.`;
653
+ return Promise.resolve(failureResponse(message, {
654
+ code: { code: "invalid_args", message, retryable: false },
925
655
  }));
926
- },
927
- replay: (runState, signal) => play(runState.program, runState, false, signal),
656
+ }
657
+ return runEdge(Effect.suspend(() => play(normalizeProgramSource(code), diagnostics, options.signal)));
928
658
  };
929
659
  }
930
- /** The execute_code handler. Exported for direct testing. */
931
- export function createExecuteTool(registry, baseUrl, executor, logger, activity, config = {}) {
932
- return createProgramRunner(registry, baseUrl, executor, logger, activity, config).execute;
933
- }
934
660
  /** An execution that never produced an ExecuteResult, as the model sees it. */
935
661
  function failedRun(err, logger, { emitted, diagnostics }) {
936
662
  const logs = err !== null && typeof err === "object" && "logs" in err
@@ -1132,14 +858,12 @@ function connectorInventory(connectors) {
1132
858
  return `${prefix}${shown.join(", ")}.`;
1133
859
  return `${prefix}${shown.join(", ")}${shown.length > 0 ? "; " : ""}+${omitted} more.`;
1134
860
  }
1135
- const executeDescription = (emitBudgets, hostLimits, connectorGuides, connectors, resumable) => `Use the configured services below to answer the task. A known address uses call_tool. Unknown-address and wider ${resumable ? "work, writes included," : "read-only work"} uses one execute_code program for discovery, calls, and reduction. Do not return catalog matches alone. ${resumable
1136
- ? `Writes pause for resume_execution. Keep an authorized batch in one program; approval "tool" covers later same-address calls, other approval-required writes pause separately. Limits: ${hostLimits.maxHostCalls} host calls, ${resumable.maxWrites} writes`
1137
- : `Only readOnlyHint: true tools are available. Limits: ${hostLimits.maxHostCalls} host calls`}, ${hostLimits.hostCallTimeoutMs / 1_000}s/host call.
861
+ const executeDescription = (emitBudgets, hostLimits, connectorGuides, connectors, maxWrites) => `Use the configured services below to answer the task. A known address uses call_tool. Unknown-address and wider read-only work uses one execute_code program for discovery, calls, and reduction. Do not return catalog matches alone. Only readOnlyHint: true tools and config-exempt writes are available; any other write goes through call_destructive_tool. Limits: ${hostLimits.maxHostCalls} host calls, ${maxWrites} exempt writes, ${hostLimits.hostCallTimeoutMs / 1_000}s/host call.
1138
862
 
1139
863
  ${connectorInventory(connectors)}
1140
864
 
1141
865
  Read guides with top-level skills. Write async () => { ... } using the global connecta:
1142
- - connecta.search({ connector, query, ${resumable ? "" : 'safety: "readOnly", '}includeSchemas: "json" }) returns { tools }. Search operations separately; choose by connectorTitle and schema.required/.properties, never guessed fields. Compact schemas are text.
866
+ - connecta.search({ connector, query, safety: "readOnly", includeSchemas: "json" }) returns { tools }. Search operations separately; choose by connectorTitle and schema.required/.properties, never guessed fields. Compact schemas are text.
1143
867
  - connecta.describe({ address }) clarifies schemas.
1144
868
  - connecta.call(address, args) returns the provider value directly.
1145
869
  - Use Promise.all for independent calls, or Promise.allSettled to retain failures. Check status; missing values are unknown, never false or zero.
@@ -1173,7 +897,8 @@ export function registerExecuteTool(server, registry, ctx) {
1173
897
  hostCallTimeoutMs: resolveBudget(ctx.hostCallTimeoutMs, EXECUTE_HOST_CALL_TIMEOUT_MS),
1174
898
  };
1175
899
  const connectors = registry.listConnectors();
1176
- const runner = createProgramRunner(registry, ctx.baseUrl, ctx.executor, ctx.logger, ctx.activity, {
900
+ const maxWrites = resolveBudget(ctx.maxWrites, DEFAULT_MAX_WRITES);
901
+ const execute = createExecuteTool(registry, ctx.baseUrl, ctx.executor, ctx.logger, ctx.activity, {
1177
902
  discoveryConcurrency: ctx.discoveryConcurrency,
1178
903
  probeTimeoutMs: ctx.probeTimeoutMs,
1179
904
  maxEmittedBytes: emitBudgets.maxBytes,
@@ -1182,26 +907,25 @@ export function registerExecuteTool(server, registry, ctx) {
1182
907
  hostCallTimeoutMs: hostLimits.hostCallTimeoutMs,
1183
908
  watchdogMs: ctx.watchdogMs,
1184
909
  defer: ctx.defer,
1185
- resumable: ctx.resumable,
1186
910
  approval: ctx.approval,
1187
- maxWrites: ctx.maxWrites,
911
+ maxWrites,
1188
912
  });
1189
913
  server.registerTool("execute_code", {
1190
- description: executeDescription(emitBudgets, hostLimits, hasConnectorGuides(connectors), connectors, ctx.resumable),
914
+ description: executeDescription(emitBudgets, hostLimits, hasConnectorGuides(connectors), connectors, maxWrites),
1191
915
  inputSchema: EXECUTE_INPUT,
1192
916
  // This hint describes connector calls: only explicitly read-only ones
1193
- // run here, and any other call pauses the run unsent (W1) — the write
1194
- // itself runs inside resume_execution, which is annotated destructive.
1195
- // The supported executor constructions deny outbound access,
1196
- // filesystem, and deployment config; X5 documents Dynamic runtime
1197
- // modules separately.
917
+ // run here, plus the writes config exempts from asking (W12) — the
918
+ // deployment's decision, which no annotation can make for it. Any
919
+ // other write is refused (E4) and crosses call_destructive_tool, which
920
+ // is annotated destructive. The supported executor constructions deny
921
+ // outbound access, filesystem, and deployment config; X5 documents
922
+ // Dynamic runtime modules separately.
1198
923
  annotations: {
1199
924
  readOnlyHint: true,
1200
925
  destructiveHint: false,
1201
926
  openWorldHint: true,
1202
927
  },
1203
- }, (args, extra) => underAnySignal([extra.mcpReq.signal, ctx.requestSignal], (signal) => runner.execute(args, {
928
+ }, (args, extra) => underAnySignal([extra.mcpReq.signal, ctx.requestSignal], (signal) => execute(args, {
1204
929
  signal,
1205
930
  })));
1206
- return runner;
1207
931
  }