@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/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
|
|
|
5
5
|
import { routeOAuthCallback, } from "./routes/oauth.js";
|
|
6
6
|
import { runEdge } from "./runtime/run.js";
|
|
7
7
|
import { withSecurityHeaders, } from "./routes/shared.js";
|
|
8
|
+
const isArtifactPath = (path) => path === "/artifacts" || path.startsWith("/artifacts/");
|
|
8
9
|
/**
|
|
9
10
|
* Build the Web-standard fetch handler.
|
|
10
11
|
*
|
|
@@ -50,6 +51,10 @@ export function createFetchHandler(opts) {
|
|
|
50
51
|
...(opts.executorName !== undefined
|
|
51
52
|
? { executor: { name: opts.executorName } }
|
|
52
53
|
: {}),
|
|
54
|
+
// Whether a program's write pauses for resume_execution or fails E4.
|
|
55
|
+
// The tool is listed either way; this is the only place an operator
|
|
56
|
+
// can see which answer it gives (#565).
|
|
57
|
+
resumableWrites: opts.resumable !== undefined,
|
|
53
58
|
// Counts only, from refreshes that already happened — the endpoint
|
|
54
59
|
// asks no downstream anything, and `connecta doctor` reads it to
|
|
55
60
|
// report a stale allowlist without a probe of its own (#343).
|
|
@@ -71,6 +76,7 @@ export function createFetchHandler(opts) {
|
|
|
71
76
|
"/health",
|
|
72
77
|
...(opts.ui?.reservedPaths ?? []),
|
|
73
78
|
...(opts.ui && opts.activity?.list ? ["/activity"] : []),
|
|
79
|
+
...(opts.ui && opts.artifactsModule ? ["/artifacts", "/artifacts/*"] : []),
|
|
74
80
|
],
|
|
75
81
|
},
|
|
76
82
|
...(opts.deploymentInfo ? { deployment: opts.deploymentInfo } : {}),
|
|
@@ -120,6 +126,26 @@ export function createFetchHandler(opts) {
|
|
|
120
126
|
const defer = runtimeContext
|
|
121
127
|
? runtimeContext.waitUntil.bind(runtimeContext)
|
|
122
128
|
: undefined;
|
|
129
|
+
// The artifact hostname has no MCP, health, OAuth, or operator routes.
|
|
130
|
+
// Dispatch before MCP origin checks so even a hostile /mcp is a plain 404.
|
|
131
|
+
if (opts.artifactOrigin && url.origin === new URL(opts.artifactOrigin).origin) {
|
|
132
|
+
if (!isArtifactPath(path) || !opts.ui || !opts.artifactsModule) {
|
|
133
|
+
return Effect.succeed(withSecurityHeaders(new Response("Not Found", { status: 404 }), url, path));
|
|
134
|
+
}
|
|
135
|
+
const context = {
|
|
136
|
+
request, url, path, baseUrl: opts.artifactOrigin, opts, defer, runtimeContext,
|
|
137
|
+
};
|
|
138
|
+
return Effect.map(Effect.promise(() => opts.ui.handle(context)), response => withSecurityHeaders(response ?? new Response("Not Found", { status: 404 }), url, path));
|
|
139
|
+
}
|
|
140
|
+
if (opts.artifactOrigin && isArtifactPath(path) && opts.ui && opts.artifactsModule) {
|
|
141
|
+
const target = new URL(opts.artifactOrigin);
|
|
142
|
+
target.pathname = path;
|
|
143
|
+
target.search = url.search;
|
|
144
|
+
return Effect.succeed(withSecurityHeaders(new Response(null, {
|
|
145
|
+
status: 308,
|
|
146
|
+
headers: { Location: target.toString() },
|
|
147
|
+
}), url, path));
|
|
148
|
+
}
|
|
123
149
|
const originRefusal = routeMcp.rejectOrigin(request);
|
|
124
150
|
if (originRefusal) {
|
|
125
151
|
return Effect.succeed(withSecurityHeaders(originRefusal, url, path));
|
package/dist/skills.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { boundedEchoText } from "./errors.js";
|
|
2
|
-
|
|
2
|
+
const ROUTE = "Choose a route before discovery. A known-address read needs only call_tool. Unknown-address read-only work starts with execute_code to discover, call, and return the answer; use the same route for reduction, multiple or dependent calls, loops, joins, or branches. Keep discovery and calls together when schemas suffice; do not return catalog matches alone. Inspect unfamiliar result shapes with a small sample before proceeding.";
|
|
3
|
+
const RECOVERY = 'After auth_required use authorize_connector. After a truncated direct result use get_result. Guidance is on demand: fetch skills({ name: "usage" }) only when these instructions and the tool description are insufficient or a run needs repair.';
|
|
4
|
+
/**
|
|
5
|
+
* The MCP instructions of a deployment whose programs pause at writes. The
|
|
6
|
+
* route sentence is its own, not ROUTE's: with writes in programs, "one known
|
|
7
|
+
* write" is the only work left for call_destructive_tool, and an agent told
|
|
8
|
+
* only that programs *may* write reads each write after a lookup as a known
|
|
9
|
+
* one and sends them one prompt at a time.
|
|
10
|
+
*/
|
|
11
|
+
export const CONNECTA_INSTRUCTIONS = `Choose a route before discovery. A known-address read needs only call_tool; one known write, call_destructive_tool. Everything else starts with execute_code to discover, call, and return the answer: unknown addresses, reduction, multiple or dependent calls, loops, joins, branches, and writes that follow reads. Keep discovery and calls together when schemas suffice; do not return catalog matches alone. Inspect unfamiliar result shapes with a small sample before proceeding. A program's call to a tool not annotated readOnlyHint: true pauses unsent; resume_execution with the returned token, address, and args approves it and continues the program (approval "tool" covers that tool for the run). Keep catalog inspection top level with search_tools. ${RECOVERY}`;
|
|
12
|
+
/**
|
|
13
|
+
* The instructions of a deployment without resumable writes, whose programs
|
|
14
|
+
* refuse every write: the routing it always had.
|
|
15
|
+
*/
|
|
16
|
+
export const READ_ONLY_PROGRAM_INSTRUCTIONS = `${ROUTE} Only readOnlyHint: true tools run there. Keep catalog inspection and unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool when a call is needed. ${RECOVERY}`;
|
|
3
17
|
const USAGE_SKILL_BASE = `# Connecta usage
|
|
4
18
|
|
|
5
19
|
## The surface
|
|
6
20
|
|
|
7
|
-
|
|
21
|
+
Eight tools: \`execute_code\`, \`resume_execution\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Discovery and multi-call work live in a program. Top-level search remains for catalog inspection and approval-required work.
|
|
8
22
|
|
|
9
23
|
Follow the MCP instructions for routing. Read at most once for syntax or repair.
|
|
10
24
|
|
|
@@ -25,7 +39,7 @@ Search and call in one run when schemas suffice. For unfamiliar result formats,
|
|
|
25
39
|
|
|
26
40
|
For top-level catalog inspection or approval-required discovery, omit \`limit\` initially (the default is 8), then page with a limit up to 50 if needed. Empty or whitespace-only queries browse all tools. A non-empty query with no ASCII terms returns no matches; mixed input searches with its ASCII terms. \`includeSchemas: "compact"\` adds bounded input and available output shapes. An \`outputSchemaSource: "observed"\` shape is a hint, not a contract. Plain objects expose \`inputKeys\`, \`requiredInputKeys\`, and \`outputKeys\`; truncation flags mark incomplete shapes; matches also carry declared annotations.
|
|
27
41
|
|
|
28
|
-
- \`connecta.search({})\` loads all catalogs. Pass \`connector: "<id>"\` when the integration is obvious. Use \`safety: "readOnly"\` for
|
|
42
|
+
- \`connecta.search({})\` loads all catalogs. Pass \`connector: "<id>"\` when the integration is obvious. Use \`safety: "readOnly"\` for reads. These inputs filter discovery; they grant no authority.
|
|
29
43
|
- Use \`includeSchemas: "json"\` for programmatic schema inspection; compact schemas are text, not objects with \`.properties\`. Check connectorTitle for the account/environment, then address, purpose, annotations, inputs, and outputs. Never select only because a result ranks first or has fewer required inputs.
|
|
30
44
|
- Supply every \`requiredInputKey\` from the task or a prior result. For dependencies, match the earlier \`outputKey\` to the later required key. An empty required-key list does not permit invented arguments. Missing \`outputKeys\` means inspect \`outputSchema\`.
|
|
31
45
|
- Use \`connecta.describe({ address })\` or \`{ addresses }\` when a compact schema is truncated or insufficient. Use \`format: "json"\` only for exact constraints. Write the property names the schema displays; never guess positions or aliases.
|
|
@@ -34,13 +48,16 @@ For top-level catalog inspection or approval-required discovery, omit \`limit\`
|
|
|
34
48
|
- Preserve the schema's JSON types exactly: a numeric id is a number, not a numeric-looking string.
|
|
35
49
|
- Validate tabular headers, row arrays, and row widths before mapping them. Never let a header or partial row become data.
|
|
36
50
|
|
|
37
|
-
Only tools explicitly annotated \`readOnlyHint: true\` are reachable. The catalog, credential, admission, and
|
|
51
|
+
Only tools explicitly annotated \`readOnlyHint: true\` are reachable unasked. Where the MCP instructions say programs pause, any other call pauses unsent and \`execute_code\` returns \`paused\` with the write and a token; repeat all three unchanged in top-level \`resume_execution\` to approve (\`approval: "tool"\` covers that tool for the run). Replay reruns the program against a journal: make the same calls in the same order. The catalog, credential, admission, and approval gates run below the sandbox; code cannot widen its authority.
|
|
38
52
|
|
|
39
53
|
## Errors and repair
|
|
40
54
|
|
|
41
55
|
Caught Connecta errors expose \`message\`, \`code\`, \`retryable\`, and \`details\`. Promise rejections retain these fields. Branch on fields, never prose. After a shared argument failure, repair one call before repeating it across other records. Do not retry \`retryable: false\`, and do not retry \`rate_limited\` immediately because portable code has no timer.
|
|
42
56
|
|
|
43
|
-
- \`destructive_tool_requires_approval\`: stop
|
|
57
|
+
- \`destructive_tool_requires_approval\`: stop and send the returned address through top-level \`call_destructive_tool\`.
|
|
58
|
+
- \`execution_paused\`: the run already stopped at a write; let it end.
|
|
59
|
+
- \`write_outcome_unknown\`: sent but unanswered, never re-sent; check the target.
|
|
60
|
+
- \`execution_expired\`, \`_diverged\`, \`_interrupted\`: check \`writes\`, then rerun.
|
|
44
61
|
- \`auth_required\`: let the failure reach the model, then use top-level \`authorize_connector\`, give its handoff to the operator, and retry after recovery.
|
|
45
62
|
- A truncated direct-call result: follow its \`get_result\` action. A truncated program result has no page handle; filter, map, or slice inside a new program.
|
|
46
63
|
- Unknown addresses and tools carry scoped search recovery. Use it inside the current run. Do not invent an address.
|
|
@@ -49,12 +66,14 @@ For a direct call, \`resultMode: "value"\` unwraps the result. \`timeoutMs\` set
|
|
|
49
66
|
|
|
50
67
|
\`get_result({ id, offset?, maxBytes? })\` returns a one-line JSON header \`{ resultId, offset, bytes, totalBytes, hasMore, nextAction? }\`, a newline, and then the page as raw text, for a direct-call result. Both sizes are byte counts: \`maxBytes\` must be a whole number at least 1, defaults to the result's cap, and is clamped to it; \`offset\` must be a whole number at least 0 and defaults to 0. An offset inside a multi-byte character moves back to its first byte, and the header reports the served offset. Follow \`nextAction\` until \`hasMore\` is false to reassemble pages. An unknown or expired id is an error.
|
|
51
68
|
|
|
52
|
-
|
|
69
|
+
The \`execute_code\` description states this deployment's host-call and write budgets and per-call deadline.
|
|
53
70
|
|
|
54
71
|
## Runtime portability
|
|
55
72
|
|
|
56
73
|
Portable code uses standard JavaScript builtins, \`connecta\`, and \`console.*\`. QuickJS blocks imports and lacks fetch, process, timers, crypto, and WebSocket. Dynamic Workers must use only \`{ loader }\`; bindings, modules, or globalOutbound grant ambient authority. With loader only, environment maps are empty; node:fs/http/https are absent; outbound fetch, WebSocket, node:net, and node:tls are denied; DNS is unresolved. Runtime builtins remain through \`import()\` and \`process.getBuiltinModule()\`, including node:path and cloudflare:workers; this set can drift. Timers, process, crypto, WebSocket, and data: fetch remain. Avoid every runtime-only capability because QuickJS fails.
|
|
57
74
|
|
|
75
|
+
\`Date.now()\` is the run's start and does not advance; \`Math.random()\` is seeded per run.
|
|
76
|
+
|
|
58
77
|
## Examples
|
|
59
78
|
|
|
60
79
|
One read-only call at a known address:
|
|
@@ -95,8 +114,8 @@ Connector guides appear in \`skills({})\` and discovery with an exact \`guide\`
|
|
|
95
114
|
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
96
115
|
export const USAGE_SKILL = USAGE_SKILL_BASE + CONNECTOR_GUIDES_SECTION;
|
|
97
116
|
/** The always-loaded MCP `instructions` string. */
|
|
98
|
-
export function instructionsFor() {
|
|
99
|
-
return CONNECTA_INSTRUCTIONS;
|
|
117
|
+
export function instructionsFor(resumableWrites) {
|
|
118
|
+
return resumableWrites ? CONNECTA_INSTRUCTIONS : READ_ONLY_PROGRAM_INSTRUCTIONS;
|
|
100
119
|
}
|
|
101
120
|
/** True when at least one of `connectors` carries a usage guide. */
|
|
102
121
|
export function hasConnectorGuides(connectors) {
|
package/dist/timeout.js
CHANGED
|
@@ -31,6 +31,32 @@ export function withDeadline(operation, options) {
|
|
|
31
31
|
catch: (error) => error,
|
|
32
32
|
}), options));
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Run `operation` under one signal that aborts when any of `signals` does —
|
|
36
|
+
* the MCP call's own and the inbound request's, for the program tools — and
|
|
37
|
+
* detach every listener afterwards, however it ends.
|
|
38
|
+
*/
|
|
39
|
+
export async function underAnySignal(signals, operation) {
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const forwarders = signals
|
|
42
|
+
.filter((signal) => signal !== undefined)
|
|
43
|
+
.map((signal) => {
|
|
44
|
+
const forward = () => controller.abort(signal.reason);
|
|
45
|
+
if (signal.aborted)
|
|
46
|
+
forward();
|
|
47
|
+
else
|
|
48
|
+
signal.addEventListener("abort", forward, { once: true });
|
|
49
|
+
return { signal, forward };
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
return await operation(controller.signal);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
for (const { signal, forward } of forwarders) {
|
|
56
|
+
signal.removeEventListener("abort", forward);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
34
60
|
/**
|
|
35
61
|
* Give one operation a caller-facing deadline and the matching cancellation
|
|
36
62
|
* signal. The timeout rejects with the stable, labelled error while aborting
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Connector, ToolDef } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The one fail-closed classification shared by discovery and invocation.
|
|
4
|
+
*
|
|
5
|
+
* A tool is read-only only when it says so without also saying it is
|
|
6
|
+
* destructive. Missing, false, and contradictory annotations all require the
|
|
7
|
+
* approval-visible call path.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isExplicitlyReadOnly(definition: ToolDef): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* A deployment's approval exemptions, resolved once at construction from
|
|
12
|
+
* `execute.approval` (#566). Config is the only source: nothing an agent, the
|
|
13
|
+
* operator UI, or a downstream catalog says can add an entry.
|
|
14
|
+
*/
|
|
15
|
+
export interface ApprovalPolicy {
|
|
16
|
+
/** Connector id → the default for that connector's tools. */
|
|
17
|
+
readonly connectors: ReadonlyMap<string, "never" | "ask">;
|
|
18
|
+
/** Canonical `connector.tool` address → that tool's own setting. */
|
|
19
|
+
readonly tools: ReadonlyMap<string, "never" | "ask">;
|
|
20
|
+
}
|
|
21
|
+
export declare const NO_EXEMPTIONS: ApprovalPolicy;
|
|
22
|
+
/**
|
|
23
|
+
* Whether a program may call this tool without pausing for approval.
|
|
24
|
+
*
|
|
25
|
+
* Only a tool that is *not* explicitly read-only can be exempt — a read-only
|
|
26
|
+
* tool needs no exemption, and reporting one would blur the two classes an
|
|
27
|
+
* exemption must never merge. The most specific setting wins: the tool's
|
|
28
|
+
* address, then its connector's entry, then the connector's own default,
|
|
29
|
+
* then `"ask"`. Annotations play no part beyond that first test: a downstream
|
|
30
|
+
* that could annotate its way out of approval would be granting itself the
|
|
31
|
+
* capability this exists to keep in config. Exempt never means read-only:
|
|
32
|
+
* discovery keeps it in the approval-required class, and `call_tool` still
|
|
33
|
+
* refuses it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isApprovalExempt(policy: ApprovalPolicy, connector: Pick<Connector, "id" | "approval">, toolName: string, definition: ToolDef): boolean;
|
package/dist/tool-safety.js
CHANGED
|
@@ -9,3 +9,34 @@ export function isExplicitlyReadOnly(definition) {
|
|
|
9
9
|
return (definition.annotations?.readOnlyHint === true &&
|
|
10
10
|
definition.annotations?.destructiveHint !== true);
|
|
11
11
|
}
|
|
12
|
+
export const NO_EXEMPTIONS = {
|
|
13
|
+
connectors: new Map(),
|
|
14
|
+
tools: new Map(),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Whether a program may call this tool without pausing for approval.
|
|
18
|
+
*
|
|
19
|
+
* Only a tool that is *not* explicitly read-only can be exempt — a read-only
|
|
20
|
+
* tool needs no exemption, and reporting one would blur the two classes an
|
|
21
|
+
* exemption must never merge. The most specific setting wins: the tool's
|
|
22
|
+
* address, then its connector's entry, then the connector's own default,
|
|
23
|
+
* then `"ask"`. Annotations play no part beyond that first test: a downstream
|
|
24
|
+
* that could annotate its way out of approval would be granting itself the
|
|
25
|
+
* capability this exists to keep in config. Exempt never means read-only:
|
|
26
|
+
* discovery keeps it in the approval-required class, and `call_tool` still
|
|
27
|
+
* refuses it.
|
|
28
|
+
*/
|
|
29
|
+
export function isApprovalExempt(policy, connector, toolName, definition) {
|
|
30
|
+
if (isExplicitlyReadOnly(definition))
|
|
31
|
+
return false;
|
|
32
|
+
// This tool starts another program. Letting a program call it while holding
|
|
33
|
+
// the only executor permit would deadlock; manual refresh is a top-level
|
|
34
|
+
// action, even though other artifact writes are exempt inside programs.
|
|
35
|
+
if (connector.id === "artifacts" && toolName === "run_refresh")
|
|
36
|
+
return false;
|
|
37
|
+
const setting = policy.tools.get(`${connector.id}.${toolName}`) ??
|
|
38
|
+
policy.connectors.get(connector.id) ??
|
|
39
|
+
connector.approval ??
|
|
40
|
+
"ask";
|
|
41
|
+
return setting === "never";
|
|
42
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -149,6 +149,11 @@ export interface ConnectorCredentialConfig {
|
|
|
149
149
|
}
|
|
150
150
|
export interface CredentialTestResult {
|
|
151
151
|
ok: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Detail for the deployment's log. The operator page shows fixed copy for
|
|
154
|
+
* `ok` and never this text, since a downstream's reply quoted here can
|
|
155
|
+
* quote the credential it rejected.
|
|
156
|
+
*/
|
|
152
157
|
message?: string;
|
|
153
158
|
}
|
|
154
159
|
export interface ConnectorContext {
|
|
@@ -240,6 +245,17 @@ export interface Connector {
|
|
|
240
245
|
title?: string;
|
|
241
246
|
/** How call_tool wraps results. "mcp" passes the content array through; anything else is JSON-wrapped. */
|
|
242
247
|
kind?: "mcp" | "api";
|
|
248
|
+
/**
|
|
249
|
+
* This connector's own default for its tools that are not explicitly
|
|
250
|
+
* read-only, inside `execute_code`: `"never"` lets a program call them
|
|
251
|
+
* without pausing for approval. Reserved for connectors connecta ships
|
|
252
|
+
* whose writes are cheap to undo (the planned artifacts connector, whose
|
|
253
|
+
* every write is a new immutable version); a deployment's own connectors
|
|
254
|
+
* leave it unset and exempt through `execute.approval`, which overrides
|
|
255
|
+
* this either way. Never read from a downstream catalog — a server cannot
|
|
256
|
+
* exempt its own tools — and never makes a tool read-only anywhere else.
|
|
257
|
+
*/
|
|
258
|
+
approval?: "never";
|
|
243
259
|
description?: string;
|
|
244
260
|
/**
|
|
245
261
|
* Max inline result size (bytes) for this connector's tools before
|
|
@@ -394,6 +410,8 @@ export interface AdmissionSnapshot {
|
|
|
394
410
|
maxQueueSize: number;
|
|
395
411
|
queueTimeoutMs: number;
|
|
396
412
|
retryAfterMs: number;
|
|
413
|
+
/** Hard admitted-request lifetime, present only on the /mcp pool. */
|
|
414
|
+
maxDurationMs?: number;
|
|
397
415
|
active: number;
|
|
398
416
|
queued: number;
|
|
399
417
|
closed: boolean;
|
package/dist/ui.d.ts
CHANGED
|
@@ -2,18 +2,21 @@ import type { OperatorSurface } from "./module-contracts.js";
|
|
|
2
2
|
import type { CredentialVault } from "./credential-contract.js";
|
|
3
3
|
import { type DeferredWork } from "./connector-scope.js";
|
|
4
4
|
import { type CredentialManagementCapability, type UiData, type UiProblem, type UiToolSafety } from "./operator-ui/model.js";
|
|
5
|
+
import { type ApprovalPolicy } from "./tool-safety.js";
|
|
5
6
|
import type { RegistryView } from "./registry.js";
|
|
6
7
|
import type { ConnectaBranding, Connector, ConnectorStatus, ToolDef, UiAuthConfig } from "./types.js";
|
|
7
8
|
export { filterUiConnectors, type CredentialManagementCapability, type UiConnector, type UiData, } from "./operator-ui/model.js";
|
|
8
9
|
export { CONNECTA_FAVICON_SVG, resolveBranding, isSafeHttpUrl, isSafeHttpsUrl, isSafeIconHref } from "./branding.js";
|
|
9
|
-
export type OperatorPage = "connections" | "activity";
|
|
10
|
+
export type OperatorPage = "connections" | "activity" | "artifacts" | "artifact";
|
|
10
11
|
export declare function operatorPageForPath(path: string): OperatorPage | undefined;
|
|
11
12
|
export declare function operatorPageTitle(page: OperatorPage, configuredTitle: string): string;
|
|
12
13
|
/**
|
|
13
|
-
* The badge a tool earns, from the
|
|
14
|
-
* here so the page reports the rule rather than restating it.
|
|
14
|
+
* The badge a tool earns, from the predicates core enforces with. Computed
|
|
15
|
+
* here so the page reports the rule rather than restating it. `exempt` is the
|
|
16
|
+
* config exemption (#566), which `isApprovalExempt` already refuses to grant
|
|
17
|
+
* a read-only tool.
|
|
15
18
|
*/
|
|
16
|
-
export declare function uiToolSafety(definition: ToolDef): UiToolSafety;
|
|
19
|
+
export declare function uiToolSafety(definition: ToolDef, exempt?: boolean): UiToolSafety;
|
|
17
20
|
/**
|
|
18
21
|
* The fix-prompt key for a connector that is not usable. Chosen from the
|
|
19
22
|
* status state and the connector's declared auth shape — configuration, never
|
|
@@ -40,12 +43,16 @@ export declare function buildUiData(registry: RegistryView, baseUrl: string, ser
|
|
|
40
43
|
mayManage?: (id: string) => boolean;
|
|
41
44
|
timeoutMs?: number;
|
|
42
45
|
signal?: AbortSignal;
|
|
46
|
+
approval?: ApprovalPolicy;
|
|
43
47
|
}): Promise<UiData>;
|
|
44
48
|
/**
|
|
45
49
|
* Every operator page serves this same data-free shell. Connector, credential,
|
|
46
50
|
* and activity data arrives only through the authenticated `/ui/*` APIs.
|
|
47
51
|
*/
|
|
48
|
-
export declare function renderUiHtml(uiAuth?: UiAuthConfig, mcpUrl?: string, branding?: ConnectaBranding, nonce?: string, page?: OperatorPage
|
|
52
|
+
export declare function renderUiHtml(uiAuth?: UiAuthConfig, mcpUrl?: string, branding?: ConnectaBranding, nonce?: string, page?: OperatorPage, options?: {
|
|
53
|
+
/** Where the Connections page lives, when this shell is on another origin. */
|
|
54
|
+
homeUrl?: string;
|
|
55
|
+
}): string;
|
|
49
56
|
/** Mount the connection UI without enabling any storage or activity module. */
|
|
50
57
|
export declare function operatorUi(options?: {
|
|
51
58
|
branding?: ConnectaBranding;
|
package/dist/ui.js
CHANGED
|
@@ -18,23 +18,39 @@ function stringForInlineScript(value) {
|
|
|
18
18
|
const OPERATOR_PAGE_LABELS = {
|
|
19
19
|
connections: "Connections",
|
|
20
20
|
activity: "Activity",
|
|
21
|
+
artifacts: "Artifacts",
|
|
22
|
+
artifact: "Artifact",
|
|
21
23
|
};
|
|
24
|
+
/** `/artifacts/<id>` and its snapshots, `/artifacts/<id>/v/<version>`. */
|
|
25
|
+
const ARTIFACT_PAGE = /^\/artifacts\/[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?(?:\/v\/\d{1,9})?$/;
|
|
22
26
|
export function operatorPageForPath(path) {
|
|
23
27
|
if (path === "/")
|
|
24
28
|
return "connections";
|
|
25
29
|
if (path === "/activity")
|
|
26
30
|
return "activity";
|
|
31
|
+
if (path === "/artifacts")
|
|
32
|
+
return "artifacts";
|
|
33
|
+
if (ARTIFACT_PAGE.test(path))
|
|
34
|
+
return "artifact";
|
|
27
35
|
return undefined;
|
|
28
36
|
}
|
|
37
|
+
/** Everything artifact pages own: shells, their API, and the frame. */
|
|
38
|
+
function isArtifactPath(path) {
|
|
39
|
+
return path === "/artifacts" || path.startsWith("/artifacts/");
|
|
40
|
+
}
|
|
29
41
|
export function operatorPageTitle(page, configuredTitle) {
|
|
30
42
|
return `${OPERATOR_PAGE_LABELS[page]} — ${configuredTitle}`;
|
|
31
43
|
}
|
|
32
44
|
/**
|
|
33
|
-
* The badge a tool earns, from the
|
|
34
|
-
* here so the page reports the rule rather than restating it.
|
|
45
|
+
* The badge a tool earns, from the predicates core enforces with. Computed
|
|
46
|
+
* here so the page reports the rule rather than restating it. `exempt` is the
|
|
47
|
+
* config exemption (#566), which `isApprovalExempt` already refuses to grant
|
|
48
|
+
* a read-only tool.
|
|
35
49
|
*/
|
|
36
|
-
export function uiToolSafety(definition) {
|
|
37
|
-
|
|
50
|
+
export function uiToolSafety(definition, exempt = false) {
|
|
51
|
+
if (isExplicitlyReadOnly(definition))
|
|
52
|
+
return "runs_in_programs";
|
|
53
|
+
return exempt ? "exempt" : "needs_approval";
|
|
38
54
|
}
|
|
39
55
|
/**
|
|
40
56
|
* The fix-prompt key for a connector that is not usable. Chosen from the
|
|
@@ -99,7 +115,7 @@ function jsonForInlineScript(value) {
|
|
|
99
115
|
* Every operator page serves this same data-free shell. Connector, credential,
|
|
100
116
|
* and activity data arrives only through the authenticated `/ui/*` APIs.
|
|
101
117
|
*/
|
|
102
|
-
export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "connections") {
|
|
118
|
+
export function renderUiHtml(uiAuth, mcpUrl = "/mcp", branding, nonce, page = "connections", options = {}) {
|
|
103
119
|
const clerk = uiAuth?.kind === "clerk" ? uiAuth : undefined;
|
|
104
120
|
// The Clerk loader's origin. A value that fails the gate is dropped rather
|
|
105
121
|
// than escaped into the page: the loader tag is simply not emitted, the gate
|
|
@@ -197,6 +213,7 @@ ${clerkScript}
|
|
|
197
213
|
const AUTH = ${jsonForInlineScript(auth)};
|
|
198
214
|
const MCP_URL = ${jsonForInlineScript(mcpUrl)};
|
|
199
215
|
const INITIAL_PAGE = ${jsonForInlineScript(page)};
|
|
216
|
+
const HOME_URL = ${jsonForInlineScript(options.homeUrl ?? "/")};
|
|
200
217
|
const TITLE_SUFFIX = ${jsonForInlineScript(brand.pageTitle)};
|
|
201
218
|
const PRODUCT_NAME = ${stringForInlineScript(brand.productName)};
|
|
202
219
|
const PRODUCT_DESCRIPTION = ${stringForInlineScript(brand.description)};
|
|
@@ -226,8 +243,17 @@ export function operatorUi(options = {}) {
|
|
|
226
243
|
// route here, and no activity module, runs for a path this surface does
|
|
227
244
|
// not own. The Activity page is the one addition; the server reserves
|
|
228
245
|
// it only when history is readable, and routeUi checks the same thing.
|
|
229
|
-
|
|
246
|
+
// Artifact pages are the other: they exist only beside the module.
|
|
247
|
+
const artifacts = context.opts.artifactsModule;
|
|
248
|
+
const artifactPath = Boolean(artifacts) && isArtifactPath(context.path);
|
|
249
|
+
if (!artifactPath && !ownsOperatorPath(reservedPaths, context.path))
|
|
230
250
|
return null;
|
|
251
|
+
if (artifactPath && !operatorPageForPath(context.path)) {
|
|
252
|
+
// The pages' JSON API and the sandboxed frame; the module owns both,
|
|
253
|
+
// and this bundle imports none of it.
|
|
254
|
+
return (await artifacts?.handle(context)) ??
|
|
255
|
+
new Response("Not Found", { status: 404 });
|
|
256
|
+
}
|
|
231
257
|
const routes = [
|
|
232
258
|
...(context.opts.credentialVault ? [routeCredentials] : []),
|
|
233
259
|
routeOAuthManagement,
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED