@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Request-scoped memo of storage reads (INV-7).
|
|
2
|
+
//
|
|
3
|
+
// One request often reads the same key many times: a catalog listing reads its
|
|
4
|
+
// generation at each step, and status, drift and detail views each open the
|
|
5
|
+
// same credential. A call site that has argued staleness
|
|
6
|
+
// within one request is safe reads through `RequestReads.read`; every other
|
|
7
|
+
// read stays a plain storage read. The memo belongs to one request owner,
|
|
8
|
+
// holds plain strings only, and is emptied when the request closes. Nothing in
|
|
9
|
+
// it crosses requests: each request owner has its own. Entries are keyed by
|
|
10
|
+
// the physical key in the underlying store, through any namespaced views, so
|
|
11
|
+
// one principal's partition never answers another's and two views of the same
|
|
12
|
+
// partition share one entry.
|
|
13
|
+
//
|
|
14
|
+
// Freshness. Every write through a store connecta can observe — the SQL
|
|
15
|
+
// drivers, `memoryStorage()`, and namespaced views over them — tells each open
|
|
16
|
+
// memo over that store, in this isolate, that its key is changing: before the
|
|
17
|
+
// write is submitted and again when it settles. The memo drops that key, and
|
|
18
|
+
// a read of it already in flight is not recorded, since it may or may not
|
|
19
|
+
// include the write. So a write by this request, or by any other request in
|
|
20
|
+
// this isolate, is never hidden by a memoized value. A store whose writes
|
|
21
|
+
// connecta cannot observe (a custom adapter, or a built-in one whose methods
|
|
22
|
+
// were replaced) is never memoized. Writes in another isolate or process, and
|
|
23
|
+
// expiry within the request, are invisible here: a memoized key can lag them
|
|
24
|
+
// for the rest of the request. So no read that gates authority, sends a
|
|
25
|
+
// credential or decides a write may be memoized; a call site that memoizes
|
|
26
|
+
// must say why that staleness is safe.
|
|
27
|
+
//
|
|
28
|
+
// A `fresh` read bypasses the lookup and records what it saw, so a memoized
|
|
29
|
+
// reader never returns a value older than this request's latest fresh read of
|
|
30
|
+
// the same key. Only the latest read of a key this request started records:
|
|
31
|
+
// an earlier read that settles later, possibly from before another isolate's
|
|
32
|
+
// write, never replaces a newer observation.
|
|
33
|
+
//
|
|
34
|
+
// The registry of open memos holds weak references, so a request owner that is
|
|
35
|
+
// never closed cannot pin its memo. A runtime without `WeakRef` and
|
|
36
|
+
// `FinalizationRegistry` (workerd before compatibility date 2025-05-05 without
|
|
37
|
+
// `enable_weak_ref`) memoizes nothing: every read goes to storage.
|
|
38
|
+
import { Effect } from "effect";
|
|
39
|
+
const observed = new WeakMap();
|
|
40
|
+
/**
|
|
41
|
+
* Open memos holding or reading entries of each store. Weak, so a request
|
|
42
|
+
* owner that is never closed still lets its memo be collected with it.
|
|
43
|
+
*/
|
|
44
|
+
const open = new WeakMap();
|
|
45
|
+
function eachOpen(store, visit) {
|
|
46
|
+
const memos = open.get(store);
|
|
47
|
+
if (!memos)
|
|
48
|
+
return;
|
|
49
|
+
for (const ref of memos) {
|
|
50
|
+
const memo = ref.deref();
|
|
51
|
+
if (memo)
|
|
52
|
+
visit(memo);
|
|
53
|
+
else
|
|
54
|
+
memos.delete(ref);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** A write to `key` of `store` is about to be submitted, or has settled. */
|
|
58
|
+
export function noteStorageWrite(store, key) {
|
|
59
|
+
eachOpen(store, (memo) => memo.changed(store, key));
|
|
60
|
+
}
|
|
61
|
+
/** Wrap one write so open memos see it begin and settle, however it ends. */
|
|
62
|
+
export function observedWrite(store, key, write) {
|
|
63
|
+
return Effect.suspend(() => {
|
|
64
|
+
noteStorageWrite(store, key);
|
|
65
|
+
return write;
|
|
66
|
+
}).pipe(Effect.ensuring(Effect.sync(() => noteStorageWrite(store, key))));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Declare that every write through `storage` reaches `noteStorageWrite` for
|
|
70
|
+
* `root`, as long as its methods stay the ones it has now; or that `storage`
|
|
71
|
+
* stores `key` at `prefix + key` in another store, observed only while that
|
|
72
|
+
* one is.
|
|
73
|
+
*/
|
|
74
|
+
export function observeStorageWrites(storage, layer) {
|
|
75
|
+
observed.set(storage, {
|
|
76
|
+
own: {
|
|
77
|
+
get: storage.get,
|
|
78
|
+
set: storage.set,
|
|
79
|
+
delete: storage.delete,
|
|
80
|
+
list: storage.list,
|
|
81
|
+
compareAndSet: storage.compareAndSet,
|
|
82
|
+
},
|
|
83
|
+
layer,
|
|
84
|
+
});
|
|
85
|
+
return storage;
|
|
86
|
+
}
|
|
87
|
+
/** The underlying store and key a read of `key` reaches, when every layer is observed. */
|
|
88
|
+
function physical(storage, key) {
|
|
89
|
+
const entry = observed.get(storage);
|
|
90
|
+
if (!entry)
|
|
91
|
+
return undefined;
|
|
92
|
+
const { own, layer } = entry;
|
|
93
|
+
if (own.get !== storage.get ||
|
|
94
|
+
own.set !== storage.set ||
|
|
95
|
+
own.delete !== storage.delete ||
|
|
96
|
+
own.list !== storage.list ||
|
|
97
|
+
own.compareAndSet !== storage.compareAndSet)
|
|
98
|
+
return undefined;
|
|
99
|
+
return "root" in layer ? { store: layer.root, key } : physical(layer.storage, layer.prefix + key);
|
|
100
|
+
}
|
|
101
|
+
const weakReferences = () => typeof WeakRef === "function" && typeof FinalizationRegistry === "function";
|
|
102
|
+
export class RequestReads {
|
|
103
|
+
closed = false;
|
|
104
|
+
stores = new Map();
|
|
105
|
+
ref = weakReferences() ? new WeakRef(this) : undefined;
|
|
106
|
+
entriesFor(store) {
|
|
107
|
+
let entries = this.stores.get(store);
|
|
108
|
+
if (!entries) {
|
|
109
|
+
entries = { values: new Map(), versions: new Map(), started: new Map() };
|
|
110
|
+
this.stores.set(store, entries);
|
|
111
|
+
eachOpen(store, () => { }); // prune collected memos
|
|
112
|
+
let memos = open.get(store);
|
|
113
|
+
if (!memos)
|
|
114
|
+
open.set(store, (memos = new Set()));
|
|
115
|
+
memos.add(this.ref);
|
|
116
|
+
}
|
|
117
|
+
return entries;
|
|
118
|
+
}
|
|
119
|
+
/** A write notice: forget `key` and refuse any read of it in flight. */
|
|
120
|
+
changed(store, key) {
|
|
121
|
+
const entries = this.stores.get(store);
|
|
122
|
+
if (!entries)
|
|
123
|
+
return;
|
|
124
|
+
entries.values.delete(key);
|
|
125
|
+
entries.versions.set(key, (entries.versions.get(key) ?? 0) + 1);
|
|
126
|
+
}
|
|
127
|
+
/** A memoized answer, or how to record the value a storage read returns. */
|
|
128
|
+
lookup(storage, key, fresh) {
|
|
129
|
+
const address = this.closed || !this.ref ? undefined : physical(storage, key);
|
|
130
|
+
if (!address)
|
|
131
|
+
return undefined;
|
|
132
|
+
const entries = this.entriesFor(address.store);
|
|
133
|
+
if (!fresh && entries.values.has(address.key))
|
|
134
|
+
return { hit: true, value: entries.values.get(address.key) };
|
|
135
|
+
const before = entries.versions.get(address.key) ?? 0;
|
|
136
|
+
const read = (entries.started.get(address.key) ?? 0) + 1;
|
|
137
|
+
entries.started.set(address.key, read);
|
|
138
|
+
return {
|
|
139
|
+
hit: false,
|
|
140
|
+
record: (value) => {
|
|
141
|
+
if (!this.closed &&
|
|
142
|
+
entries.started.get(address.key) === read &&
|
|
143
|
+
(entries.versions.get(address.key) ?? 0) === before)
|
|
144
|
+
entries.values.set(address.key, value);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `load` reads `key` from `storage`. Without `fresh`, a value this request
|
|
150
|
+
* already read, with no observed write to that key since, answers instead.
|
|
151
|
+
*/
|
|
152
|
+
read(storage, key, load, fresh = false) {
|
|
153
|
+
return Effect.suspend(() => {
|
|
154
|
+
const found = this.lookup(storage, key, fresh);
|
|
155
|
+
if (!found)
|
|
156
|
+
return load;
|
|
157
|
+
if (found.hit)
|
|
158
|
+
return Effect.succeed(found.value);
|
|
159
|
+
return Effect.tap(load, (value) => Effect.sync(() => found.record(value)));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/** `read` for a Promise-shaped caller, without another Effect edge. */
|
|
163
|
+
async readPromise(storage, key, load, fresh = false) {
|
|
164
|
+
const found = this.lookup(storage, key, fresh);
|
|
165
|
+
if (!found)
|
|
166
|
+
return load();
|
|
167
|
+
if (found.hit)
|
|
168
|
+
return found.value;
|
|
169
|
+
const value = await load();
|
|
170
|
+
found.record(value);
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
close() {
|
|
174
|
+
if (this.closed)
|
|
175
|
+
return;
|
|
176
|
+
this.closed = true;
|
|
177
|
+
for (const store of this.stores.keys())
|
|
178
|
+
if (this.ref)
|
|
179
|
+
open.get(store)?.delete(this.ref);
|
|
180
|
+
this.stores.clear();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// One owner per existing upstream request identity. Facts survive revocation;
|
|
2
2
|
// retaining them never reconstitutes a call capability or a live operation.
|
|
3
3
|
import { Context, Exit, Scope } from "effect";
|
|
4
|
+
import { RequestReads } from "./request-reads.js";
|
|
4
5
|
import { detach } from "./run.js";
|
|
5
6
|
export class RequestOwner {
|
|
6
7
|
identity;
|
|
@@ -18,6 +19,7 @@ export class RequestOwner {
|
|
|
18
19
|
secrets: undefined,
|
|
19
20
|
invocations: { entered: [], authFailures: new Map(), retryReads: new Map() },
|
|
20
21
|
handoffs: undefined,
|
|
22
|
+
reads: undefined,
|
|
21
23
|
};
|
|
22
24
|
constructor(identity) {
|
|
23
25
|
this.identity = identity;
|
|
@@ -51,6 +53,7 @@ export class RequestOwner {
|
|
|
51
53
|
}
|
|
52
54
|
this.calls.clear();
|
|
53
55
|
this.entries.clear();
|
|
56
|
+
this.retained.reads?.close();
|
|
54
57
|
attempt(() => this.controller.abort(new Error("The request ended.")));
|
|
55
58
|
for (const finalizer of this.finalizers.splice(0).reverse())
|
|
56
59
|
attempt(finalizer);
|
|
@@ -61,6 +64,19 @@ export class RequestOwner {
|
|
|
61
64
|
throw new AggregateError(failures, `Closing the request: ${failures.length} cleanup step(s) failed.`);
|
|
62
65
|
}
|
|
63
66
|
}
|
|
67
|
+
/** This request's read memo; a closed request's memo answers nothing. */
|
|
68
|
+
export function requestReads(identity) {
|
|
69
|
+
return ownerReads(requestOwner(identity));
|
|
70
|
+
}
|
|
71
|
+
export function ownerReads(owner) {
|
|
72
|
+
const retained = owner.retained;
|
|
73
|
+
if (!retained.reads) {
|
|
74
|
+
retained.reads = new RequestReads();
|
|
75
|
+
if (!owner.open)
|
|
76
|
+
retained.reads.close();
|
|
77
|
+
}
|
|
78
|
+
return retained.reads;
|
|
79
|
+
}
|
|
64
80
|
export class RequestScope extends Context.Service()("connecta/request/Scope") {
|
|
65
81
|
}
|
|
66
82
|
const requests = new WeakMap();
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// it was created.") when the Worker's compatibility date is 2025-06-16 or
|
|
11
11
|
// later, in releases since September 2026. The refusal throws out of the call
|
|
12
12
|
// that completed the Deferred, failing the request that released a slot, and
|
|
13
|
-
// the waiter never resumes
|
|
13
|
+
// the waiter never resumes. Awaiting a Promise around that wait is no
|
|
14
14
|
// help: the fiber waiting on the Deferred inside the Promise is resumed in the
|
|
15
15
|
// completing request just the same.
|
|
16
16
|
//
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Discovery and the registry both coalesce a connector's catalog read inside
|
|
4
|
-
// one request, so a search, a describe, and the call after them cost one
|
|
5
|
-
// downstream listing. The read used to run under whichever reader asked
|
|
6
|
-
// first: its signal was the read's signal, so a short-deadline call that
|
|
7
|
-
// happened to start the read failed a search that joined it with the call's
|
|
8
|
-
// timeout, and a cancelled search cancelled a read others were still waiting
|
|
9
|
-
// on (#571).
|
|
10
|
-
//
|
|
11
|
-
// So the read owns its signal. Each reader waits under its own cancellation,
|
|
12
|
-
// and the read is cancelled only when no reader is left to want it. Nothing
|
|
13
|
-
// here is shared across requests: a SharedRead lives in one request's map and
|
|
14
|
-
// is waited on only by that request's fibers, which is why a plain count of
|
|
15
|
-
// readers is enough and no edge is needed between them.
|
|
1
|
+
// Request-local catalog reads own their signal. Each reader waits under its own
|
|
2
|
+
// cancellation; the read is cancelled only when no reader remains (INV-7).
|
|
16
3
|
import { Deferred, Effect, Exit } from "effect";
|
|
17
4
|
import { forkRequest } from "./run.js";
|
|
18
5
|
import { requestOwner } from "./request-scope.js";
|
package/dist/runtime/storage.js
CHANGED
|
@@ -32,6 +32,18 @@ export function storageGet(key) {
|
|
|
32
32
|
catch: (error) => error,
|
|
33
33
|
}));
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Read several keys, in one round trip when the store can; otherwise one
|
|
37
|
+
* `get` after another. Values come back in `keys` order.
|
|
38
|
+
*/
|
|
39
|
+
export function storageGetMany(keys) {
|
|
40
|
+
return Storage.use((storage) => {
|
|
41
|
+
const native = getNativeStorage(storage);
|
|
42
|
+
if (native?.getMany)
|
|
43
|
+
return native.getMany(keys);
|
|
44
|
+
return Effect.forEach(keys, (key) => storageGet(key).pipe(Effect.provideService(Storage, storage)));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
35
47
|
/** Write one key, with the adapter's optional TTL. */
|
|
36
48
|
export function storageSet(key, value, options) {
|
|
37
49
|
return Storage.use((storage) => getNativeStorage(storage)?.set(key, value, options) ??
|
package/dist/sent-secrets.d.ts
CHANGED
|
@@ -224,7 +224,6 @@ export declare function agentOutputOperations<T extends Record<string, (...args:
|
|
|
224
224
|
/** Slot reads cover custom handlers too, including keys put in query strings. A field named
|
|
225
225
|
* as an identifier (an application or client ID, a username, a public key) is matched whole. */
|
|
226
226
|
export declare function registerCredentialValues(secrets: SentSecrets, values: import("./types.js").ConnectorCredentialValues | null): void;
|
|
227
|
-
export declare function trackCredentialReads(ctx: ConnectorContext): void;
|
|
228
227
|
/** A connector's result or error as it enters: every connector's credentials and the
|
|
229
228
|
* request's private arguments, under payload rules. */
|
|
230
229
|
export declare function redactSentSecrets<T>(ctx: ConnectorContext, value: T): T;
|
|
@@ -235,7 +234,7 @@ export declare function requestSentSecrets(ctx: ConnectorContext): SentSecrets;
|
|
|
235
234
|
* extracted from. Redact it whole first: a cut through an echoed form can leave a remnant
|
|
236
235
|
* shorter than any fragment the output boundary matches, and a normalized number or
|
|
237
236
|
* extracted substring may no longer hold the form at all. */
|
|
238
|
-
export declare
|
|
237
|
+
export declare const redactBeforeCut: typeof redactSentSecrets;
|
|
239
238
|
/** Base64, or base64url with `url`, of bytes or UTF-8 text Connecta returns to an agent: a
|
|
240
239
|
* download, an attachment, a REST body, a page cursor. Every such encoding goes through here,
|
|
241
240
|
* since no later pass reads through an encoding the output does not declare: the content is
|
package/dist/sent-secrets.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Effect } from "effect";
|
|
|
5
5
|
import { classificationCode, ConnectorCallError } from "./errors.js";
|
|
6
6
|
import { credentialUrlViews } from "./credential-url.js";
|
|
7
7
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
8
|
-
import { viewSecrets
|
|
8
|
+
import { viewSecrets } from "./runtime/call.js";
|
|
9
9
|
import { visitPrivateCallArguments } from "./argument-redaction.js";
|
|
10
10
|
import { keptErrorName, redactedCopyOf } from "./redaction-identity.js";
|
|
11
11
|
import { REDACTED } from "./redaction-marker.js";
|
|
@@ -688,7 +688,7 @@ function headerSpace(text, at) {
|
|
|
688
688
|
}
|
|
689
689
|
/**
|
|
690
690
|
* The echo's spans with the value of every sensitive header line (`headerLine`'s names) added,
|
|
691
|
-
* read on the text before the echo is replaced
|
|
691
|
+
* read on the text before the echo is replaced. An echoed private value can take part of a
|
|
692
692
|
* header's name, its `:`, or a credential prefix such as `Bearer `, so the line cannot be read
|
|
693
693
|
* after replacement; read here, the whole value goes, every cookie pair and any short tail with
|
|
694
694
|
* it. A line that is not a sensitive header keeps its text. One forward pass; nothing is kept.
|
|
@@ -2512,9 +2512,6 @@ export function registerCredentialValues(secrets, values) {
|
|
|
2512
2512
|
secrets.add(value);
|
|
2513
2513
|
}
|
|
2514
2514
|
}
|
|
2515
|
-
export function trackCredentialReads(ctx) {
|
|
2516
|
-
wrapViewCredentials(ctx);
|
|
2517
|
-
}
|
|
2518
2515
|
/** A connector's result or error as it enters: every connector's credentials and the
|
|
2519
2516
|
* request's private arguments, under payload rules. */
|
|
2520
2517
|
export function redactSentSecrets(ctx, value) {
|
|
@@ -2530,9 +2527,7 @@ export function requestSentSecrets(ctx) {
|
|
|
2530
2527
|
* extracted from. Redact it whole first: a cut through an echoed form can leave a remnant
|
|
2531
2528
|
* shorter than any fragment the output boundary matches, and a normalized number or
|
|
2532
2529
|
* extracted substring may no longer hold the form at all. */
|
|
2533
|
-
export
|
|
2534
|
-
return requestSentSecrets(ctx).redact(value);
|
|
2535
|
-
}
|
|
2530
|
+
export const redactBeforeCut = redactSentSecrets;
|
|
2536
2531
|
/** Base64, or base64url with `url`, of bytes or UTF-8 text Connecta returns to an agent: a
|
|
2537
2532
|
* download, an attachment, a REST body, a page cursor. Every such encoding goes through here,
|
|
2538
2533
|
* since no later pass reads through an encoding the output does not declare: the content is
|
package/dist/server.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createFetchHandler(opts) {
|
|
|
35
35
|
// Which sandbox, not how it is tuned: `connecta doctor` reports the
|
|
36
36
|
// executor it just exercised rather than assuming one, and a
|
|
37
37
|
// deployment whose executor identifies as nothing omits the key
|
|
38
|
-
// instead of inviting a guess
|
|
38
|
+
// instead of inviting a guess.
|
|
39
39
|
...(opts.executorName !== undefined ? { executor: { name: opts.executorName } } : {}),
|
|
40
40
|
admission: {
|
|
41
41
|
policy: "global-fifo",
|
|
@@ -64,7 +64,7 @@ export function createFetchHandler(opts) {
|
|
|
64
64
|
const defer = runtimeContext ? runtimeContext.waitUntil.bind(runtimeContext) : undefined;
|
|
65
65
|
const originRefusal = routeMcp.rejectOrigin(request);
|
|
66
66
|
if (originRefusal) {
|
|
67
|
-
return Effect.succeed(withSecurityHeaders(originRefusal, url
|
|
67
|
+
return Effect.succeed(withSecurityHeaders(originRefusal, url));
|
|
68
68
|
}
|
|
69
69
|
// Container and orchestrator probes reach /health over plain HTTP on
|
|
70
70
|
// loopback, where no proxy has set X-Forwarded-Proto. Redirecting them to
|
|
@@ -87,7 +87,7 @@ export function createFetchHandler(opts) {
|
|
|
87
87
|
return Effect.succeed(withSecurityHeaders(new Response(null, {
|
|
88
88
|
status: 308,
|
|
89
89
|
headers: { Location: target.toString() },
|
|
90
|
-
}), url
|
|
90
|
+
}), url));
|
|
91
91
|
}
|
|
92
92
|
const context = {
|
|
93
93
|
request,
|
|
@@ -98,7 +98,7 @@ export function createFetchHandler(opts) {
|
|
|
98
98
|
defer,
|
|
99
99
|
runtimeContext,
|
|
100
100
|
};
|
|
101
|
-
return Effect.map(route(context), (response) => withSecurityHeaders(response, url
|
|
101
|
+
return Effect.map(route(context), (response) => withSecurityHeaders(response, url));
|
|
102
102
|
});
|
|
103
103
|
return function fetch(request, runtimeContext) {
|
|
104
104
|
return runEdge(serve(request, runtimeContext), { signal: request.signal });
|
package/dist/skills.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { META_ROUTES, routeNames } from "./agent-routes.js";
|
|
2
|
+
import { bytesToHex, utf8Length } from "./bytes.js";
|
|
1
3
|
import { duringViewEffect } from "./runtime/call.js";
|
|
2
4
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
3
5
|
import { upwardStep } from "./upward.js";
|
|
@@ -8,15 +10,17 @@ import { Effect } from "effect";
|
|
|
8
10
|
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
9
11
|
import { closeScopeOnExit } from "./runtime/connector-scope.js";
|
|
10
12
|
import { sentSecretsForRequest } from "./sent-secrets.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// The meta instructions and built-in guides use the meta names: built-in guides
|
|
14
|
+
// are byte-identical across deployments, and the eval code arm replaces usage.
|
|
15
|
+
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.`;
|
|
16
|
+
const RECOVERY = `When an auth error includes a handoff, give it to the user or operator. Use ${META_ROUTES.authorize} for a fresh link. Page resultId notices and format:"paged" handles with connecta.result(handleOrId, { page? }). Missing guidance/repair only: skills tool "usage" or connecta.skill("usage").`;
|
|
13
17
|
/**
|
|
14
18
|
* The always-loaded MCP `instructions` string. An endpoint is writable or
|
|
15
19
|
* read-only: a writable one lets programs and call_destructive_tool write,
|
|
16
20
|
* and the client owns approval; a read-only one omits write tools from search
|
|
17
21
|
* and describe and refuses every write call. Guides may still name them.
|
|
18
22
|
*/
|
|
19
|
-
export const CONNECTA_INSTRUCTIONS = `${ROUTE} On a writable endpoint, programs and
|
|
23
|
+
export const CONNECTA_INSTRUCTIONS = `${ROUTE} On a writable endpoint, programs and ${META_ROUTES.write} may write. On a read-only endpoint, search omits write tools and every write call is refused; guides may still name them. The ${META_ROUTES.program} description says which this endpoint is. Never repeat a write to recover its output. ${RECOVERY}`;
|
|
20
24
|
/**
|
|
21
25
|
* The instructions one endpoint serves: {@link CONNECTA_INSTRUCTIONS}, then
|
|
22
26
|
* the admitted pool's `description` after a fixed lead-in. Plain `/mcp` and a
|
|
@@ -30,7 +34,8 @@ export function endpointInstructions(description, native) {
|
|
|
30
34
|
}
|
|
31
35
|
/** Instructions for pools whose host searches the listed native tools. */
|
|
32
36
|
export function nativeInstructions(executeCode) {
|
|
33
|
-
|
|
37
|
+
const routes = routeNames(executeCode ? "native-code" : "native");
|
|
38
|
+
return `Each tool here is one operation; annotations classify reads/writes. Call known tools directly; ${executeCode ? `combine work in ${routes.program} using the program addresses in tool descriptions.` : "find tools with your host's search."} Read-only endpoints omit and refuse writes. Never repeat a write for output. Give auth handoffs to the user/operator. Follow nextAction to page with ${executeCode ? `connecta.result in ${routes.program}` : "read_result"}. ${routes.skills} lists guidance.`;
|
|
34
39
|
}
|
|
35
40
|
/** True when at least one of `connectors` carries a usage guide. */
|
|
36
41
|
export function hasConnectorGuides(connectors) {
|
|
@@ -45,36 +50,37 @@ export function hasConnectorGuides(connectors) {
|
|
|
45
50
|
function usageSkill() {
|
|
46
51
|
return USAGE_SKILL;
|
|
47
52
|
}
|
|
53
|
+
const INVESTIGATE_DESCRIPTION = "Plan an investigation that spans several services; resolve scope, identities, and capability limits before querying, then return the answer with its evidence.";
|
|
48
54
|
const INVESTIGATE_SKILL = `---
|
|
49
55
|
name: investigate
|
|
50
|
-
description:
|
|
56
|
+
description: ${INVESTIGATE_DESCRIPTION}
|
|
51
57
|
---
|
|
52
58
|
|
|
53
59
|
# Investigate across services
|
|
54
60
|
|
|
55
61
|
## Plan the investigation
|
|
56
62
|
|
|
57
|
-
Start from the user's question and the evidence that would answer it. Select the
|
|
63
|
+
Start from the user's question and the evidence that would answer it. Select the account, environment, and time window from connector titles, purposes, and relevant guides before looking up records. Reuse verified ids within the task; never carry an id across connectors just because its name matches.
|
|
58
64
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
65
|
+
- Resolve the same subject in each service separately. A record in one service does not prove the matching state in another, and a missing record in one does not prove failure elsewhere.
|
|
66
|
+
- Confirm the scope a comparison needs (which records, which window, which population) before comparing. Return the requested comparison and any missing evidence; do not expand into an unrelated audit.
|
|
67
|
+
- Locate the exact record first, then follow only the records needed to explain the reported symptom. Use provider links or ids so the answer can be checked.
|
|
62
68
|
|
|
63
69
|
Establish capability limits early. A partial search is not proof that a tool is absent, and an unavailable catalog is not an empty dataset. Try a scoped search for the missing operation, inspect its guide when relevant, and distinguish unsupported work from missing data or authorization. If the required evidence is unavailable, return what was verified and the specific gap instead of approximating a different question through repeated calls.
|
|
64
70
|
|
|
65
|
-
A host transport error that requires reconnecting this MCP server cannot be repaired by a downstream tool. Reconnect in the host; do not repeatedly call \`
|
|
71
|
+
A host transport error that requires reconnecting this MCP server cannot be repaired by a downstream tool. Reconnect in the host; do not repeatedly call \`${META_ROUTES.authorize}\` through the failed connection.
|
|
66
72
|
|
|
67
|
-
Return the answer first, then the evidence and any unresolved gap. Include the
|
|
73
|
+
Return the answer first, then the evidence and any unresolved gap. Include the account or environment, time window, and source ids or links needed to check it. Separate observed facts from inferences. Stop when the requested evidence is sufficient.
|
|
68
74
|
`;
|
|
69
75
|
const AVAILABLE_SKILLS = [
|
|
70
76
|
{
|
|
71
77
|
name: "usage",
|
|
72
|
-
description:
|
|
78
|
+
description: `How to route work between one ${META_ROUTES.program} program and Connecta's explicit call, authorization, and result tools.`,
|
|
73
79
|
content: usageSkill,
|
|
74
80
|
},
|
|
75
81
|
{
|
|
76
82
|
name: "investigate",
|
|
77
|
-
description:
|
|
83
|
+
description: INVESTIGATE_DESCRIPTION,
|
|
78
84
|
content: () => INVESTIGATE_SKILL,
|
|
79
85
|
},
|
|
80
86
|
];
|
|
@@ -83,7 +89,8 @@ const AVAILABLE_SKILLS = [
|
|
|
83
89
|
* are bare identifiers and never contain ":", so `connector:<id>` cannot
|
|
84
90
|
* collide with one — not even when a connector's id is literally "usage".
|
|
85
91
|
* Canonical skill URIs and the prefixed compatibility alias reach connector
|
|
86
|
-
* guides. A bare connector id
|
|
92
|
+
* guides. A bare connector id reaches its guide too, unless it is a built-in
|
|
93
|
+
* skill name: built-ins always win, so nothing shadows them.
|
|
87
94
|
*/
|
|
88
95
|
const CONNECTOR_SKILL_PREFIX = "connector:";
|
|
89
96
|
/** The skill name that fetches `connector`'s guide. */
|
|
@@ -328,25 +335,6 @@ const MAX_SKILL_CATALOG_BYTES = 8 * 1024 * 1024;
|
|
|
328
335
|
const MAX_SKILLS = 1_024;
|
|
329
336
|
const encoder = new TextEncoder();
|
|
330
337
|
const PRIVATE = { resultType: "complete", ttlMs: 0, cacheScope: "private" };
|
|
331
|
-
/** Bound metadata before cloning it, without another payload-sized byte array. */
|
|
332
|
-
function jsonBytes(value) {
|
|
333
|
-
const json = JSON.stringify(value);
|
|
334
|
-
let bytes = 0;
|
|
335
|
-
for (let i = 0; i < json.length; i++) {
|
|
336
|
-
const code = json.charCodeAt(i);
|
|
337
|
-
if (code < 0x80)
|
|
338
|
-
bytes++;
|
|
339
|
-
else if (code < 0x800)
|
|
340
|
-
bytes += 2;
|
|
341
|
-
else if (code >= 0xd800 && code <= 0xdbff && json.charCodeAt(i + 1) >= 0xdc00 && json.charCodeAt(i + 1) <= 0xdfff) {
|
|
342
|
-
bytes += 4;
|
|
343
|
-
i++;
|
|
344
|
-
}
|
|
345
|
-
else
|
|
346
|
-
bytes += 3;
|
|
347
|
-
}
|
|
348
|
-
return bytes;
|
|
349
|
-
}
|
|
350
338
|
function localRecord(name, uri, description, content, aliases) {
|
|
351
339
|
return { entry: { uri, frontmatter: { name, description }, resources: "dynamic" }, content, aliases };
|
|
352
340
|
}
|
|
@@ -369,12 +357,7 @@ const localRecordsNative = /* @__PURE__ */ registerAuth(localRecords, function (
|
|
|
369
357
|
(a.id < b.id ? -1 : a.id > b.id ? 1 : 0)), (connector) => authFlow(function* () {
|
|
370
358
|
const name = validSkillName(connector.id)
|
|
371
359
|
? connector.id
|
|
372
|
-
: `connector-${[
|
|
373
|
-
...new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", encoder.encode(connector.id)]))),
|
|
374
|
-
]
|
|
375
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
376
|
-
.join("")
|
|
377
|
-
.slice(0, 48)}`;
|
|
360
|
+
: `connector-${bytesToHex(new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", encoder.encode(connector.id)])))).slice(0, 48)}`;
|
|
378
361
|
const description = connectorGuideSummary(connector);
|
|
379
362
|
const root = `skill://connecta/connectors/${encodeURIComponent(name)}`;
|
|
380
363
|
const content = `---\n${JSON.stringify({ name, description }, null, 2)}\n---\n\n${connectorGuide(connector)}`;
|
|
@@ -392,6 +375,13 @@ const localRecordsNative = /* @__PURE__ */ registerAuth(localRecords, function (
|
|
|
392
375
|
return [builtIns[0], ...guides, ...builtIns.slice(1)];
|
|
393
376
|
});
|
|
394
377
|
});
|
|
378
|
+
/** `granola` names connector granola's guide; built-in names and URIs never do. */
|
|
379
|
+
function bareConnectorGuide(records, name) {
|
|
380
|
+
if (name.includes(":") || AVAILABLE_SKILLS.some((skill) => skill.name === name))
|
|
381
|
+
return undefined;
|
|
382
|
+
const alias = connectorSkillName(name);
|
|
383
|
+
return records.find((record) => record.connector !== undefined && record.aliases.includes(alias));
|
|
384
|
+
}
|
|
395
385
|
/** Keep the downstream path tree, so relative supporting-file references still resolve. */
|
|
396
386
|
export function downstreamSkillUri(connectorId, uri) {
|
|
397
387
|
if (uri.length > 32_768)
|
|
@@ -474,11 +464,7 @@ const withManifestNative = /* @__PURE__ */ registerAuth(withManifest, function (
|
|
|
474
464
|
const bytes = encoder.encode(record.content);
|
|
475
465
|
if (bytes.length > MAX_SKILL_BYTES)
|
|
476
466
|
throw new ConnectorCallError("unavailable", "Local skill exceeds the byte bound.");
|
|
477
|
-
const digest = [
|
|
478
|
-
...new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", bytes]))),
|
|
479
|
-
]
|
|
480
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
481
|
-
.join("");
|
|
467
|
+
const digest = bytesToHex(new Uint8Array(yield* upwardStep(authInvoke(crypto.subtle, "digest", ["SHA-256", bytes]))));
|
|
482
468
|
return {
|
|
483
469
|
...record,
|
|
484
470
|
entry: {
|
|
@@ -552,12 +538,14 @@ export class SkillsRegistry {
|
|
|
552
538
|
const connector = this.registry.getConnector(id);
|
|
553
539
|
if (connector)
|
|
554
540
|
this.options.onRead?.(connector, "local");
|
|
555
|
-
|
|
556
|
-
|
|
541
|
+
// Skill names resolve a bare id with a guide before reaching here; a
|
|
542
|
+
// resource URI read does not.
|
|
543
|
+
const message = connector
|
|
544
|
+
? connectorGuide(connector) === undefined
|
|
557
545
|
? `Connector "${boundedEchoText(id)}" has no usage guide.`
|
|
558
|
-
: `
|
|
559
|
-
:
|
|
560
|
-
? `Unknown
|
|
546
|
+
: `Connector "${boundedEchoText(id)}" has its guide at "${boundedEchoText(connectorSkillName(id))}".`
|
|
547
|
+
: name.startsWith(CONNECTOR_SKILL_PREFIX)
|
|
548
|
+
? `Unknown connector "${boundedEchoText(id)}".`
|
|
561
549
|
: `Unknown skill "${boundedEchoText(name)}".`;
|
|
562
550
|
return new ConnectorCallError("not_found", `${message} Available skills: ${available}.`);
|
|
563
551
|
}
|
|
@@ -702,7 +690,9 @@ export class SkillsRegistry {
|
|
|
702
690
|
registerAuthMethod(SkillsRegistry.prototype, "lookup", function (uri) {
|
|
703
691
|
const self = this;
|
|
704
692
|
return duringViewEffect({ requestScope: this.scope, signal: this.options.requestSignal }, () => authFlow(function* () {
|
|
705
|
-
const
|
|
693
|
+
const records = yield* upwardStep(self.local);
|
|
694
|
+
const local = records.find((record) => record.entry.uri === uri || record.aliases.includes(uri)) ??
|
|
695
|
+
bareConnectorGuide(records, uri);
|
|
706
696
|
if (local) {
|
|
707
697
|
const record = yield* upwardStep(authCall(withManifest, [local]));
|
|
708
698
|
self.assertSafe([record]);
|
|
@@ -723,7 +713,7 @@ export class SkillsRegistry {
|
|
|
723
713
|
const records = yield* upwardStep(Effect.forEach(yield* upwardStep(self.local), (record) => authCall(withManifest, [record]), {
|
|
724
714
|
concurrency: "unbounded",
|
|
725
715
|
}));
|
|
726
|
-
let catalogBytes =
|
|
716
|
+
let catalogBytes = utf8Length(JSON.stringify(records.map((record) => record.entry)));
|
|
727
717
|
// Bounded sequential connector reads avoid unbounded fan-out and finish
|
|
728
718
|
// cleanup before a later connector's failure can end the whole listing.
|
|
729
719
|
for (const connector of self.registry.listConnectors()) {
|
|
@@ -736,7 +726,7 @@ export class SkillsRegistry {
|
|
|
736
726
|
if (!Array.isArray(entries) || records.length + entries.length > MAX_SKILLS)
|
|
737
727
|
throw new ConnectorCallError("unavailable", "Downstream skills listing exceeds its bound.");
|
|
738
728
|
for (const original of entries) {
|
|
739
|
-
if (catalogBytes +
|
|
729
|
+
if (catalogBytes + utf8Length(JSON.stringify(original)) + 1 > MAX_SKILL_CATALOG_BYTES)
|
|
740
730
|
throw new ConnectorCallError("unavailable", "Skills listing exceeds its byte bound.");
|
|
741
731
|
const entry = structuredClone(original);
|
|
742
732
|
validateEntry(entry);
|
|
@@ -758,14 +748,14 @@ export class SkillsRegistry {
|
|
|
758
748
|
if (records.some((record) => record.entry.uri === uri))
|
|
759
749
|
throw new ConnectorCallError("unavailable", "Downstream skills listing contains duplicate entries.");
|
|
760
750
|
const mapped = { ...entry, uri, resources };
|
|
761
|
-
catalogBytes +=
|
|
751
|
+
catalogBytes += utf8Length(JSON.stringify(mapped)) + 1;
|
|
762
752
|
if (catalogBytes > MAX_SKILL_CATALOG_BYTES)
|
|
763
753
|
throw new ConnectorCallError("unavailable", "Skills listing exceeds its byte bound.");
|
|
764
754
|
records.push({ entry: mapped, aliases: [], connector, files });
|
|
765
755
|
}
|
|
766
756
|
}
|
|
767
757
|
if (records.length > MAX_SKILLS ||
|
|
768
|
-
|
|
758
|
+
utf8Length(JSON.stringify(records.map((record) => record.entry))) > MAX_SKILL_CATALOG_BYTES) {
|
|
769
759
|
throw new ConnectorCallError("unavailable", "Skills listing exceeds its byte or entry bound.");
|
|
770
760
|
}
|
|
771
761
|
// Keep the old investigation guide behind every connector and downstream
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -27,22 +27,3 @@ export declare function sqliteStorage(database: SqliteDatabase): KVStorage;
|
|
|
27
27
|
* older than `retentionDays`.
|
|
28
28
|
*/
|
|
29
29
|
export declare function sqliteActivityStore(database: SqliteDatabase, options?: SqliteActivityOptions): ActivityStore;
|
|
30
|
-
export interface StateFileImport {
|
|
31
|
-
/** Entries copied into the database. */
|
|
32
|
-
imported: number;
|
|
33
|
-
/** Entries left alone because the database already held their key. */
|
|
34
|
-
kept: number;
|
|
35
|
-
/** Entries whose TTL had already passed. */
|
|
36
|
-
expired: number;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* One-shot migration from the 0.28 `fileStorage` JSON state file: copy every
|
|
40
|
-
* live entry, with its expiry, into the database's key-value table. Run it
|
|
41
|
-
* once, with the old deployment stopped, before the first start on SQLite.
|
|
42
|
-
*
|
|
43
|
-
* Keys the database already holds are kept, so a second run copies nothing
|
|
44
|
-
* twice and never overwrites state written since. The file is read, never
|
|
45
|
-
* changed; delete it once the deployment is verified. A file that is not a
|
|
46
|
-
* state file throws before anything is written.
|
|
47
|
-
*/
|
|
48
|
-
export declare function importStateFile(database: SqliteDatabase, statePath: string, now?: number): StateFileImport;
|