btcp-browser-agent 0.1.9 → 0.1.11

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.
@@ -12,7 +12,11 @@ export class SessionManager {
12
12
  activeSessionGroupId = null;
13
13
  sessionCounter = 0;
14
14
  initialized = false;
15
- constructor() {
15
+ maxSession;
16
+ maxOpenTab;
17
+ constructor(options = {}) {
18
+ this.maxSession = options.maxSession ?? 1;
19
+ this.maxOpenTab = options.maxOpenTab ?? 1;
16
20
  // Restore session on creation
17
21
  this.restoreSession();
18
22
  }
@@ -117,6 +121,13 @@ export class SessionManager {
117
121
  */
118
122
  async createGroup(options = {}) {
119
123
  console.log('[SessionManager] createGroup called with options:', options);
124
+ // Check if we can create a new session
125
+ const canCreate = await this.canCreateSession();
126
+ if (!canCreate) {
127
+ const count = await this.getSessionCount();
128
+ throw new Error(`Maximum session limit reached (${count}/${this.maxSession}). ` +
129
+ `Close an existing session before creating a new one.`);
130
+ }
120
131
  const { tabIds = [], title = this.generateSessionName(), color = 'blue', collapsed = false, } = options;
121
132
  // If no tabIds provided, create a new blank tab for the session
122
133
  let targetTabIds = tabIds;
@@ -264,14 +275,138 @@ export class SessionManager {
264
275
  getActiveSessionGroupId() {
265
276
  return this.activeSessionGroupId;
266
277
  }
278
+ /**
279
+ * Get the maximum number of sessions allowed
280
+ */
281
+ getMaxSession() {
282
+ return this.maxSession;
283
+ }
284
+ /**
285
+ * Get the maximum number of open tabs per session
286
+ */
287
+ getMaxOpenTab() {
288
+ return this.maxOpenTab;
289
+ }
290
+ /**
291
+ * Enforce the tab limit in the active session
292
+ * Closes oldest tabs if the limit is exceeded
293
+ */
294
+ async enforceTabLimit() {
295
+ if (this.activeSessionGroupId === null) {
296
+ return;
297
+ }
298
+ try {
299
+ // Get all tabs in the session
300
+ const tabs = await chrome.tabs.query({ groupId: this.activeSessionGroupId });
301
+ if (tabs.length <= this.maxOpenTab) {
302
+ return; // Within limit
303
+ }
304
+ // Sort by index (lower index = older tab position)
305
+ tabs.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
306
+ // Close excess tabs (oldest first)
307
+ const tabsToClose = tabs.slice(0, tabs.length - this.maxOpenTab);
308
+ console.log(`[SessionManager] Closing ${tabsToClose.length} excess tabs to enforce limit of ${this.maxOpenTab}`);
309
+ for (const tab of tabsToClose) {
310
+ if (tab.id) {
311
+ try {
312
+ await chrome.tabs.remove(tab.id);
313
+ }
314
+ catch (err) {
315
+ console.error('[SessionManager] Failed to close tab:', err);
316
+ }
317
+ }
318
+ }
319
+ }
320
+ catch (err) {
321
+ console.error('[SessionManager] Failed to enforce tab limit:', err);
322
+ }
323
+ }
324
+ /**
325
+ * Get the count of existing BTCP sessions by checking:
326
+ * 1. Persistent session from storage
327
+ * 2. Current active session
328
+ * 3. Existing tab groups (BTCP prefixed)
329
+ */
330
+ async getSessionCount() {
331
+ // If we have an active session, that counts as 1
332
+ if (this.activeSessionGroupId !== null) {
333
+ try {
334
+ // Verify the group still exists
335
+ await chrome.tabGroups.get(this.activeSessionGroupId);
336
+ return 1;
337
+ }
338
+ catch {
339
+ // Group no longer exists, clear it
340
+ this.activeSessionGroupId = null;
341
+ }
342
+ }
343
+ // Check if there's a persistent session in storage
344
+ try {
345
+ const result = await chrome.storage.session.get(SESSION_STORAGE_KEY);
346
+ const data = result[SESSION_STORAGE_KEY];
347
+ if (data?.groupId) {
348
+ // Verify the stored group still exists
349
+ try {
350
+ await chrome.tabGroups.get(data.groupId);
351
+ return 1;
352
+ }
353
+ catch {
354
+ // Group no longer exists, clear storage
355
+ await this.clearStoredSession();
356
+ }
357
+ }
358
+ }
359
+ catch (err) {
360
+ console.error('[SessionManager] Failed to check persistent session:', err);
361
+ }
362
+ // Count existing BTCP tab groups
363
+ try {
364
+ const groups = await chrome.tabGroups.query({});
365
+ const btcpGroups = groups.filter(g => g.title?.startsWith('BTCP'));
366
+ return btcpGroups.length;
367
+ }
368
+ catch (err) {
369
+ console.error('[SessionManager] Failed to count tab groups:', err);
370
+ return 0;
371
+ }
372
+ }
373
+ /**
374
+ * Check if a new session can be created based on maxSession limit
375
+ */
376
+ async canCreateSession() {
377
+ const count = await this.getSessionCount();
378
+ return count < this.maxSession;
379
+ }
267
380
  /**
268
381
  * Set the active session group ID
269
382
  */
270
383
  setActiveSessionGroupId(groupId) {
271
384
  this.activeSessionGroupId = groupId;
272
385
  }
386
+ /**
387
+ * Use an existing tab group as the active session
388
+ * This validates the group exists and sets it as active with persistence
389
+ */
390
+ async useExistingGroupAsSession(groupId) {
391
+ try {
392
+ // Verify the group exists
393
+ const group = await chrome.tabGroups.get(groupId);
394
+ console.log('[SessionManager] Using existing group as session:', group);
395
+ // Set as active session
396
+ this.activeSessionGroupId = groupId;
397
+ // Persist to storage
398
+ await this.persistSession();
399
+ console.log('[SessionManager] Existing group set as active session');
400
+ return true;
401
+ }
402
+ catch (err) {
403
+ console.error('[SessionManager] Failed to use existing group as session:', err);
404
+ return false;
405
+ }
406
+ }
273
407
  /**
274
408
  * Add a tab to the active session (if one exists)
409
+ * Automatically enforces the tab limit after adding
275
410
  */
276
411
  async addTabToActiveSession(tabId) {
277
412
  if (this.activeSessionGroupId === null) {
@@ -279,6 +414,8 @@ export class SessionManager {
279
414
  }
280
415
  try {
281
416
  await this.addTabsToGroup(this.activeSessionGroupId, [tabId]);
417
+ // Enforce tab limit after adding
418
+ await this.enforceTabLimit();
282
419
  return true;
283
420
  }
284
421
  catch (error) {
@@ -311,10 +448,11 @@ export class SessionManager {
311
448
  let sessionManagerInstance = null;
312
449
  /**
313
450
  * Get the singleton SessionManager instance
451
+ * @param options Options for the SessionManager (only used on first call)
314
452
  */
315
- export function getSessionManager() {
453
+ export function getSessionManager(options) {
316
454
  if (!sessionManagerInstance) {
317
- sessionManagerInstance = new SessionManager();
455
+ sessionManagerInstance = new SessionManager(options);
318
456
  }
319
457
  return sessionManagerInstance;
320
458
  }
@@ -106,8 +106,15 @@ export interface GroupGetCommand extends ExtensionBaseCommand {
106
106
  export interface SessionGetCurrentCommand extends ExtensionBaseCommand {
107
107
  action: 'sessionGetCurrent';
108
108
  }
109
+ /**
110
+ * Command to use an existing tab group as the active session
111
+ */
112
+ export interface SessionUseGroupCommand extends ExtensionBaseCommand {
113
+ action: 'sessionUseGroup';
114
+ groupId: number;
115
+ }
109
116
  /**
110
117
  * Union type of all session-related commands
111
118
  */
112
- export type SessionCommand = GroupCreateCommand | GroupUpdateCommand | GroupDeleteCommand | GroupListCommand | GroupAddTabsCommand | GroupRemoveTabsCommand | GroupGetCommand | SessionGetCurrentCommand;
119
+ export type SessionCommand = GroupCreateCommand | GroupUpdateCommand | GroupDeleteCommand | GroupListCommand | GroupAddTabsCommand | GroupRemoveTabsCommand | GroupGetCommand | SessionGetCurrentCommand | SessionUseGroupCommand;
113
120
  //# sourceMappingURL=session-types.d.ts.map
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { Command as CoreCommand, Response } from '../../core/dist/index.js';
7
7
  import type { SessionCommand } from './session-types.js';
8
- export type ExtensionAction = 'navigate' | 'back' | 'forward' | 'reload' | 'getUrl' | 'getTitle' | 'screenshot' | 'tabNew' | 'tabClose' | 'tabSwitch' | 'tabList' | 'groupCreate' | 'groupUpdate' | 'groupDelete' | 'groupList' | 'groupAddTabs' | 'groupRemoveTabs' | 'groupGet' | 'sessionGetCurrent' | 'popupInitialize' | 'scriptInject' | 'scriptSend';
8
+ export type ExtensionAction = 'navigate' | 'back' | 'forward' | 'reload' | 'getUrl' | 'getTitle' | 'screenshot' | 'tabNew' | 'tabClose' | 'tabSwitch' | 'tabList' | 'groupCreate' | 'groupUpdate' | 'groupDelete' | 'groupList' | 'groupAddTabs' | 'groupRemoveTabs' | 'groupGet' | 'sessionGetCurrent' | 'sessionUseGroup' | 'popupInitialize' | 'scriptInject' | 'scriptSend';
9
9
  export interface ExtensionBaseCommand {
10
10
  /** Optional command ID. Auto-generated if not provided. */
11
11
  id?: string;