@zackbart/connecta 0.10.4 → 0.10.6
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/CHANGELOG.md +135 -0
- package/dist/activity.d.ts +11 -1
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js +44 -3
- package/dist/activity.js.map +1 -1
- package/dist/catalog-service.d.ts +40 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +97 -15
- package/dist/catalog-service.js.map +1 -1
- package/dist/errors.d.ts +48 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +72 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +163 -10
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +9 -2
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +59 -29
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +12 -3
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +185 -30
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +1 -1
- package/dist/operator-ui/generated.d.ts.map +1 -1
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/generated.js.map +1 -1
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -2
- package/dist/registry.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +9 -0
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +4 -0
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-mode.md +125 -34
- package/documentation/meta-tools.md +91 -9
- package/documentation/rich-output-design.md +212 -0
- package/ethos.md +17 -19
- package/examples/worker/README.md +11 -3
- package/examples/worker/src/d1-activity-row.ts +40 -0
- package/examples/worker/src/d1-activity.ts +3 -2
- package/package.json +1 -1
- package/src/activity.ts +64 -3
- package/src/catalog-service.ts +166 -26
- package/src/errors.ts +102 -1
- package/src/execute.ts +240 -10
- package/src/index.ts +22 -0
- package/src/invocation.ts +59 -17
- package/src/meta-tools.ts +239 -37
- package/src/operator-ui/browser.ts +10 -2
- package/src/operator-ui/generated.ts +1 -1
- package/src/registry.ts +5 -2
- package/src/routes/mcp.ts +9 -0
- package/src/routes/shared.ts +4 -0
- package/src/skills.ts +1 -1
- package/src/version.ts +1 -1
- package/templates/node/package.json +1 -1
package/dist/skills.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const CONNECTA_INSTRUCTIONS = "Connecta exposes integrations behi
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const CODE_FIRST_INSTRUCTIONS = "Connecta exposes integrations behind seven meta-tools, and execute_code is the primary one: write an async arrow function and use connecta.search (empty query browses every catalog), connecta.describe, connecta.call, and connecta.batch inside it for discovery, two or more calls, dependent steps, loops, joins, and reducing large results before they reach you. For a single read at an unknown address, search_tools with 2\u20134 distinctive action/object terms and includeSchemas=\"compact\", then one call_tool \u2014 a lone cold call is cheaper direct than through a program. Use call_destructive_tool individually for unannotated, write-capable, or destructive tools; authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: \"usage\" }).";
|
|
12
12
|
export declare const USAGE_SKILL = "# Connecta usage\n\n## Choose the smallest execution tool\n\nUse exact addresses returned by discovery; never invent one. Search with 2\u20134 distinctive action/object terms rather than the full request, and omit `limit` initially so the default page stays small.\n\n- Unknown address: `search_tools({ query, includeSchemas: \"compact\" })`; every match then includes its input shape plus any declared output shape and annotations.\n- Compact shape still ambiguous: `describe_tools({ addresses: [...] })`; use `format: \"json\"` only for exact constraints.\n- One explicitly read-only call: `call_tool`.\n- Two to ten independent explicitly read-only calls: `batch_call`.\n- Dependent read-only calls, loops, joins, branching, or large-result reduction: `execute_code` when available.\n- Any unannotated, write-capable, or destructive call: `call_destructive_tool`, individually and only after reviewing its schema and consequences.\n- Truncated result: retry with `fields` when possible; otherwise page it with `get_result`.\n- `auth_required`: use `authorize_connector`, give its recovery handoff to the operator, then retry the original call.\n\nUse `list_connectors({ probe: false })` for a fast observed-health inventory; use `probe: true` only to diagnose live health or authorization.\n\n## Code mode\n\nUnknown addresses plus dependent calls: search inside the run, not in an outer `search_tools`. Parallelize independent calls with `Promise.all` or `connecta.batch`.\n\nConnector namespace calls and `connecta.call` use the same read-only gate and throw on downstream errors. Catch only failures the workflow can handle; let authorization failures return to the agent for recovery.\n\nSkip code mode for one call, calls suited to `batch_call`, or tools lacking `readOnlyHint: true`. Return only the needed reduction.\n";
|
|
13
|
-
export declare const CODE_FIRST_USAGE_SKILL = "# Connecta usage\n\n## The surface\n\nSeven tools: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills`. Broad discovery and multi-call work live inside a program rather than in top-level tools.\n\n## Choose the smallest execution tool\n\nUse exact addresses returned by discovery; never invent one. Search with 2\u20134 distinctive action/object terms rather than the full request.\n\n- One read at an unknown address: `search_tools({ query, includeSchemas: \"compact\" })`, then `call_tool` once. A lone cold call is cheaper direct than through a program.\n- Anything wider \u2014 two or more calls, dependent steps, loops, joins, branching, browsing a whole catalog, or a result that must be reduced: one `execute_code` run.\n- Any unannotated, write-capable, or destructive call: `call_destructive_tool`, individually and only after reviewing its schema and consequences. Generated code cannot make one.\n- Truncated result: retry with `fields` when possible; otherwise page it with `get_result`.\n- `auth_required`: use `authorize_connector`, give its recovery handoff to the operator, then retry the original call.\n\n## Inside a program\n\nOne async arrow function. The only capabilities are one global per connector (`<connectorId>.<toolName>(args)`), the four `connecta` functions, and `console.log`.\n\n- What exists: `connecta.search({})` browses every catalog; add `safety: \"readOnly\"` for only calls the program can execute, and `connector: \"<id>\"` to browse one. This filters discovery results, not authority, and each match carries its `address` and annotations.\n- Exact schemas for known addresses: `connecta.describe({ addresses: [...] })
|
|
13
|
+
export declare const CODE_FIRST_USAGE_SKILL = "# Connecta usage\n\n## The surface\n\nSeven tools: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills`. Broad discovery and multi-call work live inside a program rather than in top-level tools.\n\n## Choose the smallest execution tool\n\nUse exact addresses returned by discovery; never invent one. Search with 2\u20134 distinctive action/object terms rather than the full request.\n\n- One read at an unknown address: `search_tools({ query, includeSchemas: \"compact\" })`, then `call_tool` once. A lone cold call is cheaper direct than through a program.\n- Anything wider \u2014 two or more calls, dependent steps, loops, joins, branching, browsing a whole catalog, or a result that must be reduced: one `execute_code` run.\n- Any unannotated, write-capable, or destructive call: `call_destructive_tool`, individually and only after reviewing its schema and consequences. Generated code cannot make one.\n- Truncated result: retry with `fields` when possible; otherwise page it with `get_result`.\n- `auth_required`: use `authorize_connector`, give its recovery handoff to the operator, then retry the original call.\n\n## Inside a program\n\nOne async arrow function. The only capabilities are one global per connector (`<connectorId>.<toolName>(args)`), the four `connecta` functions, and `console.log`.\n\n- What exists: `connecta.search({})` browses every catalog; add `safety: \"readOnly\"` for only calls the program can execute, and `connector: \"<id>\"` to browse one. This filters discovery results, not authority, and each match carries its `address` and annotations.\n- Exact schemas for known addresses: `connecta.describe({ address: \"connector.tool\" })` for one or `connecta.describe({ addresses: [...] })` for many; `format: \"json\"` only for exact constraints.\n- Two to ten independent calls: `connecta.batch([...])`. Each outcome is `{ address, ok: true, data }` or `{ address, ok: false, error, errorDetails: { code, retryable } }`, which is also how a program tells a policy refusal from a transient failure.\n- Search inside the run rather than searching first, and return only the reduction the answer needs \u2014 never raw payloads.\n- Only tools annotated `readOnlyHint: true` are reachable; the read-only gate, credentials, and admission are enforced below the sandbox, so nothing a program does widens what it can reach.\n";
|
|
14
14
|
/**
|
|
15
15
|
* Appended to USAGE_SKILL only when the deployment actually has at least one
|
|
16
16
|
* connector guide. A deployment with none — every deployment that has not
|
package/dist/skills.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7D,eAAO,MAAM,qBAAqB,+vBACysB,CAAC;AAE5uB;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,+yBAC4vB,CAAC;AAEjyB,eAAO,MAAM,WAAW,yyDAwBvB,CAAC;AAEF,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7D,eAAO,MAAM,qBAAqB,+vBACysB,CAAC;AAE5uB;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,+yBAC4vB,CAAC;AAEjyB,eAAO,MAAM,WAAW,yyDAwBvB,CAAC;AAEF,eAAO,MAAM,sBAAsB,u3EAyBlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,odAIpC,CAAC;AASF,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAIhE;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAI5E;AAsCD,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAGvE;AAyDD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,eAA2B,GACnC,YAAY,EAAE,CAehB;AAED,MAAM,MAAM,WAAW,GACrB;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,eAA2B,GACnC,WAAW,CAwCb"}
|
package/dist/skills.js
CHANGED
|
@@ -54,7 +54,7 @@ Use exact addresses returned by discovery; never invent one. Search with 2–4 d
|
|
|
54
54
|
One async arrow function. The only capabilities are one global per connector (\`<connectorId>.<toolName>(args)\`), the four \`connecta\` functions, and \`console.log\`.
|
|
55
55
|
|
|
56
56
|
- What exists: \`connecta.search({})\` browses every catalog; add \`safety: "readOnly"\` for only calls the program can execute, and \`connector: "<id>"\` to browse one. This filters discovery results, not authority, and each match carries its \`address\` and annotations.
|
|
57
|
-
- Exact schemas for known addresses: \`connecta.describe({ addresses: [...] })
|
|
57
|
+
- Exact schemas for known addresses: \`connecta.describe({ address: "connector.tool" })\` for one or \`connecta.describe({ addresses: [...] })\` for many; \`format: "json"\` only for exact constraints.
|
|
58
58
|
- Two to ten independent calls: \`connecta.batch([...])\`. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails: { code, retryable } }\`, which is also how a program tells a policy refusal from a transient failure.
|
|
59
59
|
- Search inside the run rather than searching first, and return only the reduction the answer needs — never raw payloads.
|
|
60
60
|
- Only tools annotated \`readOnlyHint: true\` are reachable; the read-only gate, credentials, and admission are enforced below the sandbox, so nothing a program does widens what it can reach.
|
package/dist/version.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* a bump that forgets this file fails the build rather than shipping a stale
|
|
5
5
|
* version to `/health` and to downstream MCP handshakes.
|
|
6
6
|
*/
|
|
7
|
-
export declare const CONNECTA_VERSION = "0.10.
|
|
7
|
+
export declare const CONNECTA_VERSION = "0.10.6";
|
|
8
8
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -98,7 +98,8 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
|
|
|
98
98
|
|
|
99
99
|
1. **Expose each provider as a guest global** whose properties are its `fns`,
|
|
100
100
|
called with the program's arguments and awaited. Connecta's provider carries
|
|
101
|
-
`search`, `describe`, `call`, `batch`, and `__callNamespace` — see
|
|
101
|
+
`search`, `describe`, `call`, `batch`, `emit`, and `__callNamespace` — see
|
|
102
|
+
point 3.
|
|
102
103
|
2. **Evaluate `prelude` after the provider globals exist and before the
|
|
103
104
|
program**, in a scope where those globals are reachable. It is host-authored
|
|
104
105
|
trusted code, never model input, and skipping it is not an option: connecta's
|
|
@@ -127,6 +128,10 @@ Optionally implement `AdmittingExecutor` (`acquire()` returning a lease whose
|
|
|
127
128
|
`execute` runs once) for bounded admission (`L7`) and `close()` for shutdown;
|
|
128
129
|
connecta wraps a plain `Executor` with `withExecutorAdmission` otherwise.
|
|
129
130
|
|
|
131
|
+
Note what is *not* on this list: [emitted output](#emitted-output) asks
|
|
132
|
+
nothing of an executor — `connecta.emit` is just another provider function
|
|
133
|
+
(`M8`).
|
|
134
|
+
|
|
130
135
|
## The program
|
|
131
136
|
|
|
132
137
|
**P1.** A program is one JavaScript `async` arrow-function expression. It is
|
|
@@ -240,9 +245,10 @@ means "read the schema" where `[]` would claim the tool takes no fields. The
|
|
|
240
245
|
lists come from the same walk that renders the compact schema, so a top-level
|
|
241
246
|
`$ref` resolves and an `allOf` composes rather than reporting an empty list
|
|
242
247
|
beside a schema that plainly shows fields; an object with no properties is the
|
|
243
|
-
one case where `[]` is the truth.
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
one case where `[]` is the truth. A truncated schema omits the corresponding
|
|
249
|
+
key list rather than repeating a large partial inventory. `search_tools`
|
|
250
|
+
carries the same metadata whenever schemas are requested. Code-mode callers
|
|
251
|
+
can set `includeSchemaKeys: false` to buy the bytes back.
|
|
246
252
|
|
|
247
253
|
**S3.** Discovery is bounded and the bounds throw rather than silently shrink: a
|
|
248
254
|
`limit` outside 1–100 is `invalid_args`, and a page whose serialized form
|
|
@@ -253,6 +259,8 @@ ask for less. As with every failure, the *thrown* error carries only the message
|
|
|
253
259
|
### connecta.describe
|
|
254
260
|
|
|
255
261
|
```js
|
|
262
|
+
const one = await connecta.describe({ address: "ci.get_run" });
|
|
263
|
+
|
|
256
264
|
const { tools } = await connecta.describe({
|
|
257
265
|
addresses: ["ci.get_run", "ci.get_job_logs"], // ≤ 100
|
|
258
266
|
format: "compact", // or "json"
|
|
@@ -304,6 +312,15 @@ than ten calls throws.
|
|
|
304
312
|
error crosses the bridge as a bare message (`E1`), a batch of one is the supported
|
|
305
313
|
way for a program to *decide* something about a failure rather than report it.
|
|
306
314
|
|
|
315
|
+
### connecta.emit
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
await connecta.emit({ type: "image", data: shot.data, mimeType: "image/png" });
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The rich-output channel, delivered after the JSON envelope on success. Its
|
|
322
|
+
clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
|
|
323
|
+
|
|
307
324
|
## Errors
|
|
308
325
|
|
|
309
326
|
**E1.** There are four error channels, and only two of them are typed.
|
|
@@ -351,11 +368,12 @@ program may do about it.
|
|
|
351
368
|
sentence. A program cannot recover credentials — only an operator can — so the
|
|
352
369
|
right move is to stop and let the failure reach the model.
|
|
353
370
|
|
|
354
|
-
**E4.**
|
|
355
|
-
|
|
356
|
-
`
|
|
357
|
-
|
|
358
|
-
|
|
371
|
+
**E4.** An unannotated, write-capable, or destructive tool stays refused with
|
|
372
|
+
`destructive_tool_requires_approval`; `nextAction` carries its canonical address
|
|
373
|
+
to `call_destructive_tool`, plus the original arguments when they fit the
|
|
374
|
+
512-byte echo budget — whole or not at all, since a clipped copy is a different
|
|
375
|
+
call. The model's short `reason` for the human reviewer grants no authority,
|
|
376
|
+
never goes downstream, and generated code cannot mint the capability.
|
|
359
377
|
|
|
360
378
|
**E5.** Failures of the *execution*, not of a call, never appear inside the
|
|
361
379
|
guest: admission rejection (`executor_overloaded`, retryable, with
|
|
@@ -374,11 +392,9 @@ exactly first, by containment second — so a program that *wraps* a failure's
|
|
|
374
392
|
message in its own text still reports the underlying typed failure. Keeping the
|
|
375
393
|
type beats keeping the prose.
|
|
376
394
|
|
|
377
|
-
**E7.** `retryable` for `unknown_address`, `unknown_tool`,
|
|
378
|
-
`ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false,
|
|
379
|
-
never inferred from an address containing `503`, `429`, or `temporar`.
|
|
395
|
+
**E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. Both the message and the derived `query` clamp the address to 512 UTF-8 bytes with a `…` marker: the address is caller-authored and lands in the message, the query, the text content, and `structuredContent`, so an invented 50 KB one would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake; a short one — the common case — is exact and untagged.
|
|
380
396
|
|
|
381
|
-
**E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
|
|
397
|
+
**E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery keyed `function: "connecta.search"` like every other in-program miss. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
|
|
382
398
|
|
|
383
399
|
## Results and projection
|
|
384
400
|
|
|
@@ -420,12 +436,70 @@ on data nobody asked for.
|
|
|
420
436
|
|
|
421
437
|
**R5.** `console.log`, `console.warn`, and `console.error` are captured in call order and returned as a single `logs` string, capped at 4,000 characters with a truncation marker. Logs survive failure — they ride along with the error result, which is what makes them worth writing. How a non-string argument renders is not contract (`X4`).
|
|
422
438
|
|
|
423
|
-
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block;
|
|
439
|
+
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block; a program that emitted adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are byte-for-byte the ordinary response path.
|
|
424
440
|
|
|
425
441
|
**R7.** Timing separates admission, provider setup, total executor wall time, catalog work, and connector work. Catalog and connector values are cumulative, so parallel work can exceed executor wall time. Each used operation kind (`search`, `describe`, `call`, `batch`) gets one aggregate with count, failures, duration, returned serialized bytes, and catalog/connector time; batch adds only its total child count.
|
|
426
442
|
|
|
427
443
|
**R8.** Diagnostics contain measurements and fixed operation names only: no addresses, arguments, results, code, credentials, logs, or raw errors. Result sizes are numbers, never previews. The collector exists only for the opted-in request; it is not activity, a session, or a stream.
|
|
428
444
|
|
|
445
|
+
## Emitted output
|
|
446
|
+
|
|
447
|
+
MCP-native output a return value cannot carry: base64 is not projectable, so a
|
|
448
|
+
block that survives intake uncapped (`S5`) must not die at the `R2` exit
|
|
449
|
+
guard. The argument and the refused alternatives live in the
|
|
450
|
+
[design record](./rich-output-design.md) and `ethos.md`
|
|
451
|
+
([#267](https://github.com/zackbart/connecta/issues/267),
|
|
452
|
+
[#270](https://github.com/zackbart/connecta/issues/270)).
|
|
453
|
+
|
|
454
|
+
**M1.** `connecta.emit(block)` accepts exactly one block: `{ type: "text",
|
|
455
|
+
text }` or `{ type: "image" | "audio", data /* base64 */, mimeType }`, every
|
|
456
|
+
field a string, no extra fields, no `annotations`, no `_meta`, no sugar forms.
|
|
457
|
+
An invalid block throws catchably and nothing is accepted — rejected, not
|
|
458
|
+
stripped. The refused types are pointers: a guest-minted `resource_link` URI
|
|
459
|
+
is a lure a client may dereference.
|
|
460
|
+
|
|
461
|
+
**M2.** Blocks collect on the host in emission order and are delivered only
|
|
462
|
+
with a successful result, appended to `content` after the JSON envelope, which
|
|
463
|
+
gains `emitted: N`. A program that never emits produces the byte-for-byte
|
|
464
|
+
ordinary response (`R6`). `structuredContent` stays the envelope alone —
|
|
465
|
+
emission is presentation, not a second data channel.
|
|
466
|
+
|
|
467
|
+
**M3.** Return value and emission are independent: `R2` never measures emitted
|
|
468
|
+
bytes, a truncated return does not suppress delivered blocks, and blocks do
|
|
469
|
+
not shrink the return budget.
|
|
470
|
+
|
|
471
|
+
**M4.** A failed program delivers no blocks. The error result reports
|
|
472
|
+
`emittedDiscarded: N` when N > 0 — a field on the structured envelope, a
|
|
473
|
+
trailing line on the plain-text paths — never silently.
|
|
474
|
+
|
|
475
|
+
**M5.** Two budgets (`ConnectaConfig.execute.maxEmittedBytes` /
|
|
476
|
+
`.maxEmittedBlocks`, defaults 4,000,000 serialized bytes and 32 blocks) fail
|
|
477
|
+
loudly at the `emit` call, naming the budget and the room remaining; nothing
|
|
478
|
+
is partially accepted and prior blocks stand. No `get_result` stash: the
|
|
479
|
+
program learns while it can still choose differently. The byte default is a
|
|
480
|
+
transport bound, not a context bound — emitted media reaches the model as
|
|
481
|
+
media, not base64 text.
|
|
482
|
+
|
|
483
|
+
**M6.** No provenance is claimed: every emitted block is program output,
|
|
484
|
+
trusted exactly as much as the return value. Preservation is re-emission of
|
|
485
|
+
the raw downstream block, so `S5`'s uncapped fallthrough is contract.
|
|
486
|
+
|
|
487
|
+
**M7.** `emit` spends no host-call budget (`L4`); `M5`'s bounds are its only
|
|
488
|
+
bounds.
|
|
489
|
+
|
|
490
|
+
**M8.** Emission asks nothing of an executor: `emit` is a provider function,
|
|
491
|
+
blocks cross the guest boundary once as an argument, and `ExecuteResult` is
|
|
492
|
+
unchanged — `Executor` stays assignable from `@cloudflare/codemode`'s
|
|
493
|
+
`DynamicWorkerExecutor`, and any executor that bridges provider calls gets
|
|
494
|
+
emission for free.
|
|
495
|
+
|
|
496
|
+
**M9.** Request-local and unstreamed: blocks exist only in the finished
|
|
497
|
+
response, and `emit` resolving means "accepted," never "delivered."
|
|
498
|
+
|
|
499
|
+
**M10.** Activity stays payload-free. `diagnostics: true` adds one `emitted`
|
|
500
|
+
aggregate — count and serialized bytes, numbers only (`R8`), present only
|
|
501
|
+
when something was emitted.
|
|
502
|
+
|
|
429
503
|
## Retry semantics
|
|
430
504
|
|
|
431
505
|
**Y1.** Connecta retries nothing beneath a program. `call_tool` accepts an
|
|
@@ -516,27 +590,34 @@ itself (`R2`, `R5`).
|
|
|
516
590
|
|
|
517
591
|
## Activity
|
|
518
592
|
|
|
519
|
-
**V1.** One payload-free activity event per call
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
a
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
which is what makes moving work into the sandbox an optimization, not a
|
|
526
|
-
blindfold.
|
|
593
|
+
**V1.** One payload-free activity event per attempted call, with
|
|
594
|
+
`source: "execute_code"` — every dispatched call plus every local refusal: a
|
|
595
|
+
read-only refusal, an unknown tool, an ambiguous shortcut, an unloadable
|
|
596
|
+
catalog, a missing credential, an exhausted host-call budget, an address no
|
|
597
|
+
connector owns. Ten tools called is ten events, as legible as ten `call_tool`
|
|
598
|
+
calls — which makes moving work into the sandbox an optimization, not a blindfold.
|
|
527
599
|
|
|
528
600
|
**V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
|
|
529
601
|
`outcome` (`success`, `error`, `timeout`, `cancelled`), `durationMs`,
|
|
530
|
-
`attempts`, and `errorCode` when
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
602
|
+
`attempts`, and `errorCode` when the call *failed* — plus request, actor, and
|
|
603
|
+
server identity. Typed codes derive an optional `friction`: `tool_not_found`,
|
|
604
|
+
`schema_retry`, `destructive_reroute`, or `auth_required`. The fifth class,
|
|
605
|
+
`result_too_large`, cannot reach an `execute_code` event: it belongs to a
|
|
606
|
+
`call_tool` result — or a `batch_call` child's — too large to return inline,
|
|
607
|
+
and a program's own return is refused paging by design rather than truncated
|
|
608
|
+
into friction. There is nowhere to put arguments, results, program source, or
|
|
609
|
+
raw error text; a caught failure is still recorded. `address` is
|
|
610
|
+
canonical (`A1`) where a tool resolved, otherwise the name the program used —
|
|
611
|
+
for a shortcut its sanitized alias, the honest record of what was attempted.
|
|
612
|
+
|
|
613
|
+
**V3.** A call whose connector does not exist is recorded at the address as
|
|
614
|
+
written, *provided* it split into the two fields activity keeps — one with no
|
|
615
|
+
interior dot records nothing. An invented id is the address mistake an operator
|
|
616
|
+
most needs to see. But recording it as written puts caller-authored text in
|
|
617
|
+
fields that are otherwise operator- and connector-authored, so `connectorId`
|
|
618
|
+
and `toolName` clamp at 128 UTF-8 bytes (`address` at 257) with a `…` marker:
|
|
619
|
+
payload-free *by construction* means the event has nowhere to put a payload,
|
|
620
|
+
not merely that connecta declines to.
|
|
540
621
|
|
|
541
622
|
**V4.** The execution itself emits no event. It has no address, and its one
|
|
542
623
|
distinctive artifact is the program source, which is exactly what a payload-free
|
|
@@ -629,6 +710,10 @@ The middle three were places where the contract described behavior the code did
|
|
|
629
710
|
not quite have. The code moved, because the described behavior is the one worth
|
|
630
711
|
having.
|
|
631
712
|
|
|
713
|
+
One surface was added since: [emitted output](#emitted-output) (`M1`–`M10`,
|
|
714
|
+
[#270](https://github.com/zackbart/connecta/issues/270)) — additive by
|
|
715
|
+
construction, with the byte-for-byte no-emit promise pinned by test.
|
|
716
|
+
|
|
632
717
|
## Verification
|
|
633
718
|
|
|
634
719
|
Every clause has a test. `test/guest-contract-cases.ts` holds the case table,
|
|
@@ -678,8 +763,14 @@ the upstream `Executor` shape assignable.
|
|
|
678
763
|
| `L5`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap) |
|
|
679
764
|
| `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message) |
|
|
680
765
|
| `L7` | `test/execute.test.ts`, `test/executor-admission.test.ts` |
|
|
681
|
-
| `V1
|
|
682
|
-
| `
|
|
766
|
+
| `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, and the identity clamp) |
|
|
767
|
+
| `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
|
|
768
|
+
| `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
|
|
769
|
+
| `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths) |
|
|
770
|
+
| `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
|
|
771
|
+
| `M6`, `M9` | verdicts; `M1`'s strict typing and `M2`'s collect-then-deliver are their enforcement |
|
|
772
|
+
| `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
773
|
+
| `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
|
|
683
774
|
| `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
|
|
684
775
|
| `X4` | `test/guest-api-contract.test.ts` (string logs only) |
|
|
685
776
|
| `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
|
|
@@ -55,8 +55,11 @@ or generated code; `safety: "approvalRequired"` finds the complementary set
|
|
|
55
55
|
that must cross `call_destructive_tool`. Omitting `safety`, or setting it to
|
|
56
56
|
`"all"`, preserves the complete configured catalog. This is only a discovery
|
|
57
57
|
filter: it neither grants authority nor changes invocation admission.
|
|
58
|
-
`includeSchemas: "compact"` adds each match's input and any
|
|
59
|
-
|
|
58
|
+
`includeSchemas: "compact"` adds each match's input and any declared output
|
|
59
|
+
shape. Bounded plain-object schemas also expose `inputKeys`,
|
|
60
|
+
`requiredInputKeys`, and `outputKeys`; a truncated shape omits its corresponding
|
|
61
|
+
list rather than repeating a large partial inventory. Matches carry declared
|
|
62
|
+
behavior annotations. When
|
|
60
63
|
that shape is sufficient, call the returned address directly. Reserve schema
|
|
61
64
|
expansion — `connecta.describe` in a program, `describe_tools` on the classic
|
|
62
65
|
surface — for a search without schemas, an ambiguous compact shape, or exact
|
|
@@ -86,12 +89,24 @@ mode; they are not a duplicated Connecta object result. Newly stashed JSON and
|
|
|
86
89
|
downstream content envelopes use compact serialization, so `get_result` byte
|
|
87
90
|
offsets and totals refer to that exact compact text.
|
|
88
91
|
|
|
92
|
+
A `call_tool` or `batch_call` truncation notice carries both the historical
|
|
93
|
+
`resultId` and an exact
|
|
94
|
+
`nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The handle
|
|
95
|
+
is therefore directly actionable without copying an identifier out of prose;
|
|
96
|
+
re-calling with `fields` remains the smaller alternative when projection is
|
|
97
|
+
possible. Program results and oversized discovery responses carry no such
|
|
98
|
+
route — paging a program's return value is a refused shape, because a program
|
|
99
|
+
can shrink anything before it returns.
|
|
100
|
+
|
|
89
101
|
`fields` keeps its historical flat `{ "<path>": value }` result when every
|
|
90
|
-
requested dot-path resolves
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
requested dot-path resolves. Dot notation traverses objects; append `[]` to an
|
|
103
|
+
array field before continuing, as in `results[].id`. An exact downstream
|
|
104
|
+
`$connecta` field is always escaped under `data`. If any path misses—or that
|
|
105
|
+
reserved name is selected—the result carries matches under `data` and reserves `$connecta` for a
|
|
93
106
|
`type: "field_projection"` recovery record naming each `unmatchedFields`
|
|
94
|
-
entry.
|
|
107
|
+
entry. When a miss matches a declared array path except for `[]`, the record
|
|
108
|
+
also carries the traversal hint. The discriminator means downstream fields
|
|
109
|
+
named `data`, `projection`,
|
|
95
110
|
or `$connecta` remain ordinary values nested under `data`, never apparent
|
|
96
111
|
metadata. A declared output schema contributes a bounded `availableFields`
|
|
97
112
|
list and a `schemaCoverage` verdict. Only a completely analyzed, closed schema
|
|
@@ -166,6 +181,71 @@ only through the same-origin, Clerk-operator credential route. After OAuth
|
|
|
166
181
|
consent or an operator update, retry the original operation; a static update is
|
|
167
182
|
read from the vault on the next call and needs no redeploy.
|
|
168
183
|
|
|
184
|
+
## Routing recovery
|
|
185
|
+
|
|
186
|
+
Predictable local refusals carry structured recovery on both result modes.
|
|
187
|
+
An unknown connector suggests an unscoped discovery query derived from the
|
|
188
|
+
attempted tool name; an unknown tool scopes the same query to the connector that
|
|
189
|
+
answered. The suggested route follows the caller's own surface: `tool:
|
|
190
|
+
"search_tools"` for a top-level call, `function: "connecta.search"` with the same
|
|
191
|
+
arguments when the miss happened inside `execute_code`, which has no way to call
|
|
192
|
+
a tool. A read path that reaches an unannotated, write-capable, or destructive
|
|
193
|
+
tool returns `nextAction` for `call_destructive_tool` with the canonical
|
|
194
|
+
address. Nothing is executed by these records.
|
|
195
|
+
|
|
196
|
+
That route echoes the caller's own arguments back only while they fit a
|
|
197
|
+
512-byte budget, and then whole — never clipped. An error envelope is not
|
|
198
|
+
size-guarded the way a result is, so an unbounded echo would let a large
|
|
199
|
+
argument object produce a refusal many times the deployment's result cap, on
|
|
200
|
+
both `call_tool` and the `batch_call` envelope. Over budget, `args` is absent
|
|
201
|
+
and the `purpose` says to re-send what was just sent: the agent already holds
|
|
202
|
+
its own arguments, and half of them would describe a call nobody made.
|
|
203
|
+
|
|
204
|
+
The address gets the same budget and the opposite rule: 512 bytes, clamped
|
|
205
|
+
with a trailing `…` rather than dropped. It is caller-authored too — an
|
|
206
|
+
invented one can be any length — and it reaches the error message *and* the
|
|
207
|
+
recovery query, each of which lands in both the text content and
|
|
208
|
+
`structuredContent`; unbounded, a 50 KB typo produced a 200 KB refusal under a
|
|
209
|
+
1 KB result cap. Dropping it is not an option the way dropping arguments is:
|
|
210
|
+
the address is the thing being corrected, a clipped one still identifies the
|
|
211
|
+
mistake, and a short one — every real one — comes back exact and untagged.
|
|
212
|
+
|
|
213
|
+
Shortcut ambiguity inside `execute_code` returns every colliding canonical
|
|
214
|
+
address and points at `connecta.call`; the program or model must still choose
|
|
215
|
+
which one matches the user's intent. `call_destructive_tool` accepts an optional
|
|
216
|
+
`reason` of at most 500 characters for the host's human approval view. It is
|
|
217
|
+
outer-call context only: Connecta neither treats it as authority nor passes it
|
|
218
|
+
to the downstream connector, and an empty or whitespace-only one is read as no
|
|
219
|
+
reason rather than as a reason to refuse the call.
|
|
220
|
+
|
|
221
|
+
Activity carries an optional coarse `friction` class: `tool_not_found`,
|
|
222
|
+
`schema_retry`, `destructive_reroute`, `auth_required`, or `result_too_large`.
|
|
223
|
+
It is derived from the typed error code, except on the one call that has no
|
|
224
|
+
error code to derive from: a `call_tool` result — or a `batch_call` child's —
|
|
225
|
+
too large to return inline is friction for the agent while remaining
|
|
226
|
+
`outcome: "success"`. That is the only source of `result_too_large` friction.
|
|
227
|
+
An oversized *discovery* response and an oversized program return are shaped
|
|
228
|
+
differently and produce none, and an `errorCode` is written only when the call
|
|
229
|
+
actually failed. The category adds no arguments, results, search text,
|
|
230
|
+
generated code, credentials, or raw errors.
|
|
231
|
+
|
|
232
|
+
An address whose connector does not exist is recorded too, as written, provided
|
|
233
|
+
it has the `<connectorId>.<toolName>` shape at all — a string that never split
|
|
234
|
+
into the two fields activity keeps still records nothing. A hallucinated
|
|
235
|
+
connector id is the most common address mistake, and an operator reading
|
|
236
|
+
activity should see it; addresses are already a first-class activity field, so
|
|
237
|
+
nothing new is retained.
|
|
238
|
+
|
|
239
|
+
What *is* new is that those fields now hold caller-authored text, so the
|
|
240
|
+
recording seam clamps them: `connectorId` and `toolName` at 128 UTF-8 bytes
|
|
241
|
+
each, `address` at 257, with a `…` marker. Far past any real id or tool name,
|
|
242
|
+
and far short of a 40 KB invented one. The clamp is structural rather than a
|
|
243
|
+
policy the writer applies, because "payload-free by construction" has to mean
|
|
244
|
+
the event type has nowhere to put a payload — a 40 KB connector id is a payload
|
|
245
|
+
wearing an id's clothing. Clamped rather than skipped: the invented id is
|
|
246
|
+
exactly what an operator needs to see, and its first 128 bytes say as much
|
|
247
|
+
about the mistake as all 40,000 would.
|
|
248
|
+
|
|
169
249
|
## Argument recovery
|
|
170
250
|
|
|
171
251
|
A remote MCP tool's advertised `inputSchema` is checked in the shared
|
|
@@ -177,9 +257,11 @@ JSON Pointer `path`, schema-keyword `code`, and expected shape. Submitted
|
|
|
177
257
|
values are never copied into those findings.
|
|
178
258
|
|
|
179
259
|
At most three findings are returned; `validation.truncated` says when more
|
|
180
|
-
exist. `nextAction` points to
|
|
181
|
-
|
|
182
|
-
|
|
260
|
+
exist. `nextAction` points to discovery scoped to the same connector and tool
|
|
261
|
+
name when the compact schema is needed — routed like any other miss, so a
|
|
262
|
+
program is sent to `connecta.search` and a top-level call to `search_tools` —
|
|
263
|
+
while `retry` says to correct the listed arguments and reissue the original
|
|
264
|
+
operation. A schema the local
|
|
183
265
|
validator cannot evaluate passes through to the provider. Provider error prose
|
|
184
266
|
is not parsed or guessed, so an unknown format remains
|
|
185
267
|
`connector_call_failed`.
|