fraim-hub 2.0.227 → 2.0.229

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.
@@ -19,7 +19,18 @@ exports.COMPANY_SCOPE_KEY = '@company';
19
19
  // (id, title, jobId, jobTitle, agentName, personaKey, status, runId, createdAt, lastUpdatedAt,
20
20
  // scope, reviewHandoff, ...) is kept. Client-only runtime flags are omitted too so stale index
21
21
  // cache state cannot make the frontend skip lazy body hydration.
22
- const HEADER_OMITTED_FIELDS = ['messages', 'events', 'artifacts', 'run', 'delegation', '_bodyLoaded', '_stopping'];
22
+ const HEADER_OMITTED_FIELDS = [
23
+ 'messages',
24
+ 'events',
25
+ 'artifacts',
26
+ 'run',
27
+ 'delegation',
28
+ '_bodyLoaded',
29
+ '_stopping',
30
+ '_priorEvents',
31
+ '_priorMessages',
32
+ ];
33
+ const HEADER_OMITTED_FIELD_SET = new Set(HEADER_OMITTED_FIELDS);
23
34
  /**
24
35
  * Issue #708: resolve the conversation store bucket key for a given scope.
25
36
  * - 'manager'/'company' → a stable sentinel key (project-independent home).
@@ -292,8 +303,36 @@ function toHeader(conv) {
292
303
  const header = { ...conv };
293
304
  for (const field of HEADER_OMITTED_FIELDS)
294
305
  delete header[field];
306
+ for (const field of Object.keys(header)) {
307
+ if (field.startsWith('_'))
308
+ delete header[field];
309
+ }
295
310
  return header;
296
311
  }
312
+ function headerNeedsSanitization(header) {
313
+ const value = header;
314
+ return Object.keys(value).some((field) => HEADER_OMITTED_FIELD_SET.has(field) || field.startsWith('_'));
315
+ }
316
+ function sanitizeBucketIndex(index) {
317
+ let changed = false;
318
+ const headers = index.headers.map((header) => {
319
+ if (headerNeedsSanitization(header))
320
+ changed = true;
321
+ return toHeader(header);
322
+ }).sort(newestFirst);
323
+ const activeId = index.activeId && headers.some((header) => header.id === index.activeId) ? index.activeId : null;
324
+ if (activeId !== index.activeId)
325
+ changed = true;
326
+ if (!changed) {
327
+ for (let indexPosition = 0; indexPosition < headers.length; indexPosition += 1) {
328
+ if (headers[indexPosition].id !== index.headers[indexPosition]?.id) {
329
+ changed = true;
330
+ break;
331
+ }
332
+ }
333
+ }
334
+ return { index: { activeId, headers }, changed };
335
+ }
297
336
  function renameWithRetry(from, to) {
298
337
  // Windows can throw EPERM/EBUSY when a reader briefly holds the destination open. Retry a few
299
338
  // times; the per-bucket lock already prevents concurrent writers from colliding here.
@@ -322,6 +361,7 @@ class AiHubConversationStore {
322
361
  constructor(stateFilePath = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-hub-conversations.json')) {
323
362
  this.stateFilePath = stateFilePath;
324
363
  this.migrated = false;
364
+ this.projectPathCache = null;
325
365
  const dir = path_1.default.dirname(this.stateFilePath);
326
366
  const base = path_1.default.basename(this.stateFilePath).replace(/\.json$/i, '');
327
367
  // Sibling directory of the legacy file, e.g. ~/.fraim/ai-hub-conversations/
@@ -345,6 +385,9 @@ class AiHubConversationStore {
345
385
  lockPath(bucketDir) {
346
386
  return path_1.default.join(bucketDir, '.lock');
347
387
  }
388
+ invalidateProjectPathCache() {
389
+ this.projectPathCache = null;
390
+ }
348
391
  // ---- migration (split of the legacy monolith) ----
349
392
  importLegacyMonolithIfPresent() {
350
393
  let stat;
@@ -431,8 +474,23 @@ class AiHubConversationStore {
431
474
  // source of truth) and persist it best-effort.
432
475
  loadIndex(bucketDir, bucketKey) {
433
476
  const existing = this.readIndex(bucketDir);
434
- if (existing)
435
- return existing;
477
+ if (existing) {
478
+ const sanitized = sanitizeBucketIndex(existing);
479
+ if (sanitized.changed) {
480
+ try {
481
+ writeJsonAtomic(this.indexPath(bucketDir), {
482
+ version: 2,
483
+ bucketKey,
484
+ activeId: sanitized.index.activeId,
485
+ headers: sanitized.index.headers,
486
+ });
487
+ }
488
+ catch {
489
+ /* best-effort cache self-heal */
490
+ }
491
+ }
492
+ return sanitized.index;
493
+ }
436
494
  const headers = this.readAllConversations(bucketDir, bucketKey).map(toHeader).sort(newestFirst);
437
495
  const rebuilt = { activeId: null, headers };
438
496
  if (headers.length > 0) {
@@ -457,6 +515,7 @@ class AiHubConversationStore {
457
515
  // Author a whole bucket to exactly `conversations` (used by migration and replaceProject).
458
516
  writeBucket(bucketKey, state) {
459
517
  const bucketDir = this.bucketDir(bucketKey);
518
+ const bucketAlreadyExisted = fs_1.default.existsSync(bucketDir);
460
519
  fs_1.default.mkdirSync(bucketDir, { recursive: true });
461
520
  (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
462
521
  const desired = new Set(state.conversations.map((c) => c.id));
@@ -480,10 +539,24 @@ class AiHubConversationStore {
480
539
  this.writeConvFile(bucketDir, bucketKey, conv);
481
540
  this.writeIndex(bucketDir, bucketKey, state.activeId, state.conversations);
482
541
  });
542
+ if (!bucketAlreadyExisted)
543
+ this.invalidateProjectPathCache();
483
544
  }
484
545
  // ---- public API ----
485
546
  listProjectPaths() {
486
547
  this.ensureMigrated();
548
+ let rootStat;
549
+ try {
550
+ rootStat = fs_1.default.statSync(this.shardRoot);
551
+ }
552
+ catch {
553
+ return [];
554
+ }
555
+ if (!rootStat.isDirectory())
556
+ return [];
557
+ if (this.projectPathCache && this.projectPathCache.shardRootMtimeMs === rootStat.mtimeMs) {
558
+ return this.projectPathCache.keys.slice();
559
+ }
487
560
  let dirs;
488
561
  try {
489
562
  dirs = fs_1.default.readdirSync(this.shardRoot);
@@ -497,7 +570,15 @@ class AiHubConversationStore {
497
570
  if (bucketKey)
498
571
  keys.push(normalizeProjectPath(bucketKey));
499
572
  }
500
- return keys;
573
+ let finalRootMtimeMs = rootStat.mtimeMs;
574
+ try {
575
+ finalRootMtimeMs = fs_1.default.statSync(this.shardRoot).mtimeMs;
576
+ }
577
+ catch {
578
+ /* keep the pre-scan mtime */
579
+ }
580
+ this.projectPathCache = { shardRootMtimeMs: finalRootMtimeMs, keys };
581
+ return keys.slice();
501
582
  }
502
583
  bucketKeyOf(bucketDir) {
503
584
  try {
@@ -575,6 +656,7 @@ class AiHubConversationStore {
575
656
  fs_1.default.rmSync(path_1.default.join(bucketDir, file), { recursive: true, force: true });
576
657
  }
577
658
  });
659
+ this.invalidateProjectPathCache();
578
660
  return true;
579
661
  }
580
662
  replaceProject(projectPath, next) {
@@ -591,8 +673,9 @@ class AiHubConversationStore {
591
673
  if (!incoming)
592
674
  return this.loadProject(key);
593
675
  const bucketDir = this.bucketDir(key);
676
+ const bucketAlreadyExisted = fs_1.default.existsSync(bucketDir);
594
677
  fs_1.default.mkdirSync(bucketDir, { recursive: true });
595
- return (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
678
+ const state = (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
596
679
  // Merge with an existing copy of the same id, keeping the richer copy (a header-only shell
597
680
  // must never overwrite real run history), tie-broken by newer lastUpdatedAt.
598
681
  const convPath = this.convFilePath(bucketDir, incoming.id);
@@ -610,6 +693,9 @@ class AiHubConversationStore {
610
693
  this.writeConvFile(bucketDir, key, winner);
611
694
  return this.reindexAfterUpsert(bucketDir, key, winner, activeId);
612
695
  });
696
+ if (!bucketAlreadyExisted)
697
+ this.invalidateProjectPathCache();
698
+ return state;
613
699
  }
614
700
  // Set the active conversation without rewriting any conversation body. (Mutation methods return
615
701
  // header-shaped state, so callers must never round-trip that through replaceProject to set
@@ -618,20 +704,25 @@ class AiHubConversationStore {
618
704
  this.ensureMigrated();
619
705
  const key = normalizeConversationKey(projectPath);
620
706
  const bucketDir = this.bucketDir(key);
707
+ const bucketAlreadyExisted = fs_1.default.existsSync(bucketDir);
621
708
  fs_1.default.mkdirSync(bucketDir, { recursive: true });
622
- return (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
709
+ const state = (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
623
710
  const idx = this.loadIndex(bucketDir, key);
624
711
  const valid = activeId && idx.headers.some((h) => h.id === activeId) ? activeId : null;
625
712
  writeJsonAtomic(this.indexPath(bucketDir), { version: 2, bucketKey: key, activeId: valid, headers: idx.headers });
626
713
  return normalizeProjectState(key, { activeId: valid, conversations: idx.headers });
627
714
  });
715
+ if (!bucketAlreadyExisted)
716
+ this.invalidateProjectPathCache();
717
+ return state;
628
718
  }
629
719
  patchConversation(projectPath, conversationId, patch) {
630
720
  this.ensureMigrated();
631
721
  const key = normalizeConversationKey(projectPath);
632
722
  const bucketDir = this.bucketDir(key);
723
+ const bucketAlreadyExisted = fs_1.default.existsSync(bucketDir);
633
724
  fs_1.default.mkdirSync(bucketDir, { recursive: true });
634
- return (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
725
+ const state = (0, conversation_store_lock_1.withBucketLock)(this.lockPath(bucketDir), () => {
635
726
  const convPath = this.convFilePath(bucketDir, conversationId);
636
727
  let existing = null;
637
728
  try {
@@ -660,10 +751,13 @@ class AiHubConversationStore {
660
751
  this.writeConvFile(bucketDir, key, merged);
661
752
  return this.reindexAfterUpsert(bucketDir, key, merged, undefined);
662
753
  });
754
+ if (!bucketAlreadyExisted)
755
+ this.invalidateProjectPathCache();
756
+ return state;
663
757
  }
664
758
  // Update the index header for one conversation and return the (header-shaped) project state.
665
759
  reindexAfterUpsert(bucketDir, bucketKey, conv, activeId) {
666
- const idx = this.readIndex(bucketDir) ?? { activeId: null, headers: [] };
760
+ const idx = this.loadIndex(bucketDir, bucketKey);
667
761
  const headers = idx.headers.filter((h) => h.id !== conv.id);
668
762
  headers.push(toHeader(conv));
669
763
  headers.sort(newestFirst);
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.launchDesktopShell = launchDesktopShell;
7
7
  const electron_1 = require("electron");
8
+ const electron_updater_1 = require("electron-updater");
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const fs_1 = __importDefault(require("fs"));
10
11
  const server_1 = require("./server");
@@ -82,6 +83,14 @@ function ensureLoginItem() {
82
83
  fs_1.default.mkdirSync(path_1.default.dirname(flagPath), { recursive: true });
83
84
  fs_1.default.writeFileSync(flagPath, '1');
84
85
  }
86
+ function configureAutoUpdater() {
87
+ if (!electron_1.app.isPackaged)
88
+ return;
89
+ electron_updater_1.autoUpdater.autoDownload = true;
90
+ electron_updater_1.autoUpdater.checkForUpdatesAndNotify().catch((err) => {
91
+ console.warn('[fraim] auto-update check failed:', err);
92
+ });
93
+ }
85
94
  // ---------------------------------------------------------------------------
86
95
  // Word manifest sideload (runs once on first launch)
87
96
  // ---------------------------------------------------------------------------
@@ -366,6 +375,7 @@ async function bootstrap() {
366
375
  electron_1.app.setName('FRAIM Hub');
367
376
  // First-launch housekeeping (idempotent, fast on subsequent runs)
368
377
  ensureLoginItem();
378
+ configureAutoUpdater();
369
379
  electron_1.app.on('activate', () => {
370
380
  // macOS: clicking dock icon re-shows the window
371
381
  if (mainWindow) {
@@ -12,6 +12,7 @@ exports.detectEmployees = detectEmployees;
12
12
  exports.prepareCodexBrowserHome = prepareCodexBrowserHome;
13
13
  exports.sharedBrowserHostConfig = sharedBrowserHostConfig;
14
14
  exports.buildStartPlan = buildStartPlan;
15
+ exports.buildInteractiveResumeCommand = buildInteractiveResumeCommand;
15
16
  exports.buildContinuePlan = buildContinuePlan;
16
17
  exports.supportsDirectPath = supportsDirectPath;
17
18
  exports.buildDirectStartPlan = buildDirectStartPlan;
@@ -996,6 +997,15 @@ function buildStartPlan(hostId, message, sessionId) {
996
997
  env: browser.env,
997
998
  };
998
999
  }
1000
+ function buildInteractiveResumeCommand(hostId, sessionId) {
1001
+ if (hostId === 'codex')
1002
+ return `codex resume ${sessionId}`;
1003
+ if (hostId === 'gemini')
1004
+ return `gemini --resume ${sessionId}`;
1005
+ if (hostId === 'copilot')
1006
+ return `copilot --resume ${sessionId}`;
1007
+ return `claude -r ${sessionId}`;
1008
+ }
999
1009
  function buildContinuePlan(hostId, sessionId, message) {
1000
1010
  if (hostId === 'codex') {
1001
1011
  const browser = sharedBrowserHostConfig('codex');
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
21
21
  const os_1 = __importDefault(require("os"));
22
22
  const child_process_1 = require("child_process");
23
23
  const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
24
+ const hub_release_download_1 = require("./hub-release-download");
24
25
  // Stable install directory under ~/.fraim/bin/fraim-hub-electron/
25
26
  function resolveHubInstallDir() {
26
27
  return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'bin', 'fraim-hub-electron');
@@ -125,10 +126,9 @@ async function runHubInstall() {
125
126
  const target = resolveShortcutTarget();
126
127
  const home = os_1.default.homedir();
127
128
  const shortcuts = getOsShortcutPaths(home);
128
- // Ensure the install directory exists (the actual Electron binary download
129
- // is a separate step — this wires the OS launcher to the expected path so
130
- // the shortcut is ready as soon as the binary is downloaded).
131
129
  fs_1.default.mkdirSync(installDir, { recursive: true });
130
+ const downloaded = await (0, hub_release_download_1.downloadLatestHubBinary)(installDir, target);
131
+ console.log(` Downloaded release asset: ${downloaded.assetName}`);
132
132
  if (process.platform === 'win32' && shortcuts.startMenu) {
133
133
  fs_1.default.mkdirSync(path_1.default.dirname(shortcuts.startMenu), { recursive: true });
134
134
  writeWindowsShortcutScript(shortcuts.startMenu, target);
@@ -155,9 +155,6 @@ async function runHubInstall() {
155
155
  console.log('');
156
156
  console.log('FRAIM Hub is registered as an OS application.');
157
157
  console.log('Launch it from your Start Menu / Launchpad / application launcher.');
158
- console.log('');
159
- console.log('Note: the Hub binary will be downloaded on first launch via:');
160
- console.log(' fraim hub (or npx fraim-hub@latest)');
161
158
  }
162
159
  exports.hubInstallCommand = new commander_1.Command('install')
163
160
  .description('Register FRAIM Hub as an OS application (Start Menu / Launchpad entry)')
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.selectHubReleaseAsset = selectHubReleaseAsset;
7
+ exports.downloadLatestHubBinary = downloadLatestHubBinary;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const https_1 = __importDefault(require("https"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const promises_1 = require("stream/promises");
12
+ const adm_zip_1 = __importDefault(require("adm-zip"));
13
+ const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/mathursrus/FRAIM/releases/latest';
14
+ function archNeedles(arch) {
15
+ if (arch === 'x64')
16
+ return ['x64', 'x86_64', 'amd64'];
17
+ if (arch === 'arm64')
18
+ return ['arm64', 'aarch64'];
19
+ return [arch.toLowerCase()];
20
+ }
21
+ function nameIncludesArch(name, arch) {
22
+ const lower = name.toLowerCase();
23
+ const needles = archNeedles(arch);
24
+ return needles.some((needle) => lower.includes(needle)) || !/(x64|x86_64|amd64|arm64|aarch64)/i.test(name);
25
+ }
26
+ function selectHubReleaseAsset(assets, platform = process.platform, arch = process.arch) {
27
+ const candidates = assets.filter((asset) => /fraim[-_ ]?hub/i.test(asset.name) && nameIncludesArch(asset.name, arch));
28
+ const preferredPatterns = platform === 'win32'
29
+ ? [/portable.*\.exe$/i, /\.exe$/i, /\.msi$/i]
30
+ : platform === 'darwin'
31
+ ? [/mac.*\.zip$/i, /\.zip$/i, /\.dmg$/i]
32
+ : [/\.AppImage$/i, /\.deb$/i];
33
+ for (const pattern of preferredPatterns) {
34
+ const match = candidates.find((asset) => pattern.test(asset.name));
35
+ if (match)
36
+ return match;
37
+ }
38
+ return null;
39
+ }
40
+ function assertHttpsUrl(rawUrl) {
41
+ const url = new URL(rawUrl);
42
+ if (url.protocol !== 'https:') {
43
+ throw new Error(`Refusing non-HTTPS Hub release URL: ${rawUrl}`);
44
+ }
45
+ return url;
46
+ }
47
+ function getJson(rawUrl) {
48
+ const url = assertHttpsUrl(rawUrl);
49
+ return new Promise((resolve, reject) => {
50
+ const req = https_1.default.get(url, { headers: { 'User-Agent': 'fraim-hub-install' }, timeout: 15000 }, (res) => {
51
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
52
+ res.resume();
53
+ getJson(res.headers.location).then(resolve, reject);
54
+ return;
55
+ }
56
+ if (res.statusCode !== 200) {
57
+ res.resume();
58
+ reject(new Error(`GitHub release lookup failed with HTTP ${res.statusCode}`));
59
+ return;
60
+ }
61
+ let body = '';
62
+ res.setEncoding('utf8');
63
+ res.on('data', (chunk) => { body += chunk; });
64
+ res.on('end', () => {
65
+ try {
66
+ resolve(JSON.parse(body));
67
+ }
68
+ catch (err) {
69
+ reject(err);
70
+ }
71
+ });
72
+ });
73
+ req.on('error', reject);
74
+ req.on('timeout', () => {
75
+ req.destroy(new Error('Timed out while looking up the latest FRAIM Hub release'));
76
+ });
77
+ });
78
+ }
79
+ async function downloadFile(rawUrl, destination, redirectsRemaining = 5) {
80
+ const url = assertHttpsUrl(rawUrl);
81
+ await new Promise((resolve, reject) => {
82
+ const req = https_1.default.get(url, { headers: { 'User-Agent': 'fraim-hub-install' }, timeout: 60000 }, (res) => {
83
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && redirectsRemaining > 0) {
84
+ res.resume();
85
+ downloadFile(res.headers.location, destination, redirectsRemaining - 1).then(resolve, reject);
86
+ return;
87
+ }
88
+ if (res.statusCode !== 200) {
89
+ res.resume();
90
+ reject(new Error(`Hub release download failed with HTTP ${res.statusCode}`));
91
+ return;
92
+ }
93
+ fs_1.default.mkdirSync(path_1.default.dirname(destination), { recursive: true });
94
+ void (0, promises_1.pipeline)(res, fs_1.default.createWriteStream(destination)).then(resolve, reject);
95
+ });
96
+ req.on('error', reject);
97
+ req.on('timeout', () => {
98
+ req.destroy(new Error('Timed out while downloading the FRAIM Hub release asset'));
99
+ });
100
+ });
101
+ }
102
+ function installDownloadedAsset(downloadPath, assetName, installDir, target) {
103
+ if (process.platform === 'darwin' && /\.zip$/i.test(assetName)) {
104
+ fs_1.default.rmSync(path_1.default.join(installDir, 'FRAIM Hub.app'), { recursive: true, force: true });
105
+ new adm_zip_1.default(downloadPath).extractAllTo(installDir, true);
106
+ return;
107
+ }
108
+ fs_1.default.copyFileSync(downloadPath, target);
109
+ if (process.platform !== 'win32') {
110
+ fs_1.default.chmodSync(target, 0o755);
111
+ }
112
+ }
113
+ async function downloadLatestHubBinary(installDir, target) {
114
+ const release = await getJson(process.env.FRAIM_HUB_RELEASE_API_URL || LATEST_RELEASE_API_URL);
115
+ const asset = selectHubReleaseAsset(release.assets || []);
116
+ if (!asset) {
117
+ throw new Error('No runnable FRAIM Hub release asset was found for this platform.');
118
+ }
119
+ const downloadPath = path_1.default.join(installDir, asset.name);
120
+ await downloadFile(asset.browser_download_url, downloadPath);
121
+ installDownloadedAsset(downloadPath, asset.name, installDir, target);
122
+ return { assetName: asset.name, target };
123
+ }
@@ -24,23 +24,30 @@ class HttpHubRemoteGateway {
24
24
  timeout: 10000,
25
25
  });
26
26
  }
27
- async getPersonaState(apiKey) {
27
+ async getPersonaStateResult(apiKey) {
28
+ // No key: a definitive "unauthenticated" answer — the authority is not the problem.
28
29
  if (!apiKey)
29
- return null;
30
+ return { state: null, reachable: true };
30
31
  try {
31
32
  const res = await this.client(apiKey).get('/api/personas/me');
32
- return res.data;
33
+ return { state: res.data, reachable: true };
33
34
  }
34
35
  catch (err) {
35
- // 401 (no/expired key) or 404 (feature disabled) => treat as "no state" so the Hub
36
- // renders a clean locked/not-signed-in view instead of crashing.
36
+ // 401 (no/expired key) or 404 (feature disabled) are definitive answers from a
37
+ // reachable authority => treat as "no state" so the Hub renders a clean
38
+ // locked/not-signed-in view instead of crashing.
37
39
  const status = err?.response?.status;
38
40
  if (status === 401 || status === 404)
39
- return null;
41
+ return { state: null, reachable: true };
42
+ // Anything else (timeout, connection refused, 5xx) means we could not reach the
43
+ // authority. Issue #925: the caller must not treat this like a signed-out user.
40
44
  console.warn('[ai-hub] getPersonaState failed:', err?.message || err);
41
- return null;
45
+ return { state: null, reachable: false };
42
46
  }
43
47
  }
48
+ async getPersonaState(apiKey) {
49
+ return (await this.getPersonaStateResult(apiKey)).state;
50
+ }
44
51
  async listManagerTeam(apiKey) {
45
52
  if (!apiKey)
46
53
  return [];