@everworker/oneringai 0.4.2 → 0.4.3

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/dist/index.cjs CHANGED
@@ -17,7 +17,7 @@ var z = require('zod/v4');
17
17
  var spawn = require('cross-spawn');
18
18
  var process2 = require('process');
19
19
  var stream = require('stream');
20
- var fs18 = require('fs/promises');
20
+ var fs17 = require('fs/promises');
21
21
  var simpleIcons = require('simple-icons');
22
22
  var child_process = require('child_process');
23
23
  var util = require('util');
@@ -55,7 +55,7 @@ var z4mini__namespace = /*#__PURE__*/_interopNamespace(z4mini);
55
55
  var z__namespace = /*#__PURE__*/_interopNamespace(z);
56
56
  var spawn__default = /*#__PURE__*/_interopDefault(spawn);
57
57
  var process2__default = /*#__PURE__*/_interopDefault(process2);
58
- var fs18__namespace = /*#__PURE__*/_interopNamespace(fs18);
58
+ var fs17__namespace = /*#__PURE__*/_interopNamespace(fs17);
59
59
  var simpleIcons__namespace = /*#__PURE__*/_interopNamespace(simpleIcons);
60
60
  var vm__namespace = /*#__PURE__*/_interopNamespace(vm);
61
61
 
@@ -195,6 +195,12 @@ var init_MemoryStorage = __esm({
195
195
  size() {
196
196
  return this.tokens.size;
197
197
  }
198
+ /**
199
+ * List all storage keys (for account enumeration)
200
+ */
201
+ async listKeys() {
202
+ return Array.from(this.tokens.keys());
203
+ }
198
204
  };
199
205
  }
200
206
  });
@@ -212,14 +218,23 @@ var init_TokenStore = __esm({
212
218
  this.storage = storage || new exports.MemoryStorage();
213
219
  }
214
220
  /**
215
- * Get user-scoped storage key
216
- * For multi-user support, keys are scoped per user: "provider:userId"
217
- * For single-user (backward compatible), userId is omitted or "default"
221
+ * Get user-scoped (and optionally account-scoped) storage key
222
+ *
223
+ * Key format (backward compatible):
224
+ * - No userId, no accountId → baseKey
225
+ * - userId only → baseKey:userId
226
+ * - userId + accountId → baseKey:userId:accountId
227
+ * - accountId only → baseKey:default:accountId
218
228
  *
219
229
  * @param userId - User identifier (optional, defaults to single-user mode)
220
- * @returns Storage key scoped to user
230
+ * @param accountId - Account alias for multi-account support (optional)
231
+ * @returns Storage key scoped to user and account
221
232
  */
222
- getScopedKey(userId) {
233
+ getScopedKey(userId, accountId) {
234
+ if (accountId) {
235
+ const userPart = userId && userId !== "default" ? userId : "default";
236
+ return `${this.baseStorageKey}:${userPart}:${accountId}`;
237
+ }
223
238
  if (!userId || userId === "default") {
224
239
  return this.baseStorageKey;
225
240
  }
@@ -229,8 +244,9 @@ var init_TokenStore = __esm({
229
244
  * Store token (encrypted by storage layer)
230
245
  * @param tokenResponse - Token response from OAuth provider
231
246
  * @param userId - Optional user identifier for multi-user support
247
+ * @param accountId - Optional account alias for multi-account support
232
248
  */
233
- async storeToken(tokenResponse, userId) {
249
+ async storeToken(tokenResponse, userId, accountId) {
234
250
  if (!tokenResponse.access_token) {
235
251
  throw new Error("OAuth response missing required access_token field");
236
252
  }
@@ -248,39 +264,46 @@ var init_TokenStore = __esm({
248
264
  scope: tokenResponse.scope,
249
265
  obtained_at: Date.now()
250
266
  };
251
- const key = this.getScopedKey(userId);
267
+ const key = this.getScopedKey(userId, accountId);
252
268
  await this.storage.storeToken(key, token);
253
269
  }
254
270
  /**
255
271
  * Get access token
256
272
  * @param userId - Optional user identifier for multi-user support
273
+ * @param accountId - Optional account alias for multi-account support
257
274
  */
258
- async getAccessToken(userId) {
259
- const key = this.getScopedKey(userId);
275
+ async getAccessToken(userId, accountId) {
276
+ const key = this.getScopedKey(userId, accountId);
260
277
  const token = await this.storage.getToken(key);
261
278
  if (!token) {
262
- throw new Error(`No token stored for ${userId ? `user: ${userId}` : "default user"}`);
279
+ const userLabel = userId ? `user: ${userId}` : "default user";
280
+ const accountLabel = accountId ? `, account: ${accountId}` : "";
281
+ throw new Error(`No token stored for ${userLabel}${accountLabel}`);
263
282
  }
264
283
  return token.access_token;
265
284
  }
266
285
  /**
267
286
  * Get refresh token
268
287
  * @param userId - Optional user identifier for multi-user support
288
+ * @param accountId - Optional account alias for multi-account support
269
289
  */
270
- async getRefreshToken(userId) {
271
- const key = this.getScopedKey(userId);
290
+ async getRefreshToken(userId, accountId) {
291
+ const key = this.getScopedKey(userId, accountId);
272
292
  const token = await this.storage.getToken(key);
273
293
  if (!token?.refresh_token) {
274
- throw new Error(`No refresh token available for ${userId ? `user: ${userId}` : "default user"}`);
294
+ const userLabel = userId ? `user: ${userId}` : "default user";
295
+ const accountLabel = accountId ? `, account: ${accountId}` : "";
296
+ throw new Error(`No refresh token available for ${userLabel}${accountLabel}`);
275
297
  }
276
298
  return token.refresh_token;
277
299
  }
278
300
  /**
279
301
  * Check if has refresh token
280
302
  * @param userId - Optional user identifier for multi-user support
303
+ * @param accountId - Optional account alias for multi-account support
281
304
  */
282
- async hasRefreshToken(userId) {
283
- const key = this.getScopedKey(userId);
305
+ async hasRefreshToken(userId, accountId) {
306
+ const key = this.getScopedKey(userId, accountId);
284
307
  const token = await this.storage.getToken(key);
285
308
  return !!token?.refresh_token;
286
309
  }
@@ -289,9 +312,10 @@ var init_TokenStore = __esm({
289
312
  *
290
313
  * @param bufferSeconds - Refresh this many seconds before expiry (default: 300 = 5 min)
291
314
  * @param userId - Optional user identifier for multi-user support
315
+ * @param accountId - Optional account alias for multi-account support
292
316
  */
293
- async isValid(bufferSeconds = 300, userId) {
294
- const key = this.getScopedKey(userId);
317
+ async isValid(bufferSeconds = 300, userId, accountId) {
318
+ const key = this.getScopedKey(userId, accountId);
295
319
  const token = await this.storage.getToken(key);
296
320
  if (!token) {
297
321
  return false;
@@ -303,19 +327,46 @@ var init_TokenStore = __esm({
303
327
  /**
304
328
  * Clear stored token
305
329
  * @param userId - Optional user identifier for multi-user support
330
+ * @param accountId - Optional account alias for multi-account support
306
331
  */
307
- async clear(userId) {
308
- const key = this.getScopedKey(userId);
332
+ async clear(userId, accountId) {
333
+ const key = this.getScopedKey(userId, accountId);
309
334
  await this.storage.deleteToken(key);
310
335
  }
311
336
  /**
312
337
  * Get full token info
313
338
  * @param userId - Optional user identifier for multi-user support
339
+ * @param accountId - Optional account alias for multi-account support
314
340
  */
315
- async getTokenInfo(userId) {
316
- const key = this.getScopedKey(userId);
341
+ async getTokenInfo(userId, accountId) {
342
+ const key = this.getScopedKey(userId, accountId);
317
343
  return this.storage.getToken(key);
318
344
  }
345
+ /**
346
+ * List account aliases for a user on this connector.
347
+ * Returns account IDs that have stored tokens.
348
+ *
349
+ * @param userId - Optional user identifier
350
+ * @returns Array of account aliases (e.g., ['work', 'personal'])
351
+ */
352
+ async listAccounts(userId) {
353
+ if (!this.storage.listKeys) {
354
+ return [];
355
+ }
356
+ const allKeys = await this.storage.listKeys();
357
+ const userPart = userId && userId !== "default" ? userId : "default";
358
+ const prefix = `${this.baseStorageKey}:${userPart}:`;
359
+ const accounts = [];
360
+ for (const key of allKeys) {
361
+ if (key.startsWith(prefix)) {
362
+ const accountId = key.slice(prefix.length);
363
+ if (accountId && !accountId.includes(":")) {
364
+ accounts.push(accountId);
365
+ }
366
+ }
367
+ }
368
+ return accounts;
369
+ }
319
370
  };
320
371
  }
321
372
  });
@@ -356,20 +407,28 @@ var init_AuthCodePKCE = __esm({
356
407
  this.tokenStore = new TokenStore(storageKey, config.storage);
357
408
  }
358
409
  tokenStore;
359
- // Store PKCE data per user with timestamps for cleanup
410
+ // Store PKCE data per user+account with timestamps for cleanup
360
411
  codeVerifiers = /* @__PURE__ */ new Map();
361
412
  states = /* @__PURE__ */ new Map();
362
- // Store refresh locks per user to prevent concurrent refresh
413
+ // Store refresh locks per user+account to prevent concurrent refresh
363
414
  refreshLocks = /* @__PURE__ */ new Map();
364
415
  // PKCE data TTL: 15 minutes (auth flows should complete within this time)
365
416
  PKCE_TTL = 15 * 60 * 1e3;
417
+ /**
418
+ * Build a map key from userId and accountId for internal PKCE/state/lock maps.
419
+ */
420
+ getMapKey(userId, accountId) {
421
+ const userPart = userId || "default";
422
+ return accountId ? `${userPart}:${accountId}` : userPart;
423
+ }
366
424
  /**
367
425
  * Generate authorization URL for user to visit
368
426
  * Opens browser or redirects user to this URL
369
427
  *
370
428
  * @param userId - User identifier for multi-user support (optional)
429
+ * @param accountId - Account alias for multi-account support (optional)
371
430
  */
372
- async getAuthorizationUrl(userId) {
431
+ async getAuthorizationUrl(userId, accountId) {
373
432
  if (!this.config.authorizationUrl) {
374
433
  throw new Error("authorizationUrl is required for authorization_code flow");
375
434
  }
@@ -377,11 +436,11 @@ var init_AuthCodePKCE = __esm({
377
436
  throw new Error("redirectUri is required for authorization_code flow");
378
437
  }
379
438
  this.cleanupExpiredPKCE();
380
- const userKey = userId || "default";
439
+ const mapKey = this.getMapKey(userId, accountId);
381
440
  const { codeVerifier, codeChallenge } = generatePKCE();
382
- this.codeVerifiers.set(userKey, { verifier: codeVerifier, timestamp: Date.now() });
441
+ this.codeVerifiers.set(mapKey, { verifier: codeVerifier, timestamp: Date.now() });
383
442
  const state = generateState();
384
- this.states.set(userKey, { state, timestamp: Date.now() });
443
+ this.states.set(mapKey, { state, timestamp: Date.now() });
385
444
  const params = new URLSearchParams({
386
445
  response_type: "code",
387
446
  client_id: this.config.clientId,
@@ -395,33 +454,48 @@ var init_AuthCodePKCE = __esm({
395
454
  params.append("code_challenge", codeChallenge);
396
455
  params.append("code_challenge_method", "S256");
397
456
  }
398
- const stateWithUser = userId ? `${state}::${userId}` : state;
399
- params.set("state", stateWithUser);
457
+ let stateWithMetadata = state;
458
+ if (userId || accountId) {
459
+ stateWithMetadata = `${state}::${userId || ""}`;
460
+ if (accountId) {
461
+ stateWithMetadata += `::${accountId}`;
462
+ }
463
+ }
464
+ params.set("state", stateWithMetadata);
400
465
  return `${this.config.authorizationUrl}?${params.toString()}`;
401
466
  }
402
467
  /**
403
468
  * Exchange authorization code for access token
404
469
  *
405
470
  * @param code - Authorization code from callback
406
- * @param state - State parameter from callback (for CSRF verification, may include userId)
471
+ * @param state - State parameter from callback (for CSRF verification, may include userId/accountId)
407
472
  * @param userId - User identifier (optional, can be extracted from state)
473
+ * @param accountId - Account alias (optional, can be extracted from state)
408
474
  */
409
- async exchangeCode(code, state, userId) {
475
+ async exchangeCode(code, state, userId, accountId) {
410
476
  let actualState = state;
411
477
  let actualUserId = userId;
478
+ let actualAccountId = accountId;
412
479
  if (state.includes("::")) {
413
480
  const parts = state.split("::");
414
481
  actualState = parts[0];
415
- actualUserId = parts[1];
482
+ if (!actualUserId && parts[1]) {
483
+ actualUserId = parts[1];
484
+ }
485
+ if (!actualAccountId && parts[2]) {
486
+ actualAccountId = parts[2];
487
+ }
416
488
  }
417
- const userKey = actualUserId || "default";
418
- const stateData = this.states.get(userKey);
489
+ const mapKey = this.getMapKey(actualUserId, actualAccountId);
490
+ const stateData = this.states.get(mapKey);
419
491
  if (!stateData) {
420
- throw new Error(`No PKCE state found for user ${actualUserId}. Authorization flow may have expired (15 min TTL).`);
492
+ const label = actualAccountId ? `user ${actualUserId}, account ${actualAccountId}` : `user ${actualUserId}`;
493
+ throw new Error(`No PKCE state found for ${label}. Authorization flow may have expired (15 min TTL).`);
421
494
  }
422
495
  const expectedState = stateData.state;
423
496
  if (actualState !== expectedState) {
424
- throw new Error(`State mismatch for user ${actualUserId} - possible CSRF attack. Expected: ${expectedState}, Got: ${actualState}`);
497
+ const label = actualAccountId ? `user ${actualUserId}, account ${actualAccountId}` : `user ${actualUserId}`;
498
+ throw new Error(`State mismatch for ${label} - possible CSRF attack. Expected: ${expectedState}, Got: ${actualState}`);
425
499
  }
426
500
  if (!this.config.redirectUri) {
427
501
  throw new Error("redirectUri is required");
@@ -435,7 +509,7 @@ var init_AuthCodePKCE = __esm({
435
509
  if (this.config.clientSecret) {
436
510
  params.append("client_secret", this.config.clientSecret);
437
511
  }
438
- const verifierData = this.codeVerifiers.get(userKey);
512
+ const verifierData = this.codeVerifiers.get(mapKey);
439
513
  if (this.config.usePKCE !== false && verifierData) {
440
514
  params.append("code_verifier", verifierData.verifier);
441
515
  }
@@ -469,39 +543,43 @@ var init_AuthCodePKCE = __esm({
469
543
  throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${error}`);
470
544
  }
471
545
  const data = await response.json();
472
- await this.tokenStore.storeToken(data, actualUserId);
473
- this.codeVerifiers.delete(userKey);
474
- this.states.delete(userKey);
546
+ await this.tokenStore.storeToken(data, actualUserId, actualAccountId);
547
+ this.codeVerifiers.delete(mapKey);
548
+ this.states.delete(mapKey);
475
549
  }
476
550
  /**
477
551
  * Get valid token (auto-refreshes if needed)
478
552
  * @param userId - User identifier for multi-user support
553
+ * @param accountId - Account alias for multi-account support
479
554
  */
480
- async getToken(userId) {
481
- const key = userId || "default";
482
- if (this.refreshLocks.has(key)) {
483
- return this.refreshLocks.get(key);
555
+ async getToken(userId, accountId) {
556
+ const mapKey = this.getMapKey(userId, accountId);
557
+ if (this.refreshLocks.has(mapKey)) {
558
+ return this.refreshLocks.get(mapKey);
484
559
  }
485
- if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId)) {
486
- return this.tokenStore.getAccessToken(userId);
560
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId)) {
561
+ return this.tokenStore.getAccessToken(userId, accountId);
487
562
  }
488
- if (await this.tokenStore.hasRefreshToken(userId)) {
489
- const refreshPromise = this.refreshToken(userId);
490
- this.refreshLocks.set(key, refreshPromise);
563
+ if (await this.tokenStore.hasRefreshToken(userId, accountId)) {
564
+ const refreshPromise = this.refreshToken(userId, accountId);
565
+ this.refreshLocks.set(mapKey, refreshPromise);
491
566
  try {
492
567
  return await refreshPromise;
493
568
  } finally {
494
- this.refreshLocks.delete(key);
569
+ this.refreshLocks.delete(mapKey);
495
570
  }
496
571
  }
497
- throw new Error(`No valid token available for ${userId ? `user: ${userId}` : "default user"}. User needs to authorize (call startAuthFlow).`);
572
+ const userLabel = userId ? `user: ${userId}` : "default user";
573
+ const accountLabel = accountId ? `, account: ${accountId}` : "";
574
+ throw new Error(`No valid token available for ${userLabel}${accountLabel}. User needs to authorize (call startAuthFlow).`);
498
575
  }
499
576
  /**
500
577
  * Refresh access token using refresh token
501
578
  * @param userId - User identifier for multi-user support
579
+ * @param accountId - Account alias for multi-account support
502
580
  */
503
- async refreshToken(userId) {
504
- const refreshToken = await this.tokenStore.getRefreshToken(userId);
581
+ async refreshToken(userId, accountId) {
582
+ const refreshToken = await this.tokenStore.getRefreshToken(userId, accountId);
505
583
  const params = new URLSearchParams({
506
584
  grant_type: "refresh_token",
507
585
  refresh_token: refreshToken,
@@ -540,28 +618,30 @@ var init_AuthCodePKCE = __esm({
540
618
  throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${error}`);
541
619
  }
542
620
  const data = await response.json();
543
- await this.tokenStore.storeToken(data, userId);
621
+ await this.tokenStore.storeToken(data, userId, accountId);
544
622
  return data.access_token;
545
623
  }
546
624
  /**
547
625
  * Check if token is valid
548
626
  * @param userId - User identifier for multi-user support
627
+ * @param accountId - Account alias for multi-account support
549
628
  */
550
- async isTokenValid(userId) {
551
- return this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId);
629
+ async isTokenValid(userId, accountId) {
630
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId);
552
631
  }
553
632
  /**
554
633
  * Revoke token (if supported by provider)
555
634
  * @param revocationUrl - Optional revocation endpoint
556
635
  * @param userId - User identifier for multi-user support
636
+ * @param accountId - Account alias for multi-account support
557
637
  */
558
- async revokeToken(revocationUrl, userId) {
638
+ async revokeToken(revocationUrl, userId, accountId) {
559
639
  if (!revocationUrl) {
560
- await this.tokenStore.clear(userId);
640
+ await this.tokenStore.clear(userId, accountId);
561
641
  return;
562
642
  }
563
643
  try {
564
- const token = await this.tokenStore.getAccessToken(userId);
644
+ const token = await this.tokenStore.getAccessToken(userId, accountId);
565
645
  await fetch(revocationUrl, {
566
646
  method: "POST",
567
647
  headers: {
@@ -573,9 +653,16 @@ var init_AuthCodePKCE = __esm({
573
653
  })
574
654
  });
575
655
  } finally {
576
- await this.tokenStore.clear(userId);
656
+ await this.tokenStore.clear(userId, accountId);
577
657
  }
578
658
  }
659
+ /**
660
+ * List account aliases for a user.
661
+ * @param userId - User identifier (optional)
662
+ */
663
+ async listAccounts(userId) {
664
+ return this.tokenStore.listAccounts(userId);
665
+ }
579
666
  /**
580
667
  * Clean up expired PKCE data to prevent memory leaks
581
668
  * Removes verifiers and states older than PKCE_TTL (15 minutes)
@@ -607,17 +694,19 @@ var init_ClientCredentials = __esm({
607
694
  tokenStore;
608
695
  /**
609
696
  * Get token using client credentials
697
+ * @param userId - User identifier for multi-user support (optional, rarely used for client_credentials)
698
+ * @param accountId - Account alias for multi-account support (optional)
610
699
  */
611
- async getToken() {
612
- if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry)) {
613
- return this.tokenStore.getAccessToken();
700
+ async getToken(userId, accountId) {
701
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId)) {
702
+ return this.tokenStore.getAccessToken(userId, accountId);
614
703
  }
615
- return this.requestToken();
704
+ return this.requestToken(userId, accountId);
616
705
  }
617
706
  /**
618
707
  * Request a new token from the authorization server
619
708
  */
620
- async requestToken() {
709
+ async requestToken(userId, accountId) {
621
710
  const auth2 = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString(
622
711
  "base64"
623
712
  );
@@ -640,22 +729,26 @@ var init_ClientCredentials = __esm({
640
729
  throw new Error(`Token request failed: ${response.status} ${response.statusText} - ${error}`);
641
730
  }
642
731
  const data = await response.json();
643
- await this.tokenStore.storeToken(data);
732
+ await this.tokenStore.storeToken(data, userId, accountId);
644
733
  return data.access_token;
645
734
  }
646
735
  /**
647
736
  * Refresh token (client credentials don't use refresh tokens)
648
737
  * Just requests a new token
738
+ * @param userId - User identifier for multi-user support (optional)
739
+ * @param accountId - Account alias for multi-account support (optional)
649
740
  */
650
- async refreshToken() {
651
- await this.tokenStore.clear();
652
- return this.requestToken();
741
+ async refreshToken(userId, accountId) {
742
+ await this.tokenStore.clear(userId, accountId);
743
+ return this.requestToken(userId, accountId);
653
744
  }
654
745
  /**
655
746
  * Check if token is valid
747
+ * @param userId - User identifier for multi-user support (optional)
748
+ * @param accountId - Account alias for multi-account support (optional)
656
749
  */
657
- async isTokenValid() {
658
- return this.tokenStore.isValid(this.config.refreshBeforeExpiry);
750
+ async isTokenValid(userId, accountId) {
751
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId);
659
752
  }
660
753
  };
661
754
  }
@@ -697,17 +790,19 @@ var init_JWTBearer = __esm({
697
790
  }
698
791
  /**
699
792
  * Get token using JWT Bearer assertion
793
+ * @param userId - User identifier for multi-user support (optional)
794
+ * @param accountId - Account alias for multi-account support (optional)
700
795
  */
701
- async getToken() {
702
- if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry)) {
703
- return this.tokenStore.getAccessToken();
796
+ async getToken(userId, accountId) {
797
+ if (await this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId)) {
798
+ return this.tokenStore.getAccessToken(userId, accountId);
704
799
  }
705
- return this.requestToken();
800
+ return this.requestToken(userId, accountId);
706
801
  }
707
802
  /**
708
803
  * Request token using JWT assertion
709
804
  */
710
- async requestToken() {
805
+ async requestToken(userId, accountId) {
711
806
  const assertion = await this.generateJWT();
712
807
  const params = new URLSearchParams({
713
808
  grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
@@ -725,21 +820,25 @@ var init_JWTBearer = __esm({
725
820
  throw new Error(`JWT Bearer token request failed: ${response.status} ${response.statusText} - ${error}`);
726
821
  }
727
822
  const data = await response.json();
728
- await this.tokenStore.storeToken(data);
823
+ await this.tokenStore.storeToken(data, userId, accountId);
729
824
  return data.access_token;
730
825
  }
731
826
  /**
732
827
  * Refresh token (generate new JWT and request new token)
828
+ * @param userId - User identifier for multi-user support (optional)
829
+ * @param accountId - Account alias for multi-account support (optional)
733
830
  */
734
- async refreshToken() {
735
- await this.tokenStore.clear();
736
- return this.requestToken();
831
+ async refreshToken(userId, accountId) {
832
+ await this.tokenStore.clear(userId, accountId);
833
+ return this.requestToken(userId, accountId);
737
834
  }
738
835
  /**
739
836
  * Check if token is valid
837
+ * @param userId - User identifier for multi-user support (optional)
838
+ * @param accountId - Account alias for multi-account support (optional)
740
839
  */
741
- async isTokenValid() {
742
- return this.tokenStore.isValid(this.config.refreshBeforeExpiry);
840
+ async isTokenValid(userId, accountId) {
841
+ return this.tokenStore.isValid(this.config.refreshBeforeExpiry, userId, accountId);
743
842
  }
744
843
  };
745
844
  }
@@ -819,25 +918,28 @@ var init_OAuthManager = __esm({
819
918
  * Automatically refreshes if expired
820
919
  *
821
920
  * @param userId - User identifier for multi-user support (optional)
921
+ * @param accountId - Account alias for multi-account support (optional)
822
922
  */
823
- async getToken(userId) {
824
- return this.flow.getToken(userId);
923
+ async getToken(userId, accountId) {
924
+ return this.flow.getToken(userId, accountId);
825
925
  }
826
926
  /**
827
927
  * Force refresh the token
828
928
  *
829
929
  * @param userId - User identifier for multi-user support (optional)
930
+ * @param accountId - Account alias for multi-account support (optional)
830
931
  */
831
- async refreshToken(userId) {
832
- return this.flow.refreshToken(userId);
932
+ async refreshToken(userId, accountId) {
933
+ return this.flow.refreshToken(userId, accountId);
833
934
  }
834
935
  /**
835
936
  * Check if current token is valid
836
937
  *
837
938
  * @param userId - User identifier for multi-user support (optional)
939
+ * @param accountId - Account alias for multi-account support (optional)
838
940
  */
839
- async isTokenValid(userId) {
840
- return this.flow.isTokenValid(userId);
941
+ async isTokenValid(userId, accountId) {
942
+ return this.flow.isTokenValid(userId, accountId);
841
943
  }
842
944
  // ==================== Authorization Code Flow Methods ====================
843
945
  /**
@@ -845,13 +947,14 @@ var init_OAuthManager = __esm({
845
947
  * Returns URL for user to visit
846
948
  *
847
949
  * @param userId - User identifier for multi-user support (optional)
950
+ * @param accountId - Account alias for multi-account support (optional)
848
951
  * @returns Authorization URL for the user to visit
849
952
  */
850
- async startAuthFlow(userId) {
953
+ async startAuthFlow(userId, accountId) {
851
954
  if (!(this.flow instanceof AuthCodePKCEFlow)) {
852
955
  throw new Error("startAuthFlow() is only available for authorization_code flow");
853
956
  }
854
- return this.flow.getAuthorizationUrl(userId);
957
+ return this.flow.getAuthorizationUrl(userId, accountId);
855
958
  }
856
959
  /**
857
960
  * Handle OAuth callback (Authorization Code only)
@@ -859,8 +962,9 @@ var init_OAuthManager = __esm({
859
962
  *
860
963
  * @param callbackUrl - Full callback URL with code and state parameters
861
964
  * @param userId - Optional user identifier (can be extracted from state if embedded)
965
+ * @param accountId - Optional account alias (can be extracted from state if embedded)
862
966
  */
863
- async handleCallback(callbackUrl, userId) {
967
+ async handleCallback(callbackUrl, userId, accountId) {
864
968
  if (!(this.flow instanceof AuthCodePKCEFlow)) {
865
969
  throw new Error("handleCallback() is only available for authorization_code flow");
866
970
  }
@@ -873,21 +977,34 @@ var init_OAuthManager = __esm({
873
977
  if (!state) {
874
978
  throw new Error("Missing state parameter in callback URL");
875
979
  }
876
- await this.flow.exchangeCode(code, state, userId);
980
+ await this.flow.exchangeCode(code, state, userId, accountId);
877
981
  }
878
982
  /**
879
983
  * Revoke token (if supported by provider)
880
984
  *
881
985
  * @param revocationUrl - Optional revocation endpoint URL
882
986
  * @param userId - User identifier for multi-user support (optional)
987
+ * @param accountId - Account alias for multi-account support (optional)
883
988
  */
884
- async revokeToken(revocationUrl, userId) {
989
+ async revokeToken(revocationUrl, userId, accountId) {
885
990
  if (this.flow instanceof AuthCodePKCEFlow) {
886
- await this.flow.revokeToken(revocationUrl, userId);
991
+ await this.flow.revokeToken(revocationUrl, userId, accountId);
887
992
  } else {
888
993
  throw new Error("Token revocation not implemented for this flow");
889
994
  }
890
995
  }
996
+ /**
997
+ * List account aliases for a user (Authorization Code only)
998
+ *
999
+ * @param userId - User identifier (optional)
1000
+ * @returns Array of account aliases (e.g., ['work', 'personal'])
1001
+ */
1002
+ async listAccounts(userId) {
1003
+ if (this.flow instanceof AuthCodePKCEFlow) {
1004
+ return this.flow.listAccounts(userId);
1005
+ }
1006
+ return [];
1007
+ }
891
1008
  // ==================== Validation ====================
892
1009
  validateConfig(config) {
893
1010
  if (!config.flow) {
@@ -2093,46 +2210,59 @@ var init_Connector = __esm({
2093
2210
  /**
2094
2211
  * Get the current access token (for OAuth, JWT, or API key)
2095
2212
  * Handles automatic refresh if needed
2213
+ *
2214
+ * @param userId - Optional user identifier for multi-user support
2215
+ * @param accountId - Optional account alias for multi-account support (e.g., 'work', 'personal')
2096
2216
  */
2097
- async getToken(userId) {
2217
+ async getToken(userId, accountId) {
2098
2218
  if (this.config.auth.type === "api_key") {
2099
2219
  return this.config.auth.apiKey;
2100
2220
  }
2101
2221
  if (!this.oauthManager) {
2102
2222
  throw new Error(`OAuth manager not initialized for connector '${this.name}'`);
2103
2223
  }
2104
- return this.oauthManager.getToken(userId);
2224
+ return this.oauthManager.getToken(userId, accountId);
2105
2225
  }
2106
2226
  /**
2107
2227
  * Start OAuth authorization flow
2108
2228
  * Returns the URL to redirect the user to
2229
+ *
2230
+ * @param userId - Optional user identifier for multi-user support
2231
+ * @param accountId - Optional account alias for multi-account support (e.g., 'work', 'personal')
2109
2232
  */
2110
- async startAuth(userId) {
2233
+ async startAuth(userId, accountId) {
2111
2234
  if (!this.oauthManager) {
2112
2235
  throw new Error(`Connector '${this.name}' is not an OAuth connector`);
2113
2236
  }
2114
- return this.oauthManager.startAuthFlow(userId);
2237
+ return this.oauthManager.startAuthFlow(userId, accountId);
2115
2238
  }
2116
2239
  /**
2117
2240
  * Handle OAuth callback
2118
2241
  * Call this after user is redirected back from OAuth provider
2242
+ *
2243
+ * @param callbackUrl - Full callback URL with code and state parameters
2244
+ * @param userId - Optional user identifier (can be extracted from state if embedded)
2245
+ * @param accountId - Optional account alias (can be extracted from state if embedded)
2119
2246
  */
2120
- async handleCallback(callbackUrl, userId) {
2247
+ async handleCallback(callbackUrl, userId, accountId) {
2121
2248
  if (!this.oauthManager) {
2122
2249
  throw new Error(`Connector '${this.name}' is not an OAuth connector`);
2123
2250
  }
2124
- await this.oauthManager.handleCallback(callbackUrl, userId);
2251
+ await this.oauthManager.handleCallback(callbackUrl, userId, accountId);
2125
2252
  }
2126
2253
  /**
2127
2254
  * Check if the connector has a valid token
2255
+ *
2256
+ * @param userId - Optional user identifier for multi-user support
2257
+ * @param accountId - Optional account alias for multi-account support
2128
2258
  */
2129
- async hasValidToken(userId) {
2259
+ async hasValidToken(userId, accountId) {
2130
2260
  try {
2131
2261
  if (this.config.auth.type === "api_key") {
2132
2262
  return true;
2133
2263
  }
2134
2264
  if (this.oauthManager) {
2135
- const token = await this.oauthManager.getToken(userId);
2265
+ const token = await this.oauthManager.getToken(userId, accountId);
2136
2266
  return !!token;
2137
2267
  }
2138
2268
  return false;
@@ -2140,6 +2270,19 @@ var init_Connector = __esm({
2140
2270
  return false;
2141
2271
  }
2142
2272
  }
2273
+ /**
2274
+ * List account aliases for a user on this connector.
2275
+ * Only applicable for OAuth connectors with multi-account support.
2276
+ *
2277
+ * @param userId - Optional user identifier
2278
+ * @returns Array of account aliases (e.g., ['work', 'personal'])
2279
+ */
2280
+ async listAccounts(userId) {
2281
+ if (!this.oauthManager) {
2282
+ return [];
2283
+ }
2284
+ return this.oauthManager.listAccounts(userId);
2285
+ }
2143
2286
  /**
2144
2287
  * Get vendor-specific options from config
2145
2288
  */
@@ -2183,9 +2326,10 @@ var init_Connector = __esm({
2183
2326
  * @param endpoint - API endpoint (relative to baseURL) or full URL
2184
2327
  * @param options - Fetch options with connector-specific settings
2185
2328
  * @param userId - Optional user ID for multi-user OAuth
2329
+ * @param accountId - Optional account alias for multi-account OAuth
2186
2330
  * @returns Fetch Response
2187
2331
  */
2188
- async fetch(endpoint, options, userId) {
2332
+ async fetch(endpoint, options, userId, accountId) {
2189
2333
  if (this.disposed) {
2190
2334
  throw new Error(`Connector '${this.name}' has been disposed`);
2191
2335
  }
@@ -2204,7 +2348,7 @@ var init_Connector = __esm({
2204
2348
  this.logRequest(url2, options);
2205
2349
  }
2206
2350
  const doFetch = async () => {
2207
- const token = await this.getToken(userId);
2351
+ const token = await this.getToken(userId, accountId);
2208
2352
  const auth2 = this.config.auth;
2209
2353
  let headerName = "Authorization";
2210
2354
  let headerValue = `Bearer ${token}`;
@@ -2316,10 +2460,11 @@ var init_Connector = __esm({
2316
2460
  * @param endpoint - API endpoint (relative to baseURL) or full URL
2317
2461
  * @param options - Fetch options with connector-specific settings
2318
2462
  * @param userId - Optional user ID for multi-user OAuth
2463
+ * @param accountId - Optional account alias for multi-account OAuth
2319
2464
  * @returns Parsed JSON response
2320
2465
  */
2321
- async fetchJSON(endpoint, options, userId) {
2322
- const response = await this.fetch(endpoint, options, userId);
2466
+ async fetchJSON(endpoint, options, userId, accountId) {
2467
+ const response = await this.fetch(endpoint, options, userId, accountId);
2323
2468
  const text = await response.text();
2324
2469
  let data;
2325
2470
  try {
@@ -11268,6 +11413,18 @@ var ToolManager = class extends eventemitter3.EventEmitter {
11268
11413
  this.register(tool, options);
11269
11414
  }
11270
11415
  }
11416
+ /**
11417
+ * Register tools produced by a specific connector.
11418
+ * Sets `source: 'connector:<connectorName>'` (or `'connector:<name>:<accountId>'` for identity-bound tools)
11419
+ * so agent-level filtering can restrict which connector tools are visible to a given agent.
11420
+ */
11421
+ registerConnectorTools(connectorName, tools, options = {}) {
11422
+ const { accountId, ...toolOptions } = options;
11423
+ const source = accountId ? `connector:${connectorName}:${accountId}` : `connector:${connectorName}`;
11424
+ for (const tool of tools) {
11425
+ this.register(tool, { ...toolOptions, source });
11426
+ }
11427
+ }
11271
11428
  /**
11272
11429
  * Unregister a tool by name
11273
11430
  */
@@ -11458,6 +11615,14 @@ var ToolManager = class extends eventemitter3.EventEmitter {
11458
11615
  getEnabled() {
11459
11616
  return this.getSortedByPriority().filter((reg) => reg.enabled).map((reg) => reg.tool);
11460
11617
  }
11618
+ /**
11619
+ * Get all enabled registrations (sorted by priority).
11620
+ * Includes full registration metadata (source, namespace, etc.)
11621
+ * for use in connector-aware filtering.
11622
+ */
11623
+ getEnabledRegistrations() {
11624
+ return this.getSortedByPriority().filter((reg) => reg.enabled);
11625
+ }
11461
11626
  /**
11462
11627
  * Get all tools (enabled and disabled)
11463
11628
  */
@@ -11650,6 +11815,13 @@ var ToolManager = class extends eventemitter3.EventEmitter {
11650
11815
  if (!registration.enabled) {
11651
11816
  throw new ToolExecutionError(toolName, "Tool is disabled");
11652
11817
  }
11818
+ if (registration.source?.startsWith("connector:")) {
11819
+ const connName = registration.source.slice("connector:".length);
11820
+ const registry = this._toolContext?.connectorRegistry;
11821
+ if (registry && !registry.has(connName)) {
11822
+ throw new ToolExecutionError(toolName, `Connector '${connName}' is not available to this agent`);
11823
+ }
11824
+ }
11653
11825
  const breaker = this.getOrCreateCircuitBreaker(toolName, registration);
11654
11826
  this.toolLogger.debug({ toolName, args }, "Tool execution started");
11655
11827
  const startTime = Date.now();
@@ -16742,8 +16914,8 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16742
16914
  _agentId;
16743
16915
  /** User ID for multi-user scenarios */
16744
16916
  _userId;
16745
- /** Allowed connector names (when agent is restricted to a subset) */
16746
- _allowedConnectors;
16917
+ /** Auth identities this agent is scoped to (connector + optional accountId) */
16918
+ _identities;
16747
16919
  /** Storage backend */
16748
16920
  _storage;
16749
16921
  /** Destroyed flag */
@@ -16783,7 +16955,7 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16783
16955
  this._systemPrompt = config.systemPrompt;
16784
16956
  this._agentId = this._config.agentId;
16785
16957
  this._userId = config.userId;
16786
- this._allowedConnectors = config.connectors;
16958
+ this._identities = config.identities;
16787
16959
  const sessionFactory = exports.StorageRegistry.get("sessions");
16788
16960
  const storageCtx = exports.StorageRegistry.getContext() ?? (config.userId ? { userId: config.userId } : void 0);
16789
16961
  this._storage = config.storage ?? (sessionFactory ? sessionFactory(this._agentId, storageCtx) : void 0);
@@ -16855,7 +17027,7 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16855
17027
  * Merges with existing ToolContext to preserve other fields (memory, signal, taskId).
16856
17028
  *
16857
17029
  * Connector registry resolution order:
16858
- * 1. If `connectors` (allowed names) is set → filtered view of global registry
17030
+ * 1. If `identities` is set → filtered view showing only identity connectors
16859
17031
  * 2. If access policy + userId → scoped view via Connector.scoped()
16860
17032
  * 3. Otherwise → full global registry
16861
17033
  */
@@ -16865,6 +17037,7 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16865
17037
  ...existing,
16866
17038
  agentId: this._agentId,
16867
17039
  userId: this._userId,
17040
+ identities: this._identities,
16868
17041
  connectorRegistry: this.buildConnectorRegistry()
16869
17042
  });
16870
17043
  }
@@ -16872,13 +17045,13 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16872
17045
  * Build the connector registry appropriate for this agent's config.
16873
17046
  */
16874
17047
  buildConnectorRegistry() {
16875
- if (this._allowedConnectors?.length) {
16876
- const allowedSet = new Set(this._allowedConnectors);
17048
+ if (this._identities?.length) {
17049
+ const allowedSet = new Set(this._identities.map((id) => id.connector));
16877
17050
  const base = this._userId && exports.Connector.getAccessPolicy() ? exports.Connector.scoped({ userId: this._userId }) : exports.Connector.asRegistry();
16878
17051
  return {
16879
17052
  get: (name) => {
16880
17053
  if (!allowedSet.has(name)) {
16881
- const available = this._allowedConnectors.filter((n) => base.has(n)).join(", ") || "none";
17054
+ const available = [...allowedSet].filter((n) => base.has(n)).join(", ") || "none";
16882
17055
  throw new Error(`Connector '${name}' not found. Available: ${available}`);
16883
17056
  }
16884
17057
  return base.get(name);
@@ -16930,13 +17103,13 @@ var AgentContextNextGen = class _AgentContextNextGen extends eventemitter3.Event
16930
17103
  this._userId = value;
16931
17104
  this.syncToolContext();
16932
17105
  }
16933
- /** Get the allowed connector names (undefined = all visible connectors) */
16934
- get connectors() {
16935
- return this._allowedConnectors;
17106
+ /** Get the auth identities this agent is scoped to (undefined = all visible connectors) */
17107
+ get identities() {
17108
+ return this._identities;
16936
17109
  }
16937
- /** Set allowed connector names. Updates ToolContext.connectorRegistry. */
16938
- set connectors(value) {
16939
- this._allowedConnectors = value;
17110
+ /** Set auth identities. Updates ToolContext.connectorRegistry and identity-aware descriptions. */
17111
+ set identities(value) {
17112
+ this._identities = value;
16940
17113
  this.syncToolContext();
16941
17114
  }
16942
17115
  /** Get/set system prompt */
@@ -21415,7 +21588,7 @@ var BaseAgent = class extends eventemitter3.EventEmitter {
21415
21588
  model: config.model,
21416
21589
  agentId: config.name,
21417
21590
  userId: config.userId,
21418
- connectors: config.connectors,
21591
+ identities: config.identities,
21419
21592
  // Include storage and sessionId if session config is provided
21420
21593
  storage: config.session?.storage,
21421
21594
  // Thread tool execution timeout to ToolManager
@@ -21585,16 +21758,16 @@ var BaseAgent = class extends eventemitter3.EventEmitter {
21585
21758
  this._agentContext.userId = value;
21586
21759
  }
21587
21760
  /**
21588
- * Get the allowed connector names (undefined = all visible connectors).
21761
+ * Get the auth identities this agent is scoped to (undefined = all visible connectors).
21589
21762
  */
21590
- get connectors() {
21591
- return this._agentContext.connectors;
21763
+ get identities() {
21764
+ return this._agentContext.identities;
21592
21765
  }
21593
21766
  /**
21594
- * Restrict this agent to a subset of connectors. Updates ToolContext.connectorRegistry.
21767
+ * Set auth identities at runtime. Updates ToolContext.connectorRegistry and tool descriptions.
21595
21768
  */
21596
- set connectors(value) {
21597
- this._agentContext.connectors = value;
21769
+ set identities(value) {
21770
+ this._agentContext.identities = value;
21598
21771
  }
21599
21772
  /**
21600
21773
  * Permission management. Returns ToolPermissionManager for approval control.
@@ -21606,9 +21779,12 @@ var BaseAgent = class extends eventemitter3.EventEmitter {
21606
21779
  /**
21607
21780
  * Add a tool to the agent.
21608
21781
  * Tools are registered with AgentContext (single source of truth).
21782
+ *
21783
+ * @param tool - The tool function to register
21784
+ * @param options - Optional registration options (namespace, source, priority, etc.)
21609
21785
  */
21610
- addTool(tool) {
21611
- this._agentContext.tools.register(tool);
21786
+ addTool(tool, options) {
21787
+ this._agentContext.tools.register(tool, options);
21612
21788
  if (tool.permission) {
21613
21789
  this._permissionManager.setToolConfig(tool.definition.function.name, tool.permission);
21614
21790
  }
@@ -21648,7 +21824,34 @@ var BaseAgent = class extends eventemitter3.EventEmitter {
21648
21824
  */
21649
21825
  getEnabledToolDefinitions() {
21650
21826
  const toolContext = this._agentContext.tools.getToolContext();
21651
- return this._agentContext.tools.getEnabled().map((tool) => {
21827
+ const identities = this._agentContext.identities;
21828
+ let allowedSources;
21829
+ let allowedConnectorNames;
21830
+ if (identities) {
21831
+ allowedSources = /* @__PURE__ */ new Set();
21832
+ allowedConnectorNames = /* @__PURE__ */ new Set();
21833
+ for (const id of identities) {
21834
+ allowedConnectorNames.add(id.connector);
21835
+ if (id.accountId) {
21836
+ allowedSources.add(`connector:${id.connector}:${id.accountId}`);
21837
+ } else {
21838
+ allowedSources.add(`connector:${id.connector}`);
21839
+ allowedConnectorNames.add(id.connector);
21840
+ }
21841
+ }
21842
+ }
21843
+ return this._agentContext.tools.getEnabledRegistrations().filter((reg) => {
21844
+ if (!allowedSources) return true;
21845
+ if (!reg.source?.startsWith("connector:")) return true;
21846
+ if (allowedSources.has(reg.source)) return true;
21847
+ const sourceParts = reg.source.slice("connector:".length).split(":");
21848
+ const connectorName = sourceParts[0];
21849
+ if (allowedConnectorNames.has(connectorName) && allowedSources.has(`connector:${connectorName}`)) {
21850
+ return true;
21851
+ }
21852
+ return false;
21853
+ }).map((reg) => {
21854
+ const tool = reg.tool;
21652
21855
  if (tool.descriptionFactory) {
21653
21856
  const dynamicDescription = tool.descriptionFactory(toolContext);
21654
21857
  return {
@@ -24033,6 +24236,7 @@ function createTask(input) {
24033
24236
  suggestedTools: input.suggestedTools,
24034
24237
  validation: input.validation,
24035
24238
  expectedOutput: input.expectedOutput,
24239
+ controlFlow: input.controlFlow,
24036
24240
  attempts: 0,
24037
24241
  maxAttempts: input.maxAttempts ?? 3,
24038
24242
  createdAt: now,
@@ -24285,6 +24489,7 @@ function createRoutineDefinition(input) {
24285
24489
  instructions: input.instructions,
24286
24490
  concurrency: input.concurrency,
24287
24491
  allowDynamicTasks: input.allowDynamicTasks ?? false,
24492
+ parameters: input.parameters,
24288
24493
  tags: input.tags,
24289
24494
  author: input.author,
24290
24495
  createdAt: now,
@@ -24500,6 +24705,321 @@ function extractNumber(text, patterns = [
24500
24705
 
24501
24706
  // src/core/routineRunner.ts
24502
24707
  init_Logger();
24708
+
24709
+ // src/core/routineControlFlow.ts
24710
+ init_Logger();
24711
+ var HARD_MAX_ITERATIONS = 1e3;
24712
+ var ICM_LARGE_THRESHOLD = 5e3;
24713
+ var ROUTINE_KEYS = {
24714
+ /** Plan overview with task statuses (ICM) */
24715
+ PLAN: "__routine_plan",
24716
+ /** Dependency results location guide (ICM) */
24717
+ DEPS: "__routine_deps",
24718
+ /** Prefix for per-dependency result keys (ICM/WM) */
24719
+ DEP_RESULT_PREFIX: "__dep_result_",
24720
+ /** Current map/fold item (ICM) */
24721
+ MAP_ITEM: "__map_item",
24722
+ /** Current map/fold index, 0-based (ICM) */
24723
+ MAP_INDEX: "__map_index",
24724
+ /** Total items in map/fold (ICM) */
24725
+ MAP_TOTAL: "__map_total",
24726
+ /** Running fold accumulator (ICM) */
24727
+ FOLD_ACCUMULATOR: "__fold_accumulator",
24728
+ /** Prefix for large dep results stored in WM findings tier */
24729
+ WM_DEP_FINDINGS_PREFIX: "findings/__dep_result_"
24730
+ };
24731
+ function resolveTemplates(text, inputs, icmPlugin) {
24732
+ return text.replace(/\{\{(\w+)\.(\w+)\}\}/g, (_match, namespace, key) => {
24733
+ let value;
24734
+ if (namespace === "param") {
24735
+ value = inputs[key];
24736
+ } else if (namespace === "map") {
24737
+ const icmKey = `__map_${key}`;
24738
+ value = icmPlugin?.get(icmKey);
24739
+ } else if (namespace === "fold") {
24740
+ const icmKey = `__fold_${key}`;
24741
+ value = icmPlugin?.get(icmKey);
24742
+ } else {
24743
+ return _match;
24744
+ }
24745
+ if (value === void 0) {
24746
+ return _match;
24747
+ }
24748
+ return typeof value === "string" ? value : JSON.stringify(value);
24749
+ });
24750
+ }
24751
+ function resolveTaskTemplates(task, inputs, icmPlugin) {
24752
+ const resolvedDescription = resolveTemplates(task.description, inputs, icmPlugin);
24753
+ const resolvedExpectedOutput = task.expectedOutput ? resolveTemplates(task.expectedOutput, inputs, icmPlugin) : task.expectedOutput;
24754
+ if (resolvedDescription === task.description && resolvedExpectedOutput === task.expectedOutput) {
24755
+ return task;
24756
+ }
24757
+ return {
24758
+ ...task,
24759
+ description: resolvedDescription,
24760
+ expectedOutput: resolvedExpectedOutput
24761
+ };
24762
+ }
24763
+ function validateAndResolveInputs(parameters, inputs) {
24764
+ const resolved = { ...inputs ?? {} };
24765
+ if (!parameters || parameters.length === 0) {
24766
+ return resolved;
24767
+ }
24768
+ for (const param of parameters) {
24769
+ if (resolved[param.name] === void 0) {
24770
+ if (param.required) {
24771
+ throw new Error(`Missing required parameter: "${param.name}"`);
24772
+ }
24773
+ if (param.default !== void 0) {
24774
+ resolved[param.name] = param.default;
24775
+ }
24776
+ }
24777
+ }
24778
+ return resolved;
24779
+ }
24780
+ async function readMemoryValue(key, icmPlugin, wmPlugin) {
24781
+ if (icmPlugin) {
24782
+ const icmValue = icmPlugin.get(key);
24783
+ if (icmValue !== void 0) return icmValue;
24784
+ }
24785
+ if (wmPlugin) {
24786
+ const wmValue = await wmPlugin.retrieve(key);
24787
+ if (wmValue !== void 0) return wmValue;
24788
+ }
24789
+ return void 0;
24790
+ }
24791
+ async function storeResult(key, description, value, icmPlugin, wmPlugin) {
24792
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
24793
+ const estimatedTokens = Math.ceil(serialized.length / 4);
24794
+ if (estimatedTokens < ICM_LARGE_THRESHOLD && icmPlugin) {
24795
+ icmPlugin.set(key, description, value, "high");
24796
+ } else if (wmPlugin) {
24797
+ await wmPlugin.store(key, description, serialized, { tier: "findings" });
24798
+ } else if (icmPlugin) {
24799
+ icmPlugin.set(key, description, value, "high");
24800
+ }
24801
+ }
24802
+ function resolveSubRoutine(spec, parentTaskName) {
24803
+ if (!Array.isArray(spec)) {
24804
+ return spec;
24805
+ }
24806
+ return createRoutineDefinition({
24807
+ name: `${parentTaskName} (sub-routine)`,
24808
+ description: `Sub-routine of ${parentTaskName}`,
24809
+ tasks: spec
24810
+ });
24811
+ }
24812
+ function getPlugins(agent) {
24813
+ const icmPlugin = agent.context.getPlugin("in_context_memory");
24814
+ const wmPlugin = agent.context.memory;
24815
+ return { icmPlugin, wmPlugin };
24816
+ }
24817
+ function cleanMapKeys(icmPlugin) {
24818
+ if (!icmPlugin) return;
24819
+ icmPlugin.delete(ROUTINE_KEYS.MAP_ITEM);
24820
+ icmPlugin.delete(ROUTINE_KEYS.MAP_INDEX);
24821
+ icmPlugin.delete(ROUTINE_KEYS.MAP_TOTAL);
24822
+ }
24823
+ function cleanFoldKeys(icmPlugin) {
24824
+ if (!icmPlugin) return;
24825
+ cleanMapKeys(icmPlugin);
24826
+ icmPlugin.delete(ROUTINE_KEYS.FOLD_ACCUMULATOR);
24827
+ }
24828
+ async function readSourceArray(flow, flowType, icmPlugin, wmPlugin) {
24829
+ const sourceValue = await readMemoryValue(flow.sourceKey, icmPlugin, wmPlugin);
24830
+ if (!Array.isArray(sourceValue)) {
24831
+ return {
24832
+ completed: false,
24833
+ error: `${flowType} sourceKey "${flow.sourceKey}" is not an array (got ${typeof sourceValue})`
24834
+ };
24835
+ }
24836
+ const maxIter = Math.min(sourceValue.length, flow.maxIterations ?? sourceValue.length, HARD_MAX_ITERATIONS);
24837
+ return { array: sourceValue, maxIter };
24838
+ }
24839
+ function prepareSubRoutine(tasks, parentTaskName) {
24840
+ const subRoutine = resolveSubRoutine(tasks, parentTaskName);
24841
+ return {
24842
+ augmented: { ...subRoutine },
24843
+ baseInstructions: subRoutine.instructions ?? ""
24844
+ };
24845
+ }
24846
+ function getSubRoutineOutput(execution) {
24847
+ const tasks = execution.plan.tasks;
24848
+ for (let i = tasks.length - 1; i >= 0; i--) {
24849
+ if (tasks[i].status === "completed") {
24850
+ return tasks[i].result?.output ?? null;
24851
+ }
24852
+ }
24853
+ return null;
24854
+ }
24855
+ function setIterationKeys(icmPlugin, item, index, total, label) {
24856
+ if (!icmPlugin) return;
24857
+ icmPlugin.set(ROUTINE_KEYS.MAP_ITEM, `Current ${label} item (${index + 1}/${total})`, item, "high");
24858
+ icmPlugin.set(ROUTINE_KEYS.MAP_INDEX, `Current ${label} index (0-based)`, index, "high");
24859
+ icmPlugin.set(ROUTINE_KEYS.MAP_TOTAL, `Total items in ${label}`, total, "high");
24860
+ }
24861
+ async function withTimeout(promise, timeoutMs, label) {
24862
+ if (!timeoutMs) return promise;
24863
+ let timer;
24864
+ const timeout = new Promise((_, reject) => {
24865
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
24866
+ });
24867
+ try {
24868
+ return await Promise.race([promise, timeout]);
24869
+ } finally {
24870
+ clearTimeout(timer);
24871
+ }
24872
+ }
24873
+ async function handleMap(agent, flow, task, inputs) {
24874
+ const { icmPlugin, wmPlugin } = getPlugins(agent);
24875
+ const log = exports.logger.child({ controlFlow: "map", task: task.name });
24876
+ const sourceResult = await readSourceArray(flow, "Map", icmPlugin, wmPlugin);
24877
+ if ("completed" in sourceResult) return sourceResult;
24878
+ const { array: array3, maxIter } = sourceResult;
24879
+ const results = [];
24880
+ const { augmented, baseInstructions } = prepareSubRoutine(flow.tasks, task.name);
24881
+ log.info({ arrayLength: array3.length, maxIterations: maxIter }, "Starting map iteration");
24882
+ try {
24883
+ for (let i = 0; i < maxIter; i++) {
24884
+ setIterationKeys(icmPlugin, array3[i], i, array3.length, "map");
24885
+ augmented.instructions = [
24886
+ `You are processing item ${i + 1} of ${array3.length} in a map operation.`,
24887
+ "The current item is available in your live context as __map_item.",
24888
+ "Current index (0-based) is in __map_index, total count in __map_total.",
24889
+ "",
24890
+ baseInstructions
24891
+ ].join("\n");
24892
+ const subExecution = await withTimeout(
24893
+ executeRoutine({ definition: augmented, agent, inputs }),
24894
+ flow.iterationTimeoutMs,
24895
+ `Map iteration ${i}`
24896
+ );
24897
+ if (subExecution.status !== "completed") {
24898
+ return {
24899
+ completed: false,
24900
+ error: `Map iteration ${i} failed: ${subExecution.error ?? "sub-routine failed"}`
24901
+ };
24902
+ }
24903
+ results.push(getSubRoutineOutput(subExecution));
24904
+ }
24905
+ } finally {
24906
+ cleanMapKeys(icmPlugin);
24907
+ }
24908
+ if (flow.resultKey) {
24909
+ await storeResult(flow.resultKey, `Map results from "${task.name}"`, results, icmPlugin, wmPlugin);
24910
+ }
24911
+ log.info({ resultCount: results.length }, "Map completed");
24912
+ return { completed: true, result: results };
24913
+ }
24914
+ async function handleFold(agent, flow, task, inputs) {
24915
+ const { icmPlugin, wmPlugin } = getPlugins(agent);
24916
+ const log = exports.logger.child({ controlFlow: "fold", task: task.name });
24917
+ const sourceResult = await readSourceArray(flow, "Fold", icmPlugin, wmPlugin);
24918
+ if ("completed" in sourceResult) return sourceResult;
24919
+ const { array: array3, maxIter } = sourceResult;
24920
+ let accumulator = flow.initialValue;
24921
+ const { augmented, baseInstructions } = prepareSubRoutine(flow.tasks, task.name);
24922
+ log.info({ arrayLength: array3.length, maxIterations: maxIter }, "Starting fold iteration");
24923
+ try {
24924
+ for (let i = 0; i < maxIter; i++) {
24925
+ setIterationKeys(icmPlugin, array3[i], i, array3.length, "fold");
24926
+ if (icmPlugin) {
24927
+ icmPlugin.set(ROUTINE_KEYS.FOLD_ACCUMULATOR, "Running accumulator \u2014 update via context_set", accumulator, "high");
24928
+ }
24929
+ augmented.instructions = [
24930
+ `You are processing item ${i + 1} of ${array3.length} in a fold/accumulate operation.`,
24931
+ "The current item is in __map_item. The running accumulator is in __fold_accumulator.",
24932
+ "After processing, use context_set to update __fold_accumulator with the new accumulated value.",
24933
+ "Your final text response will also be captured as the result.",
24934
+ "",
24935
+ baseInstructions
24936
+ ].join("\n");
24937
+ const subExecution = await withTimeout(
24938
+ executeRoutine({ definition: augmented, agent, inputs }),
24939
+ flow.iterationTimeoutMs,
24940
+ `Fold iteration ${i}`
24941
+ );
24942
+ if (subExecution.status !== "completed") {
24943
+ return {
24944
+ completed: false,
24945
+ error: `Fold iteration ${i} failed: ${subExecution.error ?? "sub-routine failed"}`
24946
+ };
24947
+ }
24948
+ const taskOutput = getSubRoutineOutput(subExecution);
24949
+ if (taskOutput !== null) {
24950
+ accumulator = taskOutput;
24951
+ } else if (icmPlugin) {
24952
+ const icmAccumulator = icmPlugin.get(ROUTINE_KEYS.FOLD_ACCUMULATOR);
24953
+ if (icmAccumulator !== void 0) {
24954
+ accumulator = icmAccumulator;
24955
+ }
24956
+ }
24957
+ }
24958
+ } finally {
24959
+ cleanFoldKeys(icmPlugin);
24960
+ }
24961
+ await storeResult(flow.resultKey, `Fold result from "${task.name}"`, accumulator, icmPlugin, wmPlugin);
24962
+ log.info("Fold completed");
24963
+ return { completed: true, result: accumulator };
24964
+ }
24965
+ async function handleUntil(agent, flow, task, inputs) {
24966
+ const { icmPlugin, wmPlugin } = getPlugins(agent);
24967
+ const log = exports.logger.child({ controlFlow: "until", task: task.name });
24968
+ const { augmented, baseInstructions } = prepareSubRoutine(flow.tasks, task.name);
24969
+ log.info({ maxIterations: flow.maxIterations }, "Starting until loop");
24970
+ const memoryAccess = {
24971
+ get: (key) => readMemoryValue(key, icmPlugin, wmPlugin)
24972
+ };
24973
+ try {
24974
+ for (let i = 0; i < flow.maxIterations; i++) {
24975
+ if (flow.iterationKey && icmPlugin) {
24976
+ icmPlugin.set(flow.iterationKey, "Current iteration index", i, "high");
24977
+ }
24978
+ augmented.instructions = [
24979
+ `You are in iteration ${i + 1} of a repeating operation (max ${flow.maxIterations}).`,
24980
+ "Complete the task. The loop will continue until its exit condition is met.",
24981
+ "",
24982
+ baseInstructions
24983
+ ].join("\n");
24984
+ const subExecution = await withTimeout(
24985
+ executeRoutine({ definition: augmented, agent, inputs }),
24986
+ flow.iterationTimeoutMs,
24987
+ `Until iteration ${i}`
24988
+ );
24989
+ if (subExecution.status !== "completed") {
24990
+ return {
24991
+ completed: false,
24992
+ error: `Until iteration ${i} failed: ${subExecution.error ?? "sub-routine failed"}`
24993
+ };
24994
+ }
24995
+ const conditionMet = await evaluateCondition(flow.condition, memoryAccess);
24996
+ if (conditionMet) {
24997
+ log.info({ iteration: i + 1 }, "Until condition met");
24998
+ return { completed: true };
24999
+ }
25000
+ }
25001
+ } finally {
25002
+ if (flow.iterationKey && icmPlugin) {
25003
+ icmPlugin.delete(flow.iterationKey);
25004
+ }
25005
+ }
25006
+ return { completed: false, error: `Until loop: maxIterations (${flow.maxIterations}) exceeded` };
25007
+ }
25008
+ async function executeControlFlow(agent, task, inputs) {
25009
+ const flow = task.controlFlow;
25010
+ switch (flow.type) {
25011
+ case "map":
25012
+ return handleMap(agent, flow, task, inputs);
25013
+ case "fold":
25014
+ return handleFold(agent, flow, task, inputs);
25015
+ case "until":
25016
+ return handleUntil(agent, flow, task, inputs);
25017
+ default:
25018
+ return { completed: false, error: `Unknown control flow type: ${flow.type}` };
25019
+ }
25020
+ }
25021
+
25022
+ // src/core/routineRunner.ts
24503
25023
  function defaultSystemPrompt(definition) {
24504
25024
  const parts = [];
24505
25025
  if (definition.instructions) {
@@ -24628,6 +25148,14 @@ async function collectValidationContext(agent, responseText) {
24628
25148
  toolCallLog
24629
25149
  };
24630
25150
  }
25151
+ function isTransientError(error) {
25152
+ if (error instanceof ProviderAuthError) return false;
25153
+ if (error instanceof ProviderContextLengthError) return false;
25154
+ if (error instanceof ProviderNotFoundError) return false;
25155
+ if (error instanceof ModelNotSupportedError) return false;
25156
+ if (error instanceof InvalidConfigError) return false;
25157
+ return true;
25158
+ }
24631
25159
  function estimateTokens(text) {
24632
25160
  return Math.ceil(text.length / 4);
24633
25161
  }
@@ -24671,32 +25199,42 @@ function buildPlanOverview(execution, definition, currentTaskId) {
24671
25199
  }
24672
25200
  return parts.join("\n");
24673
25201
  }
24674
- async function injectRoutineContext(agent, execution, definition, currentTask) {
24675
- const icmPlugin = agent.context.getPlugin("in_context_memory");
24676
- const wmPlugin = agent.context.memory;
24677
- if (!icmPlugin && !wmPlugin) {
24678
- exports.logger.warn("injectRoutineContext: No ICM or WM plugin available \u2014 skipping context injection");
24679
- return;
24680
- }
24681
- const planOverview = buildPlanOverview(execution, definition, currentTask.id);
24682
- if (icmPlugin) {
24683
- icmPlugin.set("__routine_plan", "Routine plan overview with task statuses", planOverview, "high");
24684
- }
25202
+ async function cleanupMemoryKeys(icmPlugin, wmPlugin, config) {
24685
25203
  if (icmPlugin) {
24686
25204
  for (const entry of icmPlugin.list()) {
24687
- if (entry.key.startsWith("__dep_result_") || entry.key === "__routine_deps") {
24688
- icmPlugin.delete(entry.key);
24689
- }
25205
+ const shouldDelete = config.icmPrefixes.some((p) => entry.key.startsWith(p)) || (config.icmExactKeys?.includes(entry.key) ?? false);
25206
+ if (shouldDelete) icmPlugin.delete(entry.key);
24690
25207
  }
24691
25208
  }
24692
25209
  if (wmPlugin) {
24693
25210
  const { entries: wmEntries } = await wmPlugin.query();
24694
25211
  for (const entry of wmEntries) {
24695
- if (entry.key.startsWith("__dep_result_") || entry.key.startsWith("findings/__dep_result_")) {
25212
+ if (config.wmPrefixes.some((p) => entry.key.startsWith(p))) {
24696
25213
  await wmPlugin.delete(entry.key);
24697
25214
  }
24698
25215
  }
24699
25216
  }
25217
+ }
25218
+ var DEP_CLEANUP_CONFIG = {
25219
+ icmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX],
25220
+ icmExactKeys: [ROUTINE_KEYS.DEPS],
25221
+ wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
25222
+ };
25223
+ var FULL_CLEANUP_CONFIG = {
25224
+ icmPrefixes: ["__routine_", ROUTINE_KEYS.DEP_RESULT_PREFIX, "__map_", "__fold_"],
25225
+ wmPrefixes: [ROUTINE_KEYS.DEP_RESULT_PREFIX, ROUTINE_KEYS.WM_DEP_FINDINGS_PREFIX]
25226
+ };
25227
+ async function injectRoutineContext(agent, execution, definition, currentTask) {
25228
+ const { icmPlugin, wmPlugin } = getPlugins(agent);
25229
+ if (!icmPlugin && !wmPlugin) {
25230
+ exports.logger.warn("injectRoutineContext: No ICM or WM plugin available \u2014 skipping context injection");
25231
+ return;
25232
+ }
25233
+ const planOverview = buildPlanOverview(execution, definition, currentTask.id);
25234
+ if (icmPlugin) {
25235
+ icmPlugin.set(ROUTINE_KEYS.PLAN, "Routine plan overview with task statuses", planOverview, "high");
25236
+ }
25237
+ await cleanupMemoryKeys(icmPlugin, wmPlugin, DEP_CLEANUP_CONFIG);
24700
25238
  if (currentTask.dependsOn.length === 0) return;
24701
25239
  const inContextDeps = [];
24702
25240
  const workingMemoryDeps = [];
@@ -24705,7 +25243,7 @@ async function injectRoutineContext(agent, execution, definition, currentTask) {
24705
25243
  if (!depTask?.result?.output) continue;
24706
25244
  const output = typeof depTask.result.output === "string" ? depTask.result.output : JSON.stringify(depTask.result.output);
24707
25245
  const tokens = estimateTokens(output);
24708
- const depKey = `__dep_result_${depId}`;
25246
+ const depKey = `${ROUTINE_KEYS.DEP_RESULT_PREFIX}${depId}`;
24709
25247
  const depLabel = `Result from task "${depTask.name}"`;
24710
25248
  if (tokens < 5e3 && icmPlugin) {
24711
25249
  icmPlugin.set(depKey, depLabel, output, "high");
@@ -24727,27 +25265,12 @@ async function injectRoutineContext(agent, execution, definition, currentTask) {
24727
25265
  if (workingMemoryDeps.length > 0) {
24728
25266
  summaryParts.push(`In working memory (use memory_retrieve): ${workingMemoryDeps.join(", ")}`);
24729
25267
  }
24730
- icmPlugin.set("__routine_deps", "Dependency results location guide", summaryParts.join("\n"), "high");
25268
+ icmPlugin.set(ROUTINE_KEYS.DEPS, "Dependency results location guide", summaryParts.join("\n"), "high");
24731
25269
  }
24732
25270
  }
24733
25271
  async function cleanupRoutineContext(agent) {
24734
- const icmPlugin = agent.context.getPlugin("in_context_memory");
24735
- const wmPlugin = agent.context.memory;
24736
- if (icmPlugin) {
24737
- for (const entry of icmPlugin.list()) {
24738
- if (entry.key.startsWith("__routine_") || entry.key.startsWith("__dep_result_")) {
24739
- icmPlugin.delete(entry.key);
24740
- }
24741
- }
24742
- }
24743
- if (wmPlugin) {
24744
- const { entries: wmEntries } = await wmPlugin.query();
24745
- for (const entry of wmEntries) {
24746
- if (entry.key.startsWith("__dep_result_") || entry.key.startsWith("findings/__dep_result_")) {
24747
- await wmPlugin.delete(entry.key);
24748
- }
24749
- }
24750
- }
25272
+ const { icmPlugin, wmPlugin } = getPlugins(agent);
25273
+ await cleanupMemoryKeys(icmPlugin, wmPlugin, FULL_CLEANUP_CONFIG);
24751
25274
  }
24752
25275
  async function validateTaskCompletion(agent, task, responseText, validationPromptBuilder) {
24753
25276
  const hasExplicitValidation = task.validation?.skipReflection === false && task.validation?.completionCriteria && task.validation.completionCriteria.length > 0;
@@ -24796,11 +25319,13 @@ async function executeRoutine(options) {
24796
25319
  onTaskFailed,
24797
25320
  onTaskValidation,
24798
25321
  hooks,
24799
- prompts
25322
+ prompts,
25323
+ inputs: rawInputs
24800
25324
  } = options;
24801
25325
  if (!existingAgent && (!connector || !model)) {
24802
25326
  throw new Error("executeRoutine requires either `agent` or both `connector` and `model`");
24803
25327
  }
25328
+ const resolvedInputs = validateAndResolveInputs(definition.parameters, rawInputs);
24804
25329
  const ownsAgent = !existingAgent;
24805
25330
  const log = exports.logger.child({ routine: definition.name });
24806
25331
  const execution = createRoutineExecution(definition);
@@ -24893,85 +25418,115 @@ async function executeRoutine(options) {
24893
25418
  return { shouldPause: false };
24894
25419
  };
24895
25420
  agent.registerHook("pause:check", iterationLimiter);
24896
- const getTask = () => execution.plan.tasks[taskIndex];
24897
- await injectRoutineContext(agent, execution, definition, getTask());
24898
- while (!taskCompleted) {
24899
- try {
24900
- const taskPrompt = buildTaskPrompt(getTask());
24901
- const response = await agent.run(taskPrompt);
24902
- const responseText = response.output_text ?? "";
24903
- const validationResult = await validateTaskCompletion(
24904
- agent,
24905
- getTask(),
24906
- responseText,
24907
- buildValidationPrompt
24908
- );
24909
- onTaskValidation?.(getTask(), validationResult, execution);
24910
- if (validationResult.isComplete) {
24911
- execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "completed");
24912
- execution.plan.tasks[taskIndex].result = {
24913
- success: true,
24914
- output: responseText,
24915
- validationScore: validationResult.completionScore,
24916
- validationExplanation: validationResult.explanation
24917
- };
24918
- taskCompleted = true;
24919
- log.info(
24920
- { taskName: getTask().name, score: validationResult.completionScore },
24921
- "Task completed"
24922
- );
24923
- execution.progress = getRoutineProgress(execution);
24924
- execution.lastUpdatedAt = Date.now();
24925
- onTaskComplete?.(execution.plan.tasks[taskIndex], execution);
24926
- } else {
24927
- log.warn(
24928
- {
24929
- taskName: getTask().name,
24930
- score: validationResult.completionScore,
24931
- attempt: getTask().attempts,
24932
- maxAttempts: getTask().maxAttempts
24933
- },
24934
- "Task validation failed"
24935
- );
24936
- if (getTask().attempts >= getTask().maxAttempts) {
25421
+ try {
25422
+ const getTask = () => execution.plan.tasks[taskIndex];
25423
+ await injectRoutineContext(agent, execution, definition, getTask());
25424
+ const { icmPlugin } = getPlugins(agent);
25425
+ if (getTask().controlFlow) {
25426
+ try {
25427
+ const cfResult = await executeControlFlow(agent, getTask(), resolvedInputs);
25428
+ if (cfResult.completed) {
25429
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "completed");
25430
+ execution.plan.tasks[taskIndex].result = { success: true, output: cfResult.result };
25431
+ taskCompleted = true;
25432
+ execution.progress = getRoutineProgress(execution);
25433
+ execution.lastUpdatedAt = Date.now();
25434
+ onTaskComplete?.(execution.plan.tasks[taskIndex], execution);
25435
+ } else {
24937
25436
  execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "failed");
24938
- execution.plan.tasks[taskIndex].result = {
24939
- success: false,
24940
- error: validationResult.explanation,
24941
- validationScore: validationResult.completionScore,
24942
- validationExplanation: validationResult.explanation
24943
- };
24944
- break;
25437
+ execution.plan.tasks[taskIndex].result = { success: false, error: cfResult.error };
24945
25438
  }
24946
- execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "in_progress");
24947
- }
24948
- } catch (error) {
24949
- const errorMessage = error.message;
24950
- log.error({ taskName: getTask().name, error: errorMessage }, "Task execution error");
24951
- if (getTask().attempts >= getTask().maxAttempts) {
25439
+ } catch (error) {
25440
+ const errorMessage = error.message;
25441
+ log.error({ taskName: getTask().name, error: errorMessage }, "Control flow error");
24952
25442
  execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "failed");
24953
- execution.plan.tasks[taskIndex].result = {
24954
- success: false,
24955
- error: errorMessage
24956
- };
24957
- break;
25443
+ execution.plan.tasks[taskIndex].result = { success: false, error: errorMessage };
25444
+ }
25445
+ } else {
25446
+ while (!taskCompleted) {
25447
+ try {
25448
+ const resolvedTask = resolveTaskTemplates(getTask(), resolvedInputs, icmPlugin);
25449
+ const taskPrompt = buildTaskPrompt(resolvedTask);
25450
+ const response = await agent.run(taskPrompt);
25451
+ const responseText = response.output_text ?? "";
25452
+ const validationResult = await validateTaskCompletion(
25453
+ agent,
25454
+ getTask(),
25455
+ responseText,
25456
+ buildValidationPrompt
25457
+ );
25458
+ onTaskValidation?.(getTask(), validationResult, execution);
25459
+ if (validationResult.isComplete) {
25460
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "completed");
25461
+ execution.plan.tasks[taskIndex].result = {
25462
+ success: true,
25463
+ output: responseText,
25464
+ validationScore: validationResult.completionScore,
25465
+ validationExplanation: validationResult.explanation
25466
+ };
25467
+ taskCompleted = true;
25468
+ log.info(
25469
+ { taskName: getTask().name, score: validationResult.completionScore },
25470
+ "Task completed"
25471
+ );
25472
+ execution.progress = getRoutineProgress(execution);
25473
+ execution.lastUpdatedAt = Date.now();
25474
+ onTaskComplete?.(execution.plan.tasks[taskIndex], execution);
25475
+ } else {
25476
+ log.warn(
25477
+ {
25478
+ taskName: getTask().name,
25479
+ score: validationResult.completionScore,
25480
+ attempt: getTask().attempts,
25481
+ maxAttempts: getTask().maxAttempts
25482
+ },
25483
+ "Task validation failed"
25484
+ );
25485
+ if (getTask().attempts >= getTask().maxAttempts) {
25486
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "failed");
25487
+ execution.plan.tasks[taskIndex].result = {
25488
+ success: false,
25489
+ error: validationResult.explanation,
25490
+ validationScore: validationResult.completionScore,
25491
+ validationExplanation: validationResult.explanation
25492
+ };
25493
+ break;
25494
+ }
25495
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "in_progress");
25496
+ }
25497
+ } catch (error) {
25498
+ const errorMessage = error.message;
25499
+ log.error({ taskName: getTask().name, error: errorMessage }, "Task execution error");
25500
+ if (!isTransientError(error) || getTask().attempts >= getTask().maxAttempts) {
25501
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "failed");
25502
+ execution.plan.tasks[taskIndex].result = {
25503
+ success: false,
25504
+ error: errorMessage
25505
+ };
25506
+ break;
25507
+ }
25508
+ execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "in_progress");
25509
+ }
24958
25510
  }
24959
- execution.plan.tasks[taskIndex] = updateTaskStatus(getTask(), "in_progress");
24960
25511
  }
24961
- }
24962
- if (!taskCompleted) {
24963
- execution.progress = getRoutineProgress(execution);
24964
- execution.lastUpdatedAt = Date.now();
24965
- onTaskFailed?.(execution.plan.tasks[taskIndex], execution);
24966
- if (failureMode === "fail-fast") {
24967
- execution.status = "failed";
24968
- execution.error = `Task "${getTask().name}" failed after ${getTask().attempts} attempt(s)`;
24969
- execution.completedAt = Date.now();
25512
+ if (!taskCompleted) {
25513
+ execution.progress = getRoutineProgress(execution);
24970
25514
  execution.lastUpdatedAt = Date.now();
24971
- break;
25515
+ onTaskFailed?.(execution.plan.tasks[taskIndex], execution);
25516
+ if (failureMode === "fail-fast") {
25517
+ execution.status = "failed";
25518
+ execution.error = `Task "${getTask().name}" failed after ${getTask().attempts} attempt(s)`;
25519
+ execution.completedAt = Date.now();
25520
+ execution.lastUpdatedAt = Date.now();
25521
+ break;
25522
+ }
25523
+ }
25524
+ } finally {
25525
+ try {
25526
+ agent.unregisterHook("pause:check", iterationLimiter);
25527
+ } catch {
24972
25528
  }
24973
25529
  }
24974
- agent.unregisterHook("pause:check", iterationLimiter);
24975
25530
  agent.clearConversation("task-boundary");
24976
25531
  nextTasks = getNextExecutableTasks(execution.plan);
24977
25532
  }
@@ -24999,16 +25554,22 @@ async function executeRoutine(options) {
24999
25554
  } finally {
25000
25555
  try {
25001
25556
  await cleanupRoutineContext(agent);
25002
- } catch {
25557
+ } catch (e) {
25558
+ log.debug({ error: e.message }, "Failed to clean up routine context");
25003
25559
  }
25004
25560
  for (const { name, hook } of registeredHooks) {
25005
25561
  try {
25006
25562
  agent.unregisterHook(name, hook);
25007
- } catch {
25563
+ } catch (e) {
25564
+ log.debug({ hookName: name, error: e.message }, "Failed to unregister hook");
25008
25565
  }
25009
25566
  }
25010
25567
  if (ownsAgent) {
25011
- agent.destroy();
25568
+ try {
25569
+ agent.destroy();
25570
+ } catch (e) {
25571
+ log.debug({ error: e.message }, "Failed to destroy agent");
25572
+ }
25012
25573
  }
25013
25574
  }
25014
25575
  }
@@ -31815,7 +32376,7 @@ var TextToSpeech = class _TextToSpeech {
31815
32376
  */
31816
32377
  async toFile(text, filePath, options) {
31817
32378
  const response = await this.synthesize(text, options);
31818
- await fs18__namespace.writeFile(filePath, response.audio);
32379
+ await fs17__namespace.writeFile(filePath, response.audio);
31819
32380
  }
31820
32381
  // ======================== Introspection Methods ========================
31821
32382
  /**
@@ -32163,7 +32724,7 @@ var SpeechToText = class _SpeechToText {
32163
32724
  * @param options - Optional transcription parameters
32164
32725
  */
32165
32726
  async transcribeFile(filePath, options) {
32166
- const audio = await fs18__namespace.readFile(filePath);
32727
+ const audio = await fs17__namespace.readFile(filePath);
32167
32728
  return this.transcribe(audio, options);
32168
32729
  }
32169
32730
  /**
@@ -36309,7 +36870,7 @@ var DocumentReader = class _DocumentReader {
36309
36870
  async resolveSource(source) {
36310
36871
  switch (source.type) {
36311
36872
  case "file": {
36312
- const buffer = await fs18.readFile(source.path);
36873
+ const buffer = await fs17.readFile(source.path);
36313
36874
  const filename = source.path.split("/").pop() || source.path;
36314
36875
  return { buffer, filename };
36315
36876
  }
@@ -38906,10 +39467,10 @@ var FileMediaStorage = class {
38906
39467
  }
38907
39468
  async save(data, metadata) {
38908
39469
  const dir = metadata.userId ? path2__namespace.join(this.outputDir, metadata.userId) : this.outputDir;
38909
- await fs18__namespace.mkdir(dir, { recursive: true });
39470
+ await fs17__namespace.mkdir(dir, { recursive: true });
38910
39471
  const filename = metadata.suggestedFilename ?? this.generateFilename(metadata);
38911
39472
  const filePath = path2__namespace.join(dir, filename);
38912
- await fs18__namespace.writeFile(filePath, data);
39473
+ await fs17__namespace.writeFile(filePath, data);
38913
39474
  const format = metadata.format.toLowerCase();
38914
39475
  const mimeType = MIME_TYPES2[format] ?? "application/octet-stream";
38915
39476
  return {
@@ -38920,7 +39481,7 @@ var FileMediaStorage = class {
38920
39481
  }
38921
39482
  async read(location) {
38922
39483
  try {
38923
- return await fs18__namespace.readFile(location);
39484
+ return await fs17__namespace.readFile(location);
38924
39485
  } catch (err) {
38925
39486
  if (err.code === "ENOENT") {
38926
39487
  return null;
@@ -38930,7 +39491,7 @@ var FileMediaStorage = class {
38930
39491
  }
38931
39492
  async delete(location) {
38932
39493
  try {
38933
- await fs18__namespace.unlink(location);
39494
+ await fs17__namespace.unlink(location);
38934
39495
  } catch (err) {
38935
39496
  if (err.code === "ENOENT") {
38936
39497
  return;
@@ -38940,7 +39501,7 @@ var FileMediaStorage = class {
38940
39501
  }
38941
39502
  async exists(location) {
38942
39503
  try {
38943
- await fs18__namespace.access(location);
39504
+ await fs17__namespace.access(location);
38944
39505
  return true;
38945
39506
  } catch {
38946
39507
  return false;
@@ -38949,11 +39510,11 @@ var FileMediaStorage = class {
38949
39510
  async list(options) {
38950
39511
  await this.ensureDir();
38951
39512
  let entries = [];
38952
- const files = await fs18__namespace.readdir(this.outputDir);
39513
+ const files = await fs17__namespace.readdir(this.outputDir);
38953
39514
  for (const file of files) {
38954
39515
  const filePath = path2__namespace.join(this.outputDir, file);
38955
39516
  try {
38956
- const stat6 = await fs18__namespace.stat(filePath);
39517
+ const stat6 = await fs17__namespace.stat(filePath);
38957
39518
  if (!stat6.isFile()) continue;
38958
39519
  const ext = path2__namespace.extname(file).slice(1).toLowerCase();
38959
39520
  const mimeType = MIME_TYPES2[ext] ?? "application/octet-stream";
@@ -38993,7 +39554,7 @@ var FileMediaStorage = class {
38993
39554
  }
38994
39555
  async ensureDir() {
38995
39556
  if (!this.initialized) {
38996
- await fs18__namespace.mkdir(this.outputDir, { recursive: true });
39557
+ await fs17__namespace.mkdir(this.outputDir, { recursive: true });
38997
39558
  this.initialized = true;
38998
39559
  }
38999
39560
  }
@@ -39845,6 +40406,15 @@ var SERVICE_DEFINITIONS = [
39845
40406
  baseURL: "https://api.telegram.org",
39846
40407
  docsURL: "https://core.telegram.org/bots/api"
39847
40408
  },
40409
+ {
40410
+ id: "twitter",
40411
+ name: "X (Twitter)",
40412
+ category: "communication",
40413
+ urlPattern: /api\.x\.com|api\.twitter\.com/i,
40414
+ baseURL: "https://api.x.com/2",
40415
+ docsURL: "https://developer.x.com/en/docs/x-api",
40416
+ commonScopes: ["tweet.read", "tweet.write", "users.read", "offline.access"]
40417
+ },
39848
40418
  // ============ Development & Project Management ============
39849
40419
  {
39850
40420
  id: "github",
@@ -40332,19 +40902,30 @@ var ConnectorTools = class {
40332
40902
  */
40333
40903
  static for(connectorOrName, userId, options) {
40334
40904
  const connector = this.resolveConnector(connectorOrName, options?.registry);
40905
+ const accountId = options?.accountId;
40335
40906
  const tools = [];
40907
+ const namePrefix = accountId ? `${sanitizeToolName(connector.name)}_${sanitizeToolName(accountId)}` : sanitizeToolName(connector.name);
40336
40908
  if (connector.baseURL) {
40337
- tools.push(this.createGenericAPITool(connector, { userId }));
40909
+ const accountLabel = accountId ? ` (account: ${accountId})` : "";
40910
+ tools.push(this.createGenericAPITool(connector, {
40911
+ userId,
40912
+ accountId,
40913
+ toolName: `${namePrefix}_api`,
40914
+ description: `Make an authenticated API call to ${connector.displayName}${accountLabel}.` + (connector.baseURL ? ` Base URL: ${connector.baseURL}.` : " Provide full URL in endpoint.") + ' IMPORTANT: For POST/PUT/PATCH requests, pass data in the "body" parameter as a JSON object, NOT as query string parameters in the endpoint URL. The body is sent as application/json.'
40915
+ }));
40338
40916
  }
40339
40917
  const serviceType = this.detectService(connector);
40340
40918
  if (serviceType && this.factories.has(serviceType)) {
40341
40919
  const factory = this.factories.get(serviceType);
40342
40920
  const serviceTools = factory(connector, userId);
40343
40921
  for (const tool of serviceTools) {
40344
- tool.definition.function.name = `${sanitizeToolName(connector.name)}_${tool.definition.function.name}`;
40922
+ tool.definition.function.name = `${namePrefix}_${tool.definition.function.name}`;
40345
40923
  }
40346
40924
  tools.push(...serviceTools);
40347
40925
  }
40926
+ if (accountId) {
40927
+ return tools.map((tool) => this.bindAccountId(tool, accountId));
40928
+ }
40348
40929
  return tools;
40349
40930
  }
40350
40931
  /**
@@ -40494,6 +41075,56 @@ var ConnectorTools = class {
40494
41075
  }
40495
41076
  return connectorOrName;
40496
41077
  }
41078
+ /**
41079
+ * Generate tools for a set of auth identities.
41080
+ * Each identity gets its own tool set with unique name prefixes.
41081
+ *
41082
+ * @param identities - Array of auth identities
41083
+ * @param userId - Optional user ID for multi-user OAuth
41084
+ * @param options - Optional registry for scoped connector lookup
41085
+ * @returns Map of identity key to tool array
41086
+ *
41087
+ * @example
41088
+ * ```typescript
41089
+ * const toolsByIdentity = ConnectorTools.forIdentities([
41090
+ * { connector: 'microsoft', accountId: 'work' },
41091
+ * { connector: 'microsoft', accountId: 'personal' },
41092
+ * { connector: 'github' },
41093
+ * ]);
41094
+ * // Keys: 'microsoft:work', 'microsoft:personal', 'github'
41095
+ * ```
41096
+ */
41097
+ static forIdentities(identities, userId, options) {
41098
+ const result = /* @__PURE__ */ new Map();
41099
+ for (const identity of identities) {
41100
+ const key = identity.accountId ? `${identity.connector}:${identity.accountId}` : identity.connector;
41101
+ try {
41102
+ const tools = this.for(identity.connector, userId, {
41103
+ registry: options?.registry,
41104
+ accountId: identity.accountId
41105
+ });
41106
+ if (tools.length > 0) {
41107
+ result.set(key, tools);
41108
+ }
41109
+ } catch (err) {
41110
+ exports.logger.error(`[ConnectorTools.forIdentities] Error generating tools for identity ${key}: ${err instanceof Error ? err.message : String(err)}`);
41111
+ }
41112
+ }
41113
+ return result;
41114
+ }
41115
+ /**
41116
+ * Wrap a tool to inject accountId into ToolContext at execute time.
41117
+ * This allows identity-bound tools to use the correct account without
41118
+ * modifying every service tool factory.
41119
+ */
41120
+ static bindAccountId(tool, accountId) {
41121
+ return {
41122
+ ...tool,
41123
+ execute: async (args, context) => {
41124
+ return tool.execute(args, { ...context, accountId });
41125
+ }
41126
+ };
41127
+ }
40497
41128
  static createGenericAPITool(connector, options) {
40498
41129
  const toolName = options?.toolName ?? `${sanitizeToolName(connector.name)}_api`;
40499
41130
  const userId = options?.userId;
@@ -40535,6 +41166,7 @@ var ConnectorTools = class {
40535
41166
  },
40536
41167
  execute: async (args, context) => {
40537
41168
  const effectiveUserId = context?.userId ?? userId;
41169
+ const effectiveAccountId = context?.accountId;
40538
41170
  let url2 = args.endpoint;
40539
41171
  if (args.queryParams && Object.keys(args.queryParams).length > 0) {
40540
41172
  const params = new URLSearchParams();
@@ -40567,7 +41199,8 @@ var ConnectorTools = class {
40567
41199
  },
40568
41200
  body: bodyStr
40569
41201
  },
40570
- effectiveUserId
41202
+ effectiveUserId,
41203
+ effectiveAccountId
40571
41204
  );
40572
41205
  const text = await response.text();
40573
41206
  let data;
@@ -40628,8 +41261,8 @@ var FileStorage = class {
40628
41261
  }
40629
41262
  async ensureDirectory() {
40630
41263
  try {
40631
- await fs18__namespace.mkdir(this.directory, { recursive: true });
40632
- await fs18__namespace.chmod(this.directory, 448);
41264
+ await fs17__namespace.mkdir(this.directory, { recursive: true });
41265
+ await fs17__namespace.chmod(this.directory, 448);
40633
41266
  } catch (error) {
40634
41267
  }
40635
41268
  }
@@ -40643,24 +41276,27 @@ var FileStorage = class {
40643
41276
  async storeToken(key, token) {
40644
41277
  await this.ensureDirectory();
40645
41278
  const filePath = this.getFilePath(key);
40646
- const plaintext = JSON.stringify(token);
41279
+ const tokenWithKey = { ...token, _storageKey: key };
41280
+ const plaintext = JSON.stringify(tokenWithKey);
40647
41281
  const encrypted = encrypt(plaintext, this.encryptionKey);
40648
- await fs18__namespace.writeFile(filePath, encrypted, "utf8");
40649
- await fs18__namespace.chmod(filePath, 384);
41282
+ await fs17__namespace.writeFile(filePath, encrypted, "utf8");
41283
+ await fs17__namespace.chmod(filePath, 384);
40650
41284
  }
40651
41285
  async getToken(key) {
40652
41286
  const filePath = this.getFilePath(key);
40653
41287
  try {
40654
- const encrypted = await fs18__namespace.readFile(filePath, "utf8");
41288
+ const encrypted = await fs17__namespace.readFile(filePath, "utf8");
40655
41289
  const decrypted = decrypt(encrypted, this.encryptionKey);
40656
- return JSON.parse(decrypted);
41290
+ const parsed = JSON.parse(decrypted);
41291
+ const { _storageKey, ...token } = parsed;
41292
+ return token;
40657
41293
  } catch (error) {
40658
41294
  if (error.code === "ENOENT") {
40659
41295
  return null;
40660
41296
  }
40661
41297
  console.error("Failed to read/decrypt token file:", error);
40662
41298
  try {
40663
- await fs18__namespace.unlink(filePath);
41299
+ await fs17__namespace.unlink(filePath);
40664
41300
  } catch {
40665
41301
  }
40666
41302
  return null;
@@ -40669,7 +41305,7 @@ var FileStorage = class {
40669
41305
  async deleteToken(key) {
40670
41306
  const filePath = this.getFilePath(key);
40671
41307
  try {
40672
- await fs18__namespace.unlink(filePath);
41308
+ await fs17__namespace.unlink(filePath);
40673
41309
  } catch (error) {
40674
41310
  if (error.code !== "ENOENT") {
40675
41311
  throw error;
@@ -40679,18 +41315,44 @@ var FileStorage = class {
40679
41315
  async hasToken(key) {
40680
41316
  const filePath = this.getFilePath(key);
40681
41317
  try {
40682
- await fs18__namespace.access(filePath);
41318
+ await fs17__namespace.access(filePath);
40683
41319
  return true;
40684
41320
  } catch {
40685
41321
  return false;
40686
41322
  }
40687
41323
  }
41324
+ /**
41325
+ * List all storage keys by decrypting each token file and reading _storageKey.
41326
+ * Falls back to returning hashed filenames for tokens stored before multi-account support.
41327
+ */
41328
+ async listKeys() {
41329
+ try {
41330
+ const files = await fs17__namespace.readdir(this.directory);
41331
+ const tokenFiles = files.filter((f) => f.endsWith(".token"));
41332
+ const keys = [];
41333
+ for (const file of tokenFiles) {
41334
+ try {
41335
+ const filePath = path2__namespace.join(this.directory, file);
41336
+ const encrypted = await fs17__namespace.readFile(filePath, "utf8");
41337
+ const decrypted = decrypt(encrypted, this.encryptionKey);
41338
+ const parsed = JSON.parse(decrypted);
41339
+ if (parsed._storageKey) {
41340
+ keys.push(parsed._storageKey);
41341
+ }
41342
+ } catch {
41343
+ }
41344
+ }
41345
+ return keys;
41346
+ } catch {
41347
+ return [];
41348
+ }
41349
+ }
40688
41350
  /**
40689
41351
  * List all token keys (for debugging)
40690
41352
  */
40691
41353
  async listTokens() {
40692
41354
  try {
40693
- const files = await fs18__namespace.readdir(this.directory);
41355
+ const files = await fs17__namespace.readdir(this.directory);
40694
41356
  return files.filter((f) => f.endsWith(".token")).map((f) => f.replace(".token", ""));
40695
41357
  } catch {
40696
41358
  return [];
@@ -40701,10 +41363,10 @@ var FileStorage = class {
40701
41363
  */
40702
41364
  async clearAll() {
40703
41365
  try {
40704
- const files = await fs18__namespace.readdir(this.directory);
41366
+ const files = await fs17__namespace.readdir(this.directory);
40705
41367
  const tokenFiles = files.filter((f) => f.endsWith(".token"));
40706
41368
  await Promise.all(
40707
- tokenFiles.map((f) => fs18__namespace.unlink(path2__namespace.join(this.directory, f)).catch(() => {
41369
+ tokenFiles.map((f) => fs17__namespace.unlink(path2__namespace.join(this.directory, f)).catch(() => {
40708
41370
  }))
40709
41371
  );
40710
41372
  } catch {
@@ -40714,14 +41376,14 @@ var FileStorage = class {
40714
41376
 
40715
41377
  // src/connectors/authenticatedFetch.ts
40716
41378
  init_Connector();
40717
- async function authenticatedFetch(url2, options, authProvider, userId) {
41379
+ async function authenticatedFetch(url2, options, authProvider, userId, accountId) {
40718
41380
  const connector = exports.Connector.get(authProvider);
40719
- return connector.fetch(url2.toString(), options, userId);
41381
+ return connector.fetch(url2.toString(), options, userId, accountId);
40720
41382
  }
40721
- function createAuthenticatedFetch(authProvider, userId) {
41383
+ function createAuthenticatedFetch(authProvider, userId, accountId) {
40722
41384
  const connector = exports.Connector.get(authProvider);
40723
41385
  return async (url2, options) => {
40724
- return connector.fetch(url2.toString(), options, userId);
41386
+ return connector.fetch(url2.toString(), options, userId, accountId);
40725
41387
  };
40726
41388
  }
40727
41389
 
@@ -41152,14 +41814,14 @@ var FileConnectorStorage = class {
41152
41814
  await this.ensureDirectory();
41153
41815
  const filePath = this.getFilePath(name);
41154
41816
  const json = JSON.stringify(stored, null, 2);
41155
- await fs18__namespace.writeFile(filePath, json, "utf8");
41156
- await fs18__namespace.chmod(filePath, 384);
41817
+ await fs17__namespace.writeFile(filePath, json, "utf8");
41818
+ await fs17__namespace.chmod(filePath, 384);
41157
41819
  await this.updateIndex(name, "add");
41158
41820
  }
41159
41821
  async get(name) {
41160
41822
  const filePath = this.getFilePath(name);
41161
41823
  try {
41162
- const json = await fs18__namespace.readFile(filePath, "utf8");
41824
+ const json = await fs17__namespace.readFile(filePath, "utf8");
41163
41825
  return JSON.parse(json);
41164
41826
  } catch (error) {
41165
41827
  const err = error;
@@ -41172,7 +41834,7 @@ var FileConnectorStorage = class {
41172
41834
  async delete(name) {
41173
41835
  const filePath = this.getFilePath(name);
41174
41836
  try {
41175
- await fs18__namespace.unlink(filePath);
41837
+ await fs17__namespace.unlink(filePath);
41176
41838
  await this.updateIndex(name, "remove");
41177
41839
  return true;
41178
41840
  } catch (error) {
@@ -41186,7 +41848,7 @@ var FileConnectorStorage = class {
41186
41848
  async has(name) {
41187
41849
  const filePath = this.getFilePath(name);
41188
41850
  try {
41189
- await fs18__namespace.access(filePath);
41851
+ await fs17__namespace.access(filePath);
41190
41852
  return true;
41191
41853
  } catch {
41192
41854
  return false;
@@ -41212,13 +41874,13 @@ var FileConnectorStorage = class {
41212
41874
  */
41213
41875
  async clear() {
41214
41876
  try {
41215
- const files = await fs18__namespace.readdir(this.directory);
41877
+ const files = await fs17__namespace.readdir(this.directory);
41216
41878
  const connectorFiles = files.filter(
41217
41879
  (f) => f.endsWith(".connector.json") || f === "_index.json"
41218
41880
  );
41219
41881
  await Promise.all(
41220
41882
  connectorFiles.map(
41221
- (f) => fs18__namespace.unlink(path2__namespace.join(this.directory, f)).catch(() => {
41883
+ (f) => fs17__namespace.unlink(path2__namespace.join(this.directory, f)).catch(() => {
41222
41884
  })
41223
41885
  )
41224
41886
  );
@@ -41245,8 +41907,8 @@ var FileConnectorStorage = class {
41245
41907
  async ensureDirectory() {
41246
41908
  if (this.initialized) return;
41247
41909
  try {
41248
- await fs18__namespace.mkdir(this.directory, { recursive: true });
41249
- await fs18__namespace.chmod(this.directory, 448);
41910
+ await fs17__namespace.mkdir(this.directory, { recursive: true });
41911
+ await fs17__namespace.chmod(this.directory, 448);
41250
41912
  this.initialized = true;
41251
41913
  } catch {
41252
41914
  this.initialized = true;
@@ -41257,7 +41919,7 @@ var FileConnectorStorage = class {
41257
41919
  */
41258
41920
  async loadIndex() {
41259
41921
  try {
41260
- const json = await fs18__namespace.readFile(this.indexPath, "utf8");
41922
+ const json = await fs17__namespace.readFile(this.indexPath, "utf8");
41261
41923
  return JSON.parse(json);
41262
41924
  } catch {
41263
41925
  return { connectors: {} };
@@ -41275,8 +41937,8 @@ var FileConnectorStorage = class {
41275
41937
  delete index.connectors[hash];
41276
41938
  }
41277
41939
  const json = JSON.stringify(index, null, 2);
41278
- await fs18__namespace.writeFile(this.indexPath, json, "utf8");
41279
- await fs18__namespace.chmod(this.indexPath, 384);
41940
+ await fs17__namespace.writeFile(this.indexPath, json, "utf8");
41941
+ await fs17__namespace.chmod(this.indexPath, 384);
41280
41942
  }
41281
41943
  };
41282
41944
 
@@ -41499,14 +42161,15 @@ var microsoftTemplate = {
41499
42161
  name: "OAuth (Delegated Permissions)",
41500
42162
  type: "oauth",
41501
42163
  flow: "authorization_code",
41502
- description: "User signs in with Microsoft account. Best for accessing user data (mail, calendar, files)",
41503
- requiredFields: ["clientId", "clientSecret", "redirectUri", "tenantId"],
41504
- optionalFields: ["scope"],
42164
+ description: "User signs in with Microsoft account. Best for accessing user data (mail, calendar, files). Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42165
+ requiredFields: ["clientId", "redirectUri", "tenantId"],
42166
+ optionalFields: ["clientSecret", "scope"],
41505
42167
  defaults: {
41506
42168
  type: "oauth",
41507
42169
  flow: "authorization_code",
41508
42170
  authorizationUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
41509
- tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"
42171
+ tokenUrl: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
42172
+ usePKCE: true
41510
42173
  },
41511
42174
  scopes: [
41512
42175
  "User.Read",
@@ -41592,14 +42255,15 @@ var googleTemplate = {
41592
42255
  name: "OAuth (User Consent)",
41593
42256
  type: "oauth",
41594
42257
  flow: "authorization_code",
41595
- description: "User logs in with Google account. Best for accessing user data (Drive, Gmail, Calendar)",
41596
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41597
- optionalFields: ["scope"],
42258
+ description: "User logs in with Google account. Best for accessing user data (Drive, Gmail, Calendar). Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42259
+ requiredFields: ["clientId", "redirectUri"],
42260
+ optionalFields: ["clientSecret", "scope"],
41598
42261
  defaults: {
41599
42262
  type: "oauth",
41600
42263
  flow: "authorization_code",
41601
42264
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
41602
- tokenUrl: "https://oauth2.googleapis.com/token"
42265
+ tokenUrl: "https://oauth2.googleapis.com/token",
42266
+ usePKCE: true
41603
42267
  },
41604
42268
  scopes: [
41605
42269
  "https://www.googleapis.com/auth/drive",
@@ -41678,14 +42342,15 @@ var slackTemplate = {
41678
42342
  name: "OAuth (User Token)",
41679
42343
  type: "oauth",
41680
42344
  flow: "authorization_code",
41681
- description: "Distributed app - users authorize via Slack OAuth",
41682
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41683
- optionalFields: ["scope", "userScope"],
42345
+ description: "Distributed app - users authorize via Slack OAuth. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42346
+ requiredFields: ["clientId", "redirectUri"],
42347
+ optionalFields: ["clientSecret", "scope", "userScope"],
41684
42348
  defaults: {
41685
42349
  type: "oauth",
41686
42350
  flow: "authorization_code",
41687
42351
  authorizationUrl: "https://slack.com/oauth/v2/authorize",
41688
- tokenUrl: "https://slack.com/api/oauth.v2.access"
42352
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
42353
+ usePKCE: true
41689
42354
  },
41690
42355
  scopes: ["chat:write", "channels:read", "users:read", "im:write", "groups:read", "files:read", "files:write", "reactions:read", "reactions:write", "team:read"],
41691
42356
  scopeDescriptions: {
@@ -41731,14 +42396,15 @@ var discordTemplate = {
41731
42396
  name: "OAuth (User Token)",
41732
42397
  type: "oauth",
41733
42398
  flow: "authorization_code",
41734
- description: "OAuth2 for user authorization - users grant permissions to your app",
41735
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41736
- optionalFields: ["scope"],
42399
+ description: "OAuth2 for user authorization - users grant permissions to your app. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42400
+ requiredFields: ["clientId", "redirectUri"],
42401
+ optionalFields: ["clientSecret", "scope"],
41737
42402
  defaults: {
41738
42403
  type: "oauth",
41739
42404
  flow: "authorization_code",
41740
42405
  authorizationUrl: "https://discord.com/api/oauth2/authorize",
41741
- tokenUrl: "https://discord.com/api/oauth2/token"
42406
+ tokenUrl: "https://discord.com/api/oauth2/token",
42407
+ usePKCE: true
41742
42408
  },
41743
42409
  scopes: ["identify", "email", "guilds", "guilds.members.read", "messages.read", "bot", "connections"],
41744
42410
  scopeDescriptions: {
@@ -41780,6 +42446,92 @@ var telegramTemplate = {
41780
42446
  ]
41781
42447
  };
41782
42448
 
42449
+ // src/connectors/vendors/templates/twitter.ts
42450
+ var twitterTemplate = {
42451
+ id: "twitter",
42452
+ name: "X (Twitter)",
42453
+ serviceType: "twitter",
42454
+ baseURL: "https://api.x.com/2",
42455
+ docsURL: "https://developer.x.com/en/docs/x-api",
42456
+ credentialsSetupURL: "https://developer.x.com/en/portal/dashboard",
42457
+ category: "communication",
42458
+ notes: "X (formerly Twitter) API v2. OAuth 2.0 with PKCE for user-context actions, Bearer Token for app-only access.",
42459
+ authTemplates: [
42460
+ {
42461
+ id: "oauth-user",
42462
+ name: "OAuth 2.0 (User Context)",
42463
+ type: "oauth",
42464
+ flow: "authorization_code",
42465
+ description: "User authorizes via X login - required for posting tweets, managing likes/follows, and accessing private data. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42466
+ requiredFields: ["clientId", "redirectUri"],
42467
+ optionalFields: ["clientSecret", "scope"],
42468
+ defaults: {
42469
+ type: "oauth",
42470
+ flow: "authorization_code",
42471
+ authorizationUrl: "https://x.com/i/oauth2/authorize",
42472
+ tokenUrl: "https://api.x.com/2/oauth2/token",
42473
+ usePKCE: true
42474
+ },
42475
+ scopes: [
42476
+ "tweet.read",
42477
+ "tweet.write",
42478
+ "tweet.moderate.write",
42479
+ "users.read",
42480
+ "follows.read",
42481
+ "follows.write",
42482
+ "like.read",
42483
+ "like.write",
42484
+ "bookmark.read",
42485
+ "bookmark.write",
42486
+ "list.read",
42487
+ "list.write",
42488
+ "block.read",
42489
+ "block.write",
42490
+ "mute.read",
42491
+ "mute.write",
42492
+ "space.read",
42493
+ "dm.read",
42494
+ "dm.write",
42495
+ "offline.access"
42496
+ ],
42497
+ scopeDescriptions: {
42498
+ "tweet.read": "Read tweets and timelines",
42499
+ "tweet.write": "Post and delete tweets",
42500
+ "tweet.moderate.write": "Hide and unhide replies",
42501
+ "users.read": "Read user profile information",
42502
+ "follows.read": "Read following/followers lists",
42503
+ "follows.write": "Follow and unfollow users",
42504
+ "like.read": "Read liked tweets",
42505
+ "like.write": "Like and unlike tweets",
42506
+ "bookmark.read": "Read bookmarked tweets",
42507
+ "bookmark.write": "Bookmark and remove bookmarks",
42508
+ "list.read": "Read lists",
42509
+ "list.write": "Create, edit, and delete lists",
42510
+ "block.read": "Read blocked users",
42511
+ "block.write": "Block and unblock users",
42512
+ "mute.read": "Read muted users",
42513
+ "mute.write": "Mute and unmute users",
42514
+ "space.read": "Read Spaces information",
42515
+ "dm.read": "Read direct messages",
42516
+ "dm.write": "Send direct messages",
42517
+ "offline.access": "Stay connected (refresh token)"
42518
+ }
42519
+ },
42520
+ {
42521
+ id: "bearer-token",
42522
+ name: "Bearer Token (App-Only)",
42523
+ type: "api_key",
42524
+ description: "App-only access using Bearer Token from developer portal. Can read public tweets, users, and spaces but cannot post or access private data.",
42525
+ requiredFields: ["apiKey"],
42526
+ defaults: {
42527
+ type: "api_key",
42528
+ headerName: "Authorization",
42529
+ headerPrefix: "Bearer"
42530
+ }
42531
+ }
42532
+ ]
42533
+ };
42534
+
41783
42535
  // src/connectors/vendors/templates/github.ts
41784
42536
  var githubTemplate = {
41785
42537
  id: "github",
@@ -41807,14 +42559,15 @@ var githubTemplate = {
41807
42559
  name: "OAuth App (User Authorization)",
41808
42560
  type: "oauth",
41809
42561
  flow: "authorization_code",
41810
- description: "User logs in via GitHub and grants permissions to your app",
41811
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41812
- optionalFields: ["scope"],
42562
+ description: "User logs in via GitHub and grants permissions to your app. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42563
+ requiredFields: ["clientId", "redirectUri"],
42564
+ optionalFields: ["clientSecret", "scope"],
41813
42565
  defaults: {
41814
42566
  type: "oauth",
41815
42567
  flow: "authorization_code",
41816
42568
  authorizationUrl: "https://github.com/login/oauth/authorize",
41817
- tokenUrl: "https://github.com/login/oauth/access_token"
42569
+ tokenUrl: "https://github.com/login/oauth/access_token",
42570
+ usePKCE: true
41818
42571
  },
41819
42572
  scopes: ["repo", "read:user", "user:email", "read:org", "workflow", "gist", "notifications", "delete_repo", "admin:org"],
41820
42573
  scopeDescriptions: {
@@ -41873,14 +42626,15 @@ var gitlabTemplate = {
41873
42626
  name: "OAuth (User Authorization)",
41874
42627
  type: "oauth",
41875
42628
  flow: "authorization_code",
41876
- description: "OAuth2 application for user authorization",
41877
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41878
- optionalFields: ["scope"],
42629
+ description: "OAuth2 application for user authorization. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42630
+ requiredFields: ["clientId", "redirectUri"],
42631
+ optionalFields: ["clientSecret", "scope"],
41879
42632
  defaults: {
41880
42633
  type: "oauth",
41881
42634
  flow: "authorization_code",
41882
42635
  authorizationUrl: "https://gitlab.com/oauth/authorize",
41883
- tokenUrl: "https://gitlab.com/oauth/token"
42636
+ tokenUrl: "https://gitlab.com/oauth/token",
42637
+ usePKCE: true
41884
42638
  },
41885
42639
  scopes: ["api", "read_user", "read_repository", "write_repository"],
41886
42640
  scopeDescriptions: {
@@ -41921,14 +42675,15 @@ var jiraTemplate = {
41921
42675
  name: "OAuth 2.0 (3LO)",
41922
42676
  type: "oauth",
41923
42677
  flow: "authorization_code",
41924
- description: "Three-legged OAuth for user authorization. Create app at developer.atlassian.com",
41925
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41926
- optionalFields: ["scope"],
42678
+ description: "Three-legged OAuth for user authorization. Create app at developer.atlassian.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42679
+ requiredFields: ["clientId", "redirectUri"],
42680
+ optionalFields: ["clientSecret", "scope"],
41927
42681
  defaults: {
41928
42682
  type: "oauth",
41929
42683
  flow: "authorization_code",
41930
42684
  authorizationUrl: "https://auth.atlassian.com/authorize",
41931
- tokenUrl: "https://auth.atlassian.com/oauth/token"
42685
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
42686
+ usePKCE: true
41932
42687
  },
41933
42688
  scopes: ["read:jira-work", "write:jira-work", "read:jira-user", "manage:jira-project", "manage:jira-configuration"],
41934
42689
  scopeDescriptions: {
@@ -41968,14 +42723,15 @@ var confluenceTemplate = {
41968
42723
  name: "OAuth 2.0 (3LO)",
41969
42724
  type: "oauth",
41970
42725
  flow: "authorization_code",
41971
- description: "Three-legged OAuth for user authorization",
41972
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
41973
- optionalFields: ["scope"],
42726
+ description: "Three-legged OAuth for user authorization. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42727
+ requiredFields: ["clientId", "redirectUri"],
42728
+ optionalFields: ["clientSecret", "scope"],
41974
42729
  defaults: {
41975
42730
  type: "oauth",
41976
42731
  flow: "authorization_code",
41977
42732
  authorizationUrl: "https://auth.atlassian.com/authorize",
41978
- tokenUrl: "https://auth.atlassian.com/oauth/token"
42733
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
42734
+ usePKCE: true
41979
42735
  },
41980
42736
  scopes: ["read:confluence-content.all", "write:confluence-content", "read:confluence-space.summary", "write:confluence-space", "read:confluence-user"],
41981
42737
  scopeDescriptions: {
@@ -42014,14 +42770,15 @@ var bitbucketTemplate = {
42014
42770
  name: "OAuth Consumer",
42015
42771
  type: "oauth",
42016
42772
  flow: "authorization_code",
42017
- description: "OAuth consumer for user authorization. Create at Workspace Settings > OAuth consumers",
42018
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42019
- optionalFields: ["scope"],
42773
+ description: "OAuth consumer for user authorization. Create at Workspace Settings > OAuth consumers. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42774
+ requiredFields: ["clientId", "redirectUri"],
42775
+ optionalFields: ["clientSecret", "scope"],
42020
42776
  defaults: {
42021
42777
  type: "oauth",
42022
42778
  flow: "authorization_code",
42023
42779
  authorizationUrl: "https://bitbucket.org/site/oauth2/authorize",
42024
- tokenUrl: "https://bitbucket.org/site/oauth2/access_token"
42780
+ tokenUrl: "https://bitbucket.org/site/oauth2/access_token",
42781
+ usePKCE: true
42025
42782
  },
42026
42783
  scopes: ["repository", "repository:write", "pullrequest", "pullrequest:write", "account", "pipeline", "wiki"],
42027
42784
  scopeDescriptions: {
@@ -42063,14 +42820,15 @@ var trelloTemplate = {
42063
42820
  name: "OAuth 1.0a",
42064
42821
  type: "oauth",
42065
42822
  flow: "authorization_code",
42066
- description: "OAuth 1.0a for user authorization (legacy)",
42067
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42068
- optionalFields: ["scope"],
42823
+ description: "OAuth 1.0a for user authorization (legacy). Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42824
+ requiredFields: ["clientId", "redirectUri"],
42825
+ optionalFields: ["clientSecret", "scope"],
42069
42826
  defaults: {
42070
42827
  type: "oauth",
42071
42828
  flow: "authorization_code",
42072
42829
  authorizationUrl: "https://trello.com/1/authorize",
42073
- tokenUrl: "https://trello.com/1/OAuthGetAccessToken"
42830
+ tokenUrl: "https://trello.com/1/OAuthGetAccessToken",
42831
+ usePKCE: true
42074
42832
  },
42075
42833
  scopes: ["read", "write", "account"],
42076
42834
  scopeDescriptions: {
@@ -42110,14 +42868,15 @@ var linearTemplate = {
42110
42868
  name: "OAuth (User Authorization)",
42111
42869
  type: "oauth",
42112
42870
  flow: "authorization_code",
42113
- description: "OAuth application for user authorization. Create at Settings > API > OAuth applications",
42114
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42115
- optionalFields: ["scope"],
42871
+ description: "OAuth application for user authorization. Create at Settings > API > OAuth applications. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42872
+ requiredFields: ["clientId", "redirectUri"],
42873
+ optionalFields: ["clientSecret", "scope"],
42116
42874
  defaults: {
42117
42875
  type: "oauth",
42118
42876
  flow: "authorization_code",
42119
42877
  authorizationUrl: "https://linear.app/oauth/authorize",
42120
- tokenUrl: "https://api.linear.app/oauth/token"
42878
+ tokenUrl: "https://api.linear.app/oauth/token",
42879
+ usePKCE: true
42121
42880
  },
42122
42881
  scopes: ["read", "write", "issues:create", "comments:create"]
42123
42882
  }
@@ -42151,14 +42910,15 @@ var asanaTemplate = {
42151
42910
  name: "OAuth (User Authorization)",
42152
42911
  type: "oauth",
42153
42912
  flow: "authorization_code",
42154
- description: "OAuth application for user authorization. Create at developer console",
42155
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42156
- optionalFields: ["scope"],
42913
+ description: "OAuth application for user authorization. Create at developer console. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42914
+ requiredFields: ["clientId", "redirectUri"],
42915
+ optionalFields: ["clientSecret", "scope"],
42157
42916
  defaults: {
42158
42917
  type: "oauth",
42159
42918
  flow: "authorization_code",
42160
42919
  authorizationUrl: "https://app.asana.com/-/oauth_authorize",
42161
- tokenUrl: "https://app.asana.com/-/oauth_token"
42920
+ tokenUrl: "https://app.asana.com/-/oauth_token",
42921
+ usePKCE: true
42162
42922
  },
42163
42923
  scopes: ["default"]
42164
42924
  }
@@ -42192,14 +42952,15 @@ var notionTemplate = {
42192
42952
  name: "Public Integration (OAuth)",
42193
42953
  type: "oauth",
42194
42954
  flow: "authorization_code",
42195
- description: "Public integration for multi-workspace access",
42196
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42197
- optionalFields: ["scope"],
42955
+ description: "Public integration for multi-workspace access. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42956
+ requiredFields: ["clientId", "redirectUri"],
42957
+ optionalFields: ["clientSecret", "scope"],
42198
42958
  defaults: {
42199
42959
  type: "oauth",
42200
42960
  flow: "authorization_code",
42201
42961
  authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
42202
- tokenUrl: "https://api.notion.com/v1/oauth/token"
42962
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
42963
+ usePKCE: true
42203
42964
  }
42204
42965
  }
42205
42966
  ]
@@ -42232,9 +42993,9 @@ var airtableTemplate = {
42232
42993
  name: "OAuth (User Authorization)",
42233
42994
  type: "oauth",
42234
42995
  flow: "authorization_code",
42235
- description: "OAuth integration for multi-user access. Register at airtable.com/create/oauth",
42236
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42237
- optionalFields: ["scope"],
42996
+ description: "OAuth integration for multi-user access. Register at airtable.com/create/oauth. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
42997
+ requiredFields: ["clientId", "redirectUri"],
42998
+ optionalFields: ["clientSecret", "scope"],
42238
42999
  defaults: {
42239
43000
  type: "oauth",
42240
43001
  flow: "authorization_code",
@@ -42263,14 +43024,15 @@ var salesforceTemplate = {
42263
43024
  name: "OAuth (User Authorization)",
42264
43025
  type: "oauth",
42265
43026
  flow: "authorization_code",
42266
- description: "User logs in via Salesforce. Create Connected App in Setup",
42267
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42268
- optionalFields: ["scope"],
43027
+ description: "User logs in via Salesforce. Create Connected App in Setup. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43028
+ requiredFields: ["clientId", "redirectUri"],
43029
+ optionalFields: ["clientSecret", "scope"],
42269
43030
  defaults: {
42270
43031
  type: "oauth",
42271
43032
  flow: "authorization_code",
42272
43033
  authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
42273
- tokenUrl: "https://login.salesforce.com/services/oauth2/token"
43034
+ tokenUrl: "https://login.salesforce.com/services/oauth2/token",
43035
+ usePKCE: true
42274
43036
  },
42275
43037
  scopes: ["api", "refresh_token", "offline_access", "chatter_api", "wave_api", "full"],
42276
43038
  scopeDescriptions: {
@@ -42326,14 +43088,15 @@ var hubspotTemplate = {
42326
43088
  name: "OAuth (User Authorization)",
42327
43089
  type: "oauth",
42328
43090
  flow: "authorization_code",
42329
- description: "Public app OAuth for multi-portal access. Create app at developers.hubspot.com",
42330
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42331
- optionalFields: ["scope"],
43091
+ description: "Public app OAuth for multi-portal access. Create app at developers.hubspot.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43092
+ requiredFields: ["clientId", "redirectUri"],
43093
+ optionalFields: ["clientSecret", "scope"],
42332
43094
  defaults: {
42333
43095
  type: "oauth",
42334
43096
  flow: "authorization_code",
42335
43097
  authorizationUrl: "https://app.hubspot.com/oauth/authorize",
42336
- tokenUrl: "https://api.hubapi.com/oauth/v1/token"
43098
+ tokenUrl: "https://api.hubapi.com/oauth/v1/token",
43099
+ usePKCE: true
42337
43100
  },
42338
43101
  scopes: [
42339
43102
  "crm.objects.contacts.read",
@@ -42355,6 +43118,22 @@ var hubspotTemplate = {
42355
43118
  "tickets": "Read and write support tickets",
42356
43119
  "e-commerce": "Access e-commerce data (products, line items)"
42357
43120
  }
43121
+ },
43122
+ {
43123
+ id: "oauth-mcp",
43124
+ name: "MCP Auth App (OAuth 2.1)",
43125
+ type: "oauth",
43126
+ flow: "authorization_code",
43127
+ description: "HubSpot MCP Auth app using OAuth 2.1 with PKCE. Scopes are auto-granted based on user permissions at install time. Create app at developers.hubspot.com under MCP Auth Apps.",
43128
+ requiredFields: ["clientId", "redirectUri"],
43129
+ optionalFields: ["clientSecret"],
43130
+ defaults: {
43131
+ type: "oauth",
43132
+ flow: "authorization_code",
43133
+ authorizationUrl: "https://mcp.hubspot.com/oauth/authorize/user",
43134
+ tokenUrl: "https://mcp.hubspot.com/oauth/v1/token",
43135
+ usePKCE: true
43136
+ }
42358
43137
  }
42359
43138
  ]
42360
43139
  };
@@ -42386,14 +43165,15 @@ var pipedriveTemplate = {
42386
43165
  name: "OAuth (App Authorization)",
42387
43166
  type: "oauth",
42388
43167
  flow: "authorization_code",
42389
- description: "OAuth app for marketplace distribution. Create at developers.pipedrive.com",
42390
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42391
- optionalFields: ["scope"],
43168
+ description: "OAuth app for marketplace distribution. Create at developers.pipedrive.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43169
+ requiredFields: ["clientId", "redirectUri"],
43170
+ optionalFields: ["clientSecret", "scope"],
42392
43171
  defaults: {
42393
43172
  type: "oauth",
42394
43173
  flow: "authorization_code",
42395
43174
  authorizationUrl: "https://oauth.pipedrive.com/oauth/authorize",
42396
- tokenUrl: "https://oauth.pipedrive.com/oauth/token"
43175
+ tokenUrl: "https://oauth.pipedrive.com/oauth/token",
43176
+ usePKCE: true
42397
43177
  }
42398
43178
  }
42399
43179
  ]
@@ -42426,14 +43206,15 @@ var stripeTemplate = {
42426
43206
  name: "OAuth (Stripe Connect)",
42427
43207
  type: "oauth",
42428
43208
  flow: "authorization_code",
42429
- description: "Stripe Connect for marketplace platforms. Requires Connect setup in dashboard",
42430
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42431
- optionalFields: ["scope"],
43209
+ description: "Stripe Connect for marketplace platforms. Requires Connect setup in dashboard. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43210
+ requiredFields: ["clientId", "redirectUri"],
43211
+ optionalFields: ["clientSecret", "scope"],
42432
43212
  defaults: {
42433
43213
  type: "oauth",
42434
43214
  flow: "authorization_code",
42435
43215
  authorizationUrl: "https://connect.stripe.com/oauth/authorize",
42436
- tokenUrl: "https://connect.stripe.com/oauth/token"
43216
+ tokenUrl: "https://connect.stripe.com/oauth/token",
43217
+ usePKCE: true
42437
43218
  },
42438
43219
  scopes: ["read_write"]
42439
43220
  }
@@ -42483,14 +43264,15 @@ var quickbooksTemplate = {
42483
43264
  name: "OAuth (User Authorization)",
42484
43265
  type: "oauth",
42485
43266
  flow: "authorization_code",
42486
- description: "Standard OAuth 2.0 flow for accessing QuickBooks on behalf of a user. Create an app at developer.intuit.com",
42487
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42488
- optionalFields: ["scope"],
43267
+ description: "Standard OAuth 2.0 flow for accessing QuickBooks on behalf of a user. Create an app at developer.intuit.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43268
+ requiredFields: ["clientId", "redirectUri"],
43269
+ optionalFields: ["clientSecret", "scope"],
42489
43270
  defaults: {
42490
43271
  type: "oauth",
42491
43272
  flow: "authorization_code",
42492
43273
  authorizationUrl: "https://appcenter.intuit.com/connect/oauth2",
42493
- tokenUrl: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
43274
+ tokenUrl: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
43275
+ usePKCE: true
42494
43276
  },
42495
43277
  scopes: ["com.intuit.quickbooks.accounting", "com.intuit.quickbooks.payment"]
42496
43278
  }
@@ -42525,14 +43307,15 @@ var rampTemplate = {
42525
43307
  name: "OAuth (User Authorization)",
42526
43308
  type: "oauth",
42527
43309
  flow: "authorization_code",
42528
- description: "OAuth 2.0 authorization code flow for accessing Ramp on behalf of a user",
42529
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42530
- optionalFields: ["scope"],
43310
+ description: "OAuth 2.0 authorization code flow for accessing Ramp on behalf of a user. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43311
+ requiredFields: ["clientId", "redirectUri"],
43312
+ optionalFields: ["clientSecret", "scope"],
42531
43313
  defaults: {
42532
43314
  type: "oauth",
42533
43315
  flow: "authorization_code",
42534
43316
  authorizationUrl: "https://app.ramp.com/v1/authorize",
42535
- tokenUrl: "https://api.ramp.com/developer/v1/token"
43317
+ tokenUrl: "https://api.ramp.com/developer/v1/token",
43318
+ usePKCE: true
42536
43319
  },
42537
43320
  scopes: [
42538
43321
  "transactions:read",
@@ -42589,9 +43372,9 @@ var dropboxTemplate = {
42589
43372
  name: "OAuth (User Authorization)",
42590
43373
  type: "oauth",
42591
43374
  flow: "authorization_code",
42592
- description: "OAuth app for user authorization. Create app at dropbox.com/developers/apps",
42593
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42594
- optionalFields: ["scope"],
43375
+ description: "OAuth app for user authorization. Create app at dropbox.com/developers/apps. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43376
+ requiredFields: ["clientId", "redirectUri"],
43377
+ optionalFields: ["clientSecret", "scope"],
42595
43378
  defaults: {
42596
43379
  type: "oauth",
42597
43380
  flow: "authorization_code",
@@ -42628,14 +43411,15 @@ var boxTemplate = {
42628
43411
  name: "OAuth (User Authorization)",
42629
43412
  type: "oauth",
42630
43413
  flow: "authorization_code",
42631
- description: "OAuth 2.0 for user authorization. Create app at developer.box.com/console",
42632
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42633
- optionalFields: ["scope"],
43414
+ description: "OAuth 2.0 for user authorization. Create app at developer.box.com/console. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43415
+ requiredFields: ["clientId", "redirectUri"],
43416
+ optionalFields: ["clientSecret", "scope"],
42634
43417
  defaults: {
42635
43418
  type: "oauth",
42636
43419
  flow: "authorization_code",
42637
43420
  authorizationUrl: "https://account.box.com/api/oauth2/authorize",
42638
- tokenUrl: "https://api.box.com/oauth2/token"
43421
+ tokenUrl: "https://api.box.com/oauth2/token",
43422
+ usePKCE: true
42639
43423
  },
42640
43424
  scopes: ["root_readwrite", "manage_users", "manage_groups", "manage_enterprise"],
42641
43425
  scopeDescriptions: {
@@ -42713,13 +43497,15 @@ var mailchimpTemplate = {
42713
43497
  name: "OAuth (User Authorization)",
42714
43498
  type: "oauth",
42715
43499
  flow: "authorization_code",
42716
- description: "OAuth for multi-account access. Register app at mailchimp.com/developer",
42717
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
43500
+ description: "OAuth for multi-account access. Register app at mailchimp.com/developer. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43501
+ requiredFields: ["clientId", "redirectUri"],
43502
+ optionalFields: ["clientSecret"],
42718
43503
  defaults: {
42719
43504
  type: "oauth",
42720
43505
  flow: "authorization_code",
42721
43506
  authorizationUrl: "https://login.mailchimp.com/oauth2/authorize",
42722
- tokenUrl: "https://login.mailchimp.com/oauth2/token"
43507
+ tokenUrl: "https://login.mailchimp.com/oauth2/token",
43508
+ usePKCE: true
42723
43509
  }
42724
43510
  }
42725
43511
  ]
@@ -42811,14 +43597,15 @@ var pagerdutyTemplate = {
42811
43597
  name: "OAuth (App Authorization)",
42812
43598
  type: "oauth",
42813
43599
  flow: "authorization_code",
42814
- description: "OAuth app for multi-account access. Register at developer.pagerduty.com",
42815
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42816
- optionalFields: ["scope"],
43600
+ description: "OAuth app for multi-account access. Register at developer.pagerduty.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43601
+ requiredFields: ["clientId", "redirectUri"],
43602
+ optionalFields: ["clientSecret", "scope"],
42817
43603
  defaults: {
42818
43604
  type: "oauth",
42819
43605
  flow: "authorization_code",
42820
43606
  authorizationUrl: "https://app.pagerduty.com/oauth/authorize",
42821
- tokenUrl: "https://app.pagerduty.com/oauth/token"
43607
+ tokenUrl: "https://app.pagerduty.com/oauth/token",
43608
+ usePKCE: true
42822
43609
  },
42823
43610
  scopes: ["read", "write"],
42824
43611
  scopeDescriptions: {
@@ -42854,14 +43641,15 @@ var sentryTemplate = {
42854
43641
  name: "OAuth (Integration)",
42855
43642
  type: "oauth",
42856
43643
  flow: "authorization_code",
42857
- description: "OAuth integration. Create at Organization Settings > Integrations",
42858
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
42859
- optionalFields: ["scope"],
43644
+ description: "OAuth integration. Create at Organization Settings > Integrations. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43645
+ requiredFields: ["clientId", "redirectUri"],
43646
+ optionalFields: ["clientSecret", "scope"],
42860
43647
  defaults: {
42861
43648
  type: "oauth",
42862
43649
  flow: "authorization_code",
42863
43650
  authorizationUrl: "https://sentry.io/oauth/authorize/",
42864
- tokenUrl: "https://sentry.io/oauth/token/"
43651
+ tokenUrl: "https://sentry.io/oauth/token/",
43652
+ usePKCE: true
42865
43653
  },
42866
43654
  scopes: ["project:read", "project:write", "event:read", "org:read", "member:read"],
42867
43655
  scopeDescriptions: {
@@ -43058,14 +43846,15 @@ var zendeskTemplate = {
43058
43846
  name: "OAuth (User Authorization)",
43059
43847
  type: "oauth",
43060
43848
  flow: "authorization_code",
43061
- description: "OAuth client for user authorization. Create at Admin > Channels > API > OAuth Clients",
43062
- requiredFields: ["clientId", "clientSecret", "redirectUri", "subdomain"],
43063
- optionalFields: ["scope"],
43849
+ description: "OAuth client for user authorization. Create at Admin > Channels > API > OAuth Clients. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43850
+ requiredFields: ["clientId", "redirectUri", "subdomain"],
43851
+ optionalFields: ["clientSecret", "scope"],
43064
43852
  defaults: {
43065
43853
  type: "oauth",
43066
43854
  flow: "authorization_code",
43067
43855
  authorizationUrl: "https://{subdomain}.zendesk.com/oauth/authorizations/new",
43068
- tokenUrl: "https://{subdomain}.zendesk.com/oauth/tokens"
43856
+ tokenUrl: "https://{subdomain}.zendesk.com/oauth/tokens",
43857
+ usePKCE: true
43069
43858
  },
43070
43859
  scopes: ["read", "write", "tickets:read", "tickets:write"],
43071
43860
  scopeDescriptions: {
@@ -43103,13 +43892,15 @@ var intercomTemplate = {
43103
43892
  name: "OAuth (App Installation)",
43104
43893
  type: "oauth",
43105
43894
  flow: "authorization_code",
43106
- description: "OAuth for Intercom app marketplace distribution",
43107
- requiredFields: ["clientId", "clientSecret", "redirectUri"],
43895
+ description: "OAuth for Intercom app marketplace distribution. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43896
+ requiredFields: ["clientId", "redirectUri"],
43897
+ optionalFields: ["clientSecret"],
43108
43898
  defaults: {
43109
43899
  type: "oauth",
43110
43900
  flow: "authorization_code",
43111
43901
  authorizationUrl: "https://app.intercom.com/oauth",
43112
- tokenUrl: "https://api.intercom.io/auth/eagle/token"
43902
+ tokenUrl: "https://api.intercom.io/auth/eagle/token",
43903
+ usePKCE: true
43113
43904
  }
43114
43905
  }
43115
43906
  ]
@@ -43142,14 +43933,15 @@ var shopifyTemplate = {
43142
43933
  name: "OAuth (Public/Custom App)",
43143
43934
  type: "oauth",
43144
43935
  flow: "authorization_code",
43145
- description: "OAuth for public apps or per-store custom apps. Create at partners.shopify.com",
43146
- requiredFields: ["clientId", "clientSecret", "redirectUri", "subdomain"],
43147
- optionalFields: ["scope"],
43936
+ description: "OAuth for public apps or per-store custom apps. Create at partners.shopify.com. Provide clientSecret for web apps; omit for native/desktop apps (secured via PKCE).",
43937
+ requiredFields: ["clientId", "redirectUri", "subdomain"],
43938
+ optionalFields: ["clientSecret", "scope"],
43148
43939
  defaults: {
43149
43940
  type: "oauth",
43150
43941
  flow: "authorization_code",
43151
43942
  authorizationUrl: "https://{subdomain}.myshopify.com/admin/oauth/authorize",
43152
- tokenUrl: "https://{subdomain}.myshopify.com/admin/oauth/access_token"
43943
+ tokenUrl: "https://{subdomain}.myshopify.com/admin/oauth/access_token",
43944
+ usePKCE: true
43153
43945
  },
43154
43946
  scopes: ["read_products", "write_products", "read_orders", "write_orders", "read_customers", "write_customers", "read_inventory", "write_inventory", "read_fulfillments", "write_fulfillments"],
43155
43947
  scopeDescriptions: {
@@ -43177,6 +43969,7 @@ var allVendorTemplates = [
43177
43969
  slackTemplate,
43178
43970
  discordTemplate,
43179
43971
  telegramTemplate,
43972
+ twitterTemplate,
43180
43973
  // Development
43181
43974
  githubTemplate,
43182
43975
  gitlabTemplate,
@@ -43236,6 +44029,7 @@ var VENDOR_ICON_MAP = {
43236
44029
  discord: "discord",
43237
44030
  slack: "slack",
43238
44031
  telegram: "telegram",
44032
+ twitter: "x",
43239
44033
  "microsoft-teams": "microsoftteams",
43240
44034
  // CRM
43241
44035
  salesforce: "salesforce",
@@ -43300,6 +44094,7 @@ var FALLBACK_PLACEHOLDERS = {
43300
44094
  // Communication (trademark removed)
43301
44095
  slack: { color: "#4A154B", letter: "S" },
43302
44096
  "microsoft-teams": { color: "#6264A7", letter: "T" },
44097
+ twitter: { color: "#000000", letter: "X" },
43303
44098
  // CRM (trademark removed)
43304
44099
  salesforce: { color: "#00A1E0", letter: "S" },
43305
44100
  pipedrive: { color: "#1A1F26", letter: "P" },
@@ -44237,7 +45032,7 @@ EXAMPLES:
44237
45032
  };
44238
45033
  }
44239
45034
  try {
44240
- const stats = await fs18.stat(resolvedPath);
45035
+ const stats = await fs17.stat(resolvedPath);
44241
45036
  if (!stats.isFile()) {
44242
45037
  return {
44243
45038
  success: false,
@@ -44279,7 +45074,7 @@ EXAMPLES:
44279
45074
  } catch {
44280
45075
  }
44281
45076
  }
44282
- const content = await fs18.readFile(resolvedPath, "utf-8");
45077
+ const content = await fs17.readFile(resolvedPath, "utf-8");
44283
45078
  const allLines = content.split("\n");
44284
45079
  const totalLines = allLines.length;
44285
45080
  const startIndex = Math.max(0, offset - 1);
@@ -44388,9 +45183,9 @@ EXAMPLES:
44388
45183
  try {
44389
45184
  const parentDir = path2.dirname(resolvedPath);
44390
45185
  if (!fs19.existsSync(parentDir)) {
44391
- await fs18.mkdir(parentDir, { recursive: true });
45186
+ await fs17.mkdir(parentDir, { recursive: true });
44392
45187
  }
44393
- await fs18.writeFile(resolvedPath, content, "utf-8");
45188
+ await fs17.writeFile(resolvedPath, content, "utf-8");
44394
45189
  return {
44395
45190
  success: true,
44396
45191
  path: file_path,
@@ -44501,7 +45296,7 @@ EXAMPLES:
44501
45296
  };
44502
45297
  }
44503
45298
  try {
44504
- const content = await fs18.readFile(resolvedPath, "utf-8");
45299
+ const content = await fs17.readFile(resolvedPath, "utf-8");
44505
45300
  let occurrences = 0;
44506
45301
  let searchIndex = 0;
44507
45302
  while (true) {
@@ -44540,7 +45335,7 @@ EXAMPLES:
44540
45335
  } else {
44541
45336
  newContent = content.replace(old_string, new_string);
44542
45337
  }
44543
- await fs18.writeFile(resolvedPath, newContent, "utf-8");
45338
+ await fs17.writeFile(resolvedPath, newContent, "utf-8");
44544
45339
  const diffPreview = generateDiffPreview(old_string, new_string);
44545
45340
  return {
44546
45341
  success: true,
@@ -44596,7 +45391,7 @@ async function findFiles(dir, pattern, baseDir, config, results = [], depth = 0)
44596
45391
  return results;
44597
45392
  }
44598
45393
  try {
44599
- const entries = await fs18.readdir(dir, { withFileTypes: true });
45394
+ const entries = await fs17.readdir(dir, { withFileTypes: true });
44600
45395
  for (const entry of entries) {
44601
45396
  if (results.length >= config.maxResults) break;
44602
45397
  const fullPath = path2.join(dir, entry.name);
@@ -44610,7 +45405,7 @@ async function findFiles(dir, pattern, baseDir, config, results = [], depth = 0)
44610
45405
  } else if (entry.isFile()) {
44611
45406
  if (matchGlobPattern(pattern, relativePath)) {
44612
45407
  try {
44613
- const stats = await fs18.stat(fullPath);
45408
+ const stats = await fs17.stat(fullPath);
44614
45409
  results.push({
44615
45410
  path: relativePath,
44616
45411
  mtime: stats.mtimeMs
@@ -44747,7 +45542,7 @@ async function findFilesToSearch(dir, baseDir, config, globPattern, fileType, fi
44747
45542
  return files;
44748
45543
  }
44749
45544
  try {
44750
- const entries = await fs18.readdir(dir, { withFileTypes: true });
45545
+ const entries = await fs17.readdir(dir, { withFileTypes: true });
44751
45546
  for (const entry of entries) {
44752
45547
  const fullPath = path2.join(dir, entry.name);
44753
45548
  if (entry.isDirectory()) {
@@ -44780,7 +45575,7 @@ async function findFilesToSearch(dir, baseDir, config, globPattern, fileType, fi
44780
45575
  async function searchFile(filePath, regex, contextBefore, contextAfter) {
44781
45576
  const matches = [];
44782
45577
  try {
44783
- const content = await fs18.readFile(filePath, "utf-8");
45578
+ const content = await fs17.readFile(filePath, "utf-8");
44784
45579
  const lines = content.split("\n");
44785
45580
  for (let i = 0; i < lines.length; i++) {
44786
45581
  const line = lines[i] ?? "";
@@ -44937,7 +45732,7 @@ WHEN TO USE:
44937
45732
  };
44938
45733
  }
44939
45734
  try {
44940
- const stats = await fs18.stat(resolvedPath);
45735
+ const stats = await fs17.stat(resolvedPath);
44941
45736
  let filesToSearch;
44942
45737
  if (stats.isFile()) {
44943
45738
  filesToSearch = [resolvedPath];
@@ -45025,7 +45820,7 @@ async function listDir(dir, baseDir, config, recursive, filter, maxDepth = 3, cu
45025
45820
  return entries;
45026
45821
  }
45027
45822
  try {
45028
- const dirEntries = await fs18.readdir(dir, { withFileTypes: true });
45823
+ const dirEntries = await fs17.readdir(dir, { withFileTypes: true });
45029
45824
  for (const entry of dirEntries) {
45030
45825
  if (entries.length >= config.maxResults) break;
45031
45826
  const fullPath = path2.join(dir, entry.name);
@@ -45043,7 +45838,7 @@ async function listDir(dir, baseDir, config, recursive, filter, maxDepth = 3, cu
45043
45838
  }
45044
45839
  if (filter === "directories" && !isDir) continue;
45045
45840
  try {
45046
- const stats = await fs18.stat(fullPath);
45841
+ const stats = await fs17.stat(fullPath);
45047
45842
  const dirEntry = {
45048
45843
  name: entry.name,
45049
45844
  path: relativePath,
@@ -45146,7 +45941,7 @@ EXAMPLES:
45146
45941
  };
45147
45942
  }
45148
45943
  try {
45149
- const stats = await fs18.stat(resolvedPath);
45944
+ const stats = await fs17.stat(resolvedPath);
45150
45945
  if (!stats.isDirectory()) {
45151
45946
  return {
45152
45947
  success: false,
@@ -46469,21 +47264,53 @@ registerWebTools();
46469
47264
  init_Connector();
46470
47265
  var DEFAULT_TIMEOUT = 1e4;
46471
47266
  var DEFAULT_MAX_TIMEOUT = 3e4;
46472
- function formatConnectorEntry(c) {
47267
+ function formatConnectorEntry(c, accountId) {
46473
47268
  const parts = [];
46474
47269
  const serviceOrVendor = c.serviceType ?? c.vendor ?? void 0;
46475
47270
  if (serviceOrVendor) parts.push(`Service: ${serviceOrVendor}`);
47271
+ if (accountId) parts.push(`Account: "${accountId}"`);
46476
47272
  if (c.config.description) parts.push(c.config.description);
46477
47273
  if (c.baseURL) parts.push(`URL: ${c.baseURL}`);
47274
+ const label = accountId ? `"${c.name}" account "${accountId}"` : `"${c.name}"`;
46478
47275
  const details = parts.map((p) => ` ${p}`).join("\n");
46479
- return ` \u2022 "${c.name}" (${c.displayName})
47276
+ return ` \u2022 ${label} (${c.displayName})
46480
47277
  ${details}`;
46481
47278
  }
46482
- function generateDescription(context, maxTimeout) {
47279
+ function buildIdentityList(context) {
47280
+ const identities = context?.identities;
46483
47281
  const registry = context?.connectorRegistry ?? exports.Connector.asRegistry();
47282
+ if (identities?.length) {
47283
+ const entries = [];
47284
+ for (const id of identities) {
47285
+ try {
47286
+ const connector = registry.get(id.connector);
47287
+ entries.push(formatConnectorEntry(connector, id.accountId));
47288
+ } catch {
47289
+ entries.push(` \u2022 "${id.connector}"${id.accountId ? ` account "${id.accountId}"` : ""} \u2014 not available`);
47290
+ }
47291
+ }
47292
+ return entries.length > 0 ? entries.join("\n\n") : " No connectors registered.";
47293
+ }
46484
47294
  const connectors = registry.listAll();
46485
- const connectorList = connectors.length > 0 ? connectors.map(formatConnectorEntry).join("\n\n") : " No connectors registered.";
47295
+ return connectors.length > 0 ? connectors.map((c) => formatConnectorEntry(c)).join("\n\n") : " No connectors registered.";
47296
+ }
47297
+ function hasAccountIds(context) {
47298
+ return !!context?.identities?.some((id) => id.accountId);
47299
+ }
47300
+ function generateDescription(context, maxTimeout) {
47301
+ const connectorList = buildIdentityList(context);
47302
+ const showAccountId = hasAccountIds(context);
46486
47303
  const timeoutSec = Math.round(maxTimeout / 1e3);
47304
+ const accountIdParam = showAccountId ? `
47305
+ \u2022 accountId (optional): Account alias for multi-account connectors.
47306
+ Required when a connector has multiple accounts (see list below).
47307
+ Example: authenticatedFetch('/v1.0/me', {}, 'microsoft', 'work')` : "";
47308
+ const accountIdExamples = showAccountId ? `
47309
+ // Multi-account: specify accountId for connectors with multiple accounts
47310
+ const resp = await authenticatedFetch('/v1.0/me', { method: 'GET' }, 'microsoft', 'work');
47311
+ const profile = await resp.json();
47312
+ output = profile;
47313
+ ` : "";
46487
47314
  return `Execute JavaScript code in a secure sandbox with authenticated API access to external services.
46488
47315
 
46489
47316
  Use this tool when you need to:
@@ -46494,7 +47321,7 @@ Use this tool when you need to:
46494
47321
 
46495
47322
  SANDBOX API:
46496
47323
 
46497
- 1. authenticatedFetch(url, options, connectorName)
47324
+ 1. authenticatedFetch(url, options, connectorName${showAccountId ? ", accountId?" : ""})
46498
47325
  Makes authenticated HTTP requests using the connector's credentials.
46499
47326
  The current user's identity (userId) is automatically included \u2014 no need to pass it.
46500
47327
  Auth headers are added automatically \u2014 DO NOT set Authorization header manually.
@@ -46505,7 +47332,7 @@ SANDBOX API:
46505
47332
  - Relative: "/user/repos" (resolved against connector's base URL)
46506
47333
  \u2022 options: Standard fetch options { method, headers, body }
46507
47334
  - For POST/PUT: set body to JSON.stringify(data) and headers to { 'Content-Type': 'application/json' }
46508
- \u2022 connectorName: Name of a registered connector (see list below)
47335
+ \u2022 connectorName: Name of a registered connector (see list below)${accountIdParam}
46509
47336
 
46510
47337
  Returns: Promise<Response>
46511
47338
  \u2022 response.ok \u2014 true if status 200-299
@@ -46541,7 +47368,7 @@ const resp = await authenticatedFetch('/chat.postMessage', {
46541
47368
  body: JSON.stringify({ channel: '#general', text: 'Hello!' })
46542
47369
  }, 'slack');
46543
47370
  output = await resp.json();
46544
-
47371
+ ${accountIdExamples}
46545
47372
  // Data processing (no API needed)
46546
47373
  const items = input.data;
46547
47374
  output = items.filter(i => i.score > 0.8).sort((a, b) => b.score - a.score);
@@ -46629,9 +47456,10 @@ async function executeInVM(code, input, timeout, logs, userId, registry) {
46629
47456
  },
46630
47457
  // Authenticated fetch — userId auto-injected from ToolContext.
46631
47458
  // Only connectors visible in the scoped registry are accessible.
46632
- authenticatedFetch: (url2, options, connectorName) => {
47459
+ // Optional 4th param accountId for multi-account OAuth identities.
47460
+ authenticatedFetch: (url2, options, connectorName, accountId) => {
46633
47461
  registry.get(connectorName);
46634
- return authenticatedFetch(url2, options, connectorName, userId);
47462
+ return authenticatedFetch(url2, options, connectorName, userId, accountId);
46635
47463
  },
46636
47464
  // Standard fetch (no auth)
46637
47465
  fetch: globalThis.fetch,
@@ -47319,7 +48147,8 @@ async function githubFetch(connector, endpoint, options) {
47319
48147
  headers,
47320
48148
  body: options?.body ? JSON.stringify(options.body) : void 0
47321
48149
  },
47322
- options?.userId
48150
+ options?.userId,
48151
+ options?.accountId
47323
48152
  );
47324
48153
  const text = await response.text();
47325
48154
  let data;
@@ -47401,6 +48230,7 @@ EXAMPLES:
47401
48230
  },
47402
48231
  execute: async (args, context) => {
47403
48232
  const effectiveUserId = context?.userId ?? userId;
48233
+ const effectiveAccountId = context?.accountId;
47404
48234
  const resolved = resolveRepository(args.repository, connector);
47405
48235
  if (!resolved.success) {
47406
48236
  return { success: false, error: resolved.error };
@@ -47412,7 +48242,7 @@ EXAMPLES:
47412
48242
  const repoInfo = await githubFetch(
47413
48243
  connector,
47414
48244
  `/repos/${owner}/${repo}`,
47415
- { userId: effectiveUserId }
48245
+ { userId: effectiveUserId, accountId: effectiveAccountId }
47416
48246
  );
47417
48247
  ref = repoInfo.default_branch;
47418
48248
  }
@@ -47512,6 +48342,7 @@ EXAMPLES:
47512
48342
  },
47513
48343
  execute: async (args, context) => {
47514
48344
  const effectiveUserId = context?.userId ?? userId;
48345
+ const effectiveAccountId = context?.accountId;
47515
48346
  const resolved = resolveRepository(args.repository, connector);
47516
48347
  if (!resolved.success) {
47517
48348
  return { success: false, error: resolved.error };
@@ -47529,6 +48360,7 @@ EXAMPLES:
47529
48360
  `/search/code`,
47530
48361
  {
47531
48362
  userId: effectiveUserId,
48363
+ accountId: effectiveAccountId,
47532
48364
  // Request text-match fragments
47533
48365
  accept: "application/vnd.github.text-match+json",
47534
48366
  queryParams: { q, per_page: perPage }
@@ -47617,6 +48449,7 @@ NOTE: Files larger than 1MB are fetched via the Git Blob API. Very large files (
47617
48449
  },
47618
48450
  execute: async (args, context) => {
47619
48451
  const effectiveUserId = context?.userId ?? userId;
48452
+ const effectiveAccountId = context?.accountId;
47620
48453
  const resolved = resolveRepository(args.repository, connector);
47621
48454
  if (!resolved.success) {
47622
48455
  return { success: false, error: resolved.error };
@@ -47630,7 +48463,7 @@ NOTE: Files larger than 1MB are fetched via the Git Blob API. Very large files (
47630
48463
  const contentResp = await githubFetch(
47631
48464
  connector,
47632
48465
  `/repos/${owner}/${repo}/contents/${args.path}${refParam}`,
47633
- { userId: effectiveUserId }
48466
+ { userId: effectiveUserId, accountId: effectiveAccountId }
47634
48467
  );
47635
48468
  if (contentResp.type !== "file") {
47636
48469
  return {
@@ -47647,7 +48480,7 @@ NOTE: Files larger than 1MB are fetched via the Git Blob API. Very large files (
47647
48480
  const blob = await githubFetch(
47648
48481
  connector,
47649
48482
  contentResp.git_url,
47650
- { userId: effectiveUserId }
48483
+ { userId: effectiveUserId, accountId: effectiveAccountId }
47651
48484
  );
47652
48485
  fileContent = Buffer.from(blob.content, "base64").toString("utf-8");
47653
48486
  fileSize = blob.size;
@@ -47736,6 +48569,7 @@ EXAMPLES:
47736
48569
  },
47737
48570
  execute: async (args, context) => {
47738
48571
  const effectiveUserId = context?.userId ?? userId;
48572
+ const effectiveAccountId = context?.accountId;
47739
48573
  const resolved = resolveRepository(args.repository, connector);
47740
48574
  if (!resolved.success) {
47741
48575
  return { success: false, error: resolved.error };
@@ -47745,7 +48579,7 @@ EXAMPLES:
47745
48579
  const pr = await githubFetch(
47746
48580
  connector,
47747
48581
  `/repos/${owner}/${repo}/pulls/${args.pull_number}`,
47748
- { userId: effectiveUserId }
48582
+ { userId: effectiveUserId, accountId: effectiveAccountId }
47749
48583
  );
47750
48584
  return {
47751
48585
  success: true,
@@ -47823,6 +48657,7 @@ NOTE: Very large diffs may be truncated by GitHub. Patch content may be absent f
47823
48657
  },
47824
48658
  execute: async (args, context) => {
47825
48659
  const effectiveUserId = context?.userId ?? userId;
48660
+ const effectiveAccountId = context?.accountId;
47826
48661
  const resolved = resolveRepository(args.repository, connector);
47827
48662
  if (!resolved.success) {
47828
48663
  return { success: false, error: resolved.error };
@@ -47834,6 +48669,7 @@ NOTE: Very large diffs may be truncated by GitHub. Patch content may be absent f
47834
48669
  `/repos/${owner}/${repo}/pulls/${args.pull_number}/files`,
47835
48670
  {
47836
48671
  userId: effectiveUserId,
48672
+ accountId: effectiveAccountId,
47837
48673
  queryParams: { per_page: 100 }
47838
48674
  }
47839
48675
  );
@@ -47906,6 +48742,7 @@ EXAMPLES:
47906
48742
  },
47907
48743
  execute: async (args, context) => {
47908
48744
  const effectiveUserId = context?.userId ?? userId;
48745
+ const effectiveAccountId = context?.accountId;
47909
48746
  const resolved = resolveRepository(args.repository, connector);
47910
48747
  if (!resolved.success) {
47911
48748
  return { success: false, error: resolved.error };
@@ -47913,7 +48750,7 @@ EXAMPLES:
47913
48750
  const { owner, repo } = resolved.repo;
47914
48751
  try {
47915
48752
  const basePath = `/repos/${owner}/${repo}`;
47916
- const queryOpts = { userId: effectiveUserId, queryParams: { per_page: 100 } };
48753
+ const queryOpts = { userId: effectiveUserId, accountId: effectiveAccountId, queryParams: { per_page: 100 } };
47917
48754
  const [reviewComments, reviews, issueComments] = await Promise.all([
47918
48755
  githubFetch(
47919
48756
  connector,
@@ -48042,6 +48879,7 @@ EXAMPLES:
48042
48879
  },
48043
48880
  execute: async (args, context) => {
48044
48881
  const effectiveUserId = context?.userId ?? userId;
48882
+ const effectiveAccountId = context?.accountId;
48045
48883
  const resolved = resolveRepository(args.repository, connector);
48046
48884
  if (!resolved.success) {
48047
48885
  return { success: false, error: resolved.error };
@@ -48054,6 +48892,7 @@ EXAMPLES:
48054
48892
  {
48055
48893
  method: "POST",
48056
48894
  userId: effectiveUserId,
48895
+ accountId: effectiveAccountId,
48057
48896
  body: {
48058
48897
  title: args.title,
48059
48898
  body: args.body,
@@ -48145,7 +48984,8 @@ async function microsoftFetch(connector, endpoint, options) {
48145
48984
  headers,
48146
48985
  body: options?.body ? JSON.stringify(options.body) : void 0
48147
48986
  },
48148
- options?.userId
48987
+ options?.userId,
48988
+ options?.accountId
48149
48989
  );
48150
48990
  const text = await response.text();
48151
48991
  if (!response.ok) {
@@ -48198,7 +49038,7 @@ function isTeamsMeetingUrl(input) {
48198
49038
  return false;
48199
49039
  }
48200
49040
  }
48201
- async function resolveMeetingId(connector, input, prefix, effectiveUserId) {
49041
+ async function resolveMeetingId(connector, input, prefix, effectiveUserId, effectiveAccountId) {
48202
49042
  if (!input || input.trim().length === 0) {
48203
49043
  throw new Error("Meeting ID cannot be empty");
48204
49044
  }
@@ -48211,6 +49051,7 @@ async function resolveMeetingId(connector, input, prefix, effectiveUserId) {
48211
49051
  `${prefix}/onlineMeetings`,
48212
49052
  {
48213
49053
  userId: effectiveUserId,
49054
+ accountId: effectiveAccountId,
48214
49055
  queryParams: { "$filter": `JoinWebUrl eq '${trimmed}'` }
48215
49056
  }
48216
49057
  );
@@ -48289,13 +49130,14 @@ EXAMPLES:
48289
49130
  },
48290
49131
  execute: async (args, context) => {
48291
49132
  const effectiveUserId = context?.userId ?? userId;
49133
+ const effectiveAccountId = context?.accountId;
48292
49134
  try {
48293
49135
  const prefix = getUserPathPrefix(connector, args.targetUser);
48294
49136
  if (args.replyToMessageId) {
48295
49137
  const replyDraft = await microsoftFetch(
48296
49138
  connector,
48297
49139
  `${prefix}/messages/${args.replyToMessageId}/createReply`,
48298
- { method: "POST", userId: effectiveUserId, body: {} }
49140
+ { method: "POST", userId: effectiveUserId, accountId: effectiveAccountId, body: {} }
48299
49141
  );
48300
49142
  const updated = await microsoftFetch(
48301
49143
  connector,
@@ -48303,6 +49145,7 @@ EXAMPLES:
48303
49145
  {
48304
49146
  method: "PATCH",
48305
49147
  userId: effectiveUserId,
49148
+ accountId: effectiveAccountId,
48306
49149
  body: {
48307
49150
  subject: args.subject,
48308
49151
  body: { contentType: "HTML", content: args.body },
@@ -48323,6 +49166,7 @@ EXAMPLES:
48323
49166
  {
48324
49167
  method: "POST",
48325
49168
  userId: effectiveUserId,
49169
+ accountId: effectiveAccountId,
48326
49170
  body: {
48327
49171
  isDraft: true,
48328
49172
  subject: args.subject,
@@ -48411,6 +49255,7 @@ EXAMPLES:
48411
49255
  },
48412
49256
  execute: async (args, context) => {
48413
49257
  const effectiveUserId = context?.userId ?? userId;
49258
+ const effectiveAccountId = context?.accountId;
48414
49259
  try {
48415
49260
  const prefix = getUserPathPrefix(connector, args.targetUser);
48416
49261
  if (args.replyToMessageId) {
@@ -48420,6 +49265,7 @@ EXAMPLES:
48420
49265
  {
48421
49266
  method: "POST",
48422
49267
  userId: effectiveUserId,
49268
+ accountId: effectiveAccountId,
48423
49269
  body: {
48424
49270
  message: {
48425
49271
  toRecipients: formatRecipients(args.to),
@@ -48436,6 +49282,7 @@ EXAMPLES:
48436
49282
  {
48437
49283
  method: "POST",
48438
49284
  userId: effectiveUserId,
49285
+ accountId: effectiveAccountId,
48439
49286
  body: {
48440
49287
  message: {
48441
49288
  subject: args.subject,
@@ -48535,6 +49382,7 @@ EXAMPLES:
48535
49382
  },
48536
49383
  execute: async (args, context) => {
48537
49384
  const effectiveUserId = context?.userId ?? userId;
49385
+ const effectiveAccountId = context?.accountId;
48538
49386
  try {
48539
49387
  const prefix = getUserPathPrefix(connector, args.targetUser);
48540
49388
  const tz = args.timeZone ?? "UTC";
@@ -48557,7 +49405,7 @@ EXAMPLES:
48557
49405
  const event = await microsoftFetch(
48558
49406
  connector,
48559
49407
  `${prefix}/events`,
48560
- { method: "POST", userId: effectiveUserId, body: eventBody }
49408
+ { method: "POST", userId: effectiveUserId, accountId: effectiveAccountId, body: eventBody }
48561
49409
  );
48562
49410
  return {
48563
49411
  success: true,
@@ -48661,6 +49509,7 @@ EXAMPLES:
48661
49509
  },
48662
49510
  execute: async (args, context) => {
48663
49511
  const effectiveUserId = context?.userId ?? userId;
49512
+ const effectiveAccountId = context?.accountId;
48664
49513
  try {
48665
49514
  const prefix = getUserPathPrefix(connector, args.targetUser);
48666
49515
  const tz = args.timeZone ?? "UTC";
@@ -48684,7 +49533,7 @@ EXAMPLES:
48684
49533
  const event = await microsoftFetch(
48685
49534
  connector,
48686
49535
  `${prefix}/events/${args.eventId}`,
48687
- { method: "PATCH", userId: effectiveUserId, body: patchBody }
49536
+ { method: "PATCH", userId: effectiveUserId, accountId: effectiveAccountId, body: patchBody }
48688
49537
  );
48689
49538
  return {
48690
49539
  success: true,
@@ -48757,14 +49606,15 @@ EXAMPLES:
48757
49606
  },
48758
49607
  execute: async (args, context) => {
48759
49608
  const effectiveUserId = context?.userId ?? userId;
49609
+ const effectiveAccountId = context?.accountId;
48760
49610
  try {
48761
49611
  const prefix = getUserPathPrefix(connector, args.targetUser);
48762
- const resolved = await resolveMeetingId(connector, args.meetingId, prefix, effectiveUserId);
49612
+ const resolved = await resolveMeetingId(connector, args.meetingId, prefix, effectiveUserId, effectiveAccountId);
48763
49613
  const meetingId = resolved.meetingId;
48764
49614
  const transcriptList = await microsoftFetch(
48765
49615
  connector,
48766
49616
  `${prefix}/onlineMeetings/${meetingId}/transcripts`,
48767
- { userId: effectiveUserId }
49617
+ { userId: effectiveUserId, accountId: effectiveAccountId }
48768
49618
  );
48769
49619
  if (!transcriptList.value || transcriptList.value.length === 0) {
48770
49620
  return {
@@ -48777,7 +49627,8 @@ EXAMPLES:
48777
49627
  const response = await connector.fetch(
48778
49628
  contentUrl + "?$format=text/vtt",
48779
49629
  { method: "GET", headers: { "Accept": "text/vtt" } },
48780
- effectiveUserId
49630
+ effectiveUserId,
49631
+ effectiveAccountId
48781
49632
  );
48782
49633
  if (!response.ok) {
48783
49634
  const errorText = await response.text();
@@ -48869,6 +49720,7 @@ EXAMPLES:
48869
49720
  },
48870
49721
  execute: async (args, context) => {
48871
49722
  const effectiveUserId = context?.userId ?? userId;
49723
+ const effectiveAccountId = context?.accountId;
48872
49724
  try {
48873
49725
  const prefix = getUserPathPrefix(connector, args.targetUser);
48874
49726
  const tz = args.timeZone ?? "UTC";
@@ -48878,6 +49730,7 @@ EXAMPLES:
48878
49730
  {
48879
49731
  method: "POST",
48880
49732
  userId: effectiveUserId,
49733
+ accountId: effectiveAccountId,
48881
49734
  body: {
48882
49735
  attendees: formatAttendees(args.attendees),
48883
49736
  timeConstraint: {
@@ -49806,27 +50659,42 @@ var customToolDelete = createCustomToolDelete();
49806
50659
 
49807
50660
  // src/tools/custom-tools/sandboxDescription.ts
49808
50661
  init_Connector();
49809
- function formatConnectorEntry2(c) {
50662
+ function formatConnectorEntry2(c, accountId) {
49810
50663
  const parts = [];
49811
50664
  const serviceOrVendor = c.serviceType ?? c.vendor ?? void 0;
49812
50665
  if (serviceOrVendor) parts.push(`Service: ${serviceOrVendor}`);
50666
+ if (accountId) parts.push(`Account: "${accountId}"`);
49813
50667
  if (c.config.description) parts.push(c.config.description);
49814
50668
  if (c.baseURL) parts.push(`URL: ${c.baseURL}`);
50669
+ const label = accountId ? `"${c.name}" account "${accountId}"` : `"${c.name}"`;
49815
50670
  const details = parts.map((p) => ` ${p}`).join("\n");
49816
- return ` \u2022 "${c.name}" (${c.displayName})
50671
+ return ` \u2022 ${label} (${c.displayName})
49817
50672
  ${details}`;
49818
50673
  }
49819
50674
  function buildConnectorList(context) {
50675
+ const identities = context?.identities;
49820
50676
  const registry = context?.connectorRegistry ?? exports.Connector.asRegistry();
50677
+ if (identities?.length) {
50678
+ const entries = [];
50679
+ for (const id of identities) {
50680
+ try {
50681
+ const connector = registry.get(id.connector);
50682
+ entries.push(formatConnectorEntry2(connector, id.accountId));
50683
+ } catch {
50684
+ entries.push(` \u2022 "${id.connector}"${id.accountId ? ` account "${id.accountId}"` : ""} \u2014 not available`);
50685
+ }
50686
+ }
50687
+ return entries.length > 0 ? entries.join("\n\n") : " No connectors registered.";
50688
+ }
49821
50689
  const connectors = registry.listAll();
49822
50690
  if (connectors.length === 0) {
49823
50691
  return " No connectors registered.";
49824
50692
  }
49825
- return connectors.map(formatConnectorEntry2).join("\n\n");
50693
+ return connectors.map((c) => formatConnectorEntry2(c)).join("\n\n");
49826
50694
  }
49827
50695
  var SANDBOX_API_REFERENCE = `SANDBOX API (available inside custom tool code):
49828
50696
 
49829
- 1. authenticatedFetch(url, options, connectorName)
50697
+ 1. authenticatedFetch(url, options, connectorName, accountId?)
49830
50698
  Makes authenticated HTTP requests using the connector's credentials.
49831
50699
  Auth headers are added automatically \u2014 DO NOT set Authorization header manually.
49832
50700
 
@@ -49837,6 +50705,7 @@ var SANDBOX_API_REFERENCE = `SANDBOX API (available inside custom tool code):
49837
50705
  \u2022 options: Standard fetch options { method, headers, body }
49838
50706
  - For POST/PUT: set body to JSON.stringify(data) and headers to { 'Content-Type': 'application/json' }
49839
50707
  \u2022 connectorName: Name of a registered connector (see REGISTERED CONNECTORS below)
50708
+ \u2022 accountId (optional): Account alias for multi-account connectors (e.g., 'work', 'personal')
49840
50709
 
49841
50710
  Returns: Promise<Response>
49842
50711
  \u2022 response.ok \u2014 true if status 200-299
@@ -51008,6 +51877,7 @@ exports.ProviderError = ProviderError;
51008
51877
  exports.ProviderErrorMapper = ProviderErrorMapper;
51009
51878
  exports.ProviderNotFoundError = ProviderNotFoundError;
51010
51879
  exports.ProviderRateLimitError = ProviderRateLimitError;
51880
+ exports.ROUTINE_KEYS = ROUTINE_KEYS;
51011
51881
  exports.RapidAPIProvider = RapidAPIProvider;
51012
51882
  exports.RateLimitError = RateLimitError;
51013
51883
  exports.SERVICE_DEFINITIONS = SERVICE_DEFINITIONS;
@@ -51272,6 +52142,7 @@ exports.resolveMaxContextTokens = resolveMaxContextTokens;
51272
52142
  exports.resolveMeetingId = resolveMeetingId;
51273
52143
  exports.resolveModelCapabilities = resolveModelCapabilities;
51274
52144
  exports.resolveRepository = resolveRepository;
52145
+ exports.resolveTemplates = resolveTemplates;
51275
52146
  exports.retryWithBackoff = retryWithBackoff;
51276
52147
  exports.sanitizeToolName = sanitizeToolName;
51277
52148
  exports.scopeEquals = scopeEquals;