@zackbart/connecta 0.16.1 → 0.17.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 (42) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/catalog-service.d.ts +4 -0
  3. package/dist/catalog-service.js +8 -1
  4. package/dist/catalog.js +114 -12
  5. package/dist/errors.d.ts +4 -6
  6. package/dist/execute.d.ts +5 -0
  7. package/dist/execute.js +229 -161
  8. package/dist/invocation.js +3 -1
  9. package/dist/meta-tools.d.ts +4 -0
  10. package/dist/meta-tools.js +46 -14
  11. package/dist/operator-ui/generated.d.ts +1 -1
  12. package/dist/operator-ui/generated.js +1 -1
  13. package/dist/operator-ui/model.d.ts +3 -1
  14. package/dist/providers/mixpanel.d.ts +3 -5
  15. package/dist/providers/mixpanel.js +73 -5
  16. package/dist/providers/stripe.d.ts +2 -2
  17. package/dist/providers/stripe.js +13 -11
  18. package/dist/registry.d.ts +32 -9
  19. package/dist/registry.js +217 -33
  20. package/dist/routes/mcp.js +6 -0
  21. package/dist/skills.d.ts +4 -0
  22. package/dist/skills.js +157 -18
  23. package/dist/types.d.ts +14 -2
  24. package/dist/ui.js +4 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/documentation/architecture.md +7 -4
  28. package/documentation/code-mode.md +45 -53
  29. package/documentation/connector-guides.md +24 -19
  30. package/documentation/connectors.md +13 -1
  31. package/documentation/meta-tools.md +26 -17
  32. package/documentation/mixpanel.md +20 -0
  33. package/documentation/operations.md +21 -18
  34. package/documentation/operator-ui.md +12 -2
  35. package/documentation/provider-audit.md +3 -3
  36. package/documentation/provider-conventions.md +26 -13
  37. package/documentation/stripe.md +45 -14
  38. package/documentation/upgrading.md +28 -4
  39. package/ethos.md +3 -3
  40. package/examples/worker/README.md +4 -3
  41. package/package.json +1 -1
  42. package/templates/node/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,108 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.17.0 — 2026-08-13
6
+
7
+ This minor release makes catalog discovery faster and its answers more exact.
8
+ Agent reads can use a verified stale catalog while one bounded refresh runs,
9
+ guide summaries now read Markdown as prose, compact schemas retain more declared
10
+ constraints, array projection distinguishes misses from genuine nulls, and
11
+ Mixpanel carries the conditional rules its live tools enforce. One construction
12
+ contract tightens: an explicit `usageGuide.summary` over 120 characters now
13
+ refuses to boot. Deployments whose summaries fit, or which let Connecta derive
14
+ them, need no configuration change. Operator catalog reads retain their blocking
15
+ freshness behavior. This release also makes the two code sandboxes' runtime
16
+ differences explicit. The shipped
17
+ Worker example is already loader-only; deployments that added executor
18
+ `bindings`, `modules`, or `globalOutbound` must remove them. Existing portable
19
+ `execute_code` programs keep their behavior. Programs can now classify a caught
20
+ Connecta failure without parsing its message.
21
+
22
+ ### Changed
23
+
24
+ - An explicit `usageGuide.summary` longer than 120 characters after whitespace
25
+ normalization now throws during registry construction. Exactly 120 remains
26
+ valid, and a blank explicit summary still falls back to derivation (#392).
27
+
28
+ - **Agent catalog reads now serve a verified stale entry while they refresh it.**
29
+ Search, describe, and code-mode calls no longer await a downstream listing
30
+ when the runtime already holds a complete catalog inside its stale window.
31
+ The inbound request still causes the refresh; there is no timer, warmup, or
32
+ credential probe. One bounded refresh per connector owns and closes a fresh
33
+ scope, while operator status stays blocking and shows whether the last agent
34
+ read in this runtime was fresh or stale (#396).
35
+
36
+ ### Fixed
37
+
38
+ - **Caught `execute_code` failures now keep their machine-readable type.**
39
+ Calls, connector shortcuts, discovery, emitted-output and UI validation,
40
+ batch validation, and host-call budgets still throw with the same human
41
+ message, but now expose `code`, `retryable`, and full `details`. Batch entries
42
+ use the same codes. A per-run authenticated frame prevents connector prose or
43
+ guest code from forging the host transport on either executor (#393).
44
+
45
+ - **`execute_code` now tells the truth about each shipped sandbox.** QuickJS
46
+ has no `fetch`, `process`, timers, `crypto`, or `WebSocket`, and blocks
47
+ imports. Loader-only Dynamic Workers deny outbound fetch, WebSocket,
48
+ `node:net`, and `node:tls`; leave DNS unresolved; expose no environment
49
+ bindings or filesystem/HTTP builtins; but retain local `data:` fetch,
50
+ runtime globals, and a non-contract builtin set that can drift. The example
51
+ pins the required loader-only construction, and agent guidance tells portable
52
+ programs to use none of that Dynamic-only authority (#390).
53
+
54
+ - **Clerk-authenticated operator pages now wait for ClerkJS before booting.**
55
+ The loader runs before the later inline operator bundle instead of deferring
56
+ until after parsing, so a fresh page no longer mistakes normal script order
57
+ for a network failure. Clerk's major-to-pinned version redirect remains
58
+ supported, and a real loader failure keeps the existing clear error (#403).
59
+
60
+ - **Stripe's guide now treats connector identity as routing intent, not account
61
+ proof.** One OAuth session may cover several accounts in one organization,
62
+ so agents resolve the intended account through the live tool schema and stop
63
+ when the target or selector is ambiguous. The guide also keeps organization
64
+ accounts separate from the restricted-key-only Stripe Connect path (#404).
65
+
66
+ - **The no-account-model constitution now matches provider-owned sessions.**
67
+ Connecta still has no account dimension: credentials, storage, admission,
68
+ and health remain connector-scoped. A provider may expose its own account
69
+ scope only through its live schema; metadata never proves identity, and an
70
+ ambiguous target or selector stops instead of becoming a guess (#410).
71
+
72
+ - **The reviewed Notion page contracts are current again.** Notion added
73
+ create-page template and placement options plus update-page locking,
74
+ template, and erase options. The existing parent, properties, Markdown,
75
+ children, emoji, and trash request subsets remain valid, so this release
76
+ records the two changed endpoint digests without adding the new capabilities.
77
+ Their product decisions remain in #408 and #409.
78
+
79
+ - **Array field misses now report what happened.** A path that misses every
80
+ element appears in `unmatchedFields` instead of returning a clean array of
81
+ false nulls. A heterogeneous array keeps its positional result and names the
82
+ path in `partialFields`, so genuine downstream nulls remain distinguishable.
83
+ Schema-backed misses keep the same bounded guidance through nested arrays;
84
+ schema-free projections still report their observed misses (#394).
85
+ - Derived guide summaries now join a hard-wrapped opening paragraph before
86
+ selecting a complete sentence or shortening at a clause or word boundary.
87
+ Frontmatter, fences, headings, rules, tables, and description fallbacks keep
88
+ their prior roles; multi-line HTML comments are now skipped whole (#392).
89
+
90
+ - **Compact schemas now carry declared numeric and string constraints.** Search
91
+ and compact describe show numeric bounds, multiples, string length bounds,
92
+ patterns, and formats beside the affected type. Search keeps its 1,024-byte
93
+ schema ceiling and 256-byte node budget: a constraint that does not fit is
94
+ dropped whole, and the existing truncation flag sends the caller to describe
95
+ for the complete shape (#391).
96
+
97
+ - **Carry Mixpanel's three enforced conditional-input rules in its maintained
98
+ guide.** A live read-only audit confirmed that `Get-Business-Context`,
99
+ `Get-Property-Values`, and `List-Properties` accept shapes in their advertised
100
+ schemas that their implementations reject. Connecta still preserves the
101
+ hosted schemas unchanged; the guide now prevents those rejected calls, the
102
+ vetted manifest records schema digests for all 63 tools, and the provider
103
+ defect is tracked upstream. The maintainer drift check also frames
104
+ Mixpanel's service account as its documented `Bearer Basic` value instead of
105
+ ordinary HTTP Basic (#395).
106
+
5
107
  ## 0.16.1 — 2026-08-13
6
108
 
7
109
  This is the cleanup that follows 0.16.0 out the door: the packaging housekeeping
@@ -1,5 +1,6 @@
1
1
  import type { CallErrorDetails } from "./errors.js";
2
2
  import type { ConnectorOperationOptions, RegistryView } from "./registry.js";
3
+ import type { DeferredWork } from "./connector-scope.js";
3
4
  import type { Connector, ToolDef } from "./types.js";
4
5
  export declare const DEFAULT_SEARCH_LIMIT = 8;
5
6
  export declare const MAX_SEARCH_LIMIT = 100;
@@ -138,6 +139,7 @@ export declare class CatalogService {
138
139
  private readonly probeTimeoutMs;
139
140
  private readonly concurrency;
140
141
  private readonly searchRoute;
142
+ private readonly readOptions;
141
143
  private readonly loaded;
142
144
  private readonly loading;
143
145
  constructor(registry: RegistryView, baseUrl: string, options?: {
@@ -146,6 +148,8 @@ export declare class CatalogService {
146
148
  concurrency?: number;
147
149
  /** The discovery route recovery records name. Default `search_tools`. */
148
150
  searchRoute?: SearchRoute;
151
+ /** Runtime-owned tail for stale-while-revalidate catalog reads. */
152
+ defer?: DeferredWork;
149
153
  });
150
154
  /**
151
155
  * Send a caller back to discovery through the surface it can actually reach.
@@ -167,6 +167,7 @@ export class CatalogService {
167
167
  probeTimeoutMs;
168
168
  concurrency;
169
169
  searchRoute;
170
+ readOptions;
170
171
  loaded = new Map();
171
172
  loading = new Map();
172
173
  constructor(registry, baseUrl, options = {}) {
@@ -177,6 +178,12 @@ export class CatalogService {
177
178
  normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
178
179
  this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
179
180
  this.searchRoute = options.searchRoute ?? "search_tools";
181
+ this.readOptions = options.defer
182
+ ? {
183
+ defer: options.defer,
184
+ refreshTimeoutMs: this.probeTimeoutMs,
185
+ }
186
+ : undefined;
180
187
  }
181
188
  /**
182
189
  * Send a caller back to discovery through the surface it can actually reach.
@@ -207,7 +214,7 @@ export class CatalogService {
207
214
  if (inFlight)
208
215
  return inFlight;
209
216
  const loading = this.registry
210
- .getTools(id, this.baseUrl, this.requestScope, callOptions)
217
+ .getTools(id, this.baseUrl, this.requestScope, callOptions, this.readOptions)
211
218
  .then((tools) => {
212
219
  this.loaded.set(id, tools);
213
220
  return tools;
package/dist/catalog.js CHANGED
@@ -2,6 +2,7 @@ const DEFAULT_DESCRIPTION_LENGTH = 240;
2
2
  const DISCOVERY_DESCRIPTION_LENGTH = 160;
3
3
  export const MAX_COMPACT_DISCOVERY_SCHEMA_BYTES = 1_024;
4
4
  const MAX_COMPACT_DISCOVERY_ENUM_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
5
+ const MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES = MAX_COMPACT_DISCOVERY_SCHEMA_BYTES / 4;
5
6
  const schemaEncoder = new TextEncoder();
6
7
  const COMPACT_DISCOVERY_TRUNCATION = " /* truncated */";
7
8
  export function summarizeDescription(text, full) {
@@ -323,7 +324,8 @@ function declaresShape(s) {
323
324
  s.const !== undefined ||
324
325
  s.items !== undefined ||
325
326
  s.properties !== undefined ||
326
- s.type !== undefined);
327
+ s.type !== undefined ||
328
+ constraintEntries(s).length > 0);
327
329
  }
328
330
  /**
329
331
  * Parenthesize a top-level union so it doesn't read as part of a surrounding
@@ -366,6 +368,56 @@ function renderEnum(values, byteLimit, onTruncated) {
366
368
  }
367
369
  return rendered;
368
370
  }
371
+ function safeConstraintValue(value) {
372
+ return JSON.stringify(value).replaceAll("*/", "*\\/");
373
+ }
374
+ function constraintEntries(schema) {
375
+ const entries = [];
376
+ const number = (keyword, label) => {
377
+ const value = schema[keyword];
378
+ if (typeof value === "number" && Number.isFinite(value)) {
379
+ entries.push(`${label} ${value}`);
380
+ }
381
+ };
382
+ const integer = (keyword, label) => {
383
+ const value = schema[keyword];
384
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
385
+ entries.push(`${label} ${value}`);
386
+ }
387
+ };
388
+ number("minimum", ">=");
389
+ number("exclusiveMinimum", ">");
390
+ number("maximum", "<=");
391
+ number("exclusiveMaximum", "<");
392
+ number("multipleOf", "multiple of");
393
+ integer("minLength", "length >=");
394
+ integer("maxLength", "length <=");
395
+ if (typeof schema.format === "string") {
396
+ entries.push(`format ${safeConstraintValue(schema.format)}`);
397
+ }
398
+ if (typeof schema.pattern === "string") {
399
+ entries.push(`pattern ${safeConstraintValue(schema.pattern)}`);
400
+ }
401
+ return entries;
402
+ }
403
+ function renderConstraints(base, schema, byteLimit, onTruncated) {
404
+ const entries = constraintEntries(schema);
405
+ if (entries.length === 0)
406
+ return base;
407
+ const kept = [];
408
+ for (const entry of entries) {
409
+ const candidate = ` /* ${[...kept, entry].join("; ")} */`;
410
+ if (byteLimit !== undefined &&
411
+ schemaEncoder.encode(candidate).length > byteLimit) {
412
+ onTruncated?.();
413
+ continue;
414
+ }
415
+ kept.push(entry);
416
+ }
417
+ return kept.length === 0
418
+ ? base
419
+ : `${grouped(base)} /* ${kept.join("; ")} */`;
420
+ }
369
421
  function renderSchema(schema, defs, seen, depth, options) {
370
422
  if (depth > 4)
371
423
  return "…";
@@ -405,24 +457,36 @@ function renderSchema(schema, defs, seen, depth, options) {
405
457
  seen.add(name);
406
458
  const rendered = renderSchema(target, defs, seen, depth, options);
407
459
  seen.delete(name);
408
- return rendered;
460
+ return options.renderConstraints
461
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
462
+ : rendered;
409
463
  }
410
464
  const union = (s.oneOf ?? s.anyOf);
411
465
  if (Array.isArray(union)) {
412
- return (union
466
+ const rendered = union
413
467
  .map((u) => renderSchema(u, defs, seen, depth + 1, options))
414
468
  .join(" | ") ||
415
- "unknown");
469
+ "unknown";
470
+ return options.renderConstraints
471
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
472
+ : rendered;
416
473
  }
417
474
  if (Array.isArray(s.enum)) {
418
- return renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
475
+ const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
476
+ return options.renderConstraints
477
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
478
+ : rendered;
419
479
  }
420
480
  // Checked before type/properties so a discriminator like
421
481
  // { type: "string", const: "emoji" } renders as "emoji" rather than string.
422
482
  // JSON.stringify(undefined) returns undefined (not a string), so an explicit
423
483
  // `const: undefined` must fall through to the regular type rendering.
424
- if (s.const !== undefined)
425
- return JSON.stringify(s.const);
484
+ if (s.const !== undefined) {
485
+ const rendered = JSON.stringify(s.const);
486
+ return options.renderConstraints
487
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
488
+ : rendered;
489
+ }
426
490
  const type = s.type;
427
491
  if (type === "array" || s.items) {
428
492
  const items = s.items
@@ -454,10 +518,20 @@ function renderSchema(schema, defs, seen, depth, options) {
454
518
  })
455
519
  .join(", ")} }`;
456
520
  }
457
- if (typeof type === "string")
458
- return type;
459
- if (Array.isArray(type))
460
- return type.join(" | ");
521
+ if (typeof type === "string") {
522
+ return options.renderConstraints
523
+ ? renderConstraints(type, s, options.constraintByteLimit, options.onConstraintTruncated)
524
+ : type;
525
+ }
526
+ if (Array.isArray(type)) {
527
+ const rendered = type.join(" | ");
528
+ return options.renderConstraints
529
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
530
+ : rendered;
531
+ }
532
+ if (options.renderConstraints && constraintEntries(s).length > 0) {
533
+ return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
534
+ }
461
535
  return JSON.stringify(schema);
462
536
  }
463
537
  const compactSchemas = new WeakMap();
@@ -475,6 +549,7 @@ export function compactSchema(schema) {
475
549
  rendered = renderSchema(schema, defs, new Set(), 0, {
476
550
  propertyDescriptions: true,
477
551
  requiredFirst: false,
552
+ renderConstraints: true,
478
553
  });
479
554
  }
480
555
  catch {
@@ -534,6 +609,7 @@ export function compactDiscoverySchema(schema) {
534
609
  };
535
610
  let rendered;
536
611
  let enumTruncated = false;
612
+ let constraintTruncated = false;
537
613
  try {
538
614
  rendered = renderSchema(schema, defs, new Set(), 0, {
539
615
  propertyDescriptions: false,
@@ -545,15 +621,41 @@ export function compactDiscoverySchema(schema) {
545
621
  onEnumTruncated: () => {
546
622
  enumTruncated = true;
547
623
  },
624
+ renderConstraints: true,
625
+ constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
626
+ onConstraintTruncated: () => {
627
+ constraintTruncated = true;
628
+ },
548
629
  });
549
630
  }
550
631
  catch {
551
632
  rendered = JSON.stringify(schema);
552
633
  }
634
+ if (schemaEncoder.encode(rendered).length >
635
+ MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
636
+ try {
637
+ rendered = renderSchema(schema, defs, new Set(), 0, {
638
+ propertyDescriptions: false,
639
+ requiredFirst: true,
640
+ enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
641
+ onEnumTruncated: () => {
642
+ enumTruncated = true;
643
+ },
644
+ renderConstraints: false,
645
+ });
646
+ constraintTruncated = true;
647
+ }
648
+ catch {
649
+ rendered = JSON.stringify(schema);
650
+ }
651
+ }
553
652
  const bytes = schemaEncoder.encode(rendered);
554
653
  let result;
555
654
  if (bytes.length <= MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
556
- result = { text: rendered, truncated: enumTruncated };
655
+ result = {
656
+ text: rendered,
657
+ truncated: enumTruncated || constraintTruncated,
658
+ };
557
659
  }
558
660
  else {
559
661
  result = {
package/dist/errors.d.ts CHANGED
@@ -11,12 +11,10 @@ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited
11
11
  * Its own code because the next move is none of the others': not a retry,
12
12
  * not `authorize_connector`, not a reshaped argument object, but
13
13
  * re-addressing — look the identifier up again, or accept the absence and
14
- * carry on. Inside `execute_code` a program reads that difference off a
15
- * `connecta.batch` entry's `errorDetails.code` — continue past this one,
16
- * abort on `connector_call_failed` or lets the failure escape uncaught so
17
- * the model sees the typed envelope. Never off a caught error: the guest
18
- * bridge reduces a rejected host call to `new Error(message)` and drops
19
- * every own property, and message prose cannot be classified.
14
+ * carry on. Inside `execute_code` a program reads that difference from a
15
+ * caught error's `code` or a `connecta.batch` entry's `errorDetails.code` —
16
+ * continue past this one, abort on `connector_call_failed`. Message prose
17
+ * cannot be classified.
20
18
  *
21
19
  * Use it only where the provider distinguishes absence from a permission
22
20
  * gap. A status that means both "it is not there" and "you cannot see it" —
package/dist/execute.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server";
2
2
  import type { ActivityRequestContext } from "./activity.js";
3
+ import type { DeferredWork } from "./connector-scope.js";
3
4
  import { type ToolResult } from "./meta-tools.js";
4
5
  import { InvocationFailure } from "./invocation.js";
5
6
  import type { RegistryView } from "./registry.js";
@@ -157,6 +158,8 @@ export declare function buildSandboxProviders(registry: RegistryView, baseUrl: s
157
158
  * blocks nobody will ever return.
158
159
  */
159
160
  emitCollector?: EmitCollector;
161
+ /** Runtime-owned tail for stale catalog refreshes. */
162
+ defer?: DeferredWork;
160
163
  }): Promise<ExecutorProvider[]>;
161
164
  /** The execute_code handler. Exported for direct testing. */
162
165
  export declare function createExecuteTool(registry: RegistryView, baseUrl: string, executor: Executor, logger: Logger, activity?: ActivityRequestContext, config?: {
@@ -164,6 +167,7 @@ export declare function createExecuteTool(registry: RegistryView, baseUrl: strin
164
167
  probeTimeoutMs?: number;
165
168
  maxEmittedBytes?: number;
166
169
  maxEmittedBlocks?: number;
170
+ defer?: DeferredWork;
167
171
  }): ({ code, diagnostics: diagnosticsRequested }: {
168
172
  code: string;
169
173
  diagnostics?: boolean;
@@ -189,5 +193,6 @@ export declare function registerExecuteTool(server: McpServer, registry: Registr
189
193
  maxEmittedBytes?: number;
190
194
  /** Block-count budget for connecta.emit. Default 32. */
191
195
  maxEmittedBlocks?: number;
196
+ defer?: DeferredWork;
192
197
  }): void;
193
198
  export {};