@0xmaxma/claude-gateway 1.8.7 → 1.8.8

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 (72) hide show
  1. package/README.md +96 -0
  2. package/dist/agent/runner.d.ts +47 -1
  3. package/dist/agent/runner.d.ts.map +1 -1
  4. package/dist/agent/runner.js +116 -17
  5. package/dist/agent/runner.js.map +1 -1
  6. package/dist/api/connectors-router.d.ts +50 -0
  7. package/dist/api/connectors-router.d.ts.map +1 -0
  8. package/dist/api/connectors-router.js +650 -0
  9. package/dist/api/connectors-router.js.map +1 -0
  10. package/dist/api/gateway-router.d.ts +4 -0
  11. package/dist/api/gateway-router.d.ts.map +1 -1
  12. package/dist/api/gateway-router.js +26 -0
  13. package/dist/api/gateway-router.js.map +1 -1
  14. package/dist/api/oauth-connectors-router.d.ts +43 -0
  15. package/dist/api/oauth-connectors-router.d.ts.map +1 -0
  16. package/dist/api/oauth-connectors-router.js +384 -0
  17. package/dist/api/oauth-connectors-router.js.map +1 -0
  18. package/dist/api/router.d.ts.map +1 -1
  19. package/dist/api/router.js +101 -13
  20. package/dist/api/router.js.map +1 -1
  21. package/dist/apps/agent-manager.d.ts +6 -2
  22. package/dist/apps/agent-manager.d.ts.map +1 -1
  23. package/dist/apps/agent-manager.js +10 -18
  24. package/dist/apps/agent-manager.js.map +1 -1
  25. package/dist/config/config-write-lock.d.ts +45 -0
  26. package/dist/config/config-write-lock.d.ts.map +1 -0
  27. package/dist/config/config-write-lock.js +164 -0
  28. package/dist/config/config-write-lock.js.map +1 -0
  29. package/dist/config/watcher.d.ts.map +1 -1
  30. package/dist/config/watcher.js +29 -0
  31. package/dist/config/watcher.js.map +1 -1
  32. package/dist/connectors/custom-connectors-store.d.ts +54 -0
  33. package/dist/connectors/custom-connectors-store.d.ts.map +1 -0
  34. package/dist/connectors/custom-connectors-store.js +204 -0
  35. package/dist/connectors/custom-connectors-store.js.map +1 -0
  36. package/dist/connectors/custom.d.ts +69 -0
  37. package/dist/connectors/custom.d.ts.map +1 -0
  38. package/dist/connectors/custom.js +158 -0
  39. package/dist/connectors/custom.js.map +1 -0
  40. package/dist/connectors/mcp-oauth.d.ts +152 -0
  41. package/dist/connectors/mcp-oauth.d.ts.map +1 -0
  42. package/dist/connectors/mcp-oauth.js +522 -0
  43. package/dist/connectors/mcp-oauth.js.map +1 -0
  44. package/dist/connectors/oauth-refresh-sweep.d.ts +71 -0
  45. package/dist/connectors/oauth-refresh-sweep.d.ts.map +1 -0
  46. package/dist/connectors/oauth-refresh-sweep.js +337 -0
  47. package/dist/connectors/oauth-refresh-sweep.js.map +1 -0
  48. package/dist/connectors/pending-oauth-store.d.ts +39 -0
  49. package/dist/connectors/pending-oauth-store.d.ts.map +1 -0
  50. package/dist/connectors/pending-oauth-store.js +50 -0
  51. package/dist/connectors/pending-oauth-store.js.map +1 -0
  52. package/dist/connectors/resolve.d.ts +52 -0
  53. package/dist/connectors/resolve.d.ts.map +1 -0
  54. package/dist/connectors/resolve.js +176 -0
  55. package/dist/connectors/resolve.js.map +1 -0
  56. package/dist/connectors/token-env.d.ts +93 -0
  57. package/dist/connectors/token-env.d.ts.map +1 -0
  58. package/dist/connectors/token-env.js +323 -0
  59. package/dist/connectors/token-env.js.map +1 -0
  60. package/dist/connectors/types.d.ts +126 -0
  61. package/dist/connectors/types.d.ts.map +1 -0
  62. package/dist/connectors/types.js +13 -0
  63. package/dist/connectors/types.js.map +1 -0
  64. package/dist/index.js +32 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/session/process.d.ts +23 -0
  67. package/dist/session/process.d.ts.map +1 -1
  68. package/dist/session/process.js +78 -2
  69. package/dist/session/process.js.map +1 -1
  70. package/dist/types.d.ts +46 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ /**
3
+ * Periodic refresh for connectors whose credential this gateway owns
4
+ * (CustomConnectorEntry.credentialOwner === 'gateway') — the gateway-side
5
+ * counterpart to whatever refresh loop an external control plane runs for the
6
+ * 'external' entries it pushes in. Runs on the same 60s
7
+ * interval as cliPairingStore.prune() (see gateway-router.ts) via
8
+ * refreshExpiringOAuthConnectors().
9
+ *
10
+ * Storage note: the refresh_token, its client_id, the access_token expiry and this
11
+ * sweep's failure bookkeeping all live in the same mcp-token.env as connector secrets,
12
+ * but under `internalSecretKey()`'s `CUSTOMINT__` prefix, which `customSecretKey()`
13
+ * cannot produce. `PLACEHOLDER_RE` accepts a leading underscore, so it is the prefix —
14
+ * not the `__` naming convention — that keeps a pasted `{__refresh_token}` from
15
+ * resolving to this sweep's slot. See custom.ts's `internalSecretKey`.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.transientBackoffMs = transientBackoffMs;
19
+ exports.refreshTokenSecretKey = refreshTokenSecretKey;
20
+ exports.clientIdSecretKey = clientIdSecretKey;
21
+ exports.expiresAtSecretKey = expiresAtSecretKey;
22
+ exports.refreshFailCountSecretKey = refreshFailCountSecretKey;
23
+ exports.refreshBackoffUntilSecretKey = refreshBackoffUntilSecretKey;
24
+ exports.refreshTransientCountSecretKey = refreshTransientCountSecretKey;
25
+ exports.tokenGenerationSecretKey = tokenGenerationSecretKey;
26
+ exports.dcrClientIdSecretKey = dcrClientIdSecretKey;
27
+ exports.clientRedirectUriSecretKey = clientRedirectUriSecretKey;
28
+ exports.internalSecretKeysOf = internalSecretKeysOf;
29
+ exports.refreshExpiringOAuthConnectors = refreshExpiringOAuthConnectors;
30
+ const custom_1 = require("./custom");
31
+ const token_env_1 = require("./token-env");
32
+ const mcp_oauth_1 = require("./mcp-oauth");
33
+ const REFRESH_SKEW_MS = 5 * 60 * 1000;
34
+ // Base wait after a failed refresh. Without it, a permanently-broken refresh_token
35
+ // (a blip self-heals next tick; a revoked one never will) is hammered every 60s.
36
+ const REFRESH_BACKOFF_MS = 5 * 60 * 1000;
37
+ // Ceiling for the transient-failure backoff. A connector whose MCP URL is gone for
38
+ // good never produces an RFC 6749 error, so it can never reach the permanent
39
+ // give-up — it just fails transiently forever. A flat REFRESH_BACKOFF_MS would
40
+ // retry (and log) every five minutes for the life of the process; backing off
41
+ // exponentially to this cap makes that four attempts a day, while still recovering
42
+ // on its own the moment the provider comes back.
43
+ const MAX_REFRESH_BACKOFF_MS = 6 * 60 * 60 * 1000;
44
+ // Consecutive failures before giving up entirely: clear the tokens so status flips
45
+ // to "not connected" instead of an indefinitely-retrying green checkmark.
46
+ //
47
+ // Only failures the authorization server itself declared count (see
48
+ // OAuthTokenError.isPermanent). Giving up deletes the user's credentials, and an
49
+ // unreachable provider says nothing about whether the grant is still good —
50
+ // counting those meant a routine outage landing inside a token's refresh window
51
+ // silently destroyed a working sign-in. Transient failures back off instead, and
52
+ // are counted separately so status can show "still retrying".
53
+ const MAX_CONSECUTIVE_FAILURES = 3;
54
+ /** Backoff for the nth consecutive transient failure, capped. */
55
+ function transientBackoffMs(consecutiveFailures) {
56
+ const n = Math.max(1, consecutiveFailures);
57
+ // 2 ** n overflows to Infinity long before this matters, and Infinity * x is still
58
+ // Infinity — clamp the exponent, not just the product.
59
+ const exponent = Math.min(n - 1, 32);
60
+ return Math.min(REFRESH_BACKOFF_MS * 2 ** exponent, MAX_REFRESH_BACKOFF_MS);
61
+ }
62
+ /** A failure counter read fresh out of mcp-token.env, floored by `parseCounter`. Used
63
+ * only in the failure branches, which read-modify-write the counter after the network
64
+ * round trip — the due-check up front reads from one snapshot instead. */
65
+ function readCounter(key) {
66
+ return (0, token_env_1.parseCounter)((0, token_env_1.getSecret)(key));
67
+ }
68
+ function refreshTokenSecretKey(id) {
69
+ return (0, custom_1.internalSecretKey)(id, '__refresh_token');
70
+ }
71
+ function clientIdSecretKey(id) {
72
+ return (0, custom_1.internalSecretKey)(id, '__client_id');
73
+ }
74
+ function expiresAtSecretKey(id) {
75
+ return (0, custom_1.internalSecretKey)(id, '__token_expires_at');
76
+ }
77
+ function refreshFailCountSecretKey(id) {
78
+ return (0, custom_1.internalSecretKey)(id, '__refresh_fail_count');
79
+ }
80
+ function refreshBackoffUntilSecretKey(id) {
81
+ return (0, custom_1.internalSecretKey)(id, '__refresh_backoff_until');
82
+ }
83
+ /** Consecutive *transient* failures — kept apart from `__refresh_fail_count` so an
84
+ * outage can never accumulate toward deleting a live grant. Drives the exponential
85
+ * backoff and status's `refresh` block; cleared on the first success. */
86
+ function refreshTransientCountSecretKey(id) {
87
+ return (0, custom_1.internalSecretKey)(id, '__refresh_transient_count');
88
+ }
89
+ /** Bumped on every access_token write — by the OAuth callback on a fresh sign-in
90
+ * AND by this sweep on a successful refresh. Read back before this sweep commits,
91
+ * so a manual reconnect landing mid-refresh wins instead of being clobbered by a
92
+ * slower, now-stale refresh. */
93
+ function tokenGenerationSecretKey(id) {
94
+ return (0, custom_1.internalSecretKey)(id, '__token_generation');
95
+ }
96
+ /**
97
+ * DCR registration cache — deliberately NOT `clientIdSecretKey`.
98
+ *
99
+ * `oauth/start` registers a client before the user has done anything, and the flow can
100
+ * be abandoned. Writing that client_id into the key this sweep reads left the connector
101
+ * pairing refresh_token R1 (issued to the OLD client C1) with client_id C2; the next
102
+ * refresh POSTs that mismatched pair, the AS answers `invalid_client` — permanent, and
103
+ * correctly so for that pair — and three ticks later a working sign-in is deleted
104
+ * because an admin opened a Connect dialog and closed it again.
105
+ *
106
+ * `clientIdSecretKey` is written only by the callback, i.e. only once a token has
107
+ * actually been issued to that client. This key is just a cache so retried Connect
108
+ * clicks don't orphan a fresh registration at the provider every attempt. It lives
109
+ * beside the keys the sweep owns because the give-up path and DELETE /v1/connectors/:id
110
+ * both have to invalidate it (see `clearsDcrRegistration`).
111
+ */
112
+ function dcrClientIdSecretKey(id) {
113
+ return (0, custom_1.internalSecretKey)(id, '__dcr_client_id');
114
+ }
115
+ /** The redirect_uri `dcrClientIdSecretKey`'s cached client was registered with. Reuse
116
+ * that client_id only while this matches the current callback URL — otherwise the
117
+ * provider would reject it, so re-registering is correct, not a skipped optimization. */
118
+ function clientRedirectUriSecretKey(id) {
119
+ return (0, custom_1.internalSecretKey)(id, '__client_redirect_uri');
120
+ }
121
+ /** Every key this sweep and the OAuth routers store for one connector, for the paths
122
+ * that clear a connector out entirely. Enumerated once so a new key cannot be added to
123
+ * the writers and forgotten by the deleters — the bug that left dead DCR registrations
124
+ * behind forever. `includeDcrRegistration: false` keeps the cached DCR client (see
125
+ * `clearsDcrRegistration`). */
126
+ function internalSecretKeysOf(id, opts = {}) {
127
+ const { includeDcrRegistration = true } = opts;
128
+ return [
129
+ refreshTokenSecretKey(id),
130
+ clientIdSecretKey(id),
131
+ expiresAtSecretKey(id),
132
+ refreshFailCountSecretKey(id),
133
+ refreshBackoffUntilSecretKey(id),
134
+ refreshTransientCountSecretKey(id),
135
+ // Cleared with the rest: a generation left behind for a token-less id means the
136
+ // next sign-in starts from a value a still-running refresh may already have read,
137
+ // and the optimistic-concurrency check silently stops guarding.
138
+ tokenGenerationSecretKey(id),
139
+ ...(includeDcrRegistration ? [dcrClientIdSecretKey(id), clientRedirectUriSecretKey(id)] : []),
140
+ ];
141
+ }
142
+ /**
143
+ * Whether a give-up should also drop the cached DCR registration.
144
+ *
145
+ * Not on every give-up. `invalid_grant` means the refresh_token is dead while the
146
+ * registered client is still good — dropping it orphans a registration for nothing.
147
+ * `invalid_client`/`unauthorized_client` is the opposite: the provider does not
148
+ * recognise the client at all, and a cache holding a deleted client_id is
149
+ * unrecoverable through the UI — Connect finds the redirect_uri still matching,
150
+ * skips re-registration and fails again forever.
151
+ */
152
+ function clearsDcrRegistration(err) {
153
+ return (err instanceof mcp_oauth_1.OAuthTokenError &&
154
+ (err.errorCode === 'invalid_client' || err.errorCode === 'unauthorized_client'));
155
+ }
156
+ /**
157
+ * Guards against overlapping sweeps. The caller (gateway-router.ts) fires this on a
158
+ * fixed 60s interval without awaiting it, so a slow token endpoint leaves a sweep in
159
+ * flight when the next tick starts. Both would read the same `__refresh_token` and POST
160
+ * it; against a provider that rotates refresh tokens (the OAuth 2.1 default for a
161
+ * public client) the second use is replay, and the whole grant is revoked.
162
+ *
163
+ * A plain boolean suffices: one process-wide job, and skipping a tick is free — the
164
+ * next is 60s away and the work is idempotent.
165
+ */
166
+ let sweepInFlight = false;
167
+ /**
168
+ * Scan every gateway-owned connector; refresh any whose access_token is within
169
+ * REFRESH_SKEW_MS of its recorded expiry. Best-effort per connector — a failure is
170
+ * logged and skipped, never thrown, so one broken connector can't stop the rest.
171
+ * `agents` lets a successful refresh restart sessions already using the connector (a
172
+ * live MCP subprocess has the OLD token baked in and can't be hot-patched).
173
+ */
174
+ async function refreshExpiringOAuthConnectors(store, agents) {
175
+ if (sweepInFlight)
176
+ return; // previous tick still running — see sweepInFlight
177
+ sweepInFlight = true;
178
+ try {
179
+ await sweepOnce(store, agents);
180
+ }
181
+ finally {
182
+ sweepInFlight = false; // released even if a connector loop threw
183
+ }
184
+ }
185
+ async function sweepOnce(store, agents) {
186
+ const connectors = await store.read();
187
+ for (const [id, entry] of Object.entries(connectors)) {
188
+ // Only 'gateway': this sweep refreshes with a refresh_token THIS gateway
189
+ // holds, and it holds one for no other owner. An 'external' entry is the
190
+ // control plane's to renew.
191
+ if (entry.credentialOwner !== 'gateway')
192
+ continue;
193
+ try {
194
+ await refreshOne(id, entry, agents);
195
+ }
196
+ catch (err) {
197
+ // The per-connector guard, around the WHOLE step rather than the network call
198
+ // alone: `refreshOne`'s own try/catch covers the refresh, but the getSecret
199
+ // reads deciding whether it is due and the updateSecrets/deleteSecrets in its
200
+ // failure branches all touch mcp-token.env and can throw an errno. Uncaught,
201
+ // one blip on the first connector abandoned the sweep for every connector
202
+ // after it, with gateway-router.ts's `.catch()` swallowing the reason.
203
+ console.error(`oauth-refresh-sweep: connector=${id} sweep step failed: ${err.message}`);
204
+ }
205
+ }
206
+ }
207
+ async function refreshOne(id, entry, agents) {
208
+ // One read+parse of mcp-token.env for the whole due-check. Every value below is
209
+ // answering the same question at the same instant, and there is no await between
210
+ // them, so five separate getSecret() calls were five reads of one file per connector
211
+ // per tick with no added freshness — the read pattern listConnectorStatus already
212
+ // uses. (The generation re-read AFTER the network round trip is a different thing
213
+ // entirely and stays a fresh read — see below.)
214
+ const secrets = (0, token_env_1.readTokenEnv)();
215
+ const backoffUntilRaw = (0, token_env_1.getSecretFrom)(secrets, refreshBackoffUntilSecretKey(id));
216
+ if (backoffUntilRaw && Number(backoffUntilRaw) > Date.now())
217
+ return; // recent failure — still backing off
218
+ // An absent, non-numeric or infinite expiry falls through to a refresh rather than
219
+ // "not due yet". Refreshing on an unknown expiry costs one round trip and
220
+ // self-corrects (the success path always writes a fresh expiry); trusting a corrupt
221
+ // one — Infinity reads as "expires never" — silently never refreshes at all.
222
+ const expiresAt = Number((0, token_env_1.getSecretFrom)(secrets, expiresAtSecretKey(id)) ?? '');
223
+ if (Number.isFinite(expiresAt) && expiresAt - Date.now() >= REFRESH_SKEW_MS)
224
+ return; // not due yet
225
+ const refreshToken = (0, token_env_1.getSecretFrom)(secrets, refreshTokenSecretKey(id));
226
+ const clientId = (0, token_env_1.getSecretFrom)(secrets, clientIdSecretKey(id));
227
+ const mcpUrl = typeof entry.config.url === 'string' ? entry.config.url : null;
228
+ // Nothing to refresh with (disconnected, or a provider that issued no
229
+ // refresh_token). Skipped silently, and no failure recorded — nothing failed.
230
+ // `refreshStatusOf` surfaces this instead, deriving `unrefreshable` from an expired
231
+ // access_token with no refresh_token beside it.
232
+ if (!refreshToken || !clientId || !mcpUrl)
233
+ return;
234
+ // Captured before the two network round-trips so a concurrent manual reconnect (a
235
+ // fresh /oauth/mcp/callback exchange racing this refresh) is detected before this
236
+ // sweep commits a write derived from a now-stale refresh_token. Taken from the same
237
+ // snapshot as the values above — it is the "before" side of the comparison, read
238
+ // synchronously alongside them; it is the "after" side that must hit the file again.
239
+ const generationBefore = (0, token_env_1.getSecretFrom)(secrets, tokenGenerationSecretKey(id));
240
+ try {
241
+ const metadata = await (0, mcp_oauth_1.discoverOAuthMetadataCached)(mcpUrl);
242
+ const token = await (0, mcp_oauth_1.refreshAccessToken)({ metadata, clientId, refreshToken });
243
+ if ((0, token_env_1.getSecret)(tokenGenerationSecretKey(id)) !== generationBefore) {
244
+ // A manual reconnect wrote a newer token mid-flight — strictly fresher than
245
+ // this one, so abandon ours. Not a failure: leave the backoff/counters alone.
246
+ return;
247
+ }
248
+ // One rewrite for the whole result: a crash between separate writes could file a
249
+ // new access_token under the OLD expiry (read as "already due" every tick after),
250
+ // or leave a fresh token carrying a stale backoff.
251
+ (0, token_env_1.updateSecrets)({
252
+ [(0, custom_1.customSecretKey)(id, 'access_token')]: token.access_token,
253
+ ...(token.refresh_token ? { [refreshTokenSecretKey(id)]: token.refresh_token } : {}),
254
+ [expiresAtSecretKey(id)]: String(Date.now() + (token.expires_in ?? 3600) * 1000),
255
+ [tokenGenerationSecretKey(id)]: String(Date.now()),
256
+ }, [
257
+ refreshFailCountSecretKey(id),
258
+ refreshBackoffUntilSecretKey(id),
259
+ refreshTransientCountSecretKey(id),
260
+ ]);
261
+ if (agents) {
262
+ await Promise.all([...agents.values()].map((runner) =>
263
+ // Guarded per runner, as the two routers guard their own restarts.
264
+ // Unguarded, a rejecting `proc.stop()` fell into the catch below — which
265
+ // treats any non-OAuthTokenError as a transient *refresh* failure — so an
266
+ // already-succeeded refresh got logged as failed, backed off, and counted
267
+ // toward the transient streak.
268
+ runner.restartSessionsUsingConnector(id).catch((e) => {
269
+ console.error(`oauth-refresh-sweep: restart for connector=${id} failed: ${e.message}`);
270
+ })));
271
+ }
272
+ }
273
+ catch (err) {
274
+ // Only an explicit refusal from the authorization server counts toward giving up.
275
+ // DNS, timeouts, a 502, a malformed discovery response — all mean the provider is
276
+ // unreachable, which says nothing about whether the grant is still valid.
277
+ const permanent = err instanceof mcp_oauth_1.OAuthTokenError && err.isPermanent;
278
+ const failCount = permanent ? readCounter(refreshFailCountSecretKey(id)) + 1 : 0;
279
+ const transientCount = permanent
280
+ ? 0 // the AS answered, so it is reachable — the transient streak is over
281
+ : readCounter(refreshTransientCountSecretKey(id)) + 1;
282
+ const backoffMs = permanent ? REFRESH_BACKOFF_MS : transientBackoffMs(transientCount);
283
+ // Decided before the log line: this branch deletes the user's credentials, and a
284
+ // "retrying in 5m" printed immediately above that deletion is the one message an
285
+ // admin reads while working out why a connector dropped.
286
+ const givingUp = permanent && failCount >= MAX_CONSECUTIVE_FAILURES;
287
+ console.error(`oauth-refresh-sweep: connector=${id} refresh failed` +
288
+ ` (${permanent ? `permanent, attempt ${failCount}` : `transient, attempt ${transientCount}`};` +
289
+ ` ${givingUp
290
+ ? 'giving up — clearing stored credentials'
291
+ : `retrying in ${Math.round(backoffMs / 60000)}m`}): ${err.message}`);
292
+ if (givingUp) {
293
+ // The AS refused this grant this many times in a row — it is not coming back on
294
+ // its own. Clear everything so status reports "not connected" and this loop
295
+ // stops calling a dead token endpoint on every future tick.
296
+ (0, token_env_1.deleteSecrets)([
297
+ (0, custom_1.customSecretKey)(id, 'access_token'),
298
+ ...internalSecretKeysOf(id, { includeDcrRegistration: clearsDcrRegistration(err) }),
299
+ ]);
300
+ // …and restart, for the same reason the success path above does and the
301
+ // unified DELETE route does. A live session's MCP subprocess was spawned with
302
+ // the token just deleted baked into its env and cannot be hot-patched, so
303
+ // without this it keeps advertising the connector's tools and failing every
304
+ // call against them with a 401 the model has no way to interpret. Respawned,
305
+ // the connector resolves with no secret, session/process.ts leaves it out of
306
+ // the MCP config, and the tools are simply absent — which is the truth.
307
+ //
308
+ // Guarded per runner for the reason spelled out on the success path: an
309
+ // unguarded rejection lands in this same catch block and would be re-counted
310
+ // as a refresh failure.
311
+ if (agents) {
312
+ await Promise.all([...agents.values()].map((runner) => runner.restartSessionsUsingConnector(id).catch((e) => {
313
+ console.error(`oauth-refresh-sweep: restart after give-up for connector=${id} failed: ${e.message}`);
314
+ })));
315
+ }
316
+ }
317
+ else if (permanent) {
318
+ // One rewrite, same reason as the success path: splitting the new permanent
319
+ // count from the cleared transient one left a window where both were set,
320
+ // which status renders as a transient backoff that no longer exists.
321
+ (0, token_env_1.updateSecrets)({
322
+ [refreshFailCountSecretKey(id)]: String(failCount),
323
+ [refreshBackoffUntilSecretKey(id)]: String(Date.now() + backoffMs),
324
+ }, [refreshTransientCountSecretKey(id)]);
325
+ }
326
+ else {
327
+ // Transient: back off exponentially (a provider gone for good decays to a few
328
+ // attempts a day) but leave the *permanent* counter untouched — an outage must
329
+ // never accumulate toward deleting a working sign-in.
330
+ (0, token_env_1.setSecrets)({
331
+ [refreshTransientCountSecretKey(id)]: String(transientCount),
332
+ [refreshBackoffUntilSecretKey(id)]: String(Date.now() + backoffMs),
333
+ });
334
+ }
335
+ }
336
+ }
337
+ //# sourceMappingURL=oauth-refresh-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-refresh-sweep.js","sourceRoot":"","sources":["../../src/connectors/oauth-refresh-sweep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { OAuthMetadata } from './mcp-oauth';
2
+ /**
3
+ * In-memory bridge between a `POST .../oauth/start` call and the later
4
+ * `GET /oauth/mcp/callback` the provider redirects the user's browser to.
5
+ * Modeled directly on `cli-viewer/pairing-store.ts`'s `CliPairingStore`: a
6
+ * `Map` keyed by a random, single-use, TTL'd id (the OAuth `state` value
7
+ * here), pruned on the same interval as that store.
8
+ *
9
+ * Deliberately NOT persisted to disk — a flow is only ever a few minutes
10
+ * from start to callback; a gateway restart mid-flow just means the user
11
+ * clicks "Connect" again.
12
+ */
13
+ export interface PendingOAuth {
14
+ connectorId: string;
15
+ metadata: OAuthMetadata;
16
+ clientId: string;
17
+ redirectUri: string;
18
+ codeVerifier: string;
19
+ createdAt: number;
20
+ expiresAt: number;
21
+ }
22
+ export declare class PendingOAuthStore {
23
+ private readonly flows;
24
+ /** Start a flow, returning the `state` value to embed in the authorize URL. */
25
+ create(entry: Omit<PendingOAuth, 'createdAt' | 'expiresAt'>): string;
26
+ /**
27
+ * Consume (single-use) the flow for `state`. Returns null if unknown,
28
+ * expired, or already consumed — the callback handler treats all three the
29
+ * same way (a generic "this sign-in link expired, try again" response).
30
+ */
31
+ consume(state: string): PendingOAuth | null;
32
+ /** Drop expired flows. Call on the same interval as `cliPairingStore.prune()`. */
33
+ prune(): void;
34
+ /** Test/introspection helper. */
35
+ size(): number;
36
+ }
37
+ /** Process-wide singleton — mirrors `cliPairingStore`'s shape. */
38
+ export declare const pendingOAuthStore: PendingOAuthStore;
39
+ //# sourceMappingURL=pending-oauth-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-oauth-store.d.ts","sourceRoot":"","sources":["../../src/connectors/pending-oauth-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IAEzD,+EAA+E;IAC/E,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,WAAW,CAAC,GAAG,MAAM;IAOpE;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAQ3C,kFAAkF;IAClF,KAAK,IAAI,IAAI;IAOb,iCAAiC;IACjC,IAAI,IAAI,MAAM;CAGf;AAED,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.pendingOAuthStore = exports.PendingOAuthStore = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const FLOW_TTL_MS = 5 * 60 * 1000;
9
+ class PendingOAuthStore {
10
+ constructor() {
11
+ this.flows = new Map();
12
+ }
13
+ /** Start a flow, returning the `state` value to embed in the authorize URL. */
14
+ create(entry) {
15
+ const state = crypto_1.default.randomBytes(16).toString('base64url');
16
+ const now = Date.now();
17
+ this.flows.set(state, { ...entry, createdAt: now, expiresAt: now + FLOW_TTL_MS });
18
+ return state;
19
+ }
20
+ /**
21
+ * Consume (single-use) the flow for `state`. Returns null if unknown,
22
+ * expired, or already consumed — the callback handler treats all three the
23
+ * same way (a generic "this sign-in link expired, try again" response).
24
+ */
25
+ consume(state) {
26
+ const flow = this.flows.get(state);
27
+ if (!flow)
28
+ return null;
29
+ this.flows.delete(state);
30
+ if (flow.expiresAt <= Date.now())
31
+ return null;
32
+ return flow;
33
+ }
34
+ /** Drop expired flows. Call on the same interval as `cliPairingStore.prune()`. */
35
+ prune() {
36
+ const now = Date.now();
37
+ for (const [state, flow] of this.flows) {
38
+ if (flow.expiresAt <= now)
39
+ this.flows.delete(state);
40
+ }
41
+ }
42
+ /** Test/introspection helper. */
43
+ size() {
44
+ return this.flows.size;
45
+ }
46
+ }
47
+ exports.PendingOAuthStore = PendingOAuthStore;
48
+ /** Process-wide singleton — mirrors `cliPairingStore`'s shape. */
49
+ exports.pendingOAuthStore = new PendingOAuthStore();
50
+ //# sourceMappingURL=pending-oauth-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-oauth-store.js","sourceRoot":"","sources":["../../src/connectors/pending-oauth-store.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAwB5B,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAElC,MAAa,iBAAiB;IAA9B;QACmB,UAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAmC3D,CAAC;IAjCC,+EAA+E;IAC/E,MAAM,CAAC,KAAoD;QACzD,MAAM,KAAK,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,WAAW,EAAE,CAAC,CAAC;QAClF,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,KAAa;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAClF,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,iCAAiC;IACjC,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AApCD,8CAoCC;AAED,kEAAkE;AACrD,QAAA,iBAAiB,GAAG,IAAI,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Connector resolution — the single source of truth shared by the session spawner
3
+ * and the HTTP API.
4
+ *
5
+ * resolveEnabledConnectors(): for the injection point in SessionProcess.writeMcpConfig.
6
+ * listConnectorStatus(): for GET /v1/connectors.
7
+ */
8
+ import type { AgentConfig, CustomConnectorEntry } from '../types';
9
+ import { type ConnectorStatus } from './types';
10
+ /**
11
+ * Build the mcpServers entries for every connector that is (a) in customConnectors,
12
+ * (b) enabled for this agent, and (c) connected (every `{placeholder}` secret it
13
+ * declares is present). Returns a map keyed by connector id, ready to merge into
14
+ * mcp-config.json.
15
+ *
16
+ * Enablement defaults to opt-OUT: a globally-connected connector is available to
17
+ * every agent the moment it's connected — an agent only misses it if explicitly
18
+ * disabled (`{enabled: false}`). Connecting a connector at all is the security gate;
19
+ * per-agent is a refinement, not a second required step.
20
+ *
21
+ * `defaultEnabled: false` (gateway.connectorsDefaultEnabled in config.json) flips
22
+ * that to opt-IN. The default suits the common single-operator install, but a
23
+ * gateway hosting agents for several different people is a different situation:
24
+ * there, connecting one connector hands its credential to every agent on the box,
25
+ * including agents whose chat users are not the person who connected it. Opting in
26
+ * per agent is the safer posture for that deployment, so it is available — as a
27
+ * deliberate choice, not a silent change of meaning for existing installs.
28
+ */
29
+ export declare function resolveEnabledConnectors(agentConfig: Pick<AgentConfig, 'connectors'>, customConnectors?: Record<string, CustomConnectorEntry>, defaultEnabled?: boolean): Record<string, unknown>;
30
+ /**
31
+ * The `refresh` block of ConnectorStatus, or undefined when the connector's
32
+ * background refresh is healthy (or was never failing). Reads the sweep's own
33
+ * bookkeeping out of an already-loaded token env rather than re-reading the file
34
+ * per connector.
35
+ *
36
+ * Both failure streaks are reported, not just the transient one. A connector two
37
+ * refusals into the three-strike permanent count is one tick away from having its
38
+ * credentials deleted — the most actionable state this block can describe, and the
39
+ * one a caller most needs to see before it happens.
40
+ *
41
+ * `unrefreshable` covers the cases with no streak at all. A provider that issues no
42
+ * refresh_token (an AS advertising scopes that don't include `offline_access` — see
43
+ * the scope fallback in mcp-oauth.ts's `resolveScope`) leaves the sweep nothing to
44
+ * refresh with, so it skips the connector on every tick and never records a failure.
45
+ * Without this flag that connector keeps a green checkmark forever over an
46
+ * access_token that expired an hour in, which is the exact state the sweep's module
47
+ * comment says it exists to prevent.
48
+ */
49
+ export declare function refreshStatusOf(id: string, tokenEnv: Record<string, string>): ConnectorStatus['refresh'];
50
+ /** Connector list + connected state for the API. `connected` reflects secret presence. */
51
+ export declare function listConnectorStatus(customConnectors?: Record<string, CustomConnectorEntry>): ConnectorStatus[];
52
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/connectors/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAC;AAW/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,EAC5C,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAM,EAC3D,cAAc,UAAO,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0CzB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,eAAe,CAAC,SAAS,CAAC,CAkC5B;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAM,GAC1D,eAAe,EAAE,CA6CnB"}