@shawnowen/comet-mcp 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +12 -1
  2. package/dist/binding-reaper.d.ts +46 -0
  3. package/dist/binding-reaper.js +73 -0
  4. package/dist/http-server.js +121 -0
  5. package/dist/index.js +310 -6
  6. package/dist/project-config.d.ts +46 -0
  7. package/dist/project-config.js +166 -0
  8. package/dist/tab-groups.d.ts +21 -1
  9. package/dist/tab-groups.js +184 -0
  10. package/dist/window-bindings.d.ts +48 -0
  11. package/dist/window-bindings.js +85 -0
  12. package/extension/background.js +38 -17
  13. package/extension/manifest.json +16 -1
  14. package/extension/perplexity-capability-manifest.json +1181 -0
  15. package/extension/perplexity-capability-manifest.schema.json +142 -0
  16. package/extension/session-logic.js +696 -25
  17. package/extension/session-manager.html +13 -1
  18. package/extension/sidepanel.css +21 -6
  19. package/extension/sidepanel.js +598 -68
  20. package/package.json +1 -1
  21. package/dist/discovery/capability-entry.d.ts +0 -215
  22. package/dist/discovery/capability-entry.js +0 -13
  23. package/dist/discovery/description-template.d.ts +0 -40
  24. package/dist/discovery/description-template.js +0 -61
  25. package/dist/discovery/golden-queries.fixture.d.ts +0 -22
  26. package/dist/discovery/golden-queries.fixture.js +0 -137
  27. package/dist/discovery/mcp-source.d.ts +0 -38
  28. package/dist/discovery/mcp-source.js +0 -70
  29. package/dist/discovery/metadata-completeness.d.ts +0 -48
  30. package/dist/discovery/metadata-completeness.js +0 -83
  31. package/dist/discovery/registry.d.ts +0 -35
  32. package/dist/discovery/registry.js +0 -35
  33. package/dist/discovery/safety.d.ts +0 -44
  34. package/dist/discovery/safety.js +0 -59
  35. package/dist/discovery/schema-validator.d.ts +0 -36
  36. package/dist/discovery/schema-validator.js +0 -257
  37. package/dist/discovery/source-error.d.ts +0 -47
  38. package/dist/discovery/source-error.js +0 -95
  39. package/dist/discovery/tool-meta.d.ts +0 -41
  40. package/dist/discovery/tool-meta.js +0 -229
  41. package/dist/discovery/virtual-tools.d.ts +0 -20
  42. package/dist/discovery/virtual-tools.js +0 -69
  43. package/dist/task-thread-aggregator.d.ts +0 -34
  44. package/dist/task-thread-aggregator.js +0 -480
  45. package/dist/task-thread-canonical.d.ts +0 -142
  46. package/dist/task-thread-canonical.js +0 -116
@@ -243,6 +243,190 @@ export class TabGroupsClient {
243
243
  })()
244
244
  `);
245
245
  }
246
+ /** List extension-owned descriptions, window labels, archives, and selected metadata. */
247
+ async listExtensionMetadata() {
248
+ return await this.evaluate(`
249
+ (async () => {
250
+ const stored = await chrome.storage.local.get([
251
+ "sessionEntityDescriptions",
252
+ "sidebarSettings",
253
+ "archivedGroups",
254
+ "recentlyClosedGroups",
255
+ "fsSelectedGroups",
256
+ "fsSelectedTabs"
257
+ ]);
258
+ const normalizeArchiveWorkflowStatus = (status) => {
259
+ const raw = String(status || "").trim().toLowerCase();
260
+ if (raw === "done") return "done";
261
+ if (raw === "trashed") return "trashed";
262
+ return "pending";
263
+ };
264
+ const settings = stored.sidebarSettings || {};
265
+ const archives = Array.isArray(stored.archivedGroups) ? stored.archivedGroups : [];
266
+ return {
267
+ entityDescriptions: stored.sessionEntityDescriptions || {},
268
+ windowLabels: settings.windowLabels || {},
269
+ archives: archives.map((entry) => ({
270
+ taskThreadId: entry.taskThreadId || entry.id,
271
+ title: entry.title || entry.sessionName || "Untitled",
272
+ sessionName: entry.sessionName || null,
273
+ taskGoal: entry.taskGoal || null,
274
+ description: String(entry.description || entry.note || ""),
275
+ status: normalizeArchiveWorkflowStatus(entry.status),
276
+ rawStatus: entry.status || null,
277
+ tabCount: Array.isArray(entry.urls) ? entry.urls.length : 0,
278
+ urls: Array.isArray(entry.urls)
279
+ ? entry.urls.map((tab) => ({
280
+ url: String(tab && tab.url ? tab.url : ""),
281
+ title: String(tab && tab.title ? tab.title : ""),
282
+ description: String(tab && tab.description ? tab.description : "")
283
+ }))
284
+ : [],
285
+ archivedAt: entry.archivedAt || null,
286
+ closedAt: entry.closedAt || null,
287
+ restoredAt: entry.restoredAt || null,
288
+ orchestratorUrl: entry.orchestratorUrl || null,
289
+ locked: !!entry.locked,
290
+ pinned: !!entry.pinned,
291
+ starred: !!entry.starred
292
+ })),
293
+ recentGroups: Array.isArray(stored.recentlyClosedGroups) ? stored.recentlyClosedGroups : [],
294
+ selectedItems: {
295
+ groups: Array.isArray(stored.fsSelectedGroups) ? stored.fsSelectedGroups : [],
296
+ tabs: Array.isArray(stored.fsSelectedTabs) ? stored.fsSelectedTabs : []
297
+ }
298
+ };
299
+ })()
300
+ `);
301
+ }
302
+ /** Update extension-owned metadata without touching native tab state. */
303
+ async updateExtensionMetadata(options) {
304
+ const payload = JSON.stringify(options || {});
305
+ return await this.evaluate(`
306
+ (async () => {
307
+ const input = ${payload};
308
+ const now = new Date().toISOString();
309
+ const entityType = String(input.entityType || "").toLowerCase();
310
+ const taskThreadId = String(input.taskThreadId || input.entityId || input.id || "");
311
+ const makeMetadataAudit = (target) => ({
312
+ intent: String(input.intent || input.action || "update_metadata"),
313
+ target,
314
+ activeWindow: {
315
+ windowId: String(input.activeWindowId || input.activeWindow?.windowId || "extension_service_worker")
316
+ },
317
+ policyTier: String(input.policyTier || "prime"),
318
+ status: "success",
319
+ createdAt: now
320
+ });
321
+ const coerceArchiveWorkflowStatusForWrite = (status) => {
322
+ const raw = String(status || "").trim().toLowerCase();
323
+ if (raw === "saved" || raw === "archived") return "pending";
324
+ if (raw === "pending" || raw === "done" || raw === "trashed") return raw;
325
+ throw new Error("Invalid status: " + status);
326
+ };
327
+
328
+ if (entityType === "window" || entityType === "window-label") {
329
+ const windowId = String(input.windowId || input.entityId || input.id || "");
330
+ if (!windowId) throw new Error("windowId is required for window metadata updates");
331
+ const stored = await chrome.storage.local.get("sidebarSettings");
332
+ const settings = stored.sidebarSettings || {};
333
+ settings.windowLabels = settings.windowLabels || {};
334
+ const existingWindowLabel = settings.windowLabels[windowId] || {};
335
+ const nextWindowLabel = { ...existingWindowLabel };
336
+ if (input.label !== undefined || input.title !== undefined) {
337
+ nextWindowLabel.label = String(input.label ?? input.title);
338
+ }
339
+ if (typeof nextWindowLabel.label !== "string") {
340
+ nextWindowLabel.label = String(nextWindowLabel.label ?? "");
341
+ }
342
+ settings.windowLabels[windowId] = {
343
+ ...nextWindowLabel,
344
+ source: "mcp_metadata",
345
+ updatedAt: now
346
+ };
347
+ await chrome.storage.local.set({ sidebarSettings: settings });
348
+ return {
349
+ updated: true,
350
+ entityType: "window-label",
351
+ windowId,
352
+ audit: makeMetadataAudit({ entityType: "window-label", windowId })
353
+ };
354
+ }
355
+
356
+ if (entityType === "archive" || entityType === "task-thread") {
357
+ if (!taskThreadId) throw new Error("taskThreadId is required for archive metadata updates");
358
+ const stored = await chrome.storage.local.get("archivedGroups");
359
+ const archive = Array.isArray(stored.archivedGroups) ? stored.archivedGroups : [];
360
+ const entry = archive.find((item) => String(item.taskThreadId || item.id) === taskThreadId);
361
+ if (!entry) throw new Error("Archive entry not found: " + taskThreadId);
362
+ let changed = false;
363
+ if (input.title !== undefined) {
364
+ entry.title = String(input.title);
365
+ changed = true;
366
+ }
367
+ if (input.description !== undefined) {
368
+ entry.description = String(input.description);
369
+ changed = true;
370
+ }
371
+ if (input.status !== undefined) {
372
+ if (entry.locked) throw new Error("Cannot change status of a locked entry");
373
+ entry.status = coerceArchiveWorkflowStatusForWrite(input.status);
374
+ entry.statusUpdatedAt = now;
375
+ changed = true;
376
+ }
377
+ if (changed) entry.updatedAt = now;
378
+ await chrome.storage.local.set({ archivedGroups: archive });
379
+ return {
380
+ updated: true,
381
+ entityType: "archive",
382
+ taskThreadId,
383
+ audit: makeMetadataAudit({ entityType: "archive", taskThreadId })
384
+ };
385
+ }
386
+
387
+ if (entityType === "archive-tab") {
388
+ if (!taskThreadId) throw new Error("taskThreadId is required for archive tab metadata updates");
389
+ const tabIndex = Number(input.tabIndex);
390
+ if (!Number.isInteger(tabIndex) || tabIndex < 0) throw new Error("tabIndex is required");
391
+ const stored = await chrome.storage.local.get("archivedGroups");
392
+ const archive = Array.isArray(stored.archivedGroups) ? stored.archivedGroups : [];
393
+ const entry = archive.find((item) => String(item.taskThreadId || item.id) === taskThreadId);
394
+ if (!entry) throw new Error("Archive entry not found: " + taskThreadId);
395
+ entry.urls = Array.isArray(entry.urls) ? entry.urls : [];
396
+ if (!entry.urls[tabIndex]) throw new Error("Archive tab not found: " + tabIndex);
397
+ if (input.title !== undefined) entry.urls[tabIndex].title = String(input.title);
398
+ if (input.description !== undefined) entry.urls[tabIndex].description = String(input.description);
399
+ await chrome.storage.local.set({ archivedGroups: archive });
400
+ return {
401
+ updated: true,
402
+ entityType: "archive-tab",
403
+ taskThreadId,
404
+ tabIndex,
405
+ audit: makeMetadataAudit({ entityType: "archive-tab", taskThreadId, tabIndex })
406
+ };
407
+ }
408
+
409
+ const entityId = String(input.entityId || input.id || "");
410
+ if (!entityType || !entityId) throw new Error("entityType and entityId are required");
411
+ const stored = await chrome.storage.local.get("sessionEntityDescriptions");
412
+ const descriptions = stored.sessionEntityDescriptions || {};
413
+ const key = entityType + ":" + entityId;
414
+ descriptions[key] = {
415
+ entityType,
416
+ entityId,
417
+ description: String(input.description ?? ""),
418
+ updatedAt: now
419
+ };
420
+ await chrome.storage.local.set({ sessionEntityDescriptions: descriptions });
421
+ return {
422
+ updated: true,
423
+ entityType,
424
+ entityId,
425
+ audit: makeMetadataAudit({ entityType, entityId })
426
+ };
427
+ })()
428
+ `);
429
+ }
246
430
  /** Create a single tab and return its ID. Used by ETT restore_group. */
247
431
  async createTab(url, active = false) {
248
432
  return await this.evaluate(`
@@ -41,6 +41,13 @@ export interface CodexWindowBinding extends CodexSessionIdentity {
41
41
  status: CodexBindingStatus;
42
42
  createdAt: string;
43
43
  updatedAt: string;
44
+ /**
45
+ * ISO 8601 timestamp first recorded when a reap cycle found this binding's
46
+ * window absent from the live CDP set. Cleared when the window reappears.
47
+ * Drives the TTL grace period in {@link CodexWindowBindingStore.reapExpiredBindings}.
48
+ * Optional/absent on pre-existing records (backward-compatible).
49
+ */
50
+ missingSince?: string;
44
51
  }
45
52
  export interface RunBindingIndexEntry {
46
53
  runId: string;
@@ -72,6 +79,35 @@ export interface BindingMutationOptions {
72
79
  reason?: string;
73
80
  audit?: boolean;
74
81
  }
82
+ /** Options for {@link CodexWindowBindingStore.reapExpiredBindings}. */
83
+ export interface ReapOptions {
84
+ /** Distinct windowIds currently live in the browser (CDP). */
85
+ liveWindowIds: Iterable<number>;
86
+ /** Grace period in ms a binding may stay missing before it is reaped. */
87
+ ttlMs: number;
88
+ /** Injectable clock (ms epoch). Defaults to Date.now(). */
89
+ now?: number;
90
+ /**
91
+ * Archive callback invoked BEFORE a binding is deleted (e.g. recovery
92
+ * snapshot + ORPHAN_REAPED alert). If it throws, the binding is NOT deleted
93
+ * that cycle (fail-safe).
94
+ */
95
+ archive?: (binding: CodexWindowBinding) => Promise<void>;
96
+ /**
97
+ * Predicate for profile-owned bindings the auto-reaper must skip.
98
+ * Default: profileOwner is set and not "agent".
99
+ */
100
+ isProfileOwned?: (binding: CodexWindowBinding) => boolean;
101
+ }
102
+ /** Per-cycle counts returned by {@link CodexWindowBindingStore.reapExpiredBindings}. */
103
+ export interface ReapCycleResult {
104
+ evaluated: number;
105
+ newlyMissing: number;
106
+ retainedLive: number;
107
+ reaped: number;
108
+ skippedOwned: number;
109
+ reapedBindingIds: string[];
110
+ }
75
111
  export declare class WindowBindingConflictError extends Error {
76
112
  readonly conflicts: CodexWindowBinding[];
77
113
  constructor(message: string, conflicts: CodexWindowBinding[]);
@@ -105,6 +141,18 @@ export declare class CodexWindowBindingStore {
105
141
  transitionByRunId(runId: string, status: Exclude<CodexBindingStatus, "conflict">): Promise<CodexWindowBinding | null>;
106
142
  classifyConflicts(): Promise<CodexWindowBinding[]>;
107
143
  markStaleForMissingWindows(liveWindowIds: Iterable<number>): Promise<CodexWindowBinding[]>;
144
+ /**
145
+ * Reconcile every binding against the live window set and reap (delete) those
146
+ * whose window has been missing for at least {@link ReapOptions.ttlMs}.
147
+ *
148
+ * - Window live → clear `missingSince`, reactivate if the reaper marked it stale, retain.
149
+ * - Window missing, no `missingSince` → record `missingSince=now`, mark stale, retain (grace starts).
150
+ * - Window missing ≥ TTL, profile-owned → skip.
151
+ * - Window missing ≥ TTL, not owned → `archive()` then delete.
152
+ *
153
+ * Atomic + file-locked + idempotent. A no-op when nothing needs changing.
154
+ */
155
+ reapExpiredBindings(opts: ReapOptions): Promise<ReapCycleResult>;
108
156
  private assertNoActiveConflicts;
109
157
  private findActiveConflicts;
110
158
  }
@@ -443,6 +443,91 @@ export class CodexWindowBindingStore {
443
443
  return stale;
444
444
  });
445
445
  }
446
+ /**
447
+ * Reconcile every binding against the live window set and reap (delete) those
448
+ * whose window has been missing for at least {@link ReapOptions.ttlMs}.
449
+ *
450
+ * - Window live → clear `missingSince`, reactivate if the reaper marked it stale, retain.
451
+ * - Window missing, no `missingSince` → record `missingSince=now`, mark stale, retain (grace starts).
452
+ * - Window missing ≥ TTL, profile-owned → skip.
453
+ * - Window missing ≥ TTL, not owned → `archive()` then delete.
454
+ *
455
+ * Atomic + file-locked + idempotent. A no-op when nothing needs changing.
456
+ */
457
+ async reapExpiredBindings(opts) {
458
+ const live = new Set(opts.liveWindowIds);
459
+ const now = opts.now ?? Date.now();
460
+ const ttlMs = opts.ttlMs;
461
+ const isOwned = opts.isProfileOwned ??
462
+ ((b) => b.profileOwner != null && b.profileOwner !== "agent");
463
+ const result = {
464
+ evaluated: 0,
465
+ newlyMissing: 0,
466
+ retainedLive: 0,
467
+ reaped: 0,
468
+ skippedOwned: 0,
469
+ reapedBindingIds: [],
470
+ };
471
+ return withFileLock(this.lockDir, async () => {
472
+ const snapshot = await readSnapshot(this.file);
473
+ let mutated = false;
474
+ const reapCandidates = [];
475
+ for (const binding of Object.values(snapshot.bindings)) {
476
+ result.evaluated += 1;
477
+ if (live.has(binding.windowId)) {
478
+ if (binding.missingSince != null) {
479
+ delete binding.missingSince;
480
+ if (binding.status === "stale") {
481
+ binding.status = "active";
482
+ }
483
+ binding.updatedAt = nowIso();
484
+ mutated = true;
485
+ }
486
+ result.retainedLive += 1;
487
+ continue;
488
+ }
489
+ // Window is missing from the live set.
490
+ if (binding.missingSince == null) {
491
+ binding.missingSince = new Date(now).toISOString();
492
+ binding.status = "stale";
493
+ binding.updatedAt = nowIso();
494
+ mutated = true;
495
+ result.newlyMissing += 1;
496
+ continue; // retained this cycle; grace period starts
497
+ }
498
+ const missingForMs = now - Date.parse(binding.missingSince);
499
+ if (!Number.isFinite(missingForMs) || missingForMs < ttlMs) {
500
+ continue; // still within grace (or unparseable) → retain
501
+ }
502
+ if (isOwned(binding)) {
503
+ result.skippedOwned += 1;
504
+ continue;
505
+ }
506
+ reapCandidates.push(binding);
507
+ }
508
+ for (const binding of reapCandidates) {
509
+ if (opts.archive) {
510
+ try {
511
+ await opts.archive(binding);
512
+ }
513
+ catch {
514
+ // Archive failed — do NOT delete (fail-safe, recoverable next cycle).
515
+ continue;
516
+ }
517
+ }
518
+ delete snapshot.bindings[binding.bindingId];
519
+ result.reaped += 1;
520
+ result.reapedBindingIds.push(binding.bindingId);
521
+ mutated = true;
522
+ }
523
+ if (mutated) {
524
+ snapshot.updatedAt = nowIso();
525
+ snapshot.runBindingIndex = rebuildRunBindingIndex(snapshot.bindings);
526
+ await writeSnapshot(this.file, this.dir, snapshot);
527
+ }
528
+ return result;
529
+ });
530
+ }
446
531
  assertNoActiveConflicts(snapshot) {
447
532
  const conflicts = this.findActiveConflicts(snapshot);
448
533
  if (conflicts.length > 0) {
@@ -486,7 +486,7 @@ chrome.action.onClicked.addListener(async (tab) => {
486
486
  })),
487
487
  archivedAt: new Date().toISOString(),
488
488
  restoredAt: null,
489
- status: "archived",
489
+ status: "pending",
490
490
  // Spec 037: Orchestrator session data
491
491
  orchestratorUrl: sessionData?.orchestratorUrl || null,
492
492
  taskGoal: sessionData?.taskGoal || null,
@@ -594,6 +594,27 @@ function normalizeInstructionText(value) {
594
594
  return value === undefined || value === null ? "" : String(value).trim();
595
595
  }
596
596
 
597
+ function normalizeArchiveWorkflowStatus(status) {
598
+ if (globalThis.SessionLogic?.normalizeArchiveWorkflowStatus) {
599
+ return globalThis.SessionLogic.normalizeArchiveWorkflowStatus(status);
600
+ }
601
+ const raw = String(status || "")
602
+ .trim()
603
+ .toLowerCase();
604
+ if (raw === "done") return "done";
605
+ if (raw === "trashed") return "trashed";
606
+ return "pending";
607
+ }
608
+
609
+ function coerceArchiveWorkflowStatusForWrite(status) {
610
+ const raw = String(status || "")
611
+ .trim()
612
+ .toLowerCase();
613
+ if (raw === "saved" || raw === "archived") return "pending";
614
+ if (raw === "pending" || raw === "done" || raw === "trashed") return raw;
615
+ throw new Error(`Invalid status: ${status}`);
616
+ }
617
+
597
618
  function hasOwn(record, key) {
598
619
  return !!record && Object.prototype.hasOwnProperty.call(record, key);
599
620
  }
@@ -666,6 +687,7 @@ function normalizeArchiveEntry(entry) {
666
687
  normalized.urls = Array.isArray(normalized.urls)
667
688
  ? normalized.urls.map((tab) => normalizeArchiveTab(tab, normalized.description))
668
689
  : [];
690
+ normalized.status = normalizeArchiveWorkflowStatus(normalized.status);
669
691
  return normalized;
670
692
  }
671
693
 
@@ -743,7 +765,7 @@ async function buildArchiveEntryFromLiveGroup(groupId, title, snapshotOnly = fal
743
765
  })),
744
766
  archivedAt: new Date().toISOString(),
745
767
  restoredAt: null,
746
- status: "archived",
768
+ status: "pending",
747
769
  sourceWindowId: windowId,
748
770
  sourceDisplayId,
749
771
  starred: false,
@@ -912,7 +934,7 @@ registerHandler("restoreGroup", async ({ taskThreadId, target = "newWindow", sou
912
934
  // Update archive entry status
913
935
  archive[idx] = {
914
936
  ...entry,
915
- status: "saved",
937
+ status: "pending",
916
938
  restoredAt: new Date().toISOString(),
917
939
  };
918
940
  await saveArchive(archive);
@@ -1056,15 +1078,14 @@ registerHandler("updateArchiveDescription", async ({ taskThreadId, description }
1056
1078
  return { description: entry.description };
1057
1079
  });
1058
1080
 
1059
- // T052: Update status (archived, pending, trashed)
1081
+ // T052: Update workflow status (pending, done, trashed; legacy aliases saved/archived)
1060
1082
  registerHandler("updateArchiveStatus", async ({ taskThreadId, status }) => {
1061
- const valid = ["saved", "archived", "pending", "done", "trashed"];
1062
- if (!valid.includes(status)) throw new Error(`Invalid status: ${status}`);
1083
+ const nextStatus = coerceArchiveWorkflowStatusForWrite(status);
1063
1084
  const archive = await loadArchive();
1064
1085
  const entry = archive.find((e) => e.taskThreadId === taskThreadId);
1065
1086
  if (!entry) throw new Error(`Not found: ${taskThreadId}`);
1066
1087
  if (entry.locked) throw new Error("Cannot change status of a locked entry");
1067
- entry.status = status;
1088
+ entry.status = nextStatus;
1068
1089
  await saveArchive(archive);
1069
1090
  return { status: entry.status };
1070
1091
  });
@@ -1218,7 +1239,7 @@ registerHandler("saveAllTabs", async () => {
1218
1239
  }),
1219
1240
  archivedAt: new Date().toISOString(),
1220
1241
  restoredAt: null,
1221
- status: "archived",
1242
+ status: "pending",
1222
1243
  };
1223
1244
  archive.unshift(entry);
1224
1245
  archivedCount++;
@@ -1243,7 +1264,7 @@ registerHandler("saveAllTabs", async () => {
1243
1264
  })),
1244
1265
  archivedAt: new Date().toISOString(),
1245
1266
  restoredAt: null,
1246
- status: "archived",
1267
+ status: "pending",
1247
1268
  };
1248
1269
  archive.unshift(entry);
1249
1270
  archivedCount++;
@@ -1386,7 +1407,7 @@ registerHandler("importUrls", async ({ groups }) => {
1386
1407
  })),
1387
1408
  archivedAt: new Date().toISOString(),
1388
1409
  restoredAt: null,
1389
- status: "archived",
1410
+ status: "pending",
1390
1411
  };
1391
1412
  archive.unshift(entry);
1392
1413
  imported += entry.urls.length;
@@ -1767,7 +1788,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1767
1788
  urls: [{ url: tab.url || "", title: tab.title || "" }],
1768
1789
  archivedAt: new Date().toISOString(),
1769
1790
  restoredAt: null,
1770
- status: "archived",
1791
+ status: "pending",
1771
1792
  };
1772
1793
  // If tab is in a group, use the group name
1773
1794
  if (tab.groupId && tab.groupId !== -1) {
@@ -1817,7 +1838,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1817
1838
  urls: tabs.map((t) => ({ url: t.url || "", title: t.title || "" })),
1818
1839
  archivedAt: new Date().toISOString(),
1819
1840
  restoredAt: null,
1820
- status: "archived",
1841
+ status: "pending",
1821
1842
  };
1822
1843
  archive.unshift(entry);
1823
1844
  }
@@ -1856,7 +1877,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1856
1877
  urls: eligible.map((t) => ({ url: t.url || "", title: t.title || "" })),
1857
1878
  archivedAt: new Date().toISOString(),
1858
1879
  restoredAt: null,
1859
- status: "archived",
1880
+ status: "pending",
1860
1881
  };
1861
1882
 
1862
1883
  const archive = await loadArchive();
@@ -1883,7 +1904,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1883
1904
  urls: eligible.map((t) => ({ url: t.url || "", title: t.title || "" })),
1884
1905
  archivedAt: new Date().toISOString(),
1885
1906
  restoredAt: null,
1886
- status: "archived",
1907
+ status: "pending",
1887
1908
  };
1888
1909
 
1889
1910
  const archive = await loadArchive();
@@ -1921,7 +1942,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1921
1942
  urls: toArchive.map((t) => ({ url: t.url || "", title: t.title || "" })),
1922
1943
  archivedAt: new Date().toISOString(),
1923
1944
  restoredAt: null,
1924
- status: "archived",
1945
+ status: "pending",
1925
1946
  };
1926
1947
 
1927
1948
  const archive = await loadArchive();
@@ -1950,7 +1971,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1950
1971
  urls: toArchive.map((t) => ({ url: t.url || "", title: t.title || "" })),
1951
1972
  archivedAt: new Date().toISOString(),
1952
1973
  restoredAt: null,
1953
- status: "archived",
1974
+ status: "pending",
1954
1975
  };
1955
1976
 
1956
1977
  const archive = await loadArchive();
@@ -1979,7 +2000,7 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
1979
2000
  urls: toArchive.map((t) => ({ url: t.url || "", title: t.title || "" })),
1980
2001
  archivedAt: new Date().toISOString(),
1981
2002
  restoredAt: null,
1982
- status: "archived",
2003
+ status: "pending",
1983
2004
  };
1984
2005
 
1985
2006
  const archive = await loadArchive();
@@ -6,6 +6,7 @@
6
6
  "permissions": [
7
7
  "tabGroups",
8
8
  "tabs",
9
+ "activeTab",
9
10
  "alarms",
10
11
  "sidePanel",
11
12
  "sessions",
@@ -13,9 +14,14 @@
13
14
  "contextMenus",
14
15
  "system.display",
15
16
  "scripting",
16
- "bookmarks"
17
+ "bookmarks",
18
+ "debugger",
19
+ "webNavigation"
17
20
  ],
18
21
  "host_permissions": [
22
+ "https://perplexity.ai/*",
23
+ "https://*.perplexity.ai/*",
24
+ "https://comet.perplexity.ai/*",
19
25
  "http://localhost/*",
20
26
  "http://127.0.0.1/*",
21
27
  "http://localhost:11434/*",
@@ -27,6 +33,15 @@
27
33
  "128": "icons/icon128.png"
28
34
  },
29
35
  "action": {},
36
+ "commands": {
37
+ "_execute_action": {
38
+ "suggested_key": {
39
+ "default": "Ctrl+Shift+Y",
40
+ "mac": "Command+Shift+Y"
41
+ },
42
+ "description": "Open Equabotz Browser Agents"
43
+ }
44
+ },
30
45
  "background": {
31
46
  "service_worker": "background.js"
32
47
  },