@zackbart/connecta 0.40.0 → 0.41.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/CHANGELOG.md +87 -0
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +30 -4
- package/dist/activity.js +28 -2
- package/dist/agent-observation.js +24 -2
- package/dist/agent-routes.js +4 -41
- package/dist/auth/clerk.d.ts +6 -1
- package/dist/auth/clerk.js +16 -0
- package/dist/auth/cloudflare-access.js +49 -7
- package/dist/auth/downstream-oauth/consent.js +79 -29
- package/dist/auth/downstream-oauth/grant-store.js +58 -7
- package/dist/auth/downstream-oauth/refresh-records.js +6 -5
- package/dist/auth/downstream-oauth.js +12 -4
- package/dist/auth/oauth-request-not-sent.js +4 -0
- package/dist/auth/static-oauth.js +51 -21
- package/dist/catalog-cache.js +62 -21
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-service.js +39 -12
- package/dist/catalog.js +103 -38
- package/dist/config.d.ts +10 -4
- package/dist/config.js +30 -7
- package/dist/connectors/remote-mcp/call.js +1 -1
- package/dist/connectors/remote-mcp/connection.js +52 -18
- package/dist/connectors/remote-mcp/oauth.js +2 -2
- package/dist/connectors/remote-mcp/resources.js +5 -2
- package/dist/connectors/remote-mcp/transport.js +24 -6
- package/dist/connectors/remote-mcp.js +4 -4
- package/dist/credential-contract.d.ts +3 -1
- package/dist/describe-config.d.ts +1 -0
- package/dist/describe-config.js +1 -0
- package/dist/downstream-input.js +11 -2
- package/dist/errors.d.ts +8 -1
- package/dist/errors.js +2 -0
- package/dist/execute.js +20 -7
- package/dist/executor-result.js +1 -1
- package/dist/executors/quickjs-child.js +1 -1
- package/dist/executors/quickjs-runtime.js +1 -1
- package/dist/executors/quickjs.js +1 -1
- package/dist/guarded-transport.js +2 -0
- package/dist/http/client.js +7 -0
- package/dist/http/hatch.js +2 -0
- package/dist/http/node-transport.js +110 -4
- package/dist/http/oauth.js +17 -6
- package/dist/index.js +10 -21
- package/dist/invocation-failure.js +28 -0
- package/dist/invocation.js +72 -33
- package/dist/membership.d.ts +1 -1
- package/dist/meta-tools.js +27 -13
- package/dist/module-contracts.d.ts +3 -1
- package/dist/native-tools.js +11 -4
- package/dist/node.d.ts +7 -0
- package/dist/node.js +82 -3
- package/dist/operation-activity.js +103 -0
- package/dist/operator-record.d.ts +8 -0
- package/dist/operator-record.js +15 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/breeze/index.js +1 -1
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/cloudflare/index.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/forms/index.js +1 -1
- package/dist/providers/github/index.js +1 -1
- package/dist/providers/gmail/index.js +1 -1
- package/dist/providers/granola/index.js +1 -1
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/planning-center/index.js +1 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/quickbooks/api.js +28 -25
- package/dist/providers/quickbooks/index.js +1 -1
- package/dist/providers/quickbooks/lib/writes.js +55 -0
- package/dist/providers/quickbooks/safety.js +17 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +1 -1
- package/dist/providers/slides/index.js +1 -1
- package/dist/providers/stripe/index.js +2 -2
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/api.js +1 -0
- package/dist/registry.js +16 -4
- package/dist/resource-read.js +21 -2
- package/dist/route-names.js +44 -0
- package/dist/routes/activity-context.js +1 -0
- package/dist/routes/activity-disclosure.js +26 -4
- package/dist/routes/activity.js +2 -1
- package/dist/routes/mcp.js +32 -13
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui-data.js +3 -2
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +66 -14
- package/dist/runtime/request-scope.js +2 -1
- package/dist/skill-origin.js +16 -0
- package/dist/skills.js +132 -43
- package/dist/storage/d1-driver.js +33 -5
- package/dist/storage/keys.js +7 -1
- package/dist/storage/sql-native.js +60 -3
- package/dist/storage/sql.js +6 -0
- package/dist/timing-trace.js +84 -0
- package/dist/types.d.ts +5 -0
- package/dist/url-safety.js +38 -20
- package/dist/utility-recovery.js +33 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +33 -8
- package/documentation/architecture.md +48 -1
- package/documentation/auth.md +116 -36
- package/documentation/code-mode.md +61 -46
- package/documentation/deploying.md +25 -1
- package/documentation/meta-tools.md +113 -11
- package/documentation/operating.md +86 -1
- package/documentation/operator-ui.md +51 -2
- package/documentation/providers.md +16 -14
- package/package.json +10 -8
- package/templates/node/package.json +1 -1
package/dist/skills.js
CHANGED
|
@@ -5,11 +5,15 @@ import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
|
5
5
|
import { upwardStep } from "./upward.js";
|
|
6
6
|
import { authCall, authInvoke, authFlow, authMethod, registerAuth, registerAuthMethod, authOwnMethod, } from "./auth/native.js";
|
|
7
7
|
import { USAGE_SKILL } from "./usage-guide.js";
|
|
8
|
+
import { downstreamOrigin, labeledDownstreamDescription } from "./skill-origin.js";
|
|
8
9
|
import { boundedEchoText, ConnectorCallError } from "./errors.js";
|
|
9
10
|
import { Effect } from "effect";
|
|
10
11
|
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
11
12
|
import { closeScopeOnExit } from "./runtime/connector-scope.js";
|
|
12
13
|
import { sentSecretsForRequest } from "./sent-secrets.js";
|
|
14
|
+
/** Trusted diagnostics constructed from the caller-visible local registry. */
|
|
15
|
+
export class SkillLookupError extends ConnectorCallError {
|
|
16
|
+
}
|
|
13
17
|
// The meta instructions and built-in guides use the meta names: built-in guides
|
|
14
18
|
// are byte-identical across deployments, and the eval code arm replaces usage.
|
|
15
19
|
const ROUTE = `Choose a route before discovery. One known-address read uses ${META_ROUTES.read}; one known-address write uses ${META_ROUTES.write}. Unknown-address read-only work starts with ${META_ROUTES.program} 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. Sample unfamiliar reads.`;
|
|
@@ -20,22 +24,36 @@ const RECOVERY = `When an auth error includes a handoff, give it to the user or
|
|
|
20
24
|
* and the client owns approval; a read-only one omits write tools from search
|
|
21
25
|
* and describe and refuses every write call. Guides may still name them.
|
|
22
26
|
*/
|
|
23
|
-
|
|
27
|
+
function writePolicy(readOnly, routes) {
|
|
28
|
+
return readOnly
|
|
29
|
+
? "This endpoint can't perform writes; guide steps that change data will be refused. No write route exists on this endpoint."
|
|
30
|
+
: `This endpoint permits writes through ${routes.write}${routes.program === META_ROUTES.program ? ` and ${routes.program}` : ""}, subject to connector restrictions; the host owns approval.`;
|
|
31
|
+
}
|
|
32
|
+
export const CONNECTA_INSTRUCTIONS = `${ROUTE} ${writePolicy(false, META_ROUTES)} Never repeat a write to recover its output. ${RECOVERY}`;
|
|
24
33
|
/**
|
|
25
|
-
* The instructions one endpoint serves:
|
|
26
|
-
*
|
|
34
|
+
* The instructions one endpoint serves: pool scope and guide pointer first,
|
|
35
|
+
* then stable routing rules. Plain `/mcp` and a
|
|
27
36
|
* pool without a description serve the constant unchanged. The description
|
|
28
37
|
* comes from deployment config for the pool the request was admitted through;
|
|
29
38
|
* nothing a request sends reaches it.
|
|
30
39
|
*/
|
|
31
|
-
export function endpointInstructions(description, native) {
|
|
32
|
-
const instructions = native
|
|
33
|
-
|
|
40
|
+
export function endpointInstructions(description, native, readOnly = false) {
|
|
41
|
+
const instructions = native
|
|
42
|
+
? nativeInstructions(native.executeCode, readOnly)
|
|
43
|
+
: readOnly
|
|
44
|
+
? `Choose a route before discovery. One known read uses ${META_ROUTES.read}; other read work uses ${META_ROUTES.program} to discover, call and return the answer. ${writePolicy(true, META_ROUTES)} Never repeat a write for output. ${RECOVERY}`
|
|
45
|
+
: CONNECTA_INSTRUCTIONS;
|
|
46
|
+
if (description === undefined)
|
|
47
|
+
return instructions;
|
|
48
|
+
const routing = native
|
|
49
|
+
? instructions
|
|
50
|
+
: `Known read: ${META_ROUTES.read}${readOnly ? "" : `; known write: ${META_ROUTES.write}`}. Other work: ${META_ROUTES.program}; discover and call together, return answers, sample unfamiliar reads. ${writePolicy(readOnly, META_ROUTES)} Never repeat writes for output. Give auth handoffs to user/operator; ${META_ROUTES.authorize} refreshes links. Page resultId/format:"paged" with connecta.result(handleOrId,{page?}).`;
|
|
51
|
+
return `Use skills. ${description} ${routing}`;
|
|
34
52
|
}
|
|
35
53
|
/** Instructions for pools whose host searches the listed native tools. */
|
|
36
|
-
export function nativeInstructions(executeCode) {
|
|
54
|
+
export function nativeInstructions(executeCode, readOnly = false) {
|
|
37
55
|
const routes = routeNames(executeCode ? "native-code" : "native");
|
|
38
|
-
return `
|
|
56
|
+
return `Annotations classify reads/writes. Call known tools directly; ${executeCode ? `combine work in ${routes.program} using tool descriptions’ program addresses.` : "find tools with your host's search."} ${writePolicy(readOnly, routes)} Never repeat a write for output. Give auth handoffs to the user/operator. Tools missing? Ask the operator to repair, then reconnect. Follow nextAction to page with ${executeCode ? `connecta.result in ${routes.program}` : "read_result"}. ${routes.skills} lists guidance.`;
|
|
39
57
|
}
|
|
40
58
|
/** True when at least one of `connectors` carries a usage guide. */
|
|
41
59
|
export function hasConnectorGuides(connectors) {
|
|
@@ -104,7 +122,7 @@ export function connectorGuide(connector) {
|
|
|
104
122
|
if (!content || content.trim() === "")
|
|
105
123
|
return undefined;
|
|
106
124
|
return connector.readOnly === true
|
|
107
|
-
? `
|
|
125
|
+
? `This connection can't perform writes; guide steps that change data will be refused. No write route exists for this connection.\n\n${content}`
|
|
108
126
|
: content;
|
|
109
127
|
}
|
|
110
128
|
/** Discovery budget for one connector-guide summary, including an ellipsis. */
|
|
@@ -342,45 +360,103 @@ function localRecord(name, uri, description, content, aliases) {
|
|
|
342
360
|
function validSkillName(name) {
|
|
343
361
|
return name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
|
|
344
362
|
}
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
/** Resolve guide names once from the admitted connector roster, including collisions. */
|
|
364
|
+
export function resolvedConnectorGuides(connectors) {
|
|
365
|
+
return runEdge(resolvedConnectorGuidesNative(connectors));
|
|
366
|
+
}
|
|
367
|
+
const resolvedConnectorGuidesNative = /* @__PURE__ */ registerAuth(resolvedConnectorGuides, function (connectors) {
|
|
368
|
+
return authFlow(function* () {
|
|
369
|
+
const allocated = new Set(connectors.map((connector) => connector.id));
|
|
370
|
+
return yield* upwardStep(Effect.forEach(connectors
|
|
371
|
+
.filter((connector) => connectorGuide(connector) !== undefined)
|
|
372
|
+
.sort((a, b) => Number(connectorGuideRequired(b)) - Number(connectorGuideRequired(a)) ||
|
|
373
|
+
(a.id < b.id ? -1 : a.id > b.id ? 1 : 0)), (connector) => authFlow(function* () {
|
|
374
|
+
const digest = validSkillName(connector.id)
|
|
375
|
+
? ""
|
|
376
|
+
: bytesToHex(new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", encoder.encode(connector.id)]))));
|
|
377
|
+
const legacy = `connector-${digest.slice(0, 48)}`;
|
|
378
|
+
const slug = connector.id
|
|
379
|
+
.toLowerCase()
|
|
380
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
381
|
+
.replace(/^-|-$/g, "")
|
|
382
|
+
.slice(0, 42)
|
|
383
|
+
.replace(/-$/g, "") || "connector";
|
|
384
|
+
let name = connector.id;
|
|
385
|
+
if (!validSkillName(name)) {
|
|
386
|
+
const base = `${slug}-${digest.slice(0, 12)}`;
|
|
387
|
+
name = base;
|
|
388
|
+
let suffix = 1;
|
|
389
|
+
while (allocated.has(name))
|
|
390
|
+
name = `${base}-${suffix++}`;
|
|
391
|
+
allocated.add(name);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
connector,
|
|
395
|
+
name,
|
|
396
|
+
legacy,
|
|
397
|
+
reference: AVAILABLE_SKILLS.some((skill) => skill.name === name)
|
|
398
|
+
? `skill://connecta/connectors/${name}`
|
|
399
|
+
: name,
|
|
400
|
+
};
|
|
401
|
+
}), { concurrency: 1 }));
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
function localRecords(connectors, readOnly = false) {
|
|
405
|
+
return runEdge(localRecordsNative(connectors, readOnly));
|
|
347
406
|
}
|
|
348
|
-
const localRecordsNative = /* @__PURE__ */ registerAuth(localRecords, function (connectors) {
|
|
407
|
+
const localRecordsNative = /* @__PURE__ */ registerAuth(localRecords, function (connectors, readOnly = false) {
|
|
349
408
|
return authFlow(function* () {
|
|
350
409
|
const builtIns = AVAILABLE_SKILLS.map((skill) => localRecord(skill.name, `skill://connecta/${skill.name}/SKILL.md`, skill.description, skill.content(), [
|
|
351
410
|
skill.name,
|
|
352
411
|
`skill://connecta/${skill.name}`,
|
|
353
412
|
]));
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const name = validSkillName(connector.id)
|
|
359
|
-
? connector.id
|
|
360
|
-
: `connector-${bytesToHex(new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", encoder.encode(connector.id)])))).slice(0, 48)}`;
|
|
413
|
+
const identities = yield* upwardStep(authCall(resolvedConnectorGuides, [
|
|
414
|
+
connectors,
|
|
415
|
+
]));
|
|
416
|
+
const guides = identities.map(({ connector, name, legacy }) => {
|
|
361
417
|
const description = connectorGuideSummary(connector);
|
|
362
418
|
const root = `skill://connecta/connectors/${encodeURIComponent(name)}`;
|
|
363
|
-
const content = `---\n${JSON.stringify({ name, description }, null, 2)}\n---\n\n${connectorGuide(connector)}`;
|
|
419
|
+
const content = `---\n${JSON.stringify({ name, description }, null, 2)}\n---\n\n${readOnly ? `${writePolicy(true, META_ROUTES)}\n\n` : ""}${connectorGuide(connector)}`;
|
|
364
420
|
return {
|
|
365
421
|
...localRecord(name, `${root}/SKILL.md`, description, content, [
|
|
366
422
|
connectorSkillName(connector.id),
|
|
423
|
+
...(connector.id.includes(":") ? [] : [connector.id]),
|
|
424
|
+
...(name !== connector.id
|
|
425
|
+
? [legacy, `skill://connecta/connectors/${legacy}`, `skill://connecta/connectors/${legacy}/SKILL.md`]
|
|
426
|
+
: []),
|
|
367
427
|
`skill://connecta/connectors/${encodeURIComponent(connector.id)}`,
|
|
368
428
|
root,
|
|
369
429
|
]),
|
|
370
430
|
connector,
|
|
371
431
|
};
|
|
372
|
-
})
|
|
432
|
+
});
|
|
373
433
|
if (new Set(guides.map((guide) => guide.entry.uri)).size !== guides.length)
|
|
374
434
|
throw new ConnectorCallError("unavailable", "Connector skill slugs collide.");
|
|
375
435
|
return [builtIns[0], ...guides, ...builtIns.slice(1)];
|
|
376
436
|
});
|
|
377
437
|
});
|
|
378
|
-
/**
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
/** Canonical identities win; an alias shared by different guides is ambiguous. */
|
|
439
|
+
function localLookup(records, key) {
|
|
440
|
+
const canonical = new Map();
|
|
441
|
+
for (const record of records) {
|
|
442
|
+
canonical.set(record.entry.uri, record);
|
|
443
|
+
canonical.set(record.entry.uri.slice(0, -"/SKILL.md".length), record);
|
|
444
|
+
const name = record.entry.frontmatter.name;
|
|
445
|
+
if (!record.connector || !AVAILABLE_SKILLS.some((skill) => skill.name === name))
|
|
446
|
+
canonical.set(name, record);
|
|
447
|
+
}
|
|
448
|
+
const aliases = new Map();
|
|
449
|
+
for (const record of records) {
|
|
450
|
+
for (const alias of record.aliases) {
|
|
451
|
+
if (canonical.has(alias))
|
|
452
|
+
continue;
|
|
453
|
+
aliases.set(alias, aliases.has(alias) && aliases.get(alias) !== record ? null : record);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const result = canonical.get(key) ?? aliases.get(key);
|
|
457
|
+
if (result === null)
|
|
458
|
+
throw new ConnectorCallError("invalid_args", "Ambiguous skill alias; use its canonical URI.");
|
|
459
|
+
return result;
|
|
384
460
|
}
|
|
385
461
|
/** Keep the downstream path tree, so relative supporting-file references still resolve. */
|
|
386
462
|
export function downstreamSkillUri(connectorId, uri) {
|
|
@@ -485,6 +561,7 @@ export class SkillsRegistry {
|
|
|
485
561
|
baseUrl;
|
|
486
562
|
options;
|
|
487
563
|
scope;
|
|
564
|
+
cacheHints;
|
|
488
565
|
local;
|
|
489
566
|
snapshot;
|
|
490
567
|
constructor(registry, baseUrl, options = {}) {
|
|
@@ -493,7 +570,11 @@ export class SkillsRegistry {
|
|
|
493
570
|
this.options = options;
|
|
494
571
|
void SkillsRegistry.nativePrograms;
|
|
495
572
|
this.scope = options.requestScope ?? {};
|
|
496
|
-
this.
|
|
573
|
+
this.cacheHints = {
|
|
574
|
+
...PRIVATE,
|
|
575
|
+
ttlMs: registry.listConnectors().some((connector) => connector.downstreamSkills !== undefined) ? 0 : 300_000,
|
|
576
|
+
};
|
|
577
|
+
this.local = authCall(localRecords, [registry.listConnectors(), options.readOnly === true]);
|
|
497
578
|
}
|
|
498
579
|
operation(connector, read) {
|
|
499
580
|
return runEdge(authOwnMethod(SkillsRegistry.prototype, this, "operation", [connector, read]));
|
|
@@ -547,7 +628,7 @@ export class SkillsRegistry {
|
|
|
547
628
|
: name.startsWith(CONNECTOR_SKILL_PREFIX)
|
|
548
629
|
? `Unknown connector "${boundedEchoText(id)}".`
|
|
549
630
|
: `Unknown skill "${boundedEchoText(name)}".`;
|
|
550
|
-
return new
|
|
631
|
+
return new SkillLookupError("not_found", `${message} Available skills: ${available}.`);
|
|
551
632
|
}
|
|
552
633
|
list(cursor) {
|
|
553
634
|
return runEdge(authOwnMethod(SkillsRegistry.prototype, this, "list", [cursor]));
|
|
@@ -574,7 +655,9 @@ export class SkillsRegistry {
|
|
|
574
655
|
return (yield* upwardStep(authInvoke(self, "records", []))).map((record) => ({
|
|
575
656
|
name: record.aliases[0] ?? record.entry.uri,
|
|
576
657
|
uri: record.entry.uri,
|
|
577
|
-
description:
|
|
658
|
+
description: record.files
|
|
659
|
+
? labeledDownstreamDescription(record.connector.id, String(record.entry.frontmatter.description))
|
|
660
|
+
: String(record.entry.frontmatter.description),
|
|
578
661
|
}));
|
|
579
662
|
})).pipe(Effect.provideService(RequestScope, requestOwner(this.scope)));
|
|
580
663
|
}, SkillsRegistry.prototype.summaries);
|
|
@@ -583,10 +666,12 @@ export class SkillsRegistry {
|
|
|
583
666
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
584
667
|
const record = yield* upwardStep(authInvoke(self, "lookup", [name]));
|
|
585
668
|
const content = (yield* upwardStep(authInvoke(self, "read", [record.entry.uri]))).contents[0];
|
|
669
|
+
const origin = record.files ? `${downstreamOrigin(record.connector.id)}\n\n` : "";
|
|
586
670
|
if (typeof content.text === "string")
|
|
587
|
-
return content.text;
|
|
671
|
+
return origin + content.text;
|
|
588
672
|
try {
|
|
589
|
-
return
|
|
673
|
+
return (origin +
|
|
674
|
+
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Uint8Array.from(atob(content.blob), (char) => char.charCodeAt(0))));
|
|
590
675
|
}
|
|
591
676
|
catch {
|
|
592
677
|
throw new ConnectorCallError("unavailable", "Skill instructions are not UTF-8 text.");
|
|
@@ -596,7 +681,7 @@ export class SkillsRegistry {
|
|
|
596
681
|
registerAuthMethod(SkillsRegistry.prototype, "read", function (uri) {
|
|
597
682
|
const self = this;
|
|
598
683
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
599
|
-
const local = (yield* upwardStep(self.local)
|
|
684
|
+
const local = localLookup(yield* upwardStep(self.local), uri);
|
|
600
685
|
if (local) {
|
|
601
686
|
const record = yield* upwardStep(authCall(withManifest, [local]));
|
|
602
687
|
self.assertSafe([record]);
|
|
@@ -606,7 +691,7 @@ export class SkillsRegistry {
|
|
|
606
691
|
const text = record.connector
|
|
607
692
|
? sentSecretsForRequest(self.scope).redact(record.content)
|
|
608
693
|
: record.content;
|
|
609
|
-
return { ...
|
|
694
|
+
return { ...self.cacheHints, contents: [{ uri: record.entry.uri, mimeType: "text/markdown", text }] };
|
|
610
695
|
}
|
|
611
696
|
if (!uri.startsWith("skill://downstream/"))
|
|
612
697
|
throw self.missing(uri);
|
|
@@ -642,7 +727,7 @@ export class SkillsRegistry {
|
|
|
642
727
|
self.options.onRead?.(record.connector, "downstream");
|
|
643
728
|
// Downstream file contents take the request's full payload rules before any reader sees them.
|
|
644
729
|
const clean = sentSecretsForRequest(self.scope).redact(content);
|
|
645
|
-
return { ...
|
|
730
|
+
return { ...self.cacheHints, contents: [{ ...clean, uri }] };
|
|
646
731
|
})).pipe(Effect.provideService(RequestScope, requestOwner(this.scope)));
|
|
647
732
|
}, SkillsRegistry.prototype.read);
|
|
648
733
|
registerAuthMethod(SkillsRegistry.prototype, "resources", function (cursor) {
|
|
@@ -650,7 +735,7 @@ export class SkillsRegistry {
|
|
|
650
735
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
651
736
|
const page = yield* upwardStep(authInvoke(self, "list", [cursor]));
|
|
652
737
|
return {
|
|
653
|
-
...
|
|
738
|
+
...self.cacheHints,
|
|
654
739
|
resources: page.skills.flatMap((skill) => (skill.resources === "dynamic" ? [{ uri: skill.uri }] : skill.resources).map((file) => ({
|
|
655
740
|
uri: file.uri,
|
|
656
741
|
name: file.uri === skill.uri
|
|
@@ -668,7 +753,10 @@ export class SkillsRegistry {
|
|
|
668
753
|
registerAuthMethod(SkillsRegistry.prototype, "get", function (uri) {
|
|
669
754
|
const self = this;
|
|
670
755
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
671
|
-
return {
|
|
756
|
+
return {
|
|
757
|
+
...self.cacheHints,
|
|
758
|
+
skill: (yield* upwardStep(authInvoke(self, "lookup", [uri]))).entry,
|
|
759
|
+
};
|
|
672
760
|
})).pipe(Effect.provideService(RequestScope, requestOwner(this.scope)));
|
|
673
761
|
}, SkillsRegistry.prototype.get);
|
|
674
762
|
registerAuthMethod(SkillsRegistry.prototype, "list", function (cursor) {
|
|
@@ -679,7 +767,7 @@ export class SkillsRegistry {
|
|
|
679
767
|
if (!Number.isSafeInteger(offset) || offset % SKILL_PAGE_SIZE !== 0 || offset >= records.length)
|
|
680
768
|
throw new ConnectorCallError("invalid_args", "Invalid skills cursor.");
|
|
681
769
|
return {
|
|
682
|
-
...
|
|
770
|
+
...self.cacheHints,
|
|
683
771
|
skills: records.slice(offset, offset + SKILL_PAGE_SIZE).map((record) => record.entry),
|
|
684
772
|
...(offset + SKILL_PAGE_SIZE < records.length
|
|
685
773
|
? { nextCursor: `skills:${offset + SKILL_PAGE_SIZE}` }
|
|
@@ -691,8 +779,7 @@ export class SkillsRegistry {
|
|
|
691
779
|
const self = this;
|
|
692
780
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
693
781
|
const records = yield* upwardStep(self.local);
|
|
694
|
-
const local = records
|
|
695
|
-
bareConnectorGuide(records, uri);
|
|
782
|
+
const local = localLookup(records, uri);
|
|
696
783
|
if (local) {
|
|
697
784
|
const record = yield* upwardStep(authCall(withManifest, [local]));
|
|
698
785
|
self.assertSafe([record]);
|
|
@@ -791,9 +878,11 @@ export class SkillsRegistry {
|
|
|
791
878
|
...(self.options.defer ? { defer: self.options.defer } : {}),
|
|
792
879
|
});
|
|
793
880
|
yield* closeScopeOnExit(connector, ctx, self.options.defer);
|
|
794
|
-
return yield* authCall(read, [ctx]).pipe(Effect.mapError((error) => error instanceof ConnectorCallError && error.code === "
|
|
795
|
-
? new ConnectorCallError("
|
|
796
|
-
:
|
|
881
|
+
return yield* authCall(read, [ctx]).pipe(Effect.mapError((error) => error instanceof ConnectorCallError && error.code === "unsupported_capability"
|
|
882
|
+
? new ConnectorCallError("unsupported_capability", "Downstream Skills capability is not supported.", { retryable: false })
|
|
883
|
+
: error instanceof ConnectorCallError && error.code === "auth_required"
|
|
884
|
+
? new ConnectorCallError("auth_required", "Downstream skills require authorization.")
|
|
885
|
+
: new ConnectorCallError("unavailable", "Downstream skills are unavailable.")));
|
|
797
886
|
})), {
|
|
798
887
|
timeoutMs,
|
|
799
888
|
...(self.options.requestSignal ? { signal: self.options.requestSignal } : {}),
|
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import { RequestScope } from "../runtime/request-scope.js";
|
|
3
|
+
import { hasTimingTraces, traceFor } from "../timing-trace.js";
|
|
4
|
+
import { utf8Length } from "../bytes.js";
|
|
5
|
+
// Request-wide totals include admission, credentials and catalog work. These
|
|
6
|
+
// I/O-spanning observations are not pure storage latency or CPU attribution.
|
|
7
|
+
function timedD1(family, statements, operation) {
|
|
8
|
+
return Effect.suspend(() => {
|
|
9
|
+
if (!hasTimingTraces())
|
|
10
|
+
return operation;
|
|
11
|
+
return Effect.flatMap(Effect.serviceOption(RequestScope), (owner) => {
|
|
12
|
+
const trace = Option.isSome(owner) ? traceFor(owner.value.identity) : undefined;
|
|
13
|
+
if (!trace)
|
|
14
|
+
return operation;
|
|
15
|
+
const start = performance.now();
|
|
16
|
+
let bytes = 0;
|
|
17
|
+
return operation.pipe(Effect.tap((value) => Effect.sync(() => {
|
|
18
|
+
// Count serialized application result bytes, not D1 wire bytes.
|
|
19
|
+
try {
|
|
20
|
+
bytes = utf8Length(JSON.stringify(value) ?? "");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* no payload retained */
|
|
24
|
+
}
|
|
25
|
+
})), Effect.ensuring(Effect.sync(() => trace.record(family, performance.now() - start, bytes, statements))));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
1
29
|
import { effectSqlDriver, sqlDriverEdge, sqlPromise } from "./sql-driver.js";
|
|
2
30
|
export function d1Driver(db) {
|
|
3
31
|
const prepare = (statement) => statement.params.length > 0 ? db.prepare(statement.sql).bind(...statement.params) : db.prepare(statement.sql);
|
|
4
32
|
return sqlDriverEdge(effectSqlDriver({
|
|
5
|
-
all: (statement) => sqlPromise(() => prepare(statement)
|
|
33
|
+
all: (statement) => timedD1("d1.read", 1, sqlPromise(() => prepare(statement)
|
|
6
34
|
.all()
|
|
7
|
-
.then((result) => result.results)),
|
|
8
|
-
run: (statement) => sqlPromise(() => prepare(statement)
|
|
35
|
+
.then((result) => result.results))),
|
|
36
|
+
run: (statement) => timedD1("d1.write", 1, sqlPromise(() => prepare(statement)
|
|
9
37
|
.run()
|
|
10
|
-
.then((result) => result.meta.changes ?? 0)),
|
|
38
|
+
.then((result) => result.meta.changes ?? 0))),
|
|
11
39
|
// D1's public binding API preserves per-statement changes, including zero;
|
|
12
40
|
// execute the whole array once. No withTransaction, RETURNING, or replay.
|
|
13
|
-
batch: (statements) => sqlPromise(() => db.batch(statements.map(prepare)).then((results) => results.map((result) => result.meta.changes ?? 0))),
|
|
41
|
+
batch: (statements) => timedD1("d1.batch", statements.length, sqlPromise(() => db.batch(statements.map(prepare)).then((results) => results.map((result) => result.meta.changes ?? 0)))),
|
|
14
42
|
}));
|
|
15
43
|
}
|
package/dist/storage/keys.js
CHANGED
|
@@ -176,6 +176,8 @@ export const connectorHealthKeys = {
|
|
|
176
176
|
discovery: (connectorId, partitionDigest) => validateStorageKey(`connector-health:v1:${connectorId}:${partitionDigest}:discovery`),
|
|
177
177
|
status: (connectorId, partitionDigest) => validateStorageKey(`connector-health:v1:${connectorId}:${partitionDigest}:status`),
|
|
178
178
|
};
|
|
179
|
+
const responseCacheLocatorPrefix = (id, config) => validateStorageKey(`response-cache:v1:${id}:${config}:`);
|
|
180
|
+
const responseCacheLocatorSeparator = ":";
|
|
179
181
|
/**
|
|
180
182
|
* SDK catalog response cache, complete manifests published after their chunks.
|
|
181
183
|
* Secret-bearing inputs reach these keys only through `secretDigest`.
|
|
@@ -195,7 +197,11 @@ export const responseCacheKeys = {
|
|
|
195
197
|
},
|
|
196
198
|
prefix: (id) => validateStorageKey(`response-cache:v1:${id}:`),
|
|
197
199
|
generation: (id) => validateStorageKey(`response-cache:v1:${id}:generation`),
|
|
198
|
-
namespace: (id, config, generation) => validateStorageKey(
|
|
200
|
+
namespace: (id, config, generation) => validateStorageKey(`${responseCacheLocatorPrefix(id, config)}${generation}${responseCacheLocatorSeparator}`),
|
|
201
|
+
/** SQL binds these layout parts around the stored generation; it owns no delimiters. */
|
|
202
|
+
locatorPrefix: responseCacheLocatorPrefix,
|
|
203
|
+
locatorSeparator: responseCacheLocatorSeparator,
|
|
204
|
+
locatedEntry: (prefix, generation, partition) => validateStorageKey(`${prefix}${generation}${responseCacheLocatorSeparator}${partition}`),
|
|
199
205
|
entry: (namespace, partition) => validateStorageKey(`${namespace}${partition}`),
|
|
200
206
|
refreshDigest: (id, config, partition) => validateStorageKey(`response-cache:v1:${id}:refresh-digest:${config}:${partition}`),
|
|
201
207
|
chunk: (entry, revision, index) => validateStorageKey(`${entry}:chunk:${revision}:${index}`),
|
|
@@ -27,7 +27,7 @@ import { Effect } from "effect";
|
|
|
27
27
|
import { activityBehaviorFacts, activityPackageVersion, activityClientFact, InvalidActivityCursorError, } from "../activity.js";
|
|
28
28
|
import { assertKnownOptions, ConfigError, keys, optionsOf } from "../config-schema.js";
|
|
29
29
|
import { agentFrictionForCode } from "../activity-friction.js";
|
|
30
|
-
import { accessTokenKeys, validateStorageKey } from "./keys.js";
|
|
30
|
+
import { accessTokenKeys, responseCacheKeys, validateStorageKey } from "./keys.js";
|
|
31
31
|
const sql = (text, ...params) => ({
|
|
32
32
|
sql: text,
|
|
33
33
|
params,
|
|
@@ -132,6 +132,34 @@ export function nativeSqlStorage(driver) {
|
|
|
132
132
|
return new Map(rows.map((row) => [row.key, textOf(row.value, row.value_bytes, "Stored access token metadata")]));
|
|
133
133
|
});
|
|
134
134
|
},
|
|
135
|
+
catalogSnapshot(keys, generationKey, prefix, suffixes) {
|
|
136
|
+
return Effect.gen(function* () {
|
|
137
|
+
if (keys.length < 1 || keys.length > 4 || suffixes.length > 2)
|
|
138
|
+
throw new RangeError("Catalog locator exceeds bounds");
|
|
139
|
+
for (const key of [...keys, generationKey, prefix, ...suffixes])
|
|
140
|
+
validateStorageKey(key);
|
|
141
|
+
yield* ensure();
|
|
142
|
+
const rows = yield* driver.all(sql(`WITH initial AS (
|
|
143
|
+
SELECT key, value FROM connecta_kv WHERE key IN (${keys.map(() => "?").join(",")}) AND ${live}
|
|
144
|
+
), suffixes(suffix) AS (VALUES ${suffixes.map(() => "(?)").join(",") || "(NULL)"}), wanted(key) AS (
|
|
145
|
+
SELECT key FROM initial UNION ALL
|
|
146
|
+
SELECT ? || (SELECT value FROM initial WHERE key = ?) || ? || suffix FROM suffixes
|
|
147
|
+
) SELECT key, ${textColumn("value")} FROM connecta_kv
|
|
148
|
+
WHERE key IN (SELECT key FROM wanted) AND ${live}`, ...keys, ...suffixes, prefix, generationKey, responseCacheKeys.locatorSeparator));
|
|
149
|
+
const values = new Map(rows.map((row) => [row.key, textOf(row.value, row.value_bytes, "Stored catalog locator")]));
|
|
150
|
+
for (const key of keys)
|
|
151
|
+
if (!values.has(key))
|
|
152
|
+
values.set(key, null);
|
|
153
|
+
const generation = values.get(generationKey);
|
|
154
|
+
if (generation)
|
|
155
|
+
for (const suffix of suffixes) {
|
|
156
|
+
const key = responseCacheKeys.locatedEntry(prefix, generation, suffix);
|
|
157
|
+
if (!values.has(key))
|
|
158
|
+
values.set(key, null);
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
});
|
|
162
|
+
},
|
|
135
163
|
getMany(keys) {
|
|
136
164
|
return Effect.gen(function* () {
|
|
137
165
|
for (const key of keys)
|
|
@@ -157,6 +185,25 @@ export function nativeSqlStorage(driver) {
|
|
|
157
185
|
return values;
|
|
158
186
|
});
|
|
159
187
|
},
|
|
188
|
+
setMany(entries, opts) {
|
|
189
|
+
return Effect.gen(function* () {
|
|
190
|
+
if (entries.length > 64)
|
|
191
|
+
throw new RangeError("setMany accepts at most 64 rows");
|
|
192
|
+
for (const [key] of entries)
|
|
193
|
+
validateStorageKey(key);
|
|
194
|
+
const expiry = expiryOptions(opts);
|
|
195
|
+
if (!entries.length)
|
|
196
|
+
return;
|
|
197
|
+
yield* ensure();
|
|
198
|
+
const changes = yield* driver.batch([
|
|
199
|
+
expirySweep(),
|
|
200
|
+
...entries.map(([key, value]) => sql(`INSERT INTO connecta_kv (key, value, expires_at_ms) VALUES (?, ?, ${expiry.expression})
|
|
201
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value, expires_at_ms = excluded.expires_at_ms`, key, value, expiry.value)),
|
|
202
|
+
]);
|
|
203
|
+
if (changes.length !== entries.length + 1 || changes.slice(1).some((changed) => changed !== 1))
|
|
204
|
+
throw new Error("SQL multi-set confirmation is incomplete");
|
|
205
|
+
});
|
|
206
|
+
},
|
|
160
207
|
set(key, value, opts) {
|
|
161
208
|
return Effect.gen(function* () {
|
|
162
209
|
validateStorageKey(key);
|
|
@@ -334,11 +381,20 @@ function activityRow(read) {
|
|
|
334
381
|
}
|
|
335
382
|
return row;
|
|
336
383
|
}
|
|
384
|
+
// Operation metadata shares the existing kind-tagged text slot; no new columns.
|
|
385
|
+
function parseOperation(raw) {
|
|
386
|
+
try {
|
|
387
|
+
return raw === null ? undefined : JSON.parse(raw);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
337
393
|
function rowToEvent(row) {
|
|
338
394
|
// Rows written before `friction` had a column derive it from the code.
|
|
339
395
|
// Friction without a code — an oversized but successful result — exists
|
|
340
396
|
// only in the column, which is why the column exists.
|
|
341
|
-
const friction = row.friction ?? agentFrictionForCode(row.error_code ?? undefined);
|
|
397
|
+
const friction = row.event_kind === "operation" ? undefined : (row.friction ?? agentFrictionForCode(row.error_code ?? undefined));
|
|
342
398
|
const packageVersion = activityPackageVersion(row.package_version);
|
|
343
399
|
const clientName = activityClientFact(row.client_name, "name");
|
|
344
400
|
const clientVersion = activityClientFact(row.client_version, "version");
|
|
@@ -351,6 +407,7 @@ function rowToEvent(row) {
|
|
|
351
407
|
classification: row.classification,
|
|
352
408
|
resultBytes: row.result_bytes,
|
|
353
409
|
kind: row.event_kind,
|
|
410
|
+
operation: row.event_kind === "operation" ? parseOperation(row.drift_kind) : undefined,
|
|
354
411
|
pool: row.pool_name,
|
|
355
412
|
actorBasis: row.actor_basis,
|
|
356
413
|
drift: {
|
|
@@ -461,7 +518,7 @@ export function nativeSqlActivityStore(driver, kind, options = {}) {
|
|
|
461
518
|
// Stored beside the code rather than derived from it: a truncated
|
|
462
519
|
// result is friction on a call that succeeded, and
|
|
463
520
|
// `error_code IS NOT NULL` stays an honest count of failures.
|
|
464
|
-
event.friction ?? null, event.approval ?? null, activityPackageVersion(event.packageVersion) ?? null, event.serverName, event.serverVersion, activityClientFact(event.clientName, "name") ?? null, activityClientFact(event.clientVersion, "version") ?? null, event.deploymentId ?? null, facts.classification ?? null, facts.resultBytes ?? null, facts.kind ?? null, facts.drift?.kind ?? null, facts.drift?.addedTools ?? null, facts.drift?.removedTools ?? null, facts.drift?.changedTools ?? null, facts.pool ?? null, facts.actorBasis ?? null),
|
|
521
|
+
event.kind === "operation" ? null : (event.friction ?? null), event.approval ?? null, activityPackageVersion(event.packageVersion) ?? null, event.serverName, event.serverVersion, activityClientFact(event.clientName, "name") ?? null, activityClientFact(event.clientVersion, "version") ?? null, event.deploymentId ?? null, facts.classification ?? null, facts.resultBytes ?? null, facts.kind ?? null, facts.kind === "operation" ? JSON.stringify(facts.operation ?? null) : (facts.drift?.kind ?? null), facts.drift?.addedTools ?? null, facts.drift?.removedTools ?? null, facts.drift?.changedTools ?? null, facts.pool ?? null, facts.actorBasis ?? null),
|
|
465
522
|
sql(`DELETE FROM tool_call_activity WHERE id IN (
|
|
466
523
|
SELECT id FROM tool_call_activity
|
|
467
524
|
WHERE occurred_at_ms < ?
|
package/dist/storage/sql.js
CHANGED
|
@@ -10,6 +10,12 @@ export function sqlStorage(driver, kind) {
|
|
|
10
10
|
const root = {};
|
|
11
11
|
const native = {
|
|
12
12
|
...store,
|
|
13
|
+
setMany: (entries, options) => {
|
|
14
|
+
let write = store.setMany(entries, options);
|
|
15
|
+
for (const [key] of entries)
|
|
16
|
+
write = observedWrite(root, key, write);
|
|
17
|
+
return write;
|
|
18
|
+
},
|
|
13
19
|
set: (key, value, options) => observedWrite(root, key, store.set(key, value, options)),
|
|
14
20
|
delete: (key) => observedWrite(root, key, store.delete(key)),
|
|
15
21
|
compareAndSet: (key, expected, next, options) => observedWrite(root, key, store.compareAndSet(key, expected, next, options)),
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const families = new Set([
|
|
2
|
+
"catalog.cache.lookup",
|
|
3
|
+
"catalog.cache.hit",
|
|
4
|
+
"catalog.cache.expired",
|
|
5
|
+
"catalog.chunks",
|
|
6
|
+
"catalog.validated.hit",
|
|
7
|
+
"catalog.validated.miss",
|
|
8
|
+
"catalog.intake",
|
|
9
|
+
"catalog.decode",
|
|
10
|
+
"catalog.validate",
|
|
11
|
+
"catalog.accepted",
|
|
12
|
+
"catalog",
|
|
13
|
+
"admission",
|
|
14
|
+
"connector",
|
|
15
|
+
"result",
|
|
16
|
+
"d1.read",
|
|
17
|
+
"d1.write",
|
|
18
|
+
"d1.batch",
|
|
19
|
+
"memo.hit",
|
|
20
|
+
"memo.miss",
|
|
21
|
+
"memo.fresh",
|
|
22
|
+
]);
|
|
23
|
+
const traces = new WeakMap();
|
|
24
|
+
let active = 0;
|
|
25
|
+
let firstRequest = true;
|
|
26
|
+
export const hasTimingTraces = () => active > 0;
|
|
27
|
+
export const traceFor = (identity) => identity ? traces.get(identity) : undefined;
|
|
28
|
+
const finite = (value) => (Number.isFinite(value) && value >= 0 ? value : 0);
|
|
29
|
+
export class TimingTrace {
|
|
30
|
+
first;
|
|
31
|
+
started = performance.now();
|
|
32
|
+
totals = new Map();
|
|
33
|
+
closed = false;
|
|
34
|
+
constructor(first) {
|
|
35
|
+
this.first = first;
|
|
36
|
+
}
|
|
37
|
+
record(family, durationMs = 0, bytes = 0, statements = 0, absent = 0, items = 0) {
|
|
38
|
+
if (this.closed || !families.has(family))
|
|
39
|
+
return;
|
|
40
|
+
const total = this.totals.get(family) ?? { count: 0, durationMs: 0, bytes: 0, statements: 0, absent: 0, items: 0 };
|
|
41
|
+
total.count++;
|
|
42
|
+
total.durationMs += finite(durationMs);
|
|
43
|
+
total.bytes += finite(bytes);
|
|
44
|
+
total.statements += finite(statements);
|
|
45
|
+
total.absent += finite(absent);
|
|
46
|
+
total.items += finite(items);
|
|
47
|
+
this.totals.set(family, total);
|
|
48
|
+
}
|
|
49
|
+
finish(logger) {
|
|
50
|
+
if (this.closed)
|
|
51
|
+
return;
|
|
52
|
+
this.closed = true;
|
|
53
|
+
try {
|
|
54
|
+
logger.info("connecta.timing", {
|
|
55
|
+
version: 1,
|
|
56
|
+
isolateFirstRequest: this.first,
|
|
57
|
+
durationMs: performance.now() - this.started,
|
|
58
|
+
weakReferences: typeof WeakRef === "function" && typeof FinalizationRegistry === "function",
|
|
59
|
+
operations: Object.fromEntries(this.totals),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* Observability cannot change a request's outcome. */
|
|
64
|
+
}
|
|
65
|
+
this.totals.clear();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Called for each MCP request, including those not sampled. */
|
|
69
|
+
export function startTimingTrace(identity, sampleRate, logger) {
|
|
70
|
+
const first = firstRequest;
|
|
71
|
+
firstRequest = false;
|
|
72
|
+
if (sampleRate <= 0 || (sampleRate < 1 && Math.random() >= sampleRate))
|
|
73
|
+
return undefined;
|
|
74
|
+
const trace = new TimingTrace(first);
|
|
75
|
+
traces.set(identity, trace);
|
|
76
|
+
active++;
|
|
77
|
+
return () => {
|
|
78
|
+
if (traces.get(identity) !== trace)
|
|
79
|
+
return;
|
|
80
|
+
traces.delete(identity);
|
|
81
|
+
active--;
|
|
82
|
+
trace.finish(logger);
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -842,6 +842,11 @@ export interface InboundAuth {
|
|
|
842
842
|
kind: string;
|
|
843
843
|
/** This provider may admit a human identity to operator mutation routes. */
|
|
844
844
|
interactiveOperator?: true;
|
|
845
|
+
/**
|
|
846
|
+
* This provider checks credentials against the deployment's own URL and was
|
|
847
|
+
* given none, so construction refuses a configuration without `publicUrl`.
|
|
848
|
+
*/
|
|
849
|
+
requiresPublicUrl?: true;
|
|
845
850
|
/**
|
|
846
851
|
* Recognize credential syntax or trusted runtime context, without verifying
|
|
847
852
|
* it or doing I/O. The first recognizing provider owns the verdict, including
|