@zackbart/connecta 0.25.0 → 0.26.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/AGENTS.md +6 -5
- package/CHANGELOG.md +271 -0
- package/README.md +32 -15
- package/SECURITY.md +6 -5
- package/bin/connecta.mjs +5 -1
- package/dist/activity.d.ts +10 -3
- package/dist/activity.js +10 -1
- package/dist/artifacts/connector.d.ts +55 -0
- package/dist/artifacts/connector.js +724 -0
- package/dist/artifacts/document.js +76 -0
- package/dist/artifacts/guide.js +140 -0
- package/dist/artifacts/html-scan.js +329 -0
- package/dist/artifacts/json.js +68 -0
- package/dist/artifacts/kv-store.d.ts +20 -0
- package/dist/artifacts/kv-store.js +140 -0
- package/dist/artifacts/markdown.js +449 -0
- package/dist/artifacts/module.d.ts +37 -0
- package/dist/artifacts/module.js +60 -0
- package/dist/artifacts/operations.d.ts +195 -0
- package/dist/artifacts/operations.js +944 -0
- package/dist/artifacts/refresh.d.ts +53 -0
- package/dist/artifacts/refresh.js +235 -0
- package/dist/artifacts/routes.js +194 -0
- package/dist/artifacts/types.d.ts +214 -0
- package/dist/artifacts/types.js +22 -0
- package/dist/artifacts/validate.d.ts +62 -0
- package/dist/artifacts/validate.js +652 -0
- package/dist/artifacts.d.ts +6 -0
- package/dist/artifacts.js +9 -0
- package/dist/catalog-service.js +39 -29
- package/dist/catalog.js +42 -8
- package/dist/connector-access.d.ts +18 -4
- package/dist/connector-access.js +65 -20
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +19 -0
- package/dist/connectors/remote-mcp.js +16 -2
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +427 -70
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +79 -14
- package/dist/executors/quickjs.js +4 -2
- package/dist/index.d.ts +62 -6
- package/dist/index.js +230 -6
- package/dist/invocation.js +56 -10
- package/dist/meta-tools.js +7 -2
- package/dist/module-contracts.d.ts +21 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +62 -5
- package/dist/operator-ui/view.js +94 -5
- package/dist/program-source.js +116 -0
- package/dist/registry.d.ts +42 -6
- package/dist/registry.js +148 -47
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +6 -69
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +27 -5
- package/dist/routes/mcp.js +116 -59
- package/dist/routes/oauth-management.js +22 -9
- package/dist/routes/operator.js +1 -0
- package/dist/routes/shared.d.ts +23 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +2 -1
- package/dist/routes/ui.js +13 -4
- package/dist/run-journal.js +347 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/server.js +26 -0
- package/dist/skills.js +27 -8
- package/dist/timeout.js +26 -0
- package/dist/tool-safety.d.ts +35 -0
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +18 -0
- package/dist/ui.d.ts +12 -5
- package/dist/ui.js +32 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +196 -19
- package/documentation/auth.md +83 -2
- package/documentation/code-mode.md +221 -48
- package/documentation/meta-tools.md +44 -25
- package/ethos.md +12 -10
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +174 -10
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/index.ts +36 -1
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +5 -1
- package/package.json +6 -2
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +27 -0
package/dist/invocation.js
CHANGED
|
@@ -64,17 +64,33 @@ function isCallerCancellation(error, signal) {
|
|
|
64
64
|
return (signal?.aborted === true ||
|
|
65
65
|
(isCallAdmissionError(error) && error.admissionKind === "cancelled"));
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* A downstream MCP tool answered with `isError`. Classified exactly as the
|
|
69
|
+
* plain `Error` it used to be — same message, same heuristics — and marked so
|
|
70
|
+
* a write's outcome can tell "the service said no" from "nobody answered".
|
|
71
|
+
*/
|
|
72
|
+
class DownstreamToolError extends Error {
|
|
73
|
+
}
|
|
67
74
|
function assertRawMcpSuccess(kind, result) {
|
|
68
75
|
if (kind !== "mcp" || result == null || typeof result !== "object")
|
|
69
76
|
return;
|
|
70
77
|
const mcpResult = result;
|
|
71
78
|
if (!mcpResult.isError)
|
|
72
79
|
return;
|
|
73
|
-
throw new
|
|
80
|
+
throw new DownstreamToolError(boundedEchoText(mcpResult.content
|
|
74
81
|
?.filter((block) => block.type === "text")
|
|
75
82
|
.map((block) => block.text ?? "")
|
|
76
83
|
.join("") || "Downstream tool call failed"));
|
|
77
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether a failed attempt is the downstream tool's own answer: an MCP
|
|
87
|
+
* `isError`. A connector's typed error is not, by itself, one — a response
|
|
88
|
+
* too large to read or a refused redirect comes after the request was acted
|
|
89
|
+
* on — so its code decides instead (`classifyWriteOutcome`).
|
|
90
|
+
*/
|
|
91
|
+
function answeredFailure(error) {
|
|
92
|
+
return error instanceof DownstreamToolError;
|
|
93
|
+
}
|
|
78
94
|
export class InvocationFailure extends Error {
|
|
79
95
|
details;
|
|
80
96
|
code;
|
|
@@ -134,6 +150,10 @@ export class InvocationService {
|
|
|
134
150
|
let connectorMs = 0;
|
|
135
151
|
let resultProcessingMs = 0;
|
|
136
152
|
let attempts = 0;
|
|
153
|
+
let dispatchedToConnector = false;
|
|
154
|
+
let answered = false;
|
|
155
|
+
// How a write gate's refusal is recorded; see WriteGateDecision.
|
|
156
|
+
let gateActivity;
|
|
137
157
|
let resolved;
|
|
138
158
|
let activityTarget;
|
|
139
159
|
// The address as written, used for activity when resolution never reached
|
|
@@ -227,6 +247,25 @@ export class InvocationService {
|
|
|
227
247
|
const diagnostics = timing();
|
|
228
248
|
const target = resolved ?? activityTarget;
|
|
229
249
|
const details = enrich(error, target);
|
|
250
|
+
const outcome = () => ({
|
|
251
|
+
ok: false,
|
|
252
|
+
durationMs: Date.now() - started,
|
|
253
|
+
attempts,
|
|
254
|
+
timing: diagnostics,
|
|
255
|
+
...defined({ resolved }),
|
|
256
|
+
dispatched: dispatchedToConnector,
|
|
257
|
+
...(dispatchedToConnector ? { answered } : {}),
|
|
258
|
+
error: details,
|
|
259
|
+
});
|
|
260
|
+
// A pause is not a failure: it leaves one payload-free `paused` event
|
|
261
|
+
// naming the call that waits, and nothing in the operator's log. A call
|
|
262
|
+
// refused only because its run had already stopped leaves neither.
|
|
263
|
+
if (gateActivity === "none")
|
|
264
|
+
return outcome();
|
|
265
|
+
if (gateActivity === "paused") {
|
|
266
|
+
record("paused");
|
|
267
|
+
return outcome();
|
|
268
|
+
}
|
|
230
269
|
// Activity rows stay payload-free by construction; the operator's log is
|
|
231
270
|
// where the downstream reason goes, bounded and without arguments.
|
|
232
271
|
if (target && details.code !== "destructive_tool_requires_approval") {
|
|
@@ -249,14 +288,7 @@ export class InvocationService {
|
|
|
249
288
|
: details.code === "cancelled"
|
|
250
289
|
? "cancelled"
|
|
251
290
|
: "error", { errorCode: details.code });
|
|
252
|
-
return
|
|
253
|
-
ok: false,
|
|
254
|
-
durationMs: Date.now() - started,
|
|
255
|
-
attempts,
|
|
256
|
-
timing: diagnostics,
|
|
257
|
-
...defined({ resolved }),
|
|
258
|
-
error: details,
|
|
259
|
-
};
|
|
291
|
+
return outcome();
|
|
260
292
|
};
|
|
261
293
|
if (context.requestSignal?.aborted) {
|
|
262
294
|
// Preserve the cancelled admission-attempt count without starting discovery.
|
|
@@ -290,7 +322,11 @@ export class InvocationService {
|
|
|
290
322
|
const target = resolution.resolved;
|
|
291
323
|
resolved = target;
|
|
292
324
|
activityTarget = target;
|
|
293
|
-
|
|
325
|
+
const consequential = !isExplicitlyReadOnly(target.definition) && !context.allowDestructive;
|
|
326
|
+
const gate = consequential && (context.gates?.(target) ?? true)
|
|
327
|
+
? context.writeGate
|
|
328
|
+
: undefined;
|
|
329
|
+
if (consequential && !gate) {
|
|
294
330
|
const canonicalAddress = `${target.connector.id}.${target.toolName}`;
|
|
295
331
|
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is not explicitly read-only. Invoke it through call_destructive_tool so the MCP host can request explicit approval.`);
|
|
296
332
|
}
|
|
@@ -308,6 +344,13 @@ export class InvocationService {
|
|
|
308
344
|
if (invalid)
|
|
309
345
|
return classifyCallError(invalid);
|
|
310
346
|
}
|
|
347
|
+
if (gate) {
|
|
348
|
+
const decision = yield* gate(target, args ?? {});
|
|
349
|
+
if (decision.kind === "refuse") {
|
|
350
|
+
gateActivity = decision.activity;
|
|
351
|
+
return decision.error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
311
354
|
try {
|
|
312
355
|
context.beforeDispatch?.();
|
|
313
356
|
}
|
|
@@ -329,6 +372,7 @@ export class InvocationService {
|
|
|
329
372
|
// Cancellation can arrive during admission or context construction.
|
|
330
373
|
if (callSignal?.aborted)
|
|
331
374
|
throw callSignal.reason;
|
|
375
|
+
dispatchedToConnector = true;
|
|
332
376
|
return target.connector.callTool(target.toolName, args ?? {}, connectorContext);
|
|
333
377
|
};
|
|
334
378
|
// The permit belongs to this scope, so success, failure, and the
|
|
@@ -351,6 +395,7 @@ export class InvocationService {
|
|
|
351
395
|
if (callSignal?.aborted)
|
|
352
396
|
return yield* Effect.fail(callSignal.reason);
|
|
353
397
|
const attemptError = Cause.squash(attempt.cause);
|
|
398
|
+
answered = answeredFailure(attemptError);
|
|
354
399
|
return isCallerCancellation(attemptError, context.requestSignal)
|
|
355
400
|
? callerCancelledDetails()
|
|
356
401
|
: classifyCallError(attemptError);
|
|
@@ -415,6 +460,7 @@ export class InvocationService {
|
|
|
415
460
|
durationMs: Date.now() - started,
|
|
416
461
|
attempts,
|
|
417
462
|
timing: diagnostics,
|
|
463
|
+
dispatched: dispatchedToConnector,
|
|
418
464
|
};
|
|
419
465
|
}
|
|
420
466
|
catch {
|
package/dist/meta-tools.js
CHANGED
|
@@ -400,6 +400,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
400
400
|
concurrency: discoveryConcurrency,
|
|
401
401
|
defer: opts.defer,
|
|
402
402
|
requestSignal: opts.requestSignal,
|
|
403
|
+
approval: opts.approval,
|
|
403
404
|
// searchRoute keeps its top-level default. In-program callers use a
|
|
404
405
|
// separate CatalogService configured for connecta.search.
|
|
405
406
|
});
|
|
@@ -470,6 +471,8 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
470
471
|
// Sanitized `unavailable` diagnostics ride the structured shape too;
|
|
471
472
|
// the plain-text path would drop them (#539).
|
|
472
473
|
outcome.error.details !== undefined ||
|
|
474
|
+
// So does a conflict's `current`: where things stand is the retry.
|
|
475
|
+
outcome.error.current !== undefined ||
|
|
473
476
|
[
|
|
474
477
|
"auth_required",
|
|
475
478
|
"invalid_args",
|
|
@@ -780,7 +783,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
780
783
|
},
|
|
781
784
|
};
|
|
782
785
|
}
|
|
783
|
-
const SEARCH_DESC = `Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds
|
|
786
|
+
const SEARCH_DESC = `Use top-level search for catalog inspection or approval-required work before call_destructive_tool. Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds tools that run unasked; "approvalRequired" finds the fail-closed complement. These filters grant no authority. Empty query browses.`;
|
|
784
787
|
const CALL_DESC = 'Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. Unannotated or write-capable tools fail closed to call_destructive_tool. A truncated result carries a get_result action.';
|
|
785
788
|
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
786
789
|
const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns a one-line JSON header (offset, bytes, totalBytes, hasMore, nextAction) and then the page as raw text. maxBytes is an upper bound, clamped to the result's inline cap.";
|
|
@@ -868,7 +871,8 @@ const GET_RESULT_INPUT = advertisedSchema(z.object({
|
|
|
868
871
|
}));
|
|
869
872
|
/**
|
|
870
873
|
* Register the six explicit meta-tools onto an McpServer instance.
|
|
871
|
-
* `registerExecuteTool` adds the seventh, `execute_code
|
|
874
|
+
* `registerExecuteTool` adds the seventh, `execute_code`, and
|
|
875
|
+
* `registerResumeTool` the eighth, `resume_execution`. Broad discovery and
|
|
872
876
|
* multi-call work uses discovery and ordinary JavaScript promises inside a
|
|
873
877
|
* program, which `execute_code` builds over the same
|
|
874
878
|
* `CatalogService` and `InvocationService` these handlers use — one shared
|
|
@@ -884,6 +888,7 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
884
888
|
credentialHandoffUrl: ctx.credentialHandoffUrl,
|
|
885
889
|
requestSignal: ctx.requestSignal,
|
|
886
890
|
defer: ctx.defer,
|
|
891
|
+
approval: ctx.approval,
|
|
887
892
|
});
|
|
888
893
|
server.registerTool("skills", {
|
|
889
894
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ConnectaBranding } from "./types.js";
|
|
1
|
+
import type { ConnectaBranding, Connector } from "./types.js";
|
|
2
2
|
import type { ActivityStore, ActivityReadGate, recordToolActivity, recordCatalogDriftActivity } from "./activity.js";
|
|
3
3
|
import type { RouteContext } from "./routes/shared.js";
|
|
4
|
+
import type { ArtifactRefreshRuntime } from "./artifacts/refresh.js";
|
|
4
5
|
/** Construction-time UI contract. Core never imports the implementation. */
|
|
5
6
|
export interface OperatorSurface {
|
|
6
7
|
readonly branding?: ConnectaBranding;
|
|
@@ -17,3 +18,22 @@ export interface ActivityModule {
|
|
|
17
18
|
readonly recordTool: typeof recordToolActivity;
|
|
18
19
|
readonly recordDrift: typeof recordCatalogDriftActivity;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The artifacts module, created by `artifacts()` from /artifacts. Core appends
|
|
23
|
+
* its one prebuilt connector to the configured set — the same catalog,
|
|
24
|
+
* invocation, admission, and activity paths as any other — and imports none
|
|
25
|
+
* of its implementation.
|
|
26
|
+
*/
|
|
27
|
+
export interface ArtifactsModule {
|
|
28
|
+
/** The built-in `artifacts` connector. */
|
|
29
|
+
readonly connector: Connector;
|
|
30
|
+
/**
|
|
31
|
+
* Serve the pages' JSON API (`/artifacts/_api/*`) and the sandboxed frame
|
|
32
|
+
* (`/artifacts/_frame`); null for any other path. The operator UI delegates
|
|
33
|
+
* here and serves the page shells itself, so without `ui` there are no
|
|
34
|
+
* artifact routes at all.
|
|
35
|
+
*/
|
|
36
|
+
handle(context: RouteContext): Promise<Response | null>;
|
|
37
|
+
/** Bind the optional refresh runner after core has built its registry and executor. */
|
|
38
|
+
bindRefresh?(runtime: ArtifactRefreshRuntime): void;
|
|
39
|
+
}
|