@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
|
@@ -6,13 +6,16 @@ import type { CatalogAccessObservation, CatalogDriftReport } from "../types.js";
|
|
|
6
6
|
* the rule is a second place for it to be wrong.
|
|
7
7
|
*
|
|
8
8
|
* - `runs_in_programs` — explicitly read-only, so `execute_code` may call it.
|
|
9
|
-
* - `
|
|
9
|
+
* - `exempt` — not read-only, but the deployment's `execute.approval` lets a
|
|
10
|
+
* program call it without pausing (#566). Still approval-required
|
|
11
|
+
* everywhere else.
|
|
12
|
+
* - `needs_approval` — everything else: a program pauses for
|
|
13
|
+
* `resume_execution`, and a direct call crosses `call_destructive_tool`.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* is one entry, not a new branch.
|
|
15
|
+
* The badge renders from a table keyed by this union, so each value is one
|
|
16
|
+
* entry, not a branch.
|
|
14
17
|
*/
|
|
15
|
-
export type UiToolSafety = "runs_in_programs" | "needs_approval";
|
|
18
|
+
export type UiToolSafety = "runs_in_programs" | "exempt" | "needs_approval";
|
|
16
19
|
export interface UiTool {
|
|
17
20
|
name: string;
|
|
18
21
|
address: string;
|
|
@@ -122,6 +125,60 @@ export interface UiData {
|
|
|
122
125
|
* it with a 404, so the page never enumerates a pool its reader cannot open.
|
|
123
126
|
*/
|
|
124
127
|
pools?: string[];
|
|
128
|
+
/** True when artifact pages are mounted and this identity may open them. */
|
|
129
|
+
artifactsEnabled?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* One row of the artifact library. Every field is a fact the store keeps or
|
|
133
|
+
* an enum; nothing here is a message, a log line, or a downstream's words.
|
|
134
|
+
*/
|
|
135
|
+
export interface UiArtifactRow {
|
|
136
|
+
id: string;
|
|
137
|
+
title: string;
|
|
138
|
+
kind: "html" | "markdown";
|
|
139
|
+
viewVersion: number;
|
|
140
|
+
updatedAt: string;
|
|
141
|
+
updatedBy: {
|
|
142
|
+
label: string;
|
|
143
|
+
};
|
|
144
|
+
archived: boolean;
|
|
145
|
+
freshness?: UiArtifactFreshness;
|
|
146
|
+
}
|
|
147
|
+
interface UiArtifactFreshness {
|
|
148
|
+
state: "unconfigured" | "current" | "stale";
|
|
149
|
+
schedule?: "manual" | "daily" | "weekly";
|
|
150
|
+
document?: string;
|
|
151
|
+
dueAt?: string;
|
|
152
|
+
last?: {
|
|
153
|
+
at: string;
|
|
154
|
+
status: "succeeded" | "unchanged" | "failed" | "superseded";
|
|
155
|
+
};
|
|
156
|
+
running?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/** The viewer's payload: the page's facts and the one document its frame loads. */
|
|
159
|
+
export interface UiArtifactView {
|
|
160
|
+
id: string;
|
|
161
|
+
title: string;
|
|
162
|
+
kind: "html" | "markdown";
|
|
163
|
+
archived: boolean;
|
|
164
|
+
snapshot: boolean;
|
|
165
|
+
latestViewVersion: number;
|
|
166
|
+
view: {
|
|
167
|
+
version: number;
|
|
168
|
+
at: string;
|
|
169
|
+
by: {
|
|
170
|
+
label: string;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
documents: {
|
|
174
|
+
name: string;
|
|
175
|
+
version: number;
|
|
176
|
+
updatedAt: string;
|
|
177
|
+
}[];
|
|
178
|
+
url: string;
|
|
179
|
+
snapshotUrl: string;
|
|
180
|
+
document: string;
|
|
181
|
+
freshness?: UiArtifactFreshness;
|
|
125
182
|
}
|
|
126
183
|
export interface FilteredUiConnector {
|
|
127
184
|
connector: UiConnector;
|
package/dist/operator-ui/view.js
CHANGED
|
@@ -1,21 +1,85 @@
|
|
|
1
|
+
/** Pages the nav lists. A single artifact is reached from the library, not the nav. */
|
|
1
2
|
export const OPERATOR_PAGES = [
|
|
2
3
|
"connections",
|
|
3
4
|
"activity",
|
|
5
|
+
"artifacts",
|
|
4
6
|
];
|
|
5
7
|
export const PAGE_META = {
|
|
6
8
|
connections: { path: "/", label: "Connections" },
|
|
7
9
|
activity: { path: "/activity", label: "Activity" },
|
|
10
|
+
artifacts: { path: "/artifacts", label: "Artifacts" },
|
|
11
|
+
artifact: { path: "/artifacts", label: "Artifact" },
|
|
8
12
|
};
|
|
9
13
|
export function pageForPath(path) {
|
|
14
|
+
if (path.startsWith("/artifacts/"))
|
|
15
|
+
return "artifact";
|
|
10
16
|
const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === path);
|
|
11
17
|
return match ?? "connections";
|
|
12
18
|
}
|
|
19
|
+
/** Artifact pages load their own data and never ask `/ui/data` whether to open. */
|
|
20
|
+
export function isArtifactPage(page) {
|
|
21
|
+
return page === "artifacts" || page === "artifact";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Where a viewer's frame gets its page: `/artifacts/<id>` or a snapshot,
|
|
25
|
+
* `/artifacts/<id>/v/<version>?d=<name>:<version>…`, mapped to its API call.
|
|
26
|
+
*/
|
|
27
|
+
export function artifactViewRequest(pathname, search) {
|
|
28
|
+
const match = /^\/artifacts\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?)(?:\/v\/(\d{1,9}))?$/.exec(pathname);
|
|
29
|
+
if (!match?.[1])
|
|
30
|
+
return undefined;
|
|
31
|
+
const params = new URLSearchParams();
|
|
32
|
+
if (match[2]) {
|
|
33
|
+
params.set("v", match[2]);
|
|
34
|
+
for (const pin of new URLSearchParams(search).getAll("d"))
|
|
35
|
+
params.append("d", pin);
|
|
36
|
+
}
|
|
37
|
+
const query = params.toString();
|
|
38
|
+
return `/artifacts/_api/view/${match[1]}${query ? `?${query}` : ""}`;
|
|
39
|
+
}
|
|
13
40
|
export function info(message) {
|
|
14
41
|
return { message, tone: "info" };
|
|
15
42
|
}
|
|
16
43
|
export function failure(message, fix) {
|
|
17
44
|
return fix ? { message, tone: "error", fix } : { message, tone: "error" };
|
|
18
45
|
}
|
|
46
|
+
const REFUSED_COPY = {
|
|
47
|
+
oauth_disconnect: "OAuth disconnect failed. If the downstream refused it, the deployment's log has its reply.",
|
|
48
|
+
oauth_reconnect: "OAuth authorization could not restart. If the downstream refused it, the deployment's log has its reply.",
|
|
49
|
+
credential_test: "The credential test could not run.",
|
|
50
|
+
};
|
|
51
|
+
/** The route's answer to a finished OAuth action. Its state picks the sentence. */
|
|
52
|
+
export function oauthDoneNotice(action, answer) {
|
|
53
|
+
if (action === "oauth_disconnect") {
|
|
54
|
+
return info("OAuth disconnected. Restart authorization when you are ready to reconnect.");
|
|
55
|
+
}
|
|
56
|
+
return info(answer?.state === "ok"
|
|
57
|
+
? "OAuth reconnected."
|
|
58
|
+
: "Authorization restarted. Open the authorization link to reconnect.");
|
|
59
|
+
}
|
|
60
|
+
/** A credential Test's result. `ok` is the only part of the answer it reads. */
|
|
61
|
+
export function credentialTestNotice(connectorId, answer) {
|
|
62
|
+
return answer?.ok === true
|
|
63
|
+
? info("Credential is valid.")
|
|
64
|
+
: failure("Credential test failed: the downstream rejected the stored credential, or the test could not reach it. The deployment's log has the downstream's reply.", { kind: "credential_test_failed", connectorId });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A route that refused one of these actions. A credential Test that found
|
|
68
|
+
* nothing usable to test names the problem, which picks the same copy and fix
|
|
69
|
+
* prompt the Connections page uses for it; every other refusal gets the
|
|
70
|
+
* action's one sentence. `problem` is whatever the response carried, so it is
|
|
71
|
+
* checked, not trusted.
|
|
72
|
+
*/
|
|
73
|
+
export function refusedNotice(action, connectorId, problem) {
|
|
74
|
+
if (action === "credential_test" &&
|
|
75
|
+
(problem === "credential_required" || problem === "credential_mismatch")) {
|
|
76
|
+
return failure(PROBLEM_COPY[problem], { kind: problem, connectorId });
|
|
77
|
+
}
|
|
78
|
+
return failure(REFUSED_COPY[action], {
|
|
79
|
+
kind: action === "credential_test" ? "credential_test_failed" : "oauth_action_failed",
|
|
80
|
+
connectorId,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
19
83
|
export function initialState(page) {
|
|
20
84
|
return {
|
|
21
85
|
page,
|
|
@@ -46,6 +110,13 @@ function identityScopedState() {
|
|
|
46
110
|
activityEvents: [],
|
|
47
111
|
activityCursor: null,
|
|
48
112
|
activitySearch: "",
|
|
113
|
+
artifactPhase: "idle",
|
|
114
|
+
artifactNotice: null,
|
|
115
|
+
artifactRows: [],
|
|
116
|
+
artifactCursor: null,
|
|
117
|
+
artifactQuery: "",
|
|
118
|
+
artifactArchived: false,
|
|
119
|
+
artifactView: null,
|
|
49
120
|
};
|
|
50
121
|
}
|
|
51
122
|
/**
|
|
@@ -151,8 +222,8 @@ export function connectorSummaryParts(summary) {
|
|
|
151
222
|
];
|
|
152
223
|
}
|
|
153
224
|
/**
|
|
154
|
-
* The badge for each call path, keyed by the server's classification so
|
|
155
|
-
*
|
|
225
|
+
* The badge for each call path, keyed by the server's classification so each
|
|
226
|
+
* state is a row here rather than a branch in a component.
|
|
156
227
|
*/
|
|
157
228
|
export const TOOL_SAFETY_BADGE = {
|
|
158
229
|
runs_in_programs: {
|
|
@@ -160,10 +231,15 @@ export const TOOL_SAFETY_BADGE = {
|
|
|
160
231
|
tone: "ok",
|
|
161
232
|
title: "Explicitly read-only: execute_code programs may call it without asking.",
|
|
162
233
|
},
|
|
234
|
+
exempt: {
|
|
235
|
+
label: "exempt from approval",
|
|
236
|
+
tone: "neutral",
|
|
237
|
+
title: "Not read-only, but this deployment's config lets programs call it without pausing. Each call still counts against the write budget and appears in activity; call_tool still refuses it.",
|
|
238
|
+
},
|
|
163
239
|
needs_approval: {
|
|
164
|
-
label: "
|
|
240
|
+
label: "asks for approval",
|
|
165
241
|
tone: "warn",
|
|
166
|
-
title: "Not explicitly read-only:
|
|
242
|
+
title: "Not explicitly read-only: a program pauses for resume_execution, and a direct call crosses call_destructive_tool — either way the host asks first.",
|
|
167
243
|
},
|
|
168
244
|
};
|
|
169
245
|
/**
|
|
@@ -320,13 +396,26 @@ export function activitySummary(events) {
|
|
|
320
396
|
const tools = new Set(events.map((event) => event.address)).size;
|
|
321
397
|
return `${events.length} loaded call${events.length === 1 ? "" : "s"} · ${tools} tool${tools === 1 ? "" : "s"} · no arguments or results stored`;
|
|
322
398
|
}
|
|
323
|
-
|
|
399
|
+
// A pause and an approval are neither success nor failure: each gets its own
|
|
400
|
+
// class, and the stylesheet paints neither as an error.
|
|
401
|
+
const ACTIVITY_OUTCOMES = [
|
|
402
|
+
"success",
|
|
403
|
+
"error",
|
|
404
|
+
"timeout",
|
|
405
|
+
"cancelled",
|
|
406
|
+
"paused",
|
|
407
|
+
"approved",
|
|
408
|
+
];
|
|
324
409
|
export function activityOutcomeClass(outcome) {
|
|
325
410
|
return ACTIVITY_OUTCOMES.includes(outcome) ? outcome : "error";
|
|
326
411
|
}
|
|
327
412
|
/** The one-line detail under an address: source, retries, and friction. */
|
|
328
413
|
export function activityDetail(event) {
|
|
329
414
|
const parts = [event.source];
|
|
415
|
+
if (event.approval === "tool")
|
|
416
|
+
parts.push("approved for the rest of the run");
|
|
417
|
+
if (event.approval === "call")
|
|
418
|
+
parts.push("approved for this call");
|
|
330
419
|
if (event.attempts > 1)
|
|
331
420
|
parts.push(`${event.attempts} attempts`);
|
|
332
421
|
if (event.friction)
|
package/dist/program-source.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// QuickJS normalizer runs it too for callers that drive that executor directly.
|
|
4
4
|
/** A whole-program markdown fence, as QuickJS's normalizer recognizes one. */
|
|
5
5
|
const FENCE = /^```[\w-]*\s*\n([\s\S]*?)\n?```$/;
|
|
6
|
+
/** Intake accepts one JavaScript fence, optionally surrounded by prose. */
|
|
7
|
+
const PROGRAM_FENCE = /^```(?:js|javascript)?[ \t]*\r?\n([\s\S]*?)\r?\n```[ \t]*$/gm;
|
|
6
8
|
/** Leading whitespace and comments, which may precede the arrow expression. */
|
|
7
9
|
const LEADING_TRIVIA = /^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/;
|
|
8
10
|
/** One character of an identifier, keyword, or number; `\\` for escapes. */
|
|
@@ -44,6 +46,120 @@ export function withoutProgramTerminator(code) {
|
|
|
44
46
|
const stripped = stripTerminators(source);
|
|
45
47
|
return stripped === source ? code : stripped;
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Recover wrappers that models put around a single program. This runs in the
|
|
51
|
+
* host, so QuickJS and Dynamic Workers receive the same source. It does not
|
|
52
|
+
* turn arbitrary statements into a program: an arrow remains the contract.
|
|
53
|
+
*/
|
|
54
|
+
export function normalizeProgramSource(code) {
|
|
55
|
+
let source = code.trim();
|
|
56
|
+
const head = source.replace(LEADING_TRIVIA, "");
|
|
57
|
+
// A program may itself contain fenced Markdown in a comment or string. Once
|
|
58
|
+
// the source starts as a program, a fence inside it is data, never a wrapper.
|
|
59
|
+
if (!/^(?:async\b|\(|export\s+default\s+async\b)/.test(head)) {
|
|
60
|
+
const fences = [...source.matchAll(PROGRAM_FENCE)];
|
|
61
|
+
if (fences.length === 1) {
|
|
62
|
+
const fence = fences[0];
|
|
63
|
+
const outside = source.slice(0, fence.index) + source.slice(fence.index + fence[0].length);
|
|
64
|
+
const body = fence[1].trim();
|
|
65
|
+
const fencedHead = body.replace(LEADING_TRIVIA, "");
|
|
66
|
+
if (!outside.includes("```") && /^(?:async\b|\(|export\s+default\s+async\b)/.test(fencedHead)) {
|
|
67
|
+
source = body;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
source = source.replace(/^export\s+default\s+(?=async\s*(?:\(|[A-Za-z_$]))/, "");
|
|
72
|
+
// The declaration must be the whole source. A scanner finds its own closing
|
|
73
|
+
// brace rather than mistaking a brace in a string or a second statement for
|
|
74
|
+
// the end of the function.
|
|
75
|
+
const named = /^async\s+function\s+([A-Za-z_$][\w$]*)\s*\(([^()]*)\)\s*\{/.exec(source);
|
|
76
|
+
if (named) {
|
|
77
|
+
const end = functionBodyEnd(source, named[0].length - 1);
|
|
78
|
+
if (end !== undefined && /^(?:\s|;|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*$/.test(source.slice(end + 1))) {
|
|
79
|
+
// Invoke a named function expression inside the arrow. Its own name and
|
|
80
|
+
// `arguments` binding then work exactly as in the original declaration.
|
|
81
|
+
source = `async (...args) => (async function ${named[1]}(${named[2]}) {${source.slice(named[0].length, end)}})(...args)${source.slice(end + 1)}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return withoutProgramTerminator(source);
|
|
85
|
+
}
|
|
86
|
+
/** Find the matching closing brace of a named function's body. */
|
|
87
|
+
function functionBodyEnd(source, open) {
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let previous = "";
|
|
90
|
+
for (let i = open; i < source.length; i++) {
|
|
91
|
+
const c = source[i];
|
|
92
|
+
if (/\s/.test(c))
|
|
93
|
+
continue;
|
|
94
|
+
if (c === "/" && source[i + 1] === "/") {
|
|
95
|
+
const end = source.indexOf("\n", i + 2);
|
|
96
|
+
i = end === -1 ? source.length : end;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (c === "/" && source[i + 1] === "*") {
|
|
100
|
+
const end = source.indexOf("*/", i + 2);
|
|
101
|
+
if (end === -1)
|
|
102
|
+
return undefined;
|
|
103
|
+
i = end + 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
107
|
+
let end = i + 1;
|
|
108
|
+
for (; end < source.length && source[end] !== c; end++) {
|
|
109
|
+
if (source[end] === "\\")
|
|
110
|
+
end++;
|
|
111
|
+
else if (source[end] === "\n" && c !== "`")
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (end >= source.length)
|
|
115
|
+
return undefined;
|
|
116
|
+
i = end;
|
|
117
|
+
previous = "0";
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (c === "/" && startsRegex(previous)) {
|
|
121
|
+
let end = i + 1;
|
|
122
|
+
let inClass = false;
|
|
123
|
+
for (; end < source.length; end++) {
|
|
124
|
+
const r = source[end];
|
|
125
|
+
if (r === "\n")
|
|
126
|
+
return undefined;
|
|
127
|
+
if (r === "\\")
|
|
128
|
+
end++;
|
|
129
|
+
else if (r === "[")
|
|
130
|
+
inClass = true;
|
|
131
|
+
else if (r === "]")
|
|
132
|
+
inClass = false;
|
|
133
|
+
else if (r === "/" && !inClass)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (end >= source.length)
|
|
137
|
+
return undefined;
|
|
138
|
+
i = end;
|
|
139
|
+
previous = "0";
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (WORD.test(c)) {
|
|
143
|
+
let end = i + 1;
|
|
144
|
+
while (end < source.length && WORD.test(source[end]))
|
|
145
|
+
end++;
|
|
146
|
+
previous = source.slice(i, end);
|
|
147
|
+
i = end - 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (c === "{")
|
|
151
|
+
depth++;
|
|
152
|
+
else if (c === "}") {
|
|
153
|
+
depth--;
|
|
154
|
+
if (depth === 0)
|
|
155
|
+
return i;
|
|
156
|
+
if (depth < 0)
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
previous = c;
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
47
163
|
function stripTerminators(source) {
|
|
48
164
|
const head = source.replace(LEADING_TRIVIA, "");
|
|
49
165
|
if (!/^async\b/.test(head) && !head.startsWith("("))
|
package/dist/registry.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CatalogDriftReport, Connector, ConnectorContext, ConnectorStatus,
|
|
|
3
3
|
import { type DeferredWork } from "./connector-scope.js";
|
|
4
4
|
import { type CatalogDriftActivityContext } from "./activity.js";
|
|
5
5
|
import { type CallAdmissionPermit, type ConnectorCallAdmissionSnapshot } from "./call-admission.js";
|
|
6
|
+
import { type ConnectorCaller } from "./connector-caller.js";
|
|
6
7
|
/**
|
|
7
8
|
* Split `"<connectorId>.<toolName>"` on the first dot. Connector ids contain
|
|
8
9
|
* no dots, so a downstream tool name may. Exported because an address that
|
|
@@ -129,8 +130,11 @@ export type ToolAccess = ReadonlyMap<string, ReadonlySet<string>>;
|
|
|
129
130
|
export interface RegistryScope {
|
|
130
131
|
connectorIds: "all" | readonly string[];
|
|
131
132
|
toolAccess?: ToolAccess;
|
|
133
|
+
guardedToolAccess?: ToolAccess;
|
|
132
134
|
subjectKey?: string;
|
|
133
135
|
principalKey?: string;
|
|
136
|
+
/** The admitted caller, readable only by built-in connectors; see connector-caller.ts. */
|
|
137
|
+
caller?: ConnectorCaller;
|
|
134
138
|
}
|
|
135
139
|
/**
|
|
136
140
|
* Holds the connector set, resolves addresses, and caches per-connector tool
|
|
@@ -147,7 +151,7 @@ export declare class Registry implements RegistryView {
|
|
|
147
151
|
private readonly catalogGenerations;
|
|
148
152
|
/** Serialize persisted catalog set/delete operations within this isolate. */
|
|
149
153
|
private readonly catalogMutations;
|
|
150
|
-
/** Same-request cold loads share one
|
|
154
|
+
/** Same-request cold loads share one read without retaining the request.
|
|
151
155
|
* See documentation/architecture.md#the-two-lifetimes. */
|
|
152
156
|
private readonly requestCatalogLoads;
|
|
153
157
|
/** One live refresh per connector across agent and operator requests. */
|
|
@@ -283,11 +287,37 @@ export declare class Registry implements RegistryView {
|
|
|
283
287
|
private enqueueCatalogMutation;
|
|
284
288
|
private refreshToolsWithContext;
|
|
285
289
|
/**
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
290
|
+
* Whether a refresh may still enter the shared cache layers: its generation
|
|
291
|
+
* is current, and the flight it runs in has been neither abandoned nor
|
|
292
|
+
* outlived its bound. Asked at each publication point, since listing and
|
|
293
|
+
* snapshotting both yield, and a late result must never overwrite the one a
|
|
294
|
+
* fresh attempt published in the meantime.
|
|
295
|
+
*/
|
|
296
|
+
private mayPublish;
|
|
297
|
+
/**
|
|
298
|
+
* Publish one shared refresh before starting its connector work. The first
|
|
299
|
+
* caller owns the scope and signal, and the flight lives `boundMs` from now
|
|
300
|
+
* at most; every later caller joins the result without gaining access to
|
|
301
|
+
* that context. A flight found past its bound is abandoned here, and this
|
|
302
|
+
* caller starts the fresh attempt.
|
|
289
303
|
*/
|
|
290
304
|
private startCatalogRefresh;
|
|
305
|
+
/**
|
|
306
|
+
* Give up on a flight that outlived its bound. Later readers start a fresh
|
|
307
|
+
* attempt, its joiners are told to, and nothing its owner lists from now on
|
|
308
|
+
* reaches a cache. The owner's work is left alone: it belongs to another
|
|
309
|
+
* request, which nothing here may reach into, and it can no longer publish.
|
|
310
|
+
*/
|
|
311
|
+
private abandonCatalogRefresh;
|
|
312
|
+
/**
|
|
313
|
+
* Wait on another caller's flight, no longer than its bound. Succeeds with
|
|
314
|
+
* its tools, or with `undefined` when it will not answer for this caller —
|
|
315
|
+
* past its bound, when this waiter abandons it, or failed for its owner
|
|
316
|
+
* alone — so the caller makes a fresh attempt. The timer is this waiter's
|
|
317
|
+
* own: on workerd it is what keeps a request that waits on another alive,
|
|
318
|
+
* and bounded.
|
|
319
|
+
*/
|
|
320
|
+
private joinCatalogRefresh;
|
|
291
321
|
/** Force a live listTools refresh and replace both catalog cache layers. */
|
|
292
322
|
private refreshTools;
|
|
293
323
|
private observeCatalogAccess;
|
|
@@ -303,9 +333,15 @@ export declare class Registry implements RegistryView {
|
|
|
303
333
|
* WeakMap neither roots the request scope nor lets its connector context
|
|
304
334
|
* escape into another request; settled entries are also removed eagerly.
|
|
305
335
|
*
|
|
336
|
+
* The read owns its signal, and each caller waits under its own: one that
|
|
337
|
+
* is cancelled while others wait leaves with its own failure, and the read
|
|
338
|
+
* is cancelled only when every caller has gone (src/runtime/shared-read.ts).
|
|
339
|
+
*
|
|
306
340
|
* The deployment-wide flight below this layer coalesces the actual live
|
|
307
|
-
* refresh across requests. Its
|
|
308
|
-
* later callers join only its result, never its
|
|
341
|
+
* refresh across requests. Its owner's context governs the listing, and its
|
|
342
|
+
* owner's deadline bounds it; later callers join only its result, never its
|
|
343
|
+
* request scope, and a failure that was the owner's alone sends them to a
|
|
344
|
+
* fresh attempt rather than to them.
|
|
309
345
|
*/
|
|
310
346
|
getTools(id: string, baseUrl: string, requestScope?: object, callOptions?: ConnectorOperationOptions, readOptions?: CatalogReadOptions): Promise<ToolDef[]>;
|
|
311
347
|
credentialDriftFor(id: string): Promise<string | undefined>;
|