@alfe.ai/agent-api-client 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7,6 +7,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  function encodeFilePath(filePath) {
8
8
  return filePath.split("/").map(encodeURIComponent).join("/");
9
9
  }
10
+ const REQUEST_TIMEOUT_MS = 2e4;
11
+ const RETRYABLE_STATUS = new Set([
12
+ 500,
13
+ 502,
14
+ 503,
15
+ 504
16
+ ]);
17
+ const RETRY_DELAY_MS = 500;
18
+ function sleep(ms) {
19
+ return new Promise((resolve) => {
20
+ setTimeout(resolve, ms);
21
+ });
22
+ }
23
+ function isRetryableNetworkError(err) {
24
+ if (!(err instanceof Error)) return false;
25
+ if (err.name === "TimeoutError" || err.name === "AbortError") return true;
26
+ if (err.name === "TypeError") return true;
27
+ return false;
28
+ }
10
29
  var AgentApiClient = class {
11
30
  apiKey;
12
31
  apiUrl;
@@ -19,15 +38,33 @@ var AgentApiClient = class {
19
38
  const headers = new Headers(options?.headers);
20
39
  headers.set("Authorization", `Bearer ${this.apiKey}`);
21
40
  headers.set("Content-Type", "application/json");
22
- const res = await fetch(url, {
23
- ...options,
24
- headers
25
- });
26
- if (!res.ok) {
27
- await res.text();
28
- throw new Error(`Agent API request failed (${String(res.status)})`);
41
+ let lastError;
42
+ for (let attempt = 1; attempt <= 2; attempt++) try {
43
+ const res = await fetch(url, {
44
+ ...options,
45
+ headers,
46
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
47
+ });
48
+ if (!res.ok) {
49
+ await res.text();
50
+ const error = /* @__PURE__ */ new Error(`Agent API request failed (${String(res.status)})`);
51
+ if (attempt === 1 && RETRYABLE_STATUS.has(res.status)) {
52
+ lastError = error;
53
+ await sleep(RETRY_DELAY_MS);
54
+ continue;
55
+ }
56
+ throw error;
57
+ }
58
+ return (await res.json()).data;
59
+ } catch (err) {
60
+ if (attempt === 1 && isRetryableNetworkError(err)) {
61
+ lastError = err;
62
+ await sleep(RETRY_DELAY_MS);
63
+ continue;
64
+ }
65
+ throw err;
29
66
  }
30
- return (await res.json()).data;
67
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
31
68
  }
32
69
  async syncRegister(args) {
33
70
  return this.request("/agent/sync/register", {
@@ -146,6 +183,14 @@ var AgentApiClient = class {
146
183
  async getGoogleChatCredentials() {
147
184
  return this.request("/agent/google-chat/credentials");
148
185
  }
186
+ /**
187
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
188
+ * default-connection" shape). Use `getGithubAccounts()` for the multi-
189
+ * account shape required by Pattern A — explicit selector args on every
190
+ * tool. Retained because the `@alfe.ai/openclaw-github` proxy is the
191
+ * only consumer that knows about Pattern A; legacy env-interpolation
192
+ * callers will keep hitting `/credentials` until they move to the proxy.
193
+ */
149
194
  async getGithubCredentials() {
150
195
  const raw = await this.request("/agent/connect/github/credentials");
151
196
  return {
@@ -153,6 +198,37 @@ var AgentApiClient = class {
153
198
  accessToken: raw.accessToken
154
199
  };
155
200
  }
201
+ /**
202
+ * Pattern A: multi-account credential fetch for GitHub.
203
+ *
204
+ * Returns every agent-scoped GitHub connection. The caller is expected
205
+ * to require a `login` selector on every credential-touching tool and
206
+ * look up the matching account at dispatch time.
207
+ *
208
+ * GitHub OAuth tokens have no expiry (`tokenLifecycle: "no_expiry"`),
209
+ * so there is intentionally no `refreshGithubAccountToken` method — if
210
+ * a token is revoked the user must re-run the OAuth flow.
211
+ *
212
+ * Returned `accounts[i].login` is the GitHub username — the stable
213
+ * cross-session identifier the LLM should pass.
214
+ */
215
+ async getGithubAccounts() {
216
+ return { accounts: (await this.request("/agent/connect/github/accounts")).accounts.map((a) => ({
217
+ connectionId: a.connectionId,
218
+ accountIdentifier: a.accountIdentifier,
219
+ displayName: a.displayName,
220
+ connectedAt: a.connectedAt,
221
+ accessToken: a.accessToken ?? "",
222
+ login: a.login ?? a.accountIdentifier,
223
+ scopes: a.scopes ?? ""
224
+ })) };
225
+ }
226
+ /**
227
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
228
+ * default-connection" shape). Use `getXeroAccounts()` for the multi-
229
+ * account shape required by Pattern A — explicit selector args on every
230
+ * tool. This method will be removed once all consumers migrate.
231
+ */
156
232
  async getXeroCredentials() {
157
233
  const raw = await this.request("/agent/connect/xero/credentials");
158
234
  return {
@@ -161,9 +237,49 @@ var AgentApiClient = class {
161
237
  xeroTenantId: raw.xeroTenantId ?? ""
162
238
  };
163
239
  }
240
+ /**
241
+ * Pattern A: multi-account credential fetch for Xero. Returns every
242
+ * agent-scoped Xero connection. The caller is expected to require a
243
+ * selector arg (e.g. `xeroTenantId`) on every credential-touching tool
244
+ * and look up the matching account by that selector at dispatch time.
245
+ *
246
+ * Returned `accounts[i].accountIdentifier` is the Xero tenantId — the
247
+ * stable cross-session identifier the LLM should pass.
248
+ */
249
+ async getXeroAccounts() {
250
+ return { accounts: (await this.request("/agent/connect/xero/accounts")).accounts.map((a) => ({
251
+ connectionId: a.connectionId,
252
+ accountIdentifier: a.accountIdentifier,
253
+ displayName: a.displayName,
254
+ connectedAt: a.connectedAt,
255
+ accessToken: a.accessToken,
256
+ accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
257
+ xeroTenantId: a.xeroTenantId ?? a.accountIdentifier
258
+ })) };
259
+ }
164
260
  async refreshXeroToken() {
165
261
  return this.request("/agent/connect/xero/refresh", { method: "POST" });
166
262
  }
263
+ /**
264
+ * Pattern A: refresh a specific Xero connection by its `accountIdentifier`
265
+ * (the Xero `tenantId`). The legacy `refreshXeroToken()` only refreshes
266
+ * the *primary* connection, which is wrong for multi-tenant Xero where
267
+ * each tenant has its own non-interchangeable access token.
268
+ */
269
+ async refreshXeroAccountToken(xeroTenantId) {
270
+ const path = `/agent/connect/xero/accounts/${encodeURIComponent(xeroTenantId)}/refresh`;
271
+ const raw = await this.request(path, { method: "POST" });
272
+ return {
273
+ accessToken: raw.accessToken,
274
+ accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
275
+ expiresAt: raw.expiresAt ?? ""
276
+ };
277
+ }
278
+ /**
279
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
280
+ * default-connection" shape). Use `getNotionAccounts()` for the multi-
281
+ * account shape required by Pattern A.
282
+ */
167
283
  async getNotionCredentials() {
168
284
  const raw = await this.request("/agent/connect/notion/credentials");
169
285
  return {
@@ -172,6 +288,32 @@ var AgentApiClient = class {
172
288
  workspaceName: raw.workspaceName ?? ""
173
289
  };
174
290
  }
291
+ /**
292
+ * Pattern A: multi-account credential fetch for Notion. Returns every
293
+ * agent-scoped Notion connection. The caller is expected to require a
294
+ * selector arg (e.g. `workspaceId`) on every credential-touching tool.
295
+ *
296
+ * Returned `accounts[i].accountIdentifier` is the Notion workspaceId.
297
+ */
298
+ async getNotionAccounts() {
299
+ return { accounts: (await this.request("/agent/connect/notion/accounts")).accounts.map((a) => ({
300
+ connectionId: a.connectionId,
301
+ accountIdentifier: a.accountIdentifier,
302
+ displayName: a.displayName,
303
+ connectedAt: a.connectedAt,
304
+ accessToken: a.accessToken,
305
+ workspaceId: a.workspaceId ?? a.accountIdentifier,
306
+ workspaceName: a.workspaceName ?? a.displayName ?? ""
307
+ })) };
308
+ }
309
+ /**
310
+ * @deprecated Returns a single primary Atlassian Connection's credentials
311
+ * (one OAuth user, one cloudId) — the legacy "pick-the-default-connection"
312
+ * shape. Atlassian is multi-site by nature (each OAuth user may have
313
+ * access to multiple Cloud sites), so Pattern A plugins MUST use
314
+ * `getAtlassianAccounts()` to discover the full set and dispatch via
315
+ * the `cloudId` selector arg.
316
+ */
175
317
  async getAtlassianCredentials() {
176
318
  const raw = await this.request("/agent/connect/atlassian/credentials");
177
319
  return {
@@ -190,6 +332,72 @@ var AgentApiClient = class {
190
332
  async refreshAtlassianToken() {
191
333
  return this.request("/agent/connect/atlassian/refresh", { method: "POST" });
192
334
  }
335
+ /**
336
+ * Pattern A: multi-account / multi-site credential fetch for Atlassian.
337
+ *
338
+ * Returns every agent-scoped Atlassian Connection. Each Connection is
339
+ * one OAuth user with a single access token and N accessible Cloud
340
+ * sites (`availableSites`). The caller is expected to:
341
+ *
342
+ * 1. Flatten (connection × cloudId) into one MCP child per site.
343
+ * 2. Require a `cloudId` selector on every credential-touching tool.
344
+ * 3. Use the access token bound to the Connection that owns the
345
+ * requested `cloudId` (Atlassian shares one access token across
346
+ * all sites accessible to the OAuth user).
347
+ *
348
+ * Per-account token refresh uses `refreshAtlassianAccountToken(email)`
349
+ * — refreshing one Connection rotates its single access token, which
350
+ * then applies to every cloudId for that Connection.
351
+ *
352
+ * Returned `accounts[i].accountIdentifier` is the OAuth user's email
353
+ * — the stable cross-session identifier for refresh purposes. The LLM
354
+ * never sees this directly: it picks a site via the `cloudId` arg
355
+ * instead.
356
+ */
357
+ async getAtlassianAccounts() {
358
+ return { accounts: (await this.request("/agent/connect/atlassian/accounts")).accounts.map((a) => ({
359
+ connectionId: a.connectionId,
360
+ accountIdentifier: a.accountIdentifier,
361
+ displayName: a.displayName,
362
+ connectedAt: a.connectedAt,
363
+ accessToken: a.accessToken ?? "",
364
+ accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
365
+ clientId: a.clientId ?? "",
366
+ clientSecret: a.clientSecret ?? "",
367
+ cloudId: a.cloudId ?? "",
368
+ siteName: a.siteName ?? "",
369
+ siteUrl: a.siteUrl ?? "",
370
+ availableSites: a.availableSites ?? []
371
+ })) };
372
+ }
373
+ /**
374
+ * Pattern A: refresh a specific Atlassian Connection by `accountIdentifier`
375
+ * (the OAuth user's email).
376
+ *
377
+ * Atlassian rotates refresh tokens (`rotatesRefreshToken: true`); the
378
+ * server-side per-account refresh endpoint handles rotation and
379
+ * persistence. Refreshing one Connection updates its single access
380
+ * token, which applies to every accessible Cloud site (cloudId) for
381
+ * that OAuth user.
382
+ *
383
+ * Returns the new access token + expiry. The proxy is responsible for
384
+ * fanning the new token out to every child server it spawned for
385
+ * cloudIds owned by this Connection.
386
+ */
387
+ async refreshAtlassianAccountToken(accountIdentifier) {
388
+ const path = `/agent/connect/atlassian/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
389
+ const raw = await this.request(path, { method: "POST" });
390
+ return {
391
+ accessToken: raw.accessToken,
392
+ accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
393
+ expiresAt: raw.expiresAt ?? ""
394
+ };
395
+ }
396
+ /**
397
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
398
+ * default-connection" shape). Use `getMYOBAccounts()` for the multi-
399
+ * account shape required by Pattern A.
400
+ */
193
401
  async getMYOBCredentials() {
194
402
  const raw = await this.request("/agent/connect/myob/credentials");
195
403
  return {
@@ -199,9 +407,109 @@ var AgentApiClient = class {
199
407
  clientId: raw.clientId
200
408
  };
201
409
  }
410
+ /**
411
+ * Pattern A: multi-account credential fetch for MYOB. Returns every
412
+ * agent-scoped MYOB connection. The caller is expected to require a
413
+ * selector arg (e.g. `myobBusinessId` / `accountIdentifier`) on every
414
+ * credential-touching tool.
415
+ *
416
+ * Returned `accounts[i].accountIdentifier` is the MYOB businessId.
417
+ */
418
+ async getMYOBAccounts() {
419
+ return { accounts: (await this.request("/agent/connect/myob/accounts")).accounts.map((a) => ({
420
+ connectionId: a.connectionId,
421
+ accountIdentifier: a.accountIdentifier,
422
+ displayName: a.displayName,
423
+ connectedAt: a.connectedAt,
424
+ accessToken: a.accessToken,
425
+ accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
426
+ myobBusinessId: a.myobBusinessId ?? a.accountIdentifier,
427
+ clientId: a.clientId
428
+ })) };
429
+ }
202
430
  async refreshMYOBToken() {
203
431
  return this.request("/agent/connect/myob/refresh", { method: "POST" });
204
432
  }
433
+ /**
434
+ * Microsoft 365 (delegated OAuth) credential fetch — single-account shape.
435
+ *
436
+ * @deprecated Use `getMicrosoftAccounts()` and dispatch via the `email`
437
+ * selector once per-account plugins land. Retained because the existing
438
+ * `integrations/connect/microsoft/hooks/post_activate.mjs` writes
439
+ * `mgc` credentials for the single (default) Microsoft account.
440
+ *
441
+ * Returns the agent's effective Microsoft delegated-OAuth credentials.
442
+ * Distinct from `getTeamsCredentials()` (Azure bot credentials for the
443
+ * Teams adapter, which is admin-consent flow on services/microsoft, not
444
+ * delegated OAuth on services/connect).
445
+ */
446
+ async getMicrosoftCredentials() {
447
+ const raw = await this.request("/agent/connect/microsoft/credentials");
448
+ return {
449
+ accessToken: raw.accessToken ?? "",
450
+ accessTokenExpiresAt: raw.accessTokenExpiresAt,
451
+ refreshToken: raw.refreshToken ?? "",
452
+ clientId: raw.clientId ?? "",
453
+ clientSecret: raw.clientSecret ?? "",
454
+ email: raw.email ?? raw.accountIdentifier,
455
+ microsoftTenantId: raw.microsoftTenantId,
456
+ workspaceDomain: raw.workspaceDomain
457
+ };
458
+ }
459
+ /**
460
+ * Pattern A: multi-account credential fetch for Microsoft 365.
461
+ *
462
+ * Returns every agent-scoped Microsoft connection. The caller is expected
463
+ * to require an `email` selector on every credential-touching tool and
464
+ * look up the matching account at dispatch time.
465
+ *
466
+ * Returned `accounts[i].accountIdentifier` is the user's primary email
467
+ * (or the tid claim as fallback) — the stable cross-session identifier
468
+ * the LLM should pass.
469
+ *
470
+ * Per-account token refresh is exposed via `refreshMicrosoftAccountToken`,
471
+ * NOT `refreshXeroAccountToken` — Microsoft refresh tokens are not
472
+ * interchangeable across (tenant, user) pairs.
473
+ */
474
+ async getMicrosoftAccounts() {
475
+ return { accounts: (await this.request("/agent/connect/microsoft/accounts")).accounts.map((a) => ({
476
+ connectionId: a.connectionId,
477
+ accountIdentifier: a.accountIdentifier,
478
+ displayName: a.displayName,
479
+ connectedAt: a.connectedAt,
480
+ accessToken: a.accessToken ?? "",
481
+ accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
482
+ refreshToken: a.refreshToken ?? "",
483
+ clientId: a.clientId ?? "",
484
+ clientSecret: a.clientSecret ?? "",
485
+ email: a.email ?? a.accountIdentifier,
486
+ microsoftTenantId: a.microsoftTenantId ?? "",
487
+ workspaceDomain: a.workspaceDomain ?? ""
488
+ })) };
489
+ }
490
+ /**
491
+ * Pattern A: refresh a specific Microsoft 365 connection by its
492
+ * `accountIdentifier`. For Microsoft, `accountIdentifier` is the user's
493
+ * email when the Graph profile fetch succeeded at connect time, and the
494
+ * Azure tenant id (`tid` claim) as fallback. Callers should pass the
495
+ * value returned by `getMicrosoftAccounts()` rather than synthesising
496
+ * an email locally.
497
+ *
498
+ * Microsoft refresh tokens are bound to a specific (tenant, user) pair —
499
+ * they are NOT interchangeable across accounts, so per-account refresh
500
+ * is mandatory. The generic /accounts/{accountIdentifier}/refresh
501
+ * endpoint walks the agent's full visible scope chain to find a matching
502
+ * connection (works for inherited team/project Microsoft connections).
503
+ */
504
+ async refreshMicrosoftAccountToken(accountIdentifier) {
505
+ const path = `/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
506
+ const raw = await this.request(path, { method: "POST" });
507
+ return {
508
+ accessToken: raw.accessToken,
509
+ accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
510
+ expiresAt: raw.expiresAt ?? ""
511
+ };
512
+ }
205
513
  async getTeamsCredentials() {
206
514
  return this.request("/agent/microsoft/credentials");
207
515
  }
package/dist/index.d.cts CHANGED
@@ -202,24 +202,125 @@ declare class AgentApiClient {
202
202
  clientSecret: string;
203
203
  displayName?: string;
204
204
  }>;
205
+ /**
206
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
207
+ * default-connection" shape). Use `getGithubAccounts()` for the multi-
208
+ * account shape required by Pattern A — explicit selector args on every
209
+ * tool. Retained because the `@alfe.ai/openclaw-github` proxy is the
210
+ * only consumer that knows about Pattern A; legacy env-interpolation
211
+ * callers will keep hitting `/credentials` until they move to the proxy.
212
+ */
205
213
  getGithubCredentials(): Promise<{
206
214
  login: string;
207
215
  accessToken: string;
208
216
  }>;
217
+ /**
218
+ * Pattern A: multi-account credential fetch for GitHub.
219
+ *
220
+ * Returns every agent-scoped GitHub connection. The caller is expected
221
+ * to require a `login` selector on every credential-touching tool and
222
+ * look up the matching account at dispatch time.
223
+ *
224
+ * GitHub OAuth tokens have no expiry (`tokenLifecycle: "no_expiry"`),
225
+ * so there is intentionally no `refreshGithubAccountToken` method — if
226
+ * a token is revoked the user must re-run the OAuth flow.
227
+ *
228
+ * Returned `accounts[i].login` is the GitHub username — the stable
229
+ * cross-session identifier the LLM should pass.
230
+ */
231
+ getGithubAccounts(): Promise<{
232
+ accounts: {
233
+ connectionId: string;
234
+ accountIdentifier: string;
235
+ displayName: string | null;
236
+ connectedAt: string;
237
+ accessToken: string;
238
+ login: string;
239
+ scopes: string;
240
+ }[];
241
+ }>;
242
+ /**
243
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
244
+ * default-connection" shape). Use `getXeroAccounts()` for the multi-
245
+ * account shape required by Pattern A — explicit selector args on every
246
+ * tool. This method will be removed once all consumers migrate.
247
+ */
209
248
  getXeroCredentials(): Promise<{
210
249
  accessToken: string;
211
250
  accessTokenExpiresAt: string;
212
251
  xeroTenantId: string;
213
252
  }>;
253
+ /**
254
+ * Pattern A: multi-account credential fetch for Xero. Returns every
255
+ * agent-scoped Xero connection. The caller is expected to require a
256
+ * selector arg (e.g. `xeroTenantId`) on every credential-touching tool
257
+ * and look up the matching account by that selector at dispatch time.
258
+ *
259
+ * Returned `accounts[i].accountIdentifier` is the Xero tenantId — the
260
+ * stable cross-session identifier the LLM should pass.
261
+ */
262
+ getXeroAccounts(): Promise<{
263
+ accounts: {
264
+ connectionId: string;
265
+ accountIdentifier: string;
266
+ displayName: string | null;
267
+ connectedAt: string;
268
+ accessToken: string;
269
+ accessTokenExpiresAt: string;
270
+ xeroTenantId: string;
271
+ }[];
272
+ }>;
214
273
  refreshXeroToken(): Promise<{
215
274
  accessToken: string;
216
275
  expiresAt: string;
217
276
  }>;
277
+ /**
278
+ * Pattern A: refresh a specific Xero connection by its `accountIdentifier`
279
+ * (the Xero `tenantId`). The legacy `refreshXeroToken()` only refreshes
280
+ * the *primary* connection, which is wrong for multi-tenant Xero where
281
+ * each tenant has its own non-interchangeable access token.
282
+ */
283
+ refreshXeroAccountToken(xeroTenantId: string): Promise<{
284
+ accessToken: string;
285
+ accessTokenExpiresAt: string;
286
+ expiresAt: string;
287
+ }>;
288
+ /**
289
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
290
+ * default-connection" shape). Use `getNotionAccounts()` for the multi-
291
+ * account shape required by Pattern A.
292
+ */
218
293
  getNotionCredentials(): Promise<{
219
294
  accessToken: string;
220
295
  workspaceId: string;
221
296
  workspaceName: string;
222
297
  }>;
298
+ /**
299
+ * Pattern A: multi-account credential fetch for Notion. Returns every
300
+ * agent-scoped Notion connection. The caller is expected to require a
301
+ * selector arg (e.g. `workspaceId`) on every credential-touching tool.
302
+ *
303
+ * Returned `accounts[i].accountIdentifier` is the Notion workspaceId.
304
+ */
305
+ getNotionAccounts(): Promise<{
306
+ accounts: {
307
+ connectionId: string;
308
+ accountIdentifier: string;
309
+ displayName: string | null;
310
+ connectedAt: string;
311
+ accessToken: string;
312
+ workspaceId: string;
313
+ workspaceName: string;
314
+ }[];
315
+ }>;
316
+ /**
317
+ * @deprecated Returns a single primary Atlassian Connection's credentials
318
+ * (one OAuth user, one cloudId) — the legacy "pick-the-default-connection"
319
+ * shape. Atlassian is multi-site by nature (each OAuth user may have
320
+ * access to multiple Cloud sites), so Pattern A plugins MUST use
321
+ * `getAtlassianAccounts()` to discover the full set and dispatch via
322
+ * the `cloudId` selector arg.
323
+ */
223
324
  getAtlassianCredentials(): Promise<{
224
325
  accessToken: string;
225
326
  refreshToken: string;
@@ -236,16 +337,177 @@ declare class AgentApiClient {
236
337
  accessToken: string;
237
338
  expiresAt: string;
238
339
  }>;
340
+ /**
341
+ * Pattern A: multi-account / multi-site credential fetch for Atlassian.
342
+ *
343
+ * Returns every agent-scoped Atlassian Connection. Each Connection is
344
+ * one OAuth user with a single access token and N accessible Cloud
345
+ * sites (`availableSites`). The caller is expected to:
346
+ *
347
+ * 1. Flatten (connection × cloudId) into one MCP child per site.
348
+ * 2. Require a `cloudId` selector on every credential-touching tool.
349
+ * 3. Use the access token bound to the Connection that owns the
350
+ * requested `cloudId` (Atlassian shares one access token across
351
+ * all sites accessible to the OAuth user).
352
+ *
353
+ * Per-account token refresh uses `refreshAtlassianAccountToken(email)`
354
+ * — refreshing one Connection rotates its single access token, which
355
+ * then applies to every cloudId for that Connection.
356
+ *
357
+ * Returned `accounts[i].accountIdentifier` is the OAuth user's email
358
+ * — the stable cross-session identifier for refresh purposes. The LLM
359
+ * never sees this directly: it picks a site via the `cloudId` arg
360
+ * instead.
361
+ */
362
+ getAtlassianAccounts(): Promise<{
363
+ accounts: {
364
+ connectionId: string;
365
+ accountIdentifier: string;
366
+ displayName: string | null;
367
+ connectedAt: string;
368
+ accessToken: string;
369
+ accessTokenExpiresAt: string;
370
+ clientId: string;
371
+ clientSecret: string;
372
+ cloudId: string;
373
+ siteName: string;
374
+ siteUrl: string;
375
+ availableSites: {
376
+ id: string;
377
+ url: string;
378
+ name: string;
379
+ scopes?: string[];
380
+ avatarUrl?: string;
381
+ }[];
382
+ }[];
383
+ }>;
384
+ /**
385
+ * Pattern A: refresh a specific Atlassian Connection by `accountIdentifier`
386
+ * (the OAuth user's email).
387
+ *
388
+ * Atlassian rotates refresh tokens (`rotatesRefreshToken: true`); the
389
+ * server-side per-account refresh endpoint handles rotation and
390
+ * persistence. Refreshing one Connection updates its single access
391
+ * token, which applies to every accessible Cloud site (cloudId) for
392
+ * that OAuth user.
393
+ *
394
+ * Returns the new access token + expiry. The proxy is responsible for
395
+ * fanning the new token out to every child server it spawned for
396
+ * cloudIds owned by this Connection.
397
+ */
398
+ refreshAtlassianAccountToken(accountIdentifier: string): Promise<{
399
+ accessToken: string;
400
+ accessTokenExpiresAt: string;
401
+ expiresAt: string;
402
+ }>;
403
+ /**
404
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
405
+ * default-connection" shape). Use `getMYOBAccounts()` for the multi-
406
+ * account shape required by Pattern A.
407
+ */
239
408
  getMYOBCredentials(): Promise<{
240
409
  accessToken: string;
241
410
  accessTokenExpiresAt: string;
242
411
  myobBusinessId: string;
243
412
  clientId: string;
244
413
  }>;
414
+ /**
415
+ * Pattern A: multi-account credential fetch for MYOB. Returns every
416
+ * agent-scoped MYOB connection. The caller is expected to require a
417
+ * selector arg (e.g. `myobBusinessId` / `accountIdentifier`) on every
418
+ * credential-touching tool.
419
+ *
420
+ * Returned `accounts[i].accountIdentifier` is the MYOB businessId.
421
+ */
422
+ getMYOBAccounts(): Promise<{
423
+ accounts: {
424
+ connectionId: string;
425
+ accountIdentifier: string;
426
+ displayName: string | null;
427
+ connectedAt: string;
428
+ accessToken: string;
429
+ accessTokenExpiresAt: string;
430
+ myobBusinessId: string;
431
+ clientId: string;
432
+ }[];
433
+ }>;
245
434
  refreshMYOBToken(): Promise<{
246
435
  accessToken: string;
247
436
  expiresAt: string;
248
437
  }>;
438
+ /**
439
+ * Microsoft 365 (delegated OAuth) credential fetch — single-account shape.
440
+ *
441
+ * @deprecated Use `getMicrosoftAccounts()` and dispatch via the `email`
442
+ * selector once per-account plugins land. Retained because the existing
443
+ * `integrations/connect/microsoft/hooks/post_activate.mjs` writes
444
+ * `mgc` credentials for the single (default) Microsoft account.
445
+ *
446
+ * Returns the agent's effective Microsoft delegated-OAuth credentials.
447
+ * Distinct from `getTeamsCredentials()` (Azure bot credentials for the
448
+ * Teams adapter, which is admin-consent flow on services/microsoft, not
449
+ * delegated OAuth on services/connect).
450
+ */
451
+ getMicrosoftCredentials(): Promise<{
452
+ accessToken: string;
453
+ accessTokenExpiresAt?: string;
454
+ refreshToken: string;
455
+ clientId: string;
456
+ clientSecret: string;
457
+ email?: string;
458
+ microsoftTenantId?: string;
459
+ workspaceDomain?: string;
460
+ }>;
461
+ /**
462
+ * Pattern A: multi-account credential fetch for Microsoft 365.
463
+ *
464
+ * Returns every agent-scoped Microsoft connection. The caller is expected
465
+ * to require an `email` selector on every credential-touching tool and
466
+ * look up the matching account at dispatch time.
467
+ *
468
+ * Returned `accounts[i].accountIdentifier` is the user's primary email
469
+ * (or the tid claim as fallback) — the stable cross-session identifier
470
+ * the LLM should pass.
471
+ *
472
+ * Per-account token refresh is exposed via `refreshMicrosoftAccountToken`,
473
+ * NOT `refreshXeroAccountToken` — Microsoft refresh tokens are not
474
+ * interchangeable across (tenant, user) pairs.
475
+ */
476
+ getMicrosoftAccounts(): Promise<{
477
+ accounts: {
478
+ connectionId: string;
479
+ accountIdentifier: string;
480
+ displayName: string | null;
481
+ connectedAt: string;
482
+ accessToken: string;
483
+ accessTokenExpiresAt: string;
484
+ refreshToken: string;
485
+ clientId: string;
486
+ clientSecret: string;
487
+ email: string;
488
+ microsoftTenantId: string;
489
+ workspaceDomain: string;
490
+ }[];
491
+ }>;
492
+ /**
493
+ * Pattern A: refresh a specific Microsoft 365 connection by its
494
+ * `accountIdentifier`. For Microsoft, `accountIdentifier` is the user's
495
+ * email when the Graph profile fetch succeeded at connect time, and the
496
+ * Azure tenant id (`tid` claim) as fallback. Callers should pass the
497
+ * value returned by `getMicrosoftAccounts()` rather than synthesising
498
+ * an email locally.
499
+ *
500
+ * Microsoft refresh tokens are bound to a specific (tenant, user) pair —
501
+ * they are NOT interchangeable across accounts, so per-account refresh
502
+ * is mandatory. The generic /accounts/{accountIdentifier}/refresh
503
+ * endpoint walks the agent's full visible scope chain to find a matching
504
+ * connection (works for inherited team/project Microsoft connections).
505
+ */
506
+ refreshMicrosoftAccountToken(accountIdentifier: string): Promise<{
507
+ accessToken: string;
508
+ accessTokenExpiresAt: string;
509
+ expiresAt: string;
510
+ }>;
249
511
  getTeamsCredentials(): Promise<{
250
512
  agentId: string;
251
513
  tenantId: string;