@zackbart/connecta 0.22.3 → 0.23.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.
package/dist/execute.js CHANGED
@@ -1,15 +1,13 @@
1
1
  import { z } from "zod";
2
- import { PROGRAM_UI_META_KEY, PROGRAM_UI_RESOURCE_URI, } from "./apps-shell.js";
3
2
  import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, flatSearchResult, } from "./catalog-service.js";
4
3
  import { errorResult, jsonResult } from "./meta-tools.js";
5
4
  import { guardExecuteResultValue, MAX_EXECUTE_LOG_CHARS, truncateExecuteText, } from "./executor-result.js";
6
5
  import { ExecutorAdmissionError, ExecutorExecutionError, isAdmittingExecutor, } from "./executor-admission.js";
7
- import { boundedEchoText, classifyCallError, msg } from "./errors.js";
6
+ import { boundedEchoText, msg } from "./errors.js";
8
7
  import { InvocationFailure, InvocationService, } from "./invocation.js";
9
8
  import { connectorGuide, connectorGuideRequired, connectorSkillName, hasConnectorGuides, } from "./skills.js";
10
9
  /** Keep one model-written program from amplifying into an unbounded fan-out. */
11
10
  const EXECUTE_MAX_HOST_CALLS = 20;
12
- export const EXECUTE_MAX_BATCH_CALLS = 10;
13
11
  const EXECUTE_HOST_CALL_TIMEOUT_MS = 15_000;
14
12
  /** Complete entries plus an exact omission count, all inside this byte cap. */
15
13
  export const CONNECTOR_INVENTORY_MAX_BYTES = 256;
@@ -30,21 +28,12 @@ class ExecuteDiagnostics {
30
28
  setupMs = 0;
31
29
  executorWallMs = 0;
32
30
  emitted;
33
- ui;
34
31
  /** Numbers only, per R8 — and only once something was emitted, so a
35
32
  * non-emitting run's diagnostics stay byte-for-byte what they were. */
36
33
  recordEmitted(count, bytes) {
37
34
  if (count > 0)
38
35
  this.emitted = { count, bytes };
39
36
  }
40
- /**
41
- * U9: the UI payload gets its own aggregate — one number, the payload's
42
- * serialized size. Folding it into `emitted` would desync that aggregate's
43
- * pair, which reports the bytes a specific block count cost.
44
- */
45
- recordUi(bytes) {
46
- this.ui = bytes;
47
- }
48
37
  stats(operation) {
49
38
  let stats = this.operations.get(operation);
50
39
  if (!stats) {
@@ -70,36 +59,16 @@ class ExecuteDiagnostics {
70
59
  if (ok)
71
60
  stats.resultBytes += serializedDiagnosticBytes(result);
72
61
  }
73
- recordCall(operation, outcome) {
74
- const stats = this.stats(operation);
75
- if (operation === "call") {
76
- stats.count++;
77
- if (outcome.ok) {
78
- stats.resultBytes += serializedDiagnosticBytes(outcome.value);
79
- }
80
- }
81
- else {
82
- stats.calls = (stats.calls ?? 0) + 1;
83
- }
62
+ recordCall(outcome) {
63
+ const stats = this.stats("call");
64
+ stats.count++;
65
+ if (outcome.ok)
66
+ stats.resultBytes += serializedDiagnosticBytes(outcome.value);
84
67
  stats.failures += outcome.ok ? 0 : 1;
85
- if (operation === "call")
86
- stats.durationMs += outcome.durationMs;
68
+ stats.durationMs += outcome.durationMs;
87
69
  stats.catalogMs += outcome.timing.catalogMs;
88
70
  stats.connectorMs += outcome.timing.connectorMs;
89
71
  }
90
- recordBatch(durationMs, ok, calls, result) {
91
- const stats = this.stats("batch");
92
- stats.count++;
93
- // Calls normally accrue while each child runs. Invalid batch input never
94
- // starts children, so retain the attempted cardinality here.
95
- if (!ok)
96
- stats.calls = Math.max(stats.calls ?? 0, calls);
97
- stats.durationMs += durationMs;
98
- if (!ok)
99
- stats.failures++;
100
- else
101
- stats.resultBytes += serializedDiagnosticBytes(result);
102
- }
103
72
  finish() {
104
73
  const operations = [...this.operations.values()];
105
74
  return {
@@ -113,7 +82,6 @@ class ExecuteDiagnostics {
113
82
  },
114
83
  operations,
115
84
  ...(this.emitted ? { emitted: this.emitted } : {}),
116
- ...(this.ui !== undefined ? { ui: this.ui } : {}),
117
85
  };
118
86
  }
119
87
  }
@@ -148,58 +116,12 @@ function requireEmittedBlock(raw) {
148
116
  }
149
117
  return raw;
150
118
  }
151
- const UI_SHAPE_HINT = "connecta.ui accepts exactly one argument: a non-empty string of HTML";
152
- /** What the argument was, named the way the emit validator names a bad field. */
153
- function describeUiArgument(raw) {
154
- if (raw === null)
155
- return "null";
156
- if (raw === undefined)
157
- return "undefined";
158
- if (Array.isArray(raw))
159
- return "an array";
160
- if (typeof raw === "string")
161
- return "an empty string";
162
- const kind = typeof raw;
163
- return `${/^[aeiou]/.test(kind) ? "an" : "a"} ${kind}`;
164
- }
165
- /**
166
- * Strict U1 validation. There is no options parameter and no sugar form, for
167
- * M1's reason: sugar is how a one-shape contract grows hair. An options bag or
168
- * an MCP block object is just a non-string, and fails as one.
169
- */
170
- function requireUiHtml(raw) {
171
- if (typeof raw !== "string" || raw.length === 0) {
172
- throw guestFailure("invalid_args", `${UI_SHAPE_HINT}; got ${describeUiArgument(raw)}`);
173
- }
174
- return raw;
175
- }
176
- function requireUiPayload(values) {
177
- if (values.length !== 1) {
178
- throw guestFailure("invalid_args", `${UI_SHAPE_HINT}; got ${values.length} arguments`);
179
- }
180
- return { html: requireUiHtml(values[0]) };
181
- }
182
- /**
183
- * Request-local collection for `connecta.emit` and `connecta.ui`. Budgets fail
184
- * loudly at the crossing call — nothing is partially accepted and prior blocks
185
- * are unaffected — so a program learns it is over budget while it can still
186
- * choose differently (M5, U4). Accepted output never rides `ExecuteResult`; the
187
- * handler that owns this collector delivers it on the final tool result.
188
- *
189
- * The two channels share the byte aggregate and nothing else: a UI payload is
190
- * not a block, so it spends no block count, and at most one is ever accepted.
191
- */
192
119
  export class EmitCollector {
193
120
  maxBytes;
194
121
  maxBlocks;
195
122
  diagnostics;
196
123
  blocks = [];
197
- /** The shared transport aggregate: emitted blocks plus the UI payload. */
198
124
  bytes = 0;
199
- /** The one accepted UI payload (U2), delivered in result `_meta` on success. */
200
- ui;
201
- /** What the blocks alone cost, so the `emitted` aggregate stays a true pair. */
202
- blockBytes = 0;
203
125
  constructor(maxBytes, maxBlocks, diagnostics) {
204
126
  this.maxBytes = maxBytes;
205
127
  this.maxBlocks = maxBlocks;
@@ -216,45 +138,7 @@ export class EmitCollector {
216
138
  }
217
139
  this.blocks.push(block);
218
140
  this.bytes += size;
219
- this.blockBytes += size;
220
- this.diagnostics?.recordEmitted(this.blocks.length, this.blockBytes);
221
- }
222
- /**
223
- * U2 and U4: one payload per run, measured as the serialized bytes of
224
- * `{ html }` against the same aggregate emit spends. A second call throws
225
- * naming the constraint rather than replacing the first — one tool result
226
- * renders one view, and last-wins would silently discard a payload the
227
- * program deliberately supplied.
228
- *
229
- * Multiplicity is checked before shape, so the second call is told what it
230
- * actually broke. A program whose second payload is also malformed has one
231
- * problem worth naming — that there is a second payload at all — and a
232
- * complaint about its type would send the author to fix the wrong thing.
233
- */
234
- acceptUi(...values) {
235
- this.assertUiVacant();
236
- this.acceptUiPayload(requireUiPayload(values));
237
- }
238
- assertUiVacant() {
239
- if (this.ui) {
240
- throw guestFailure("invalid_args", "connecta.ui accepts at most one payload per run: a view was already accepted and stands");
241
- }
242
- }
243
- acceptUiPayload(payload) {
244
- let serialized;
245
- try {
246
- serialized = JSON.stringify(payload);
247
- }
248
- catch {
249
- throw guestFailure("invalid_args", "connecta.ui payload must be JSON-serializable");
250
- }
251
- const size = diagnosticsEncoder.encode(serialized).byteLength;
252
- if (this.bytes + size > this.maxBytes) {
253
- throw guestFailure("budget_exceeded", `connecta.ui byte budget exceeded: payload is ${size} serialized bytes with ${this.maxBytes - this.bytes} of ${this.maxBytes} remaining`);
254
- }
255
- this.ui = payload;
256
- this.bytes += size;
257
- this.diagnostics?.recordUi(size);
141
+ this.diagnostics?.recordEmitted(this.blocks.length, this.bytes);
258
142
  }
259
143
  }
260
144
  /** A configured emit budget must be a finite number >= 1; anything else falls back. */
@@ -276,85 +160,6 @@ function serializedDiagnosticBytes(value) {
276
160
  return 0;
277
161
  }
278
162
  }
279
- // deno-fmt-ignore
280
- const RESERVED = new Set([
281
- "break",
282
- "case",
283
- "catch",
284
- "class",
285
- "const",
286
- "continue",
287
- "debugger",
288
- "default",
289
- "delete",
290
- "do",
291
- "else",
292
- "enum",
293
- "export",
294
- "extends",
295
- "false",
296
- "finally",
297
- "for",
298
- "function",
299
- "if",
300
- "import",
301
- "in",
302
- "instanceof",
303
- "let",
304
- "new",
305
- "null",
306
- "return",
307
- "static",
308
- "super",
309
- "switch",
310
- "this",
311
- "throw",
312
- "true",
313
- "try",
314
- "typeof",
315
- "var",
316
- "void",
317
- "while",
318
- "with",
319
- "yield",
320
- "await",
321
- "async",
322
- ]);
323
- /** Convert a connector/tool name into a valid JS identifier. */
324
- export function sanitizeIdentifier(name) {
325
- let id = name.replace(/[^A-Za-z0-9_$]/g, "_");
326
- if (/^[0-9]/.test(id))
327
- id = `_${id}`;
328
- if (RESERVED.has(id))
329
- id = `${id}_`;
330
- return id;
331
- }
332
- const SANDBOX_RESERVED_NAMES = new Set([
333
- "connecta",
334
- "console",
335
- "arguments",
336
- "result",
337
- "undefined",
338
- "setTimeout",
339
- "Promise",
340
- "Error",
341
- "WorkerEntrypoint",
342
- "CodeExecutor",
343
- "__invoke",
344
- "__namespace",
345
- "__call",
346
- "__log",
347
- "__dispatchers",
348
- "__connectors",
349
- "__logs",
350
- "__CODEMODE_BINARY_TAG",
351
- "__bytesToBase64",
352
- "__base64ToBytes",
353
- "__encodeCodemodeValue",
354
- "__decodeCodemodeValue",
355
- "__stringifyForCodemode",
356
- "__parseForCodemode",
357
- ]);
358
163
  function guestFailure(code, message, retryable = false) {
359
164
  return new InvocationFailure({ code, message, retryable });
360
165
  }
@@ -373,15 +178,8 @@ function framedGuestFailure(secret, failure) {
373
178
  guestFailureFrames.set(failure, framed.message);
374
179
  return framed;
375
180
  }
376
- /**
377
- * Rebuilds host failures as guest Error instances, then installs connector
378
- * shortcuts. The random frame is hidden in this closure and Error is locked:
379
- * connector prose and model code can neither collide with nor forge its frame.
380
- */
381
- function lazyNamespacePrelude(connectors, failureSecret) {
382
- const declarations = connectors
383
- .map(({ id, namespace }) => `globalThis[${JSON.stringify(namespace)}] = __makeConnectaNamespace(${JSON.stringify(id)});`)
384
- .join("\n");
181
+ /** Rebuild host failures as guest errors without exposing the private frame. */
182
+ function guestErrorPrelude(failureSecret) {
385
183
  return `((failurePrefix) => {
386
184
  const NativeError = globalThis.Error;
387
185
  const startsWith = Function.prototype.call.bind(String.prototype.startsWith);
@@ -417,20 +215,13 @@ function lazyNamespacePrelude(connectors, failureSecret) {
417
215
  writable: false,
418
216
  configurable: false
419
217
  });
420
- const __makeConnectaNamespace = (connectorId) => Object.freeze(new Proxy(Object.create(null), {
421
- get: (_target, toolName) => typeof toolName === "string"
422
- ? (args) => connecta.__callNamespace(connectorId, toolName, args)
423
- : undefined
424
- }));
425
- ${declarations}
426
218
  })(${JSON.stringify(`${GUEST_FAILURE_FRAME}${failureSecret}:`)});`;
427
219
  }
428
220
  /**
429
- * Expose one fixed host provider plus trusted sandbox setup that creates a
430
- * lazy proxy global per connector. No connector catalog is touched until code
431
- * calls that namespace or explicitly asks search/describe.
221
+ * Expose one host provider and typed guest errors. Catalogs load only when
222
+ * the program calls a tool or asks search/describe.
432
223
  */
433
- export async function buildSandboxProviders(registry, baseUrl, logger, activity, limits = {}) {
224
+ export async function buildSandboxProviders(registry, baseUrl, _logger, activity, limits = {}) {
434
225
  // All host calls made by one execute_code invocation share a downstream
435
226
  // connection, while a later invocation receives a fresh request scope.
436
227
  const requestScope = {};
@@ -447,24 +238,6 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
447
238
  const hostCallTimeoutMs = Math.max(1, Math.trunc(limits.hostCallTimeoutMs ?? EXECUTE_HOST_CALL_TIMEOUT_MS));
448
239
  const failureSecret = guestFailureSecret();
449
240
  let hostCalls = 0;
450
- const connectors = registry.listConnectors();
451
- const namespaces = [];
452
- const namespaceOwners = new Map();
453
- for (const connector of connectors) {
454
- const namespace = sanitizeIdentifier(connector.id);
455
- const owner = namespaceOwners.get(namespace);
456
- const problem = SANDBOX_RESERVED_NAMES.has(namespace)
457
- ? `Connector "${connector.id}" sanitizes to reserved execute_code namespace "${namespace}". Rename or exclude the connector before using execute_code.`
458
- : owner
459
- ? `Connector ids "${owner}" and "${connector.id}" both sanitize to execute_code namespace "${namespace}". Rename or exclude one connector before using execute_code.`
460
- : undefined;
461
- if (problem) {
462
- logger.warn(`[connecta] execute_code: ${problem}`);
463
- throw new Error(problem);
464
- }
465
- namespaceOwners.set(namespace, connector.id);
466
- namespaces.push({ id: connector.id, namespace });
467
- }
468
241
  const invocationContext = () => ({
469
242
  source: "execute_code",
470
243
  timeoutMs: hostCallTimeoutMs,
@@ -504,21 +277,14 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
504
277
  throw err;
505
278
  }
506
279
  };
507
- const called = async (operation, invoke) => {
508
- const outcome = await invoke();
509
- limits.diagnostics?.recordCall(operation, outcome);
280
+ const callAddress = async (address, args) => {
281
+ const outcome = await invocation.invoke(String(address), args ?? {}, invocationContext());
282
+ limits.diagnostics?.recordCall(outcome);
510
283
  if (!outcome.ok)
511
284
  throw new InvocationFailure(outcome.error);
512
285
  return outcome.value;
513
286
  };
514
- const callAddress = async (address, args, diagnosticOperation = "call") => {
515
- return called(diagnosticOperation, () => invocation.invoke(String(address), args ?? {}, invocationContext()));
516
- };
517
- const callNamespace = async (connectorId, toolAlias, args) => {
518
- return called("call", () => invocation.invokeToolAlias(String(connectorId), String(toolAlias), sanitizeIdentifier, args ?? {}, invocationContext()));
519
- };
520
287
  const fns = {
521
- __callNamespace: callNamespace,
522
288
  call: (address, args) => callAddress(address, args),
523
289
  // Emission is a provider function, never an ExecuteResult field —
524
290
  // that is what keeps the Executor contract untouched and parity
@@ -530,55 +296,6 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
530
296
  }
531
297
  limits.emitCollector.accept(block);
532
298
  },
533
- // The rendered-output channel rides the same bridge for the same
534
- // reason (U7): one more provider fn, no change to ExecuteResult or
535
- // the Executor contract. Delivery is the handler's job, not the
536
- // guest's — nothing here becomes addressable.
537
- ui: async (...values) => {
538
- if (!limits.emitCollector) {
539
- throw guestFailure("unavailable", "connecta.ui is unavailable: no emission collector was configured for this execution", true);
540
- }
541
- limits.emitCollector.acceptUi(...values);
542
- },
543
- batch: async (calls) => {
544
- const started = Date.now();
545
- const callCount = Array.isArray(calls) ? calls.length : 0;
546
- try {
547
- if (!Array.isArray(calls)) {
548
- throw guestFailure("invalid_args", "calls must be an array");
549
- }
550
- if (calls.length > EXECUTE_MAX_BATCH_CALLS) {
551
- throw guestFailure("invalid_args", `connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS} calls`);
552
- }
553
- const result = await Promise.all(calls.map(async (call) => {
554
- const item = call;
555
- try {
556
- return {
557
- address: String(item.address),
558
- ok: true,
559
- data: await callAddress(item.address, item.args, "batch"),
560
- };
561
- }
562
- catch (err) {
563
- const details = err instanceof InvocationFailure
564
- ? err.details
565
- : classifyCallError(err, "batch_call_failed");
566
- return {
567
- address: String(item.address),
568
- ok: false,
569
- error: details.message,
570
- errorDetails: details,
571
- };
572
- }
573
- }));
574
- limits.diagnostics?.recordBatch(Date.now() - started, true, callCount, result);
575
- return result;
576
- }
577
- catch (err) {
578
- limits.diagnostics?.recordBatch(Date.now() - started, false, callCount);
579
- throw err;
580
- }
581
- },
582
299
  search: async (raw) => timedCatalog("search", () => typedDiscovery(async () => {
583
300
  const args = (raw ?? {});
584
301
  const result = flatSearchResult(await catalog.search({
@@ -614,7 +331,7 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
614
331
  return [
615
332
  {
616
333
  name: "connecta",
617
- prelude: lazyNamespacePrelude(namespaces, failureSecret),
334
+ prelude: guestErrorPrelude(failureSecret),
618
335
  fns: transportedFns,
619
336
  },
620
337
  ];
@@ -790,19 +507,9 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
790
507
  const response = jsonResult({
791
508
  result,
792
509
  ...(emitted.blocks.length > 0 ? { emitted: emitted.blocks.length } : {}),
793
- // U3: the model learns a view rendered without seeing its bytes. Spread
794
- // conditionally so a program that never called connecta.ui produces
795
- // today's byte-for-byte response.
796
- ...(emitted.ui ? { ui: true } : {}),
797
510
  ...(logs ? { logs } : {}),
798
511
  ...(diagnostics ? { diagnostics: diagnostics.finish() } : {}),
799
512
  });
800
- if (emitted.ui) {
801
- // _meta is where the Apps spec's best practices put data "not intended
802
- // for model context", and how shipped hosts behave. The shell reads
803
- // exactly this key out of the tool result the host delivers to it.
804
- response._meta = { [PROGRAM_UI_META_KEY]: emitted.ui };
805
- }
806
513
  if (emitted.blocks.length > 0) {
807
514
  // Emitted image/audio blocks are valid MCP content that ToolResult's
808
515
  // text-only typing does not model — the same acknowledged gap
@@ -829,42 +536,35 @@ function failureResponse(message, options) {
829
536
  return errorResult(`${message}${logs ? `\n\nLogs:\n${logs}` : ""}${emitted ? discardedEmitsText(emitted) : ""}`);
830
537
  }
831
538
  /**
832
- * M4 and U3: a failed program delivers no blocks and no view, but each
833
- * discard is visible — and one failure can discard both.
539
+ * A failed program delivers no blocks and reports how many were discarded.
834
540
  */
835
541
  function discardedEmits(emitted) {
836
- return {
837
- ...(emitted.blocks.length > 0
838
- ? { emittedDiscarded: emitted.blocks.length }
839
- : {}),
840
- ...(emitted.ui ? { uiDiscarded: true } : {}),
841
- };
542
+ return emitted.blocks.length > 0
543
+ ? { emittedDiscarded: emitted.blocks.length }
544
+ : {};
842
545
  }
843
546
  /** The same visibility for the plain-text error paths. */
844
547
  function discardedEmitsText(emitted) {
845
- const lines = [
846
- ...(emitted.blocks.length > 0
847
- ? [`emittedDiscarded: ${emitted.blocks.length}`]
848
- : []),
849
- ...(emitted.ui ? ["uiDiscarded: true"] : []),
850
- ];
851
- return lines.length > 0 ? `\n\n${lines.join("\n")}` : "";
548
+ return emitted.blocks.length > 0
549
+ ? `\n\nemittedDiscarded: ${emitted.blocks.length}`
550
+ : "";
852
551
  }
853
552
  function connectorInventory(connectors) {
854
553
  const prefix = "Connectors: ";
855
554
  if (connectors.length === 0)
856
555
  return `${prefix}none.`;
857
556
  const entries = connectors.map((connector) => {
858
- const shortcut = sanitizeIdentifier(connector.id);
859
- const address = shortcut === connector.id
860
- ? connector.id
861
- : `${connector.id} (shortcut ${shortcut})`;
557
+ const address = connector.id;
558
+ const title = connector.title?.replace(/\s+/g, " ").trim();
559
+ const label = title && title !== connector.id
560
+ ? `${address}: ${boundedEchoText(title, 45)}`
561
+ : address;
862
562
  if (!connectorGuide(connector))
863
- return address;
563
+ return label;
864
564
  const requirement = connectorGuideRequired(connector)
865
565
  ? "required guide"
866
566
  : "guide";
867
- return `${address} (${requirement} ${connectorSkillName(connector.id)})`;
567
+ return `${label} (${requirement} ${connectorSkillName(connector.id)})`;
868
568
  });
869
569
  const shown = [];
870
570
  for (let index = 0; index < entries.length; index++) {
@@ -885,18 +585,19 @@ function connectorInventory(connectors) {
885
585
  return `${prefix}${shown.join(", ")}.`;
886
586
  return `${prefix}${shown.join(", ")}${shown.length > 0 ? "; " : ""}+${omitted} more.`;
887
587
  }
888
- const executeDescription = (emitBudgets, connectorGuides, connectors) => `Choose the route before discovery. A known address uses call_tool. Unknown-address and wider read-only work use exactly one execute_code call that discovers, calls, and returns the answer. Finish in that program; don't return catalog matches for a later call. Only readOnlyHint: true tools are available. Limits: ${EXECUTE_MAX_HOST_CALLS} host calls per run, ${EXECUTE_MAX_BATCH_CALLS} per batch, ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second host deadline.
588
+ const executeDescription = (emitBudgets, connectorGuides, connectors) => `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 are available. Limits: ${EXECUTE_MAX_HOST_CALLS} host calls, ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}s/host call.
889
589
 
890
590
  ${connectorInventory(connectors)}
891
591
 
892
- Fetch required guides named above before executing. Write one plain-JavaScript async arrow function. Use only:
893
- - <connectorId>.<toolName>(args) for a sanitized shortcut, or connecta.call(address, args) for a canonical address.
894
- - connecta.search(args) returns { tools }; connecta.describe(args) returns { tools }; use entry key lists. connecta.batch(calls) accepts canonical connector addresses only.
895
- - connecta.emit(block) — { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }. Success-only; ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes; invalid/over-budget throws.
896
- - connecta.ui(html) for one display-only, success-only view; return the same summary the HTML renders.
897
- - console.log(...) captured.
592
+ Read relevant guides using top-level skills, not sandbox code. Write a plain-JavaScript async arrow:
593
+ - connecta.search({ connector, query, safety: "readOnly", includeSchemas: "json" }) returns { tools }. Search each operation separately; choose by connectorTitle and schemas. Use schema.required and .properties to build args, never guessed fields. Compact schemas are text.
594
+ - connecta.describe({ address }) returns { tools } for unclear schemas.
595
+ - connecta.call(address, args) returns the provider value directly.
596
+ - Use Promise.all for independent calls, or Promise.allSettled to retain failures. Check status before reading value; rejected calls and missing fields are unknown, never false or zero.
597
+ - connecta.emit(block): { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }; success-only, ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes.
598
+ - console.log(...) is captured. Return data for the client to render.
898
599
 
899
- No portable ambient capabilities. Return JSON; reduce large results before truncation. Build arguments from required input keys and schemas, never descriptions or output keys. Fetch skills({ name: "usage" }) only when this is insufficient or repair is needed; it has full rules, examples${connectorGuides ? ", guide handling" : ""}, and runtime details.`;
600
+ No portable ambient capabilities. Return reduced JSON. If a provider result has an unfamiliar shape, return a small sample and continue in another call; never guess fields or use the whole text as an id. Top-level skills({ name: "usage" }): repair${connectorGuides ? ", guide handling" : ""}; skills({ name: "investigate" }): task planning.`;
900
601
  /** Register the execute_code meta-tool. Only called when an executor is configured. */
901
602
  export function registerExecuteTool(server, registry, ctx) {
902
603
  // Resolved once so the description and the collector cannot disagree about
@@ -932,14 +633,6 @@ export function registerExecuteTool(server, registry, ctx) {
932
633
  destructiveHint: false,
933
634
  openWorldHint: true,
934
635
  },
935
- // U5 and U10 are specified in documentation/code-mode.md; explicit model
936
- // visibility prevents hosts from offering execute_code to the view.
937
- _meta: {
938
- ui: {
939
- resourceUri: PROGRAM_UI_RESOURCE_URI,
940
- visibility: ["model"],
941
- },
942
- },
943
636
  }, async (args, extra) => {
944
637
  const controller = new AbortController();
945
638
  const signals = [extra.mcpReq.signal, ctx.requestSignal].filter((signal) => signal !== undefined);
@@ -54,13 +54,6 @@ export interface ExecutionPayload {
54
54
  */
55
55
  timedOut?: boolean;
56
56
  }
57
- /**
58
- * How a host call should be named in an error a program will read. The lazy
59
- * connector namespaces all dispatch through one internal function, so the raw
60
- * provider/function pair would report every shortcut call as
61
- * `connecta.__callNamespace` — an internal name that appears nowhere in the
62
- * documented surface. Report the address the program actually called.
63
- */
64
57
  export declare function hostCallLabel(payload: {
65
58
  namespace: string;
66
59
  functionName: string;
@@ -8,17 +8,9 @@ export const MAX_QUICKJS_IPC_BYTES = 1024 * 1024;
8
8
  export const MAX_QUICKJS_LOG_TRANSPORT_BYTES = 512 * 1024;
9
9
  /** Preserve #84's stopgap before a host value enters the child/WASM process. */
10
10
  export const MAX_QUICKJS_HOST_RPC_BYTES = 256 * 1024;
11
- /**
12
- * How a host call should be named in an error a program will read. The lazy
13
- * connector namespaces all dispatch through one internal function, so the raw
14
- * provider/function pair would report every shortcut call as
15
- * `connecta.__callNamespace` — an internal name that appears nowhere in the
16
- * documented surface. Report the address the program actually called.
17
- */
18
11
  export function hostCallLabel(payload) {
19
- if (payload.functionName === "__callNamespace") {
20
- const [connectorId, toolAlias] = payload.args;
21
- return `${String(connectorId)}.${String(toolAlias)}`;
12
+ if (payload.namespace === "connecta" && payload.functionName === "call") {
13
+ return String(payload.args[0]);
22
14
  }
23
15
  return `${payload.namespace}.${payload.functionName}`;
24
16
  }
@@ -411,7 +411,7 @@ class QuickJsChildPool {
411
411
  }
412
412
  catch (err) {
413
413
  // The guest reads this text, so it names the address the program called
414
- // rather than the internal dispatcher every shortcut namespace shares.
414
+ // rather than only the generic connecta.call bridge function.
415
415
  const label = hostCallLabel(payload);
416
416
  const detail = err instanceof RangeError
417
417
  ? `Host result from ${label} exceeds the ${MAX_QUICKJS_HOST_RPC_BYTES}-byte serialized bridge limit.`
package/dist/index.d.ts CHANGED
@@ -65,7 +65,7 @@ export interface ConnectaCallsConfig {
65
65
  * `timeoutMs`. An explicit per-call value wins. Opt-in: unset by default, so
66
66
  * existing long-running calls gain no surprise deadline.
67
67
  *
68
- * This bounds one attempt, not all retries. `execute_code` host calls are
68
+ * Each call makes one attempt. `execute_code` host calls are
69
69
  * unaffected because they already carry their own bound.
70
70
  */
71
71
  defaultTimeoutMs?: number;
@@ -2,36 +2,10 @@ import { type ActivityCallSource, type ActivityRequestContext, type AgentFrictio
2
2
  import { CatalogService, type ResolvedCatalogTool } from "./catalog-service.js";
3
3
  import { type CallErrorDetails } from "./errors.js";
4
4
  import { type RegistryView } from "./registry.js";
5
- /**
6
- * The longest the engine will park a synchronous inbound request in *waiting
7
- * alone*. The engine already treats ~15 s as the outer bound of one reasonable
8
- * connector call (EXECUTE_HOST_CALL_TIMEOUT_MS), so sleeping for minutes trades
9
- * a fast, informative failure for a hung one. A connector-reported window this
10
- * long isn't truncated — it's declined (see `retryBackoffMs`) and reported
11
- * verbatim as `error.retryAfterMs`, so the agent, which can afford to wait,
12
- * decides when to re-issue.
13
- */
14
- export declare const MAX_RETRY_BACKOFF_MS = 10000;
15
- /**
16
- * How long to wait before the next attempt, or `undefined` for "don't retry".
17
- *
18
- * A connector that read a `Retry-After` header knows the window exactly, so it
19
- * is honoured **exactly or not at all**: truncating an exponential *guess* is
20
- * harmless, but truncating a *known* window means deliberately retrying inside
21
- * a rate limit — the harm this channel exists to prevent. A window longer than
22
- * `MAX_RETRY_BACKOFF_MS` therefore declines the retry rather than shortening
23
- * it. (`retryAfterMs` is normalized non-negative, so `0` means "retry now".)
24
- * Connectors that report no window keep the historical exponential guess.
25
- *
26
- * Waits are per attempt, matching the per-attempt `timeoutMs` race in
27
- * `InvocationService.invoke`. Exported for direct testing.
28
- */
29
- export declare function retryBackoffMs(attempt: number, retryAfterMs: number | undefined): number | undefined;
30
5
  export interface InvocationTiming {
31
6
  catalogMs: number;
32
7
  admissionMs: number;
33
8
  connectorMs: number;
34
- backoffMs: number;
35
9
  resultProcessingMs: number;
36
10
  totalMs: number;
37
11
  }
@@ -53,7 +27,6 @@ export interface InvocationContext<T> {
53
27
  source: ActivityCallSource;
54
28
  allowDestructive?: boolean;
55
29
  timeoutMs?: number;
56
- maxRetries?: number;
57
30
  requestSignal?: AbortSignal;
58
31
  unwrapResult?: boolean;
59
32
  /**
@@ -97,11 +70,5 @@ export declare class InvocationService {
97
70
  private readonly activity?;
98
71
  constructor(registry: RegistryView, catalog: CatalogService, activity?: ActivityRequestContext | undefined);
99
72
  invoke<T = unknown>(address: string, args: unknown, context: InvocationContext<T>): Promise<InvocationOutcome<T>>;
100
- /**
101
- * Code-mode namespace dispatch preserves JavaScript-safe tool aliases while
102
- * still feeding the resolved catalog entry through the one invocation path.
103
- */
104
- invokeToolAlias<T = unknown>(connectorId: string, toolAlias: string, aliasFor: (toolName: string) => string, args: unknown, context: InvocationContext<T>): Promise<InvocationOutcome<T>>;
105
- private invokeWithResolution;
106
73
  }
107
74
  export {};