@zackbart/connecta 0.5.0 → 0.6.1

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +505 -0
  2. package/README.md +159 -267
  3. package/dist/auth/bearer.d.ts +10 -3
  4. package/dist/auth/bearer.d.ts.map +1 -1
  5. package/dist/auth/bearer.js +21 -0
  6. package/dist/auth/bearer.js.map +1 -1
  7. package/dist/auth/clerk.d.ts +28 -3
  8. package/dist/auth/clerk.d.ts.map +1 -1
  9. package/dist/auth/clerk.js +161 -4
  10. package/dist/auth/clerk.js.map +1 -1
  11. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  12. package/dist/connectors/remote-mcp.js +8 -0
  13. package/dist/connectors/remote-mcp.js.map +1 -1
  14. package/dist/credential-health.d.ts +220 -0
  15. package/dist/credential-health.d.ts.map +1 -0
  16. package/dist/credential-health.js +551 -0
  17. package/dist/credential-health.js.map +1 -0
  18. package/dist/credentials.d.ts +35 -1
  19. package/dist/credentials.d.ts.map +1 -1
  20. package/dist/credentials.js +42 -0
  21. package/dist/credentials.js.map +1 -1
  22. package/dist/execute.d.ts.map +1 -1
  23. package/dist/execute.js +16 -4
  24. package/dist/execute.js.map +1 -1
  25. package/dist/index.d.ts +46 -5
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +118 -15
  28. package/dist/index.js.map +1 -1
  29. package/dist/meta-tools.d.ts +56 -5
  30. package/dist/meta-tools.d.ts.map +1 -1
  31. package/dist/meta-tools.js +249 -92
  32. package/dist/meta-tools.js.map +1 -1
  33. package/dist/registry.d.ts +62 -0
  34. package/dist/registry.d.ts.map +1 -1
  35. package/dist/registry.js +85 -1
  36. package/dist/registry.js.map +1 -1
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +305 -40
  39. package/dist/server.js.map +1 -1
  40. package/dist/skills.d.ts +1 -1
  41. package/dist/skills.d.ts.map +1 -1
  42. package/dist/skills.js +3 -3
  43. package/dist/skills.js.map +1 -1
  44. package/dist/timeout.d.ts +16 -0
  45. package/dist/timeout.d.ts.map +1 -0
  46. package/dist/timeout.js +38 -0
  47. package/dist/timeout.js.map +1 -0
  48. package/dist/toolkits.d.ts +95 -1
  49. package/dist/toolkits.d.ts.map +1 -1
  50. package/dist/toolkits.js +190 -5
  51. package/dist/toolkits.js.map +1 -1
  52. package/dist/types.d.ts +81 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/ui.d.ts +52 -0
  55. package/dist/ui.d.ts.map +1 -1
  56. package/dist/ui.js +144 -13
  57. package/dist/ui.js.map +1 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/package.json +1 -1
  61. package/src/auth/bearer.ts +35 -1
  62. package/src/auth/clerk.ts +204 -7
  63. package/src/connectors/remote-mcp.ts +9 -0
  64. package/src/credential-health.ts +753 -0
  65. package/src/credentials.ts +71 -1
  66. package/src/execute.ts +28 -4
  67. package/src/index.ts +204 -22
  68. package/src/meta-tools.ts +286 -109
  69. package/src/registry.ts +125 -1
  70. package/src/server.ts +366 -38
  71. package/src/skills.ts +3 -3
  72. package/src/timeout.ts +49 -0
  73. package/src/toolkits.ts +241 -6
  74. package/src/types.ts +87 -1
  75. package/src/ui.ts +156 -14
  76. 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.health.get(id)?.lastSuccessAt !== undefined;
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,