@zackbart/connecta 0.5.0 → 0.6.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 +358 -0
- package/README.md +53 -12
- package/dist/auth/bearer.d.ts +10 -3
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +21 -0
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/clerk.d.ts +26 -1
- package/dist/auth/clerk.d.ts.map +1 -1
- package/dist/auth/clerk.js +161 -4
- package/dist/auth/clerk.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +8 -0
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/credential-health.d.ts +212 -0
- package/dist/credential-health.d.ts.map +1 -0
- package/dist/credential-health.js +535 -0
- package/dist/credential-health.js.map +1 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +16 -4
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +46 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -13
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts +56 -5
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +249 -92
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +62 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +85 -1
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +292 -37
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +1 -1
- package/dist/timeout.d.ts +16 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +38 -0
- package/dist/timeout.js.map +1 -0
- package/dist/toolkits.d.ts +95 -1
- package/dist/toolkits.d.ts.map +1 -1
- package/dist/toolkits.js +190 -5
- package/dist/toolkits.js.map +1 -1
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +35 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +87 -3
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/auth/bearer.ts +35 -1
- package/src/auth/clerk.ts +202 -5
- package/src/connectors/remote-mcp.ts +9 -0
- package/src/credential-health.ts +736 -0
- package/src/execute.ts +28 -4
- package/src/index.ts +176 -20
- package/src/meta-tools.ts +286 -109
- package/src/registry.ts +125 -1
- package/src/server.ts +349 -34
- package/src/skills.ts +1 -1
- package/src/timeout.ts +49 -0
- package/src/toolkits.ts +241 -6
- package/src/types.ts +76 -1
- package/src/ui.ts +98 -3
- package/src/version.ts +1 -1
package/src/registry.ts
CHANGED
|
@@ -7,6 +7,13 @@ import type {
|
|
|
7
7
|
ToolDef,
|
|
8
8
|
} from "./types.js";
|
|
9
9
|
import type { CredentialVault } from "./credentials.js";
|
|
10
|
+
import {
|
|
11
|
+
CredentialHealthChecker,
|
|
12
|
+
type CredentialCheckOptions,
|
|
13
|
+
type CredentialCheckResult,
|
|
14
|
+
type CredentialHealthConfig,
|
|
15
|
+
type CredentialHealthRecord,
|
|
16
|
+
} from "./credential-health.js";
|
|
10
17
|
import { splitAddress, type Toolkit } from "./toolkits.js";
|
|
11
18
|
|
|
12
19
|
const ID_RE = /^[a-z0-9_-]+$/;
|
|
@@ -131,6 +138,8 @@ export interface RegistryOptions {
|
|
|
131
138
|
* to the default 50_000.
|
|
132
139
|
*/
|
|
133
140
|
maxResultBytes?: number;
|
|
141
|
+
/** Tuning for the credential liveness checks (issue #24). */
|
|
142
|
+
credentialHealth?: CredentialHealthConfig;
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
function namespaced(storage: KVStorage, prefix: string): KVStorage {
|
|
@@ -187,6 +196,15 @@ export interface RegistryView {
|
|
|
187
196
|
recordFailure(id: string, latencyMs: number, error: unknown): void;
|
|
188
197
|
healthFor(id: string): HealthObservation | undefined;
|
|
189
198
|
hasObservedSuccess(id: string): boolean;
|
|
199
|
+
/** When ANY view last saw a successful call to `id`, deployment-wide. */
|
|
200
|
+
observedSuccessAt(id: string): string | undefined;
|
|
201
|
+
/** Last credential-liveness verdict for `id`. Cached; no downstream I/O. */
|
|
202
|
+
credentialHealthFor(id: string): Promise<CredentialHealthRecord | undefined>;
|
|
203
|
+
/** Store a liveness verdict a live status check just produced. */
|
|
204
|
+
recordCredentialHealth(
|
|
205
|
+
id: string,
|
|
206
|
+
record: CredentialHealthRecord,
|
|
207
|
+
): Promise<void>;
|
|
190
208
|
statusFor(
|
|
191
209
|
id: string,
|
|
192
210
|
baseUrl: string,
|
|
@@ -211,6 +229,8 @@ export class Registry implements RegistryView {
|
|
|
211
229
|
private readonly persistToolCatalog: boolean;
|
|
212
230
|
/** Result-size guard cap threaded to the meta-tools. */
|
|
213
231
|
readonly maxResultBytes: number;
|
|
232
|
+
/** Proactive liveness checks over stored downstream credentials (issue #24). */
|
|
233
|
+
private readonly credentialHealth: CredentialHealthChecker;
|
|
214
234
|
|
|
215
235
|
constructor(
|
|
216
236
|
connectors: Connector[],
|
|
@@ -238,6 +258,18 @@ export class Registry implements RegistryView {
|
|
|
238
258
|
}
|
|
239
259
|
this.checkConventions(opts.logger);
|
|
240
260
|
this.checkResultCaps(opts.logger, opts.maxResultBytes);
|
|
261
|
+
this.credentialHealth = new CredentialHealthChecker(
|
|
262
|
+
{
|
|
263
|
+
listConnectors: () => this.listConnectors(),
|
|
264
|
+
getConnector: (id) => this.getConnector(id),
|
|
265
|
+
contextFor: (id, baseUrl, requestScope) =>
|
|
266
|
+
this.contextFor(id, baseUrl, requestScope),
|
|
267
|
+
storage: opts.storage,
|
|
268
|
+
logger: opts.logger,
|
|
269
|
+
credentialVault: opts.credentialVault,
|
|
270
|
+
},
|
|
271
|
+
opts.credentialHealth,
|
|
272
|
+
);
|
|
241
273
|
}
|
|
242
274
|
|
|
243
275
|
/**
|
|
@@ -521,7 +553,71 @@ export class Registry implements RegistryView {
|
|
|
521
553
|
* failed — stays strictly per view.
|
|
522
554
|
*/
|
|
523
555
|
hasObservedSuccess(id: string): boolean {
|
|
524
|
-
return this.
|
|
556
|
+
return this.observedSuccessAt(id) !== undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* The timestamp behind `hasObservedSuccess`, on the same deployment-wide
|
|
561
|
+
* terms and for the same reason: it says only *when* the connector last
|
|
562
|
+
* answered, never what was called or what failed. Credential health reads it
|
|
563
|
+
* to decide whether a failed verdict has been overtaken by real traffic.
|
|
564
|
+
*/
|
|
565
|
+
observedSuccessAt(id: string): string | undefined {
|
|
566
|
+
return this.health.get(id)?.lastSuccessAt;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* The last credential-liveness verdict for `id` — the layer that lets a cached
|
|
571
|
+
* status read report `auth_required` before a real call discovers it. Read
|
|
572
|
+
* from storage (mirrored in memory for a few seconds) rather than held in
|
|
573
|
+
* memory alone, because on Workers the isolate that ran the check is usually
|
|
574
|
+
* not the isolate answering this read.
|
|
575
|
+
*/
|
|
576
|
+
credentialHealthFor(id: string): Promise<CredentialHealthRecord | undefined> {
|
|
577
|
+
return this.credentialHealth.healthFor(id);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
recordCredentialHealth(
|
|
581
|
+
id: string,
|
|
582
|
+
record: CredentialHealthRecord,
|
|
583
|
+
): Promise<void> {
|
|
584
|
+
return this.credentialHealth.record(id, record);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Check stored downstream credentials now and return one outcome per
|
|
589
|
+
* connector considered. THE operator-facing entry point behind
|
|
590
|
+
* `Connecta.checkCredentials()`: wire it to a Worker cron trigger or a Node
|
|
591
|
+
* interval. Never rejects; connectors checked recently are reported as
|
|
592
|
+
* `fresh` unless `force` is set.
|
|
593
|
+
*/
|
|
594
|
+
checkCredentialHealth(
|
|
595
|
+
baseUrl: string,
|
|
596
|
+
opts?: CredentialCheckOptions,
|
|
597
|
+
): Promise<CredentialCheckResult[]> {
|
|
598
|
+
return this.credentialHealth.check(baseUrl, opts);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* The traffic-triggered sweep: a promise for the caller to defer (Workers:
|
|
603
|
+
* `ctx.waitUntil`), or `undefined` when nothing is due — which is the common
|
|
604
|
+
* case and costs no I/O. Called by the server after an authenticated request;
|
|
605
|
+
* the checker owns the rate limiting.
|
|
606
|
+
*/
|
|
607
|
+
sweepCredentialHealthIfDue(
|
|
608
|
+
baseUrl: string,
|
|
609
|
+
): Promise<CredentialCheckResult[]> | undefined {
|
|
610
|
+
return this.credentialHealth.sweepIfDue(baseUrl);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Drop a connector's liveness verdict, because its credential just changed
|
|
615
|
+
* under us (OAuth callback completed, credential stored or removed in /ui). A
|
|
616
|
+
* stale `auth_required` must not outlive the re-authorization that fixed it —
|
|
617
|
+
* that is the difference between recovery working and needing a restart.
|
|
618
|
+
*/
|
|
619
|
+
clearCredentialHealth(id: string): Promise<void> {
|
|
620
|
+
return this.credentialHealth.clear(id);
|
|
525
621
|
}
|
|
526
622
|
|
|
527
623
|
/** Best-effort connector status for list_connectors. */
|
|
@@ -768,6 +864,34 @@ export class ScopedRegistry implements RegistryView {
|
|
|
768
864
|
return this.visible(id) ? this.base.hasObservedSuccess(id) : false;
|
|
769
865
|
}
|
|
770
866
|
|
|
867
|
+
/** Deployment-wide for the same reason as `hasObservedSuccess` above. */
|
|
868
|
+
observedSuccessAt(id: string): string | undefined {
|
|
869
|
+
return this.visible(id) ? this.base.observedSuccessAt(id) : undefined;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Also deliberately NOT per view, for the same reason as
|
|
874
|
+
* `hasObservedSuccess`: whether the credential connecta stores for a connector
|
|
875
|
+
* still works is a fact about the deployment's credential, not about a team's
|
|
876
|
+
* traffic. Withholding it would leave a scoped session unable to see that the
|
|
877
|
+
* connector it shares needs re-authorization. The verdict carries the
|
|
878
|
+
* connector's own connector-level reason — never a tool name — so the per-view
|
|
879
|
+
* isolation of `lastError` above is unchanged.
|
|
880
|
+
*/
|
|
881
|
+
credentialHealthFor(id: string): Promise<CredentialHealthRecord | undefined> {
|
|
882
|
+
return this.visible(id)
|
|
883
|
+
? this.base.credentialHealthFor(id)
|
|
884
|
+
: Promise.resolve(undefined);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async recordCredentialHealth(
|
|
888
|
+
id: string,
|
|
889
|
+
record: CredentialHealthRecord,
|
|
890
|
+
): Promise<void> {
|
|
891
|
+
if (!this.visible(id)) return;
|
|
892
|
+
await this.base.recordCredentialHealth(id, record);
|
|
893
|
+
}
|
|
894
|
+
|
|
771
895
|
async statusFor(
|
|
772
896
|
id: string,
|
|
773
897
|
baseUrl: string,
|