@zackbart/connecta 0.24.4 → 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 +43 -12
- package/CHANGELOG.md +524 -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/advertised-schema.js +53 -0
- 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/auth/downstream-oauth.js +490 -106
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +152 -106
- package/dist/catalog.js +297 -38
- package/dist/concurrency.js +24 -23
- 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/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +330 -240
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +745 -282
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +145 -63
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +255 -212
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +72 -7
- package/dist/index.js +281 -26
- package/dist/invocation.js +371 -286
- package/dist/meta-tools.js +213 -94
- package/dist/module-contracts.d.ts +21 -1
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +103 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +128 -3
- package/dist/program-source.js +321 -0
- package/dist/providers/notion.js +2 -2
- package/dist/providers/revenuecat.js +32 -5
- package/dist/providers/stripe.d.ts +3 -2
- package/dist/providers/vercel.js +1 -1
- package/dist/registry.d.ts +43 -7
- package/dist/registry.js +434 -288
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +74 -154
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +196 -163
- package/dist/routes/mcp.js +228 -112
- package/dist/routes/oauth-management.js +73 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +83 -0
- package/dist/routes/shared.d.ts +25 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +211 -0
- package/dist/routes/ui.js +76 -35
- package/dist/run-journal.js +347 -0
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +137 -89
- package/dist/skills.js +28 -9
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +39 -43
- package/dist/tool-safety.d.ts +27 -1
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +45 -7
- package/dist/ui.d.ts +25 -4
- package/dist/ui.js +77 -167
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +367 -16
- package/documentation/auth.md +163 -2
- package/documentation/code-mode.md +274 -56
- package/documentation/meta-tools.md +200 -44
- package/ethos.md +46 -47
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +224 -14
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +42 -7
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +21 -5
- package/package.json +16 -4
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +27 -0
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -96
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -188
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
|
@@ -20,5 +20,14 @@ export interface CredentialVault {
|
|
|
20
20
|
set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
|
|
21
21
|
setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
|
|
22
22
|
delete(connectorId: string, owner?: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Encrypt downstream OAuth state (tokens, a registered client, a PKCE
|
|
25
|
+
* verifier) before it enters storage. `purpose` is the storage key the value
|
|
26
|
+
* is written under, so the result is bound to one connector, owner, and key.
|
|
27
|
+
* Without both `seal` and `open`, that state is stored as plaintext.
|
|
28
|
+
*/
|
|
29
|
+
seal?(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
|
|
30
|
+
/** Reverse `seal` for the same connector, purpose, and owner; rejects otherwise. */
|
|
31
|
+
open?(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
|
|
23
32
|
}
|
|
24
33
|
export {};
|
package/dist/credential-rules.js
CHANGED
|
@@ -3,7 +3,7 @@ export const STORED_CREDENTIAL_SHAPE_MISMATCH_ERROR = "Stored credential fields
|
|
|
3
3
|
/**
|
|
4
4
|
* Compare a connector's current declaration with the keys in its stored
|
|
5
5
|
* credential. Values are deliberately ignored: callers may pass decrypted
|
|
6
|
-
* values or
|
|
6
|
+
* values or the operator UI's masked credential field metadata and get the same answer.
|
|
7
7
|
*
|
|
8
8
|
* The test is CONTAINMENT, not equality: the stored key set is compatible when
|
|
9
9
|
* it holds every field currently declared — the reserved `value` key for a
|
|
@@ -65,7 +65,7 @@ export function describeUndeclaredCredentialFields(fields) {
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* The one rule deciding whether a connector's credential can be tested — read
|
|
68
|
-
* by
|
|
68
|
+
* by the operator UI's credential `testable` flag, by `POST /ui/credentials/<id>/test` when
|
|
69
69
|
* it picks a hook, and by the construction-time mismatch warning, so those
|
|
70
70
|
* three cannot drift apart.
|
|
71
71
|
*
|
package/dist/credentials.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { ConnectorCredentialValues, KVStorage } from "./types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Encrypted, connector-scoped credential vault over the deployment's existing
|
|
5
5
|
* KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
|
|
6
|
-
* secret outside the store.
|
|
6
|
+
* secret outside the store. The same key seals downstream OAuth state, which
|
|
7
|
+
* the OAuth provider stores itself.
|
|
7
8
|
*/
|
|
8
9
|
export declare class CredentialVault implements Vault {
|
|
9
10
|
private readonly storage;
|
|
@@ -17,5 +18,9 @@ export declare class CredentialVault implements Vault {
|
|
|
17
18
|
set(connectorId: string, value: string, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
|
|
18
19
|
setAll(connectorId: string, values: ConnectorCredentialValues, updatedBy: string, owner?: string): Promise<CredentialMetadata>;
|
|
19
20
|
delete(connectorId: string, owner?: string): Promise<void>;
|
|
21
|
+
/** Encrypt downstream OAuth state under the vault key; nothing is stored. */
|
|
22
|
+
seal(connectorId: string, purpose: string, plaintext: string, owner?: string): Promise<string>;
|
|
23
|
+
/** Decrypt what `seal` produced for this connector, purpose, and owner. */
|
|
24
|
+
open(connectorId: string, purpose: string, sealed: string, owner?: string): Promise<string>;
|
|
20
25
|
}
|
|
21
26
|
export declare function encryptedCredentialVault(storage: KVStorage, encryptionKey: string): Vault;
|
package/dist/credentials.js
CHANGED
|
@@ -3,6 +3,16 @@ const IV_BYTES = 12;
|
|
|
3
3
|
const MAX_CREDENTIAL_BYTES = 16_384;
|
|
4
4
|
const encoder = new TextEncoder();
|
|
5
5
|
const decoder = new TextDecoder();
|
|
6
|
+
/** Version tag of the compact `seal` output: `v1.<iv>.<ciphertext>`. */
|
|
7
|
+
const SEALED_VERSION = "v1";
|
|
8
|
+
/**
|
|
9
|
+
* Binds sealed OAuth state to its connector, owner, and physical storage key
|
|
10
|
+
* (which carries the authorization epoch), so ciphertext copied anywhere else
|
|
11
|
+
* does not open. Connector ids cannot contain `|`, and the owner is a hash.
|
|
12
|
+
*/
|
|
13
|
+
function oauthStateAdditionalData(connectorId, purpose, owner) {
|
|
14
|
+
return encoder.encode(`connecta:oauth-state:v1|${connectorId}|${owner ?? ""}|${purpose}`);
|
|
15
|
+
}
|
|
6
16
|
function storageKey(connectorId, owner) {
|
|
7
17
|
return owner
|
|
8
18
|
? `principal:${owner}:conn:${connectorId}:credential:v1`
|
|
@@ -20,7 +30,7 @@ function base64ToBytes(value) {
|
|
|
20
30
|
binary = atob(value);
|
|
21
31
|
}
|
|
22
32
|
catch {
|
|
23
|
-
throw new Error("
|
|
33
|
+
throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
|
|
24
34
|
}
|
|
25
35
|
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
26
36
|
}
|
|
@@ -87,7 +97,8 @@ function validateValues(values) {
|
|
|
87
97
|
/**
|
|
88
98
|
* Encrypted, connector-scoped credential vault over the deployment's existing
|
|
89
99
|
* KVStorage. Only ciphertext enters KV; the AES-GCM key remains an environment
|
|
90
|
-
* secret outside the store.
|
|
100
|
+
* secret outside the store. The same key seals downstream OAuth state, which
|
|
101
|
+
* the OAuth provider stores itself.
|
|
91
102
|
*/
|
|
92
103
|
export class CredentialVault {
|
|
93
104
|
storage;
|
|
@@ -96,7 +107,7 @@ export class CredentialVault {
|
|
|
96
107
|
this.storage = storage;
|
|
97
108
|
const raw = base64ToBytes(encryptionKey.trim());
|
|
98
109
|
if (raw.byteLength !== KEY_BYTES) {
|
|
99
|
-
throw new Error("
|
|
110
|
+
throw new Error("The credential vault encryption key must be a base64-encoded 32-byte key");
|
|
100
111
|
}
|
|
101
112
|
this.key = crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
102
113
|
}
|
|
@@ -182,6 +193,37 @@ export class CredentialVault {
|
|
|
182
193
|
async delete(connectorId, owner) {
|
|
183
194
|
await this.storage.delete(storageKey(connectorId, owner));
|
|
184
195
|
}
|
|
196
|
+
/** Encrypt downstream OAuth state under the vault key; nothing is stored. */
|
|
197
|
+
async seal(connectorId, purpose, plaintext, owner) {
|
|
198
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
199
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
200
|
+
name: "AES-GCM",
|
|
201
|
+
iv,
|
|
202
|
+
additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
|
|
203
|
+
}, await this.key, encoder.encode(plaintext));
|
|
204
|
+
return `${SEALED_VERSION}.${bytesToBase64(iv)}.${bytesToBase64(new Uint8Array(ciphertext))}`;
|
|
205
|
+
}
|
|
206
|
+
/** Decrypt what `seal` produced for this connector, purpose, and owner. */
|
|
207
|
+
async open(connectorId, purpose, sealed, owner) {
|
|
208
|
+
const [version, iv, ciphertext, ...rest] = sealed.split(".");
|
|
209
|
+
if (version !== SEALED_VERSION ||
|
|
210
|
+
iv === undefined ||
|
|
211
|
+
ciphertext === undefined ||
|
|
212
|
+
rest.length > 0) {
|
|
213
|
+
throw new Error("Sealed OAuth state is invalid or corrupted");
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const plaintext = await crypto.subtle.decrypt({
|
|
217
|
+
name: "AES-GCM",
|
|
218
|
+
iv: base64ToBytes(iv),
|
|
219
|
+
additionalData: oauthStateAdditionalData(connectorId, purpose, owner),
|
|
220
|
+
}, await this.key, base64ToBytes(ciphertext));
|
|
221
|
+
return decoder.decode(plaintext);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw new Error("Sealed OAuth state could not be decrypted");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
185
227
|
}
|
|
186
228
|
export function encryptedCredentialVault(storage, encryptionKey) {
|
|
187
229
|
return new CredentialVault(storage, encryptionKey);
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,7 +15,14 @@ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited
|
|
|
15
15
|
* code stays `connector_call_failed` or `auth_required` with a message that
|
|
16
16
|
* states the ambiguity (H11).
|
|
17
17
|
*/
|
|
18
|
-
| "not_found"
|
|
18
|
+
| "not_found"
|
|
19
|
+
/**
|
|
20
|
+
* The write named a base version or revision that is no longer current:
|
|
21
|
+
* someone else changed the thing first. The caller re-reads, reapplies its
|
|
22
|
+
* change, and retries with the base `current` reports — repeating the same
|
|
23
|
+
* call can never succeed, and waiting will not help either.
|
|
24
|
+
*/
|
|
25
|
+
| "conflict" | "input_required_unsupported" | "connector_call_failed";
|
|
19
26
|
/** One bounded, payload-free explanation of an input-schema mismatch. */
|
|
20
27
|
export interface ArgumentValidationIssue {
|
|
21
28
|
/** JSON Pointer into the submitted arguments; "/" means the root value. */
|
|
@@ -88,12 +95,18 @@ export declare class ConnectorCallError extends Error {
|
|
|
88
95
|
readonly validation: ArgumentValidationDetails | undefined;
|
|
89
96
|
/** Sanitized transport diagnostics for `unavailable` only. */
|
|
90
97
|
readonly details: UnavailableDetails | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* For `conflict` only: where each thing the write touched stands now, e.g.
|
|
100
|
+
* `{ revision: 12, view: 7 }`. Bounded to 20 whole-number entries.
|
|
101
|
+
*/
|
|
102
|
+
readonly current: Readonly<Record<string, number>> | undefined;
|
|
91
103
|
constructor(code: ConnectorCallErrorCode, message: string, opts?: {
|
|
92
104
|
retryable?: boolean;
|
|
93
105
|
retryAfterMs?: number;
|
|
94
106
|
cause?: unknown;
|
|
95
107
|
validation?: ArgumentValidationDetails;
|
|
96
108
|
details?: UnavailableDetails;
|
|
109
|
+
current?: Readonly<Record<string, number>>;
|
|
97
110
|
});
|
|
98
111
|
}
|
|
99
112
|
/** The `error` object surfaced in value-mode call results and rejected promises. */
|
|
@@ -107,6 +120,8 @@ export interface CallErrorDetails {
|
|
|
107
120
|
retryAfterMs?: number;
|
|
108
121
|
/** Bounded input-schema findings; paths and expectations, never values. */
|
|
109
122
|
validation?: ArgumentValidationDetails;
|
|
123
|
+
/** On a `conflict`: where each thing the write touched stands now. */
|
|
124
|
+
current?: Readonly<Record<string, number>>;
|
|
110
125
|
/** Connector whose failed operation needs recovery. */
|
|
111
126
|
connector?: string;
|
|
112
127
|
/** Canonical downstream address the agent may retry after recovery. */
|
package/dist/errors.js
CHANGED
|
@@ -162,9 +162,30 @@ const RETRYABLE_BY_CODE = {
|
|
|
162
162
|
auth_required: false,
|
|
163
163
|
invalid_args: false,
|
|
164
164
|
not_found: false,
|
|
165
|
+
conflict: false,
|
|
165
166
|
input_required_unsupported: false,
|
|
166
167
|
connector_call_failed: false,
|
|
167
168
|
};
|
|
169
|
+
/** Entries a conflict's `current` map may carry. */
|
|
170
|
+
const MAX_CURRENT_ENTRIES = 20;
|
|
171
|
+
const MAX_CURRENT_KEY_CHARS = 128;
|
|
172
|
+
/**
|
|
173
|
+
* Where each thing a conflicting write touched stands now: short names to
|
|
174
|
+
* whole non-negative numbers, at most 20 of them. Anything else is dropped
|
|
175
|
+
* whole rather than clipped, because a truncated key names a different thing.
|
|
176
|
+
*/
|
|
177
|
+
function boundedCurrent(current) {
|
|
178
|
+
if (!current || typeof current !== "object")
|
|
179
|
+
return undefined;
|
|
180
|
+
const entries = Object.entries(current).filter(([key, value]) => key.length > 0 &&
|
|
181
|
+
key.length <= MAX_CURRENT_KEY_CHARS &&
|
|
182
|
+
key !== "__proto__" &&
|
|
183
|
+
Number.isSafeInteger(value) &&
|
|
184
|
+
value >= 0);
|
|
185
|
+
if (entries.length === 0)
|
|
186
|
+
return undefined;
|
|
187
|
+
return Object.freeze(Object.fromEntries(entries.slice(0, MAX_CURRENT_ENTRIES)));
|
|
188
|
+
}
|
|
168
189
|
/** Non-negative integer milliseconds, or undefined for anything else. */
|
|
169
190
|
function normalizeRetryAfterMs(value) {
|
|
170
191
|
if (value === undefined)
|
|
@@ -201,6 +222,11 @@ export class ConnectorCallError extends Error {
|
|
|
201
222
|
validation;
|
|
202
223
|
/** Sanitized transport diagnostics for `unavailable` only. */
|
|
203
224
|
details;
|
|
225
|
+
/**
|
|
226
|
+
* For `conflict` only: where each thing the write touched stands now, e.g.
|
|
227
|
+
* `{ revision: 12, view: 7 }`. Bounded to 20 whole-number entries.
|
|
228
|
+
*/
|
|
229
|
+
current;
|
|
204
230
|
constructor(code, message, opts = {}) {
|
|
205
231
|
super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
206
232
|
this.name = "ConnectorCallError";
|
|
@@ -211,6 +237,7 @@ export class ConnectorCallError extends Error {
|
|
|
211
237
|
code === "unavailable" ? sanitizedUnavailableDetails(opts.details) : undefined;
|
|
212
238
|
this.validation =
|
|
213
239
|
code === "invalid_args" ? boundedValidation(opts.validation) : undefined;
|
|
240
|
+
this.current = code === "conflict" ? boundedCurrent(opts.current) : undefined;
|
|
214
241
|
}
|
|
215
242
|
}
|
|
216
243
|
/**
|
|
@@ -262,6 +289,7 @@ export function classifyCallError(err, fallbackCode = "connector_call_failed") {
|
|
|
262
289
|
: {}),
|
|
263
290
|
...(err.validation ? { validation: err.validation } : {}),
|
|
264
291
|
...(err.details ? { details: err.details } : {}),
|
|
292
|
+
...(err.current ? { current: err.current } : {}),
|
|
265
293
|
};
|
|
266
294
|
}
|
|
267
295
|
// An aborted fetch rejects with a DOMException named "AbortError" whose
|