@zackbart/connecta 0.16.0 → 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 (51) hide show
  1. package/AGENTS.md +12 -5
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +6 -1
  4. package/dist/catalog-service.d.ts +4 -0
  5. package/dist/catalog-service.js +49 -5
  6. package/dist/catalog.d.ts +11 -0
  7. package/dist/catalog.js +134 -12
  8. package/dist/errors.d.ts +28 -2
  9. package/dist/errors.js +1 -0
  10. package/dist/execute.d.ts +5 -0
  11. package/dist/execute.js +229 -161
  12. package/dist/invocation.js +3 -1
  13. package/dist/meta-tools.d.ts +4 -0
  14. package/dist/meta-tools.js +46 -14
  15. package/dist/operator-ui/generated.d.ts +1 -1
  16. package/dist/operator-ui/generated.js +1 -1
  17. package/dist/operator-ui/model.d.ts +3 -1
  18. package/dist/providers/cloudflare.js +13 -25
  19. package/dist/providers/mixpanel.d.ts +3 -5
  20. package/dist/providers/mixpanel.js +73 -5
  21. package/dist/providers/stripe.d.ts +2 -2
  22. package/dist/providers/stripe.js +13 -11
  23. package/dist/registry.d.ts +32 -9
  24. package/dist/registry.js +217 -33
  25. package/dist/routes/mcp.js +6 -0
  26. package/dist/skills.d.ts +4 -0
  27. package/dist/skills.js +157 -18
  28. package/dist/types.d.ts +14 -2
  29. package/dist/ui.js +4 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/documentation/architecture.md +7 -4
  33. package/documentation/cloudflare.md +40 -8
  34. package/documentation/code-first-exploration.md +2 -2
  35. package/documentation/code-mode.md +45 -53
  36. package/documentation/connector-guides.md +24 -19
  37. package/documentation/connectors.md +13 -1
  38. package/documentation/meta-tools.md +33 -18
  39. package/documentation/mixpanel.md +20 -0
  40. package/documentation/notion.md +7 -2
  41. package/documentation/operations.md +74 -29
  42. package/documentation/operator-ui.md +12 -2
  43. package/documentation/provider-audit.md +4 -4
  44. package/documentation/provider-conventions.md +68 -19
  45. package/documentation/stripe.md +45 -14
  46. package/documentation/upgrading.md +478 -0
  47. package/ethos.md +4 -4
  48. package/examples/worker/README.md +13 -6
  49. package/package.json +7 -2
  50. package/templates/node/AGENTS.md +5 -0
  51. package/templates/node/package.json +1 -1
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) {
@@ -166,6 +167,26 @@ function matchingTokenCandidates(term) {
166
167
  }
167
168
  return candidates;
168
169
  }
170
+ /**
171
+ * Whether one query term matches a whole token of arbitrary text, under the
172
+ * same inflection rules the tool index uses.
173
+ *
174
+ * Connector identity — an `id` or a `title` — is deliberately not a document in
175
+ * that index: making it one would move ranking for every query that already
176
+ * matches tools. This lets a caller ask the index's question of a string that
177
+ * never became a document, which is what the no-match analysis needs to tell
178
+ * "nothing like this exists here" from "that word is a connector".
179
+ */
180
+ export function matchesLexicalTerm(text, term) {
181
+ const tokens = new Set(lexicalTokens(text));
182
+ if (tokens.size === 0)
183
+ return false;
184
+ for (const candidate of matchingTokenCandidates(term)) {
185
+ if (tokens.has(candidate))
186
+ return true;
187
+ }
188
+ return false;
189
+ }
169
190
  /**
170
191
  * Compute query-specific document frequencies across every available catalog.
171
192
  * The caller does this once per search and shares the result with each
@@ -303,7 +324,8 @@ function declaresShape(s) {
303
324
  s.const !== undefined ||
304
325
  s.items !== undefined ||
305
326
  s.properties !== undefined ||
306
- s.type !== undefined);
327
+ s.type !== undefined ||
328
+ constraintEntries(s).length > 0);
307
329
  }
308
330
  /**
309
331
  * Parenthesize a top-level union so it doesn't read as part of a surrounding
@@ -346,6 +368,56 @@ function renderEnum(values, byteLimit, onTruncated) {
346
368
  }
347
369
  return rendered;
348
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
+ }
349
421
  function renderSchema(schema, defs, seen, depth, options) {
350
422
  if (depth > 4)
351
423
  return "…";
@@ -385,24 +457,36 @@ function renderSchema(schema, defs, seen, depth, options) {
385
457
  seen.add(name);
386
458
  const rendered = renderSchema(target, defs, seen, depth, options);
387
459
  seen.delete(name);
388
- return rendered;
460
+ return options.renderConstraints
461
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
462
+ : rendered;
389
463
  }
390
464
  const union = (s.oneOf ?? s.anyOf);
391
465
  if (Array.isArray(union)) {
392
- return (union
466
+ const rendered = union
393
467
  .map((u) => renderSchema(u, defs, seen, depth + 1, options))
394
468
  .join(" | ") ||
395
- "unknown");
469
+ "unknown";
470
+ return options.renderConstraints
471
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
472
+ : rendered;
396
473
  }
397
474
  if (Array.isArray(s.enum)) {
398
- 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;
399
479
  }
400
480
  // Checked before type/properties so a discriminator like
401
481
  // { type: "string", const: "emoji" } renders as "emoji" rather than string.
402
482
  // JSON.stringify(undefined) returns undefined (not a string), so an explicit
403
483
  // `const: undefined` must fall through to the regular type rendering.
404
- if (s.const !== undefined)
405
- 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
+ }
406
490
  const type = s.type;
407
491
  if (type === "array" || s.items) {
408
492
  const items = s.items
@@ -434,10 +518,20 @@ function renderSchema(schema, defs, seen, depth, options) {
434
518
  })
435
519
  .join(", ")} }`;
436
520
  }
437
- if (typeof type === "string")
438
- return type;
439
- if (Array.isArray(type))
440
- 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
+ }
441
535
  return JSON.stringify(schema);
442
536
  }
443
537
  const compactSchemas = new WeakMap();
@@ -455,6 +549,7 @@ export function compactSchema(schema) {
455
549
  rendered = renderSchema(schema, defs, new Set(), 0, {
456
550
  propertyDescriptions: true,
457
551
  requiredFirst: false,
552
+ renderConstraints: true,
458
553
  });
459
554
  }
460
555
  catch {
@@ -514,6 +609,7 @@ export function compactDiscoverySchema(schema) {
514
609
  };
515
610
  let rendered;
516
611
  let enumTruncated = false;
612
+ let constraintTruncated = false;
517
613
  try {
518
614
  rendered = renderSchema(schema, defs, new Set(), 0, {
519
615
  propertyDescriptions: false,
@@ -525,15 +621,41 @@ export function compactDiscoverySchema(schema) {
525
621
  onEnumTruncated: () => {
526
622
  enumTruncated = true;
527
623
  },
624
+ renderConstraints: true,
625
+ constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
626
+ onConstraintTruncated: () => {
627
+ constraintTruncated = true;
628
+ },
528
629
  });
529
630
  }
530
631
  catch {
531
632
  rendered = JSON.stringify(schema);
532
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
+ }
533
652
  const bytes = schemaEncoder.encode(rendered);
534
653
  let result;
535
654
  if (bytes.length <= MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
536
- result = { text: rendered, truncated: enumTruncated };
655
+ result = {
656
+ text: rendered,
657
+ truncated: enumTruncated || constraintTruncated,
658
+ };
537
659
  }
538
660
  else {
539
661
  result = {
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,31 @@
1
- /** Machine-readable classification of a failed connector tool call. */
2
- export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited" | "unavailable" | "invalid_args" | "input_required_unsupported" | "connector_call_failed";
1
+ /**
2
+ * Machine-readable classification of a failed connector tool call.
3
+ *
4
+ * A code earns its place by changing what the caller does next, never by
5
+ * naming a cause — the rule provider conventions call H11.
6
+ */
7
+ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited" | "unavailable" | "invalid_args"
8
+ /**
9
+ * The downstream answered, and the thing addressed is not there.
10
+ *
11
+ * Its own code because the next move is none of the others': not a retry,
12
+ * not `authorize_connector`, not a reshaped argument object, but
13
+ * re-addressing — look the identifier up again, or accept the absence and
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.
18
+ *
19
+ * Use it only where the provider distinguishes absence from a permission
20
+ * gap. A status that means both "it is not there" and "you cannot see it" —
21
+ * Notion's `object_not_found` is the worked example — stays
22
+ * `connector_call_failed` with a message that states the ambiguity, because
23
+ * inventing certainty here is how an agent concludes a page was deleted when
24
+ * it was simply never shared. Addresses connecta itself cannot resolve are
25
+ * already framed as `unknown_address` or `unknown_tool` and never reach a
26
+ * connector, so this code is always about a resource the downstream owns.
27
+ */
28
+ | "not_found" | "input_required_unsupported" | "connector_call_failed";
3
29
  /** One bounded, payload-free explanation of an input-schema mismatch. */
4
30
  export interface ArgumentValidationIssue {
5
31
  /** JSON Pointer into the submitted arguments; "/" means the root value. */
package/dist/errors.js CHANGED
@@ -108,6 +108,7 @@ const RETRYABLE_BY_CODE = {
108
108
  unavailable: true,
109
109
  auth_required: false,
110
110
  invalid_args: false,
111
+ not_found: false,
111
112
  input_required_unsupported: false,
112
113
  connector_call_failed: false,
113
114
  };
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 {};